/* Copyright (c) 2001, Loki software, inc. All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. Neither the name of Loki software nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ // // Shaders Manager Plugin // // Leonardo Zide (leo@lokigames.com) // // standard headers #include #include #include "plugin.h" #include "mathlib.h" #include "missing.h" //++timo FIXME: this one is intended to go away some day, it's MFC compatibility classes #include "shaders.h" // some forward declarations IShader *WINAPI QERApp_Shader_ForName (const char *name); qtexture_t *WINAPI QERApp_Try_Texture_ForName (const char *name); qtexture_t *WINAPI QERApp_Texture_ForName2 (const char *filename); IShader *WINAPI QERApp_ColorShader_ForName (const char *name); void WINAPI QERApp_LoadShaderFile (const char *filename); //++timo TODO: use stl::map !! (I tried having a look to CMap but it obviously sucks) CShaderArray g_Shaders; // whenever a shader gets activated / deactivated this list is updated // NOTE: make sure you don't add a shader that's already in // NOTE: all shaders in this array are in the main g_Shaders CShaderArray g_ActiveShaders; // clean a texture name to the qtexture_t name format we use internally // NOTE: there are so many cases .. this may need to get updated to cover all of them // we expect a "textures/" path on top, except if bAddTexture is set to true .. in case we add in needed // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name, // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive. //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present const char *WINAPI QERApp_CleanTextureName (const char *name, bool bAddTexture = false) { static char stdName[QER_MAX_NAMELEN]; #ifdef _DEBUG if (strlen(name)>QER_MAX_NAMELEN) g_FuncTable.m_pfnSysFPrintf(SYS_WRN, "WARNING: name exceeds QER_MAX_NAMELEN in CleanTextureName\n"); #endif strcpy (stdName, name); g_FuncTable.m_pfnQE_ConvertDOSToUnixName (stdName, stdName); if (stdName[strlen (name) - 4] == '.') // strip extension stdName[strlen (stdName) - 4] = '\0'; if (bAddTexture) { char aux[QER_MAX_NAMELEN]; sprintf (aux, "textures/%s", stdName); strcpy (stdName, aux); } return stdName; } int WINAPI QERApp_GetActiveShaderCount () { return g_ActiveShaders.GetSize (); } IShader *WINAPI QERApp_ActiveShader_ForIndex (int i) { return static_cast < CShader * >(g_ActiveShaders.GetAt (i)); } void CShaderArray::SortShaders () { CPtrArray aux; int i, icount; int j, jcount; CShader *pSort; const char *sSort; // dumb sort .. would it ever grow big enough so we would have to do something clever? noooo icount = CPtrArray::GetSize (); for (i = 0; i < icount; i++) { pSort = static_cast < CShader * >(GetAt (i)); sSort = pSort->getName (); jcount = aux.GetSize (); for (j = 0; j < jcount; j++) { if (strcmp (sSort, static_cast < CShader * >(aux.GetAt (j))->getName ()) < 0) break; } aux.InsertAt (j, pSort); } CPtrArray::RemoveAll (); CPtrArray::InsertAt (0, &aux); } // will sort the active shaders list by name // NOTE: it would be easier if the thing would stay sorted by using a map thing //++timo FIXME: would need to export that to allow external override? void WINAPI QERApp_SortActiveShaders () { g_ActiveShaders.SortShaders (); } // NOTE: case sensitivity // although we store shader names with case information, Radiant does case insensitive searches // (we assume there's no case conflict with the names) CShader *CShaderArray::Shader_ForName (const char *name) const { int i; for (i = 0; i < CPtrArray::GetSize (); i++) { CShader *pShader = static_cast < CShader * >(CPtrArray::GetAt (i)); if (stricmp (pShader->getName (), name) == 0) return pShader; } return NULL; } void CShader::CreateDefault (const char *name) { const char *stdName = QERApp_CleanTextureName (name); m_strTextureName = stdName; setName (name); } CShader *CShaderArray::Shader_ForTextureName (const char *name) const { #ifdef _DEBUG // check we were given a texture name that fits the qtexture_t naming conventions if (strcmp (name, QERApp_CleanTextureName (name)) != 0) Sys_Printf ("WARNING: texture name %s doesn't fit qtexture_t conventions in CShaderArray::Shader_ForTextureName\n", name); #endif int i; for (i = 0; i < CPtrArray::GetSize (); i++) { CShader *pShader = static_cast < CShader * >(CPtrArray::GetAt (i)); if (strcmp (name, QERApp_CleanTextureName (pShader->getTextureName ())) == 0) return pShader; } return NULL; } IShader *WINAPI QERApp_ActiveShader_ForTextureName (char *name) { return g_ActiveShaders.Shader_ForTextureName (name); } void CShaderArray::AddSingle (void *lp) { int i; for (i = 0; i < CPtrArray::GetSize (); i++) { if (CPtrArray::GetAt (i) == lp) return; } CPtrArray::Add (lp); static_cast < CShader * >(CPtrArray::GetAt (i))->IncRef(); } void CShaderArray::operator = (const class CShaderArray & src) { int i; #ifdef _DEBUG if (CPtrArray::GetSize () != 0) Sys_Printf ("WARNING: CShaderArray::operator = expects an empty array\n"); #endif Copy (src); // now go through and IncRef for (i = 0; i < CPtrArray::GetSize (); i++) static_cast < IShader * >(CPtrArray::GetAt (i))->IncRef (); } //++timo NOTE: for debugging we may need to keep track and tell wether everything has been properly unloaded void CShaderArray::ReleaseAll () { int i; int count = CPtrArray::GetSize (); // decref for (i = 0; i < count; i++) static_cast < IShader * >(CPtrArray::GetAt (i))->DecRef (); // get rid CPtrArray::RemoveAll (); } // NOTE TTimo: // this was hacked to work a long time ago // in Loki's fenris tracker as bug #104655 // since that info is no longer available, and the hack has been there for so long, it's part of the code now // don't remember the details, but basically across a flush and reload for the shaders // we have to keep track of the patches texture names in a seperate entry // not sure why anymore, but I know that doesn't happen with brushes typedef struct patchEntry_s { char name[QER_MAX_NAMELEN]; patchMesh_t *p; } patchEntry_t; CPtrArray PatchShaders; void PushPatch (patchMesh_t * patch) { patchEntry_t *pEntry = new patchEntry_s; pEntry->p = patch; strcpy (pEntry->name, patch->pShader->getName ()); PatchShaders.Add (pEntry); } char *ShaderNameLookup (patchMesh_t * patch) { int i; int count = PatchShaders.GetSize (); for (i = 0; i < count; i++) { if (static_cast < patchEntry_t * >(PatchShaders.GetAt (i))->p == patch) return static_cast < patchEntry_t * >(PatchShaders.GetAt (i))->name; } Sys_Printf ("ERROR: failed to lookup name in ShaderNameLookup??\n"); return SHADER_NOT_FOUND; } //++timo end clean // will free all GL binded qtextures and shaders // NOTE: doesn't make much sense out of Radiant exit or called during a reload void WINAPI QERApp_FreeShaders () { int i; brush_t *b; brush_t *active_brushes; brush_t *selected_brushes; brush_t *filtered_brushes; qtexture_t **d_qtextures; active_brushes = g_DataTable.m_pfnActiveBrushes (); selected_brushes = g_DataTable.m_pfnSelectedBrushes (); filtered_brushes = g_DataTable.m_pfnFilteredBrushes (); d_qtextures = g_ShadersTable.m_pfnQTextures (); // store the shader names used by the patches for (i = 0; i < PatchShaders.GetSize (); i++) delete static_cast < patchMesh_t * >(PatchShaders.GetAt (i)); PatchShaders.RemoveAll (); for (b = active_brushes->next; b != NULL && b != active_brushes; b = b->next) { if (b->patchBrush) PushPatch (b->pPatch); } for (b = selected_brushes->next; b != NULL && b != selected_brushes; b = b->next) { if (b->patchBrush) PushPatch (b->pPatch); } for (b = filtered_brushes->next; b != NULL && b != filtered_brushes; b = b->next) { if (b->patchBrush) PushPatch (b->pPatch); } // reload shaders // empty the actives shaders list g_ActiveShaders.ReleaseAll (); g_Shaders.ReleaseAll (); // empty the main g_qeglobals.d_qtextures list // FIXME: when we reload later on, we need to have the shader names // for brushes it's stored in the texdef // but patches don't have texdef // see bug 104655 for details // so the solution, build an array of patchMesh_t* and their shader names #ifdef _DEBUG Sys_Printf ("FIXME: patch shader reload workaround (old fenris? bug 104655)\n"); #endif //GtkWidget *widget = g_QglTable.m_pfn_GetQeglobalsGLWidget (); GHashTable *texmap = g_ShadersTable.m_pfnQTexmap (); // NOTE: maybe before we'd like to set all qtexture_t in the shaders list to notex? // NOTE: maybe there are some qtexture_t we don't want to erase? For plain color faces maybe? while (*d_qtextures) { qtexture_t *pTex = *d_qtextures; qtexture_t *pNextTex = pTex->next; //if (widget != NULL) g_QglTable.m_pfn_qglDeleteTextures (1, &pTex->texture_number); g_hash_table_remove (texmap, pTex->name); // all qtexture_t should be manipulated with the glib alloc handlers for now g_free (pTex); *d_qtextures = pNextTex; } g_QglTable.m_pfn_QE_CheckOpenGLForErrors (); } // those functions are only used during a shader reload phase // the patch one relies on ShaderNameLookup, a table that is being built only when a flush is performed // so it's not something we want to expose publicly void SetShader (patchMesh_t * patch) { // unhook current shader patch->pShader->DecRef(); // don't access this one! it has been deleted .. it's DEAD patch->d_texture = NULL; // hook the new one, increment the refcount // NOTE TTimo this function increments the refcount, don't incref ourselves patch->pShader = QERApp_Shader_ForName (ShaderNameLookup (patch)); patch->d_texture = patch->pShader->getTexture (); } void SetShader (face_t * f) { // unhook current shader f->pShader->DecRef(); // don't access the texdef! it's DEAD f->d_texture = NULL; // hook // NOTE TTimo this function increments the refcount, don't incref ourselves f->pShader = QERApp_Shader_ForName (f->texdef.GetName()); f->d_texture = f->pShader->getTexture (); } void Brush_RefreshShader(brush_t *b) { if (b->patchBrush) SetShader(b->pPatch); else if (b->owner->eclass->fixedsize) { /*eclass_t *eclass = HasModel(b); if (eclass) { for(entitymodel *model = eclass->model; model!=NULL; model=model->pNext) if(model && model->strSkin) model->nTextureBind = g_FuncTable.m_pfnTexture_LoadSkin(((GString *)model->strSkin)->str, &model->nSkinWidth, &model->nSkinHeight); }*/ } else for (face_t *f=b->brush_faces ; f ; f=f->next) SetShader(f); } void WINAPI QERApp_ReloadShaders () { brush_t *b; brush_t *active_brushes; brush_t *selected_brushes; brush_t *filtered_brushes; QERApp_FreeShaders (); g_DataTable.m_pfnLstSkinCache()->RemoveAll(); //md3 skins active_brushes = g_DataTable.m_pfnActiveBrushes (); selected_brushes = g_DataTable.m_pfnSelectedBrushes (); filtered_brushes = g_DataTable.m_pfnFilteredBrushes (); // now we must reload the shader information from shaderfiles g_ShadersTable.m_pfnBuildShaderList(); g_ShadersTable.m_pfnPreloadShaders(); // refresh the map visuals: replace our old shader objects by the new ones // on brush faces we have the shader name in texdef.name // on patches we have the shader name in PatchShaders // while we walk through the map data, we DecRef the old shaders and push the new ones in // if all goes well, most of our old shaders will get deleted on the way // FIXME: bug 104655, when we come accross a patch, we use the above array since the d_texture is lost // NOTE: both face_t and patchMesh_t store pointers to the shader and qtexture_t // in an ideal world they would only store shader and access the qtexture_t through it // reassign all current shaders for (b = active_brushes->next; b != NULL && b != active_brushes; b = b->next) Brush_RefreshShader(b); for (b = selected_brushes->next; b != NULL && b != selected_brushes; b = b->next) Brush_RefreshShader(b); // do that to the filtered brushes as well (we might have some region compiling going on) for (b = filtered_brushes->next; b != NULL && b != filtered_brushes; b = b->next) Brush_RefreshShader(b); } int WINAPI QERApp_LoadShadersFromDir (const char *path) { int count = 0; // scan g_Shaders, and call QERApp_Shader_ForName for each in the given path // this will load the texture if needed and will set it in use.. int nSize = g_Shaders.GetSize (); for (int i = 0; i < nSize; i++) { CShader *pShader = reinterpret_cast < CShader * >(g_Shaders[i]); if (strstr (pShader->getShaderFileName (), path) || strstr (pShader->getName (), path)) { count++; // request the shader, this will load the texture if needed and set "inuse" //++timo FIXME: should we put an Activate member on CShader? // this QERApp_Shader_ForName call is a kind of hack IShader *pFoo = QERApp_Shader_ForName (pShader->getName ()); #ifdef _DEBUG // check we activated the right shader // NOTE: if there was something else loaded, the size of g_Shaders may have changed and strange behaviours are to be expected if (pFoo != pShader) Sys_Printf ("WARNING: unexpected pFoo != pShader in QERApp_LoadShadersFromDir\n"); #else pFoo = NULL; // leo: shut up the compiler #endif } } return count; } bool CShader::Parse () { char *token = g_ScripLibTable.m_pfnToken (); // the parsing needs to be taken out in another module // Sys_Printf("TODO: CShader::Parse\n"); // token is shader name (full path with a "textures\") // we remove the "textures\" part //setName ((char *) &token[9])); // no we don't setName (token); // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before) const char *stdName = QERApp_CleanTextureName (token); m_strTextureName = stdName; // FIXME: BC reports stdName is uninitialised? g_ScripLibTable.m_pfnGetToken (true); if (strcmp (token, "{")) return false; else { // we need to read until we hit a balanced } int nMatch = 1; while (nMatch > 0 && g_ScripLibTable.m_pfnGetToken (true)) { if (strcmp (token, "{") == 0) { nMatch++; continue; } else if (strcmp (token, "}") == 0) { nMatch--; continue; } if (nMatch > 1) continue; // ignore layers for now if (strcmpi (token, "qer_nocarve") == 0) { m_nFlags |= QER_NOCARVE; } else if (strcmpi (token, "qer_trans") == 0) { if (g_ScripLibTable.m_pfnGetToken (true)) { m_fTrans = (float) atof (token); } m_nFlags |= QER_TRANS; } else if (strcmpi (token, "qer_editorimage") == 0) { if (g_ScripLibTable.m_pfnGetToken (true)) { // bAddTexture changed to false to allow editorimages in other locations than "textures/" m_strTextureName = QERApp_CleanTextureName (token, false); } } else if (strcmpi (token, "qer_alphafunc") == 0) { if (g_ScripLibTable.m_pfnGetToken (true)) { if(stricmp( token, "greater" ) == 0 ) { m_nAlphaFunc = GL_GREATER; } else if(stricmp( token, "less" ) == 0 ) { m_nAlphaFunc = GL_LESS; } else if(stricmp( token, "gequal" ) == 0 ) { m_nAlphaFunc = GL_GEQUAL; } if( m_nAlphaFunc ) m_nFlags |= QER_ALPHAFUNC; } if (g_ScripLibTable.m_pfnGetToken (true)) { m_fAlphaRef = (float) atof (token); } } else if (strcmpi (token, "cull") == 0) { if (g_ScripLibTable.m_pfnGetToken (true)) { if( stricmp( token, "none" ) == 0 || stricmp( token, "twosided" ) == 0 || stricmp( token, "disable" ) == 0 ) { m_nCull = 2; } else if( stricmp( token, "back" ) == 0 || stricmp( token, "backside" ) == 0 || stricmp( token, "backsided" ) == 0 ) { m_nCull = 1; } if( m_nCull ) m_nFlags |= QER_CULL; } } else if (strcmpi (token, "surfaceparm") == 0) { if (g_ScripLibTable.m_pfnGetToken (true)) { if (strcmpi (token, "fog") == 0) { m_nFlags |= QER_FOG; if (m_fTrans == 1.0f) // has not been explicitly set by qer_trans { m_fTrans = 0.35f; } } else if (strcmpi (token, "nodraw") == 0) { m_nFlags |= QER_NODRAW; } else if (strcmpi (token, "nonsolid") == 0) { m_nFlags |= QER_NONSOLID; } else if (strcmpi (token, "water") == 0) { m_nFlags |= QER_WATER; } else if (strcmpi (token, "lava") == 0) { m_nFlags |= QER_LAVA; } } } } if (nMatch != 0) return false; } return true; } void CShader::RegisterActivate () { // fill the qtexture_t with shader information //++timo FIXME: a lot of that won't be necessary, will be stored at IShader* level // strcpy (m_pTexture->shadername, m_Name); // this flag is set only if we have a shaderfile name // if (m_ShaderFileName[0] != '\0') // m_pTexture->bFromShader = true; // else // m_pTexture->bFromShader = false; //++timo FIXME: what do we do with that? //m_pTexture->fTrans = pInfo->m_fTransValue; // m_pTexture->fTrans = 1.0f; // if != 1.0 it's ot getting drawn in Cam_Draw // m_pTexture->nShaderFlags = m_nFlags; // store in the active shaders list (if necessary) g_ActiveShaders.AddSingle (this); // when you activate a shader, it gets displayed in the texture browser m_bDisplayed = true; IncRef (); } void CShader::Try_Activate () { m_pTexture = QERApp_Try_Texture_ForName (m_strTextureName.GetBuffer()); if (m_pTexture) RegisterActivate (); } // Hydra: now returns false if the ORIGINAL shader could not be activated // (missing texture, or incorrect shader script), true otherwise // the shader is still activated in all cases. bool CShader::Activate () { Try_Activate (); if (!m_pTexture) { m_pTexture = QERApp_Texture_ForName2 (SHADER_NOTEX); RegisterActivate (); return false; } return true; } void WINAPI QERApp_LoadShaderFile (const char *filename) { char *pBuff; int nSize = vfsLoadFile (filename, reinterpret_cast < void **>(&pBuff), 0); if (nSize > 0) { Sys_Printf ("Parsing shaderfile %s\n", filename); g_ScripLibTable.m_pfnStartTokenParsing (pBuff); while (g_ScripLibTable.m_pfnGetToken (true)) { // first token should be the path + name.. (from base) CShader *pShader = new CShader (); // we want the relative filename only, it's easier for later lookup .. see QERApp_ReloadShaderFile char cTmp[1024]; g_FuncTable.m_pfnQE_ConvertDOSToUnixName (cTmp, filename); // given the vfs, we should not store the full path //pShader->setShaderFileName( filename + strlen(ValueForKey(g_qeglobals.d_project_entity, "basepath"))); pShader->setShaderFileName (filename); if (pShader->Parse ()) { // do we already have this shader? //++timo NOTE: this may a bit slow, we may need to use a map instead of a dumb list if (g_Shaders.Shader_ForName (pShader->getName ()) != NULL) { #ifdef _DEBUG Sys_Printf ("WARNING: shader %s is already in memory, definition in %s ignored.\n", pShader->getName (), filename); #endif delete pShader; } else { pShader->IncRef (); g_Shaders.Add ((void *) pShader); } } else { Sys_Printf ("Error parsing shader %s\n", pShader->getName ()); delete pShader; } } vfsFreeFile (pBuff); } else { Sys_Printf ("Unable to read shaderfile %s\n", filename); } } IShader *WINAPI QERApp_Try_Shader_ForName (const char *name) { // look for the shader CShader *pShader = g_Shaders.Shader_ForName (name); if (!pShader) // not found return NULL; // we may need to load the texture or use the "shader without texture" one pShader->Activate (); pShader->SetDisplayed (true); return pShader; } IShader *WINAPI QERApp_CreateShader_ForTextureName (const char *name) { CShader *pShader; pShader = new CShader; // CreateDefault expects a texture / shader name relative to the "textures" directory // (cause shader names are reletive to "textures/") pShader->CreateDefault (name); // hook it into the shader list g_Shaders.Add ((void *) pShader); pShader->IncRef (); // if it can't find the texture, SHADER_NOT_FOUND will be used // Hydra: display an error message, so the user can quickly find a list of missing // textures by looking at the console. if (!pShader->Activate ()) { Sys_Printf ("WARNING: Activate shader failed for %s\n",pShader->getName()); } pShader->SetDisplayed (true); return pShader; } IShader *WINAPI QERApp_Shader_ForName (const char *name) { if (name == NULL || strlen (name) == 0) { // Hydra: This error can occur if the user loaded a map with/dropped an entity that // did not set a texture name "(r g b)" - check the entity definition loader g_FuncTable.m_pfnSysFPrintf (SYS_ERR, "FIXME: name == NULL || strlen(name) == 0 in QERApp_Shader_ForName\n"); return QERApp_Shader_ForName (SHADER_NOT_FOUND); } // entities that should be represented with plain colors instead of textures // request a texture name with (r g b) (it's stored in their class_t) if (name[0] == '(') { return QERApp_ColorShader_ForName (name); } CShader *pShader = static_cast < CShader * >(QERApp_Try_Shader_ForName (name)); if (pShader) { pShader->SetDisplayed (true); return pShader; } return QERApp_CreateShader_ForTextureName (name); } qtexture_t *WINAPI QERApp_Try_Texture_ForName (const char *name) { qtexture_t *q; // char f1[1024], f2[1024]; unsigned char *pPixels = NULL; int nWidth, nHeight; // convert the texture name to the standard format we use in qtexture_t const char *stdName = QERApp_CleanTextureName (name); // use the hash table q = (qtexture_t*)g_hash_table_lookup (g_ShadersTable.m_pfnQTexmap (), stdName); if (q) return q; #ifdef QTEXMAP_DEBUG for (q = g_qeglobals.d_qtextures; q; q = q->next) { if (!strcmp (stdName, q->name)) { Sys_Printf ("ERROR: %s is not in texture map, but was found in texture list\n"); return q; } } #endif g_FuncTable.m_pfnLoadImage (name, &pPixels, &nWidth, &nHeight); if (!pPixels) return NULL; // we failed else Sys_Printf ("LOADED: %s\n", name); // instanciate a new qtexture_t // NOTE: when called by a plugin we must make sure we have set Radiant's GL context before binding the texture // we'll be binding the GL texture now // need to check we are using a right GL context // with GL plugins that have their own window, the GL context may be the plugin's, in which case loading textures will bug // g_QglTable.m_pfn_glwidget_make_current (g_QglTable.m_pfn_GetQeglobalsGLWidget ()); q = g_FuncTable.m_pfnLoadTextureRGBA (pPixels, nWidth, nHeight); if (!q) return NULL; g_free (pPixels); strcpy (q->name, name); // only strip extension if extension there is! if (q->name[strlen (q->name) - 4] == '.') q->name[strlen (q->name) - 4] = '\0'; // hook into the main qtexture_t list qtexture_t **d_qtextures = g_ShadersTable.m_pfnQTextures (); q->next = *d_qtextures; *d_qtextures = q; // push it in the map g_hash_table_insert (g_ShadersTable.m_pfnQTexmap (), q->name, q); return q; } int WINAPI QERApp_HasShader (const char *pName) { // mickey check the global shader array for existense of pName CShader *pShader = g_Shaders.Shader_ForName (pName); if (pShader) return 1; return 0; } IShader *WINAPI QERApp_Shader_ForName_NoLoad (const char *pName) { CShader *pShader = g_Shaders.Shader_ForName (pName); return pShader; } /*! This should NEVER return NULL, it is the last-chance call in the load cascade */ qtexture_t *WINAPI QERApp_Texture_ForName2 (const char *filename) { qtexture_t *q; q = QERApp_Try_Texture_ForName (filename); if (q) return q; // not found? use "texture not found" q = QERApp_Try_Texture_ForName (SHADER_NOT_FOUND); if (q) return q; // still not found? this is a fatal error g_FuncTable.m_pfnError("Failed to load " SHADER_NOT_FOUND ". Looks like your installation is broken / missing some essential elements."); return NULL; } void CShader::CreateColor (const char *name) { // parse sscanf (name, "(%g %g %g)", m_vColor, m_vColor + 1, m_vColor + 2); m_strTextureName = name; setName ("color"); // create the qtexture_t qtexture_t *q1 = QERApp_Texture_ForName2 (SHADER_NOT_FOUND); // copy this one qtexture_t *q2 = new qtexture_t; memcpy (q2, q1, sizeof (qtexture_t)); strcpy (q2->name, m_strTextureName.GetBuffer ()); VectorCopy (m_vColor, q2->color); m_pTexture = q2; } IShader *WINAPI QERApp_ColorShader_ForName (const char *name) { CShader *pShader = new CShader (); pShader->CreateColor (name); // hook it into the shader list pShader->IncRef (); g_Shaders.Add ((void *) pShader); return pShader; } void CShaderArray::ReleaseForShaderFile (const char *name) { int i; // decref for (i = 0; i < CPtrArray::GetSize (); i++) { IShader *pShader = static_cast < IShader * >(CPtrArray::GetAt (i)); if (!strcmp (name, pShader->getShaderFileName ())) { pShader->DecRef (); CPtrArray::RemoveAt (i); i--; // get ready for next loop } } } void WINAPI QERApp_ReloadShaderFile (const char *name) { brush_t *b; face_t *f; brush_t *active_brushes; brush_t *selected_brushes; brush_t *filtered_brushes; // Sys_Printf("TODO: QERApp_ReloadShaderFile\n"); active_brushes = g_DataTable.m_pfnActiveBrushes (); selected_brushes = g_DataTable.m_pfnSelectedBrushes (); filtered_brushes = g_DataTable.m_pfnFilteredBrushes (); #ifdef _DEBUG // check the shader name is a reletive path // I hacked together a few quick tests to make sure :-) if (strstr (name, ":\\") || !strstr (name, "scripts")) Sys_Printf ("WARNING: is %s a reletive path to a shader file? (QERApp_ReloadShaderFile\n"); #endif // in the actives and global shaders lists, decref and unhook the shaders //++timo NOTE: maybe we'd like to keep track of the shaders we are unhooking? g_ActiveShaders.ReleaseForShaderFile (name); g_Shaders.ReleaseForShaderFile (name); // go through a reload of the shader file QERApp_LoadShaderFile (name); // scan all the brushes, replace all the old ones by refs to their new equivalents for (b = active_brushes->next; b != NULL && b != active_brushes; b = b->next) { if (b->patchBrush && !strcmp (b->pPatch->pShader->getShaderFileName (), name)) SetShader (b->pPatch); else for (f = b->brush_faces; f; f = f->next) if (!strcmp (f->pShader->getShaderFileName (), name)) SetShader (f); } for (b = selected_brushes->next; b != NULL && b != selected_brushes; b = b->next) { if (b->patchBrush && !strcmp (b->pPatch->pShader->getShaderFileName (), name)) SetShader (b->pPatch); else for (f = b->brush_faces; f; f = f->next) if (!strcmp (f->pShader->getShaderFileName (), name)) SetShader (f); } // do that to the filtered brushes as well (we might have some region compiling going on) for (b = filtered_brushes->next; b != NULL && b != filtered_brushes; b = b->next) { if (b->patchBrush && !strcmp (b->pPatch->pShader->getShaderFileName (), name)) SetShader (b->pPatch); else for (f = b->brush_faces; f; f = f->next) if (!strcmp (f->pShader->getShaderFileName (), name)) SetShader (f); } // call Texture_ShowInUse to clean and display only what's required g_ShadersTable.m_pfnTexture_ShowInuse (); QERApp_SortActiveShaders (); g_FuncTable.m_pfnSysUpdateWindows (W_TEXTURE); } void CShaderArray::SetDisplayed (bool b) { int i, count; count = CPtrArray::GetSize (); for (i = 0; i < count; i++) static_cast < IShader * >(CPtrArray::GetAt (i))->SetDisplayed (b); } void CShaderArray::SetInUse (bool b) { int i, count; count = CPtrArray::GetSize (); for (i = 0; i < count; i++) static_cast < IShader * >(CPtrArray::GetAt (i))->SetInUse (b); } // Set the IsDisplayed flag on all active shaders void WINAPI QERApp_ActiveShaders_SetDisplayed (bool b) { g_ActiveShaders.SetDisplayed (b); } void WINAPI QERApp_ActiveShaders_SetInUse (bool b) { g_ActiveShaders.SetInUse (b); } // ============================================================================= // SYNAPSE bool CSynapseClientShaders::RequestAPI(APIDescriptor_t *pAPI) { if (!strcmp(pAPI->major_name, SHADERS_MAJOR)) { _QERShadersTable* pTable= static_cast<_QERShadersTable*>(pAPI->mpTable); pTable->m_pfnFreeShaders = QERApp_FreeShaders; pTable->m_pfnReloadShaders = QERApp_ReloadShaders; pTable->m_pfnLoadShadersFromDir = QERApp_LoadShadersFromDir; pTable->m_pfnReloadShaderFile = QERApp_ReloadShaderFile; pTable->m_pfnLoadShaderFile = QERApp_LoadShaderFile; pTable->m_pfnHasShader = QERApp_HasShader; pTable->m_pfnTry_Shader_ForName = QERApp_Try_Shader_ForName; pTable->m_pfnShader_ForName = QERApp_Shader_ForName; pTable->m_pfnTry_Texture_ForName = QERApp_Try_Texture_ForName; pTable->m_pfnTexture_ForName = QERApp_Texture_ForName2; pTable->m_pfnGetActiveShaderCount = QERApp_GetActiveShaderCount; pTable->m_pfnColorShader_ForName = QERApp_ColorShader_ForName; pTable->m_pfnShader_ForName_NoLoad = QERApp_Shader_ForName_NoLoad; pTable->m_pfnActiveShaders_SetInUse = QERApp_ActiveShaders_SetInUse; pTable->m_pfnSortActiveShaders = QERApp_SortActiveShaders; pTable->m_pfnActiveShader_ForTextureName = QERApp_ActiveShader_ForTextureName; pTable->m_pfnCreateShader_ForTextureName = QERApp_CreateShader_ForTextureName; pTable->m_pfnActiveShaders_SetDisplayed = QERApp_ActiveShaders_SetDisplayed; pTable->m_pfnActiveShader_ForIndex = QERApp_ActiveShader_ForIndex; pTable->m_pfnCleanTextureName = QERApp_CleanTextureName; return true; } Syn_Printf("ERROR: RequestAPI( '%s' ) not found in '%s'\n", pAPI->major_name, GetInfo()); return false; }