X-Git-Url: https://de.git.xonotic.org/?p=xonotic%2Fnetradiant.git;a=blobdiff_plain;f=plugins%2Fshaders%2Fshaders.cpp;h=c4213b870c90a0ebc12c7388ebb29555d0ca6c5f;hp=b151c505e5f3ee10c8fa9a063825b4272e69128f;hb=b1bfb19ecd5ec8355065b2028a6de0c850197b2d;hpb=ab3a99dbbe84a0d130fea4d0ceb7b79d7ed07eb7 diff --git a/plugins/shaders/shaders.cpp b/plugins/shaders/shaders.cpp index b151c505..c4213b87 100644 --- a/plugins/shaders/shaders.cpp +++ b/plugins/shaders/shaders.cpp @@ -1,989 +1,989 @@ -/* -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; -} +/* +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; +}