2 Copyright (c) 2001, Loki software, inc.
5 Redistribution and use in source and binary forms, with or without modification,
6 are permitted provided that the following conditions are met:
8 Redistributions of source code must retain the above copyright notice, this list
9 of conditions and the following disclaimer.
11 Redistributions in binary form must reproduce the above copyright notice, this
12 list of conditions and the following disclaimer in the documentation and/or
13 other materials provided with the distribution.
15 Neither the name of Loki software nor the names of its contributors may be used
16 to endorse or promote products derived from this software without specific prior
19 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
20 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22 DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
23 DIRECT,INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25 LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
26 ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 // Shaders Manager Plugin
34 // Leonardo Zide (leo@lokigames.com)
44 #include "ifilesystem.h"
46 #include "iscriplib.h"
47 #include "itextures.h"
48 #include "qerplugin.h"
51 #include <glib/gslist.h>
53 #include "debugging/debugging.h"
54 #include "string/string.h"
55 #include "math/vector.h"
56 #include "generic/callback.h"
57 #include "generic/referencecounted.h"
58 #include "stream/memstream.h"
59 #include "stream/stringstream.h"
60 #include "stream/textfilestream.h"
65 #include "shaderlib.h"
66 #include "texturelib.h"
68 #include "moduleobservers.h"
69 #include "archivelib.h"
72 const char* g_shadersExtension = "";
73 const char* g_shadersDirectory = "";
74 bool g_enableDefaultShaders = true;
75 ShaderLanguage g_shaderLanguage = SHADERLANGUAGE_QUAKE3;
76 bool g_useShaderList = true;
77 _QERPlugImageTable* g_bitmapModule = 0;
78 const char* g_texturePrefix = "textures/";
80 void ActiveShaders_IteratorBegin();
81 bool ActiveShaders_IteratorAtEnd();
82 IShader *ActiveShaders_IteratorCurrent();
83 void ActiveShaders_IteratorIncrement();
84 Callback g_ActiveShadersChangedNotify;
87 void LoadShaderFile (const char *filename);
88 qtexture_t *Texture_ForName (const char *filename);
92 NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX:
93 SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this
94 SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it
95 this was in the initial design of the shader code since early GtkRadiant alpha, and got sort of foxed in 1.2 and put back in
98 Image* loadBitmap(void* environment, const char* name)
100 DirectoryArchiveFile file(name, name);
103 return g_bitmapModule->loadImage(file);
108 inline byte* getPixel(byte* pixels, int width, int height, int x, int y)
110 return pixels + (((((y + height) % height) * width) + ((x + width) % width)) * 4);
120 Image& convertHeightmapToNormalmap(Image& heightmap, float scale)
122 int w = heightmap.getWidth();
123 int h = heightmap.getHeight();
125 Image& normalmap = *(new RGBAImage(heightmap.getWidth(), heightmap.getHeight()));
127 byte* in = heightmap.getRGBAPixels();
128 byte* out = normalmap.getRGBAPixels();
132 const int kernelSize = 2;
133 KernelElement kernel_du[kernelSize] = {
137 KernelElement kernel_dv[kernelSize] = {
143 const int kernelSize = 6;
144 KernelElement kernel_du[kernelSize] = {
152 KernelElement kernel_dv[kernelSize] = {
169 for(KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i)
171 du += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w;
174 for(KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i)
176 dv += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w;
179 float nx = -du * scale;
180 float ny = -dv * scale;
184 float norm = 1.0/sqrt(nx*nx + ny*ny + nz*nz);
185 out[0] = float_to_integer(((nx * norm) + 1) * 127.5);
186 out[1] = float_to_integer(((ny * norm) + 1) * 127.5);
187 out[2] = float_to_integer(((nz * norm) + 1) * 127.5);
200 Image* loadHeightmap(void* environment, const char* name)
202 Image* heightmap = GlobalTexturesCache().loadImage(name);
205 Image& normalmap = convertHeightmapToNormalmap(*heightmap, *reinterpret_cast<float*>(environment));
206 heightmap->release();
213 Image* loadSpecial(void* environment, const char* name)
215 if(*name == '_') // special image
217 StringOutputStream bitmapName(256);
218 bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".bmp";
219 Image* image = loadBitmap(environment, bitmapName.c_str());
225 return GlobalTexturesCache().loadImage(name);
230 // clean a texture name to the qtexture_t name format we use internally
231 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
232 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
233 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
234 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
235 void parseTextureName(CopiedString& name, const char* token)
237 StringOutputStream cleaned(256);
238 cleaned << PathCleaned(token);
239 name = CopiedString(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str())); // remove extension
242 bool Tokeniser_parseTextureName(Tokeniser& tokeniser, CopiedString& name)
244 const char* token = tokeniser.getToken();
247 Tokeniser_unexpectedError(tokeniser, token, "#texture-name");
250 parseTextureName(name, token);
254 void parseShaderName(CopiedString& name, const char* token)
256 StringOutputStream cleaned(256);
257 cleaned << PathCleaned(token);
258 name = cleaned.c_str();
261 bool Tokeniser_parseShaderName(Tokeniser& tokeniser, CopiedString& name)
263 const char* token = tokeniser.getToken();
266 Tokeniser_unexpectedError(tokeniser, token, "#shader-name");
269 parseShaderName(name, token);
273 bool Tokeniser_parseString(Tokeniser& tokeniser, CopiedString& string)
275 const char* token = tokeniser.getToken();
278 Tokeniser_unexpectedError(tokeniser, token, "#string");
287 typedef std::list<CopiedString> ShaderParameters;
288 typedef std::list<CopiedString> ShaderArguments;
290 typedef CopiedString TextureExpression;
291 typedef CopiedString ShaderValue;
292 typedef std::pair<CopiedString, CopiedString> BlendFuncExpression;
296 std::size_t m_refcount;
300 ShaderParameters m_params;
302 TextureExpression m_textureName;
303 TextureExpression m_diffuse;
304 TextureExpression m_bump;
305 ShaderValue m_heightmapScale;
306 TextureExpression m_specular;
307 TextureExpression m_lightFalloffImage;
313 IShader::EAlphaFunc m_AlphaFunc;
316 IShader::ECull m_Cull;
331 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
332 if(--m_refcount == 0)
338 std::size_t refcount()
343 const char* getName() const
345 return m_Name.c_str();
347 void setName(const char* name)
352 // -----------------------------------------
354 bool parseDoom3(Tokeniser& tokeniser);
355 bool parseQuake3(Tokeniser& tokeniser);
356 bool parseTemplate(Tokeniser& tokeniser);
359 void CreateDefault(const char *name)
361 if(g_enableDefaultShaders)
363 m_textureName = name;
373 class MapLayerTemplate
375 TextureExpression m_texture;
376 BlendFuncExpression m_blendFunc;
377 bool m_clampToBorder;
378 ShaderValue m_alphaTest;
380 MapLayerTemplate(const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest) :
382 m_blendFunc(blendFunc),
383 m_clampToBorder(false),
384 m_alphaTest(alphaTest)
387 const TextureExpression& texture() const
391 const BlendFuncExpression& blendFunc() const
395 bool clampToBorder() const
397 return m_clampToBorder;
399 const ShaderValue& alphaTest() const
404 typedef std::vector<MapLayerTemplate> MapLayers;
409 bool Doom3Shader_parseHeightmap(Tokeniser& tokeniser, CopiedString& bump, CopiedString& heightmapScale)
411 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
412 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
413 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
414 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, heightmapScale));
415 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
419 bool Doom3Shader_parseAddnormals(Tokeniser& tokeniser, CopiedString& bump)
421 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
422 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
423 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
424 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "heightmap"));
425 CopiedString heightmapName;
426 CopiedString heightmapScale;
427 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, heightmapName, heightmapScale));
428 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
432 bool Doom3Shader_parseBumpmap(Tokeniser& tokeniser, CopiedString& bump, CopiedString& heightmapScale)
434 const char* token = tokeniser.getToken();
437 Tokeniser_unexpectedError(tokeniser, token, "#bumpmap");
440 if(string_equal(token, "heightmap"))
442 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, bump, heightmapScale));
444 else if(string_equal(token, "addnormals"))
446 RETURN_FALSE_IF_FAIL(Doom3Shader_parseAddnormals(tokeniser, bump));
450 parseTextureName(bump, token);
468 CopiedString m_texture;
469 BlendFuncExpression m_blendFunc;
470 bool m_clampToBorder;
471 ShaderValue m_alphaTest;
472 ShaderValue m_heightmapScale;
474 LayerTemplate() : m_type(LAYER_NONE), m_blendFunc("GL_ONE", "GL_ZERO"), m_clampToBorder(false), m_alphaTest("-1"), m_heightmapScale("0")
479 bool parseShaderParameters(Tokeniser& tokeniser, ShaderParameters& params)
481 Tokeniser_parseToken(tokeniser, "(");
484 const char* param = tokeniser.getToken();
485 if(string_equal(param, ")"))
489 params.push_back(param);
490 const char* comma = tokeniser.getToken();
491 if(string_equal(comma, ")"))
495 if(!string_equal(comma, ","))
497 Tokeniser_unexpectedError(tokeniser, comma, ",");
504 bool ShaderTemplate::parseTemplate(Tokeniser& tokeniser)
506 m_Name = tokeniser.getToken();
507 if(!parseShaderParameters(tokeniser, m_params))
509 globalErrorStream() << "shader template: " << makeQuoted(m_Name.c_str()) << ": parameter parse failed\n";
513 return parseDoom3(tokeniser);
516 bool ShaderTemplate::parseDoom3(Tokeniser& tokeniser)
518 LayerTemplate currentLayer;
521 // we need to read until we hit a balanced }
525 tokeniser.nextLine();
526 const char* token = tokeniser.getToken();
531 if(string_equal(token, "{"))
536 else if(string_equal(token, "}"))
539 if(depth < 0) // error
543 if(depth == 0) // end of shader
547 if(depth == 1) // end of layer
549 if(currentLayer.m_type == LAYER_DIFFUSEMAP)
551 m_diffuse = currentLayer.m_texture;
553 else if(currentLayer.m_type == LAYER_BUMPMAP)
555 m_bump = currentLayer.m_texture;
557 else if(currentLayer.m_type == LAYER_SPECULARMAP)
559 m_specular = currentLayer.m_texture;
561 else if(!string_empty(currentLayer.m_texture.c_str()))
563 m_layers.push_back(MapLayerTemplate(
564 currentLayer.m_texture.c_str(),
565 currentLayer.m_blendFunc,
566 currentLayer.m_clampToBorder,
567 currentLayer.m_alphaTest
570 currentLayer.m_type = LAYER_NONE;
571 currentLayer.m_texture = "";
576 if(depth == 2) // in layer
578 if(string_equal_nocase(token, "blend"))
580 const char* blend = tokeniser.getToken();
584 Tokeniser_unexpectedError(tokeniser, blend, "#blend");
588 if(string_equal_nocase(blend, "diffusemap"))
590 currentLayer.m_type = LAYER_DIFFUSEMAP;
592 else if(string_equal_nocase(blend, "bumpmap"))
594 currentLayer.m_type = LAYER_BUMPMAP;
596 else if(string_equal_nocase(blend, "specularmap"))
598 currentLayer.m_type = LAYER_SPECULARMAP;
602 currentLayer.m_blendFunc.first = blend;
604 const char* comma = tokeniser.getToken();
608 Tokeniser_unexpectedError(tokeniser, comma, "#comma");
612 if(string_equal(comma, ","))
614 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, currentLayer.m_blendFunc.second));
618 currentLayer.m_blendFunc.second = "";
619 tokeniser.ungetToken();
623 else if(string_equal_nocase(token, "map"))
625 if(currentLayer.m_type == LAYER_BUMPMAP)
627 RETURN_FALSE_IF_FAIL(Doom3Shader_parseBumpmap(tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale));
631 const char* map = tokeniser.getToken();
635 Tokeniser_unexpectedError(tokeniser, map, "#map");
639 if(string_equal(map, "makealpha"))
641 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
642 const char* texture = tokeniser.getToken();
645 Tokeniser_unexpectedError(tokeniser, texture, "#texture");
648 currentLayer.m_texture = texture;
649 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
653 parseTextureName(currentLayer.m_texture, map);
657 else if(string_equal_nocase(token, "zeroclamp"))
659 currentLayer.m_clampToBorder = true;
662 else if(string_equal_nocase(token, "alphaTest"))
664 Tokeniser_getFloat(tokeniser, currentLayer.m_alphaTest);
670 if(string_equal_nocase(token, "qer_editorimage"))
672 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
674 else if (string_equal_nocase(token, "qer_trans"))
676 m_fTrans = string_read_float(tokeniser.getToken());
677 m_nFlags |= QER_TRANS;
679 else if (string_equal_nocase(token, "translucent"))
682 m_nFlags |= QER_TRANS;
684 else if (string_equal(token, "DECAL_MACRO"))
687 m_nFlags |= QER_TRANS;
689 else if (string_equal_nocase(token, "bumpmap"))
691 RETURN_FALSE_IF_FAIL(Doom3Shader_parseBumpmap(tokeniser, m_bump, m_heightmapScale));
693 else if (string_equal_nocase(token, "diffusemap"))
695 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_diffuse));
697 else if (string_equal_nocase(token, "specularmap"))
699 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_specular));
701 else if (string_equal_nocase(token, "twosided"))
703 m_Cull = IShader::eCullNone;
704 m_nFlags |= QER_CULL;
706 else if (string_equal_nocase(token, "nodraw"))
708 m_nFlags |= QER_NODRAW;
710 else if (string_equal_nocase(token, "nonsolid"))
712 m_nFlags |= QER_NONSOLID;
714 else if (string_equal_nocase(token, "liquid"))
716 m_nFlags |= QER_WATER;
718 else if (string_equal_nocase(token, "areaportal"))
720 m_nFlags |= QER_AREAPORTAL;
722 else if (string_equal_nocase(token, "playerclip")
723 || string_equal_nocase(token, "monsterclip")
724 || string_equal_nocase(token, "ikclip")
725 || string_equal_nocase(token, "moveableclip"))
727 m_nFlags |= QER_CLIP;
729 if (string_equal_nocase(token, "fogLight"))
733 else if (!isFog && string_equal_nocase(token, "lightFalloffImage"))
735 const char* lightFalloffImage = tokeniser.getToken();
736 if(lightFalloffImage == 0)
738 Tokeniser_unexpectedError(tokeniser, lightFalloffImage, "#lightFalloffImage");
741 if(string_equal_nocase(lightFalloffImage, "makeintensity"))
743 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
745 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, name));
746 m_lightFalloffImage = name.c_str();
747 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
751 m_lightFalloffImage = lightFalloffImage;
757 if(string_empty(m_textureName.c_str()))
759 m_textureName = m_diffuse;
765 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
766 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
768 ShaderTemplateMap g_shaders;
769 ShaderTemplateMap g_shaderTemplates;
771 ShaderTemplate* findTemplate(const char* name)
773 ShaderTemplateMap::iterator i = g_shaderTemplates.find(name);
774 if(i != g_shaderTemplates.end())
776 return (*i).second.get();
781 class ShaderDefinition
784 ShaderDefinition(ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename)
785 : shaderTemplate(shaderTemplate), args(args), filename(filename)
788 ShaderTemplate* shaderTemplate;
789 ShaderArguments args;
790 const char* filename;
793 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
795 ShaderDefinitionMap g_shaderDefinitions;
797 bool parseTemplateInstance(Tokeniser& tokeniser, const char* filename)
800 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, name));
801 const char* templateName = tokeniser.getToken();
802 ShaderTemplate* shaderTemplate = findTemplate(templateName);
803 if(shaderTemplate == 0)
805 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": shader template not found: " << makeQuoted(templateName) << "\n";
808 ShaderArguments args;
809 if(!parseShaderParameters(tokeniser, args))
811 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": argument parse failed\n";
815 if(shaderTemplate != 0)
817 if(!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate, args, filename))).second)
819 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": already exists, second definition ignored\n";
826 const char* evaluateShaderValue(const char* value, const ShaderParameters& params, const ShaderArguments& args)
828 ShaderArguments::const_iterator j = args.begin();
829 for(ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j)
831 const char* other = (*i).c_str();
832 if(string_equal(value, other))
840 ///\todo BlendFunc parsing
841 BlendFunc evaluateBlendFunc(const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args)
843 return BlendFunc(BLEND_ONE, BLEND_ZERO);
846 qtexture_t* evaluateTexture(const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader())
848 StringOutputStream result(64);
849 const char* expression = texture.c_str();
850 const char* end = expression + string_length(expression);
851 if(!string_empty(expression))
855 const char* best = end;
856 const char* bestParam = 0;
857 const char* bestArg = 0;
858 ShaderArguments::const_iterator j = args.begin();
859 for(ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j)
861 const char* found = strstr(expression, (*i).c_str());
862 if(found != 0 && found < best)
865 bestParam = (*i).c_str();
866 bestArg = (*j).c_str();
871 result << StringRange(expression, best);
872 result << PathCleaned(bestArg);
873 expression = best + string_length(bestParam);
880 result << expression;
882 return GlobalTexturesCache().capture(loader, result.c_str());
885 float evaluateFloat(const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args)
887 const char* result = evaluateShaderValue(value.c_str(), params, args);
889 if(!string_parse_float(result, f))
891 globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n";
896 BlendFactor evaluateBlendFactor(const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args)
898 const char* result = evaluateShaderValue(value.c_str(), params, args);
900 if(string_equal_nocase(result, "gl_zero"))
904 if(string_equal_nocase(result, "gl_one"))
908 if(string_equal_nocase(result, "gl_src_color"))
910 return BLEND_SRC_COLOUR;
912 if(string_equal_nocase(result, "gl_one_minus_src_color"))
914 return BLEND_ONE_MINUS_SRC_COLOUR;
916 if(string_equal_nocase(result, "gl_src_alpha"))
918 return BLEND_SRC_ALPHA;
920 if(string_equal_nocase(result, "gl_one_minus_src_alpha"))
922 return BLEND_ONE_MINUS_SRC_ALPHA;
924 if(string_equal_nocase(result, "gl_dst_color"))
926 return BLEND_DST_COLOUR;
928 if(string_equal_nocase(result, "gl_one_minus_dst_color"))
930 return BLEND_ONE_MINUS_DST_COLOUR;
932 if(string_equal_nocase(result, "gl_dst_alpha"))
934 return BLEND_DST_ALPHA;
936 if(string_equal_nocase(result, "gl_one_minus_dst_alpha"))
938 return BLEND_ONE_MINUS_DST_ALPHA;
940 if(string_equal_nocase(result, "gl_src_alpha_saturate"))
942 return BLEND_SRC_ALPHA_SATURATE;
945 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted(result) << "\n";
949 class CShader : public IShader
951 std::size_t m_refcount;
953 const ShaderTemplate& m_template;
954 const ShaderArguments& m_args;
955 const char* m_filename;
956 // name is shader-name, otherwise texture-name (if not a real shader)
959 qtexture_t* m_pTexture;
960 qtexture_t* m_notfound;
961 qtexture_t* m_pDiffuse;
962 float m_heightmapScale;
964 qtexture_t* m_pSpecular;
965 qtexture_t* m_pLightFalloffImage;
966 BlendFunc m_blendFunc;
972 static bool m_lightingEnabled;
974 CShader(const ShaderDefinition& definition) :
976 m_template(*definition.shaderTemplate),
977 m_args(definition.args),
978 m_filename(definition.filename),
979 m_blendFunc(BLEND_ONE, BLEND_ZERO),
995 ASSERT_MESSAGE(m_refcount == 0, "deleting active shader");
998 // IShaders implementation -----------------
1005 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
1006 if(--m_refcount == 0)
1012 std::size_t refcount()
1017 // get/set the qtexture_t* Radiant uses to represent this shader object
1018 qtexture_t* getTexture() const
1022 qtexture_t* getDiffuse() const
1026 qtexture_t* getBump() const
1030 qtexture_t* getSpecular() const
1035 const char* getName() const
1037 return m_Name.c_str();
1039 bool IsInUse() const
1043 void SetInUse(bool bInUse)
1046 g_ActiveShadersChangedNotify();
1048 // get the shader flags
1049 int getFlags() const
1051 return m_template.m_nFlags;
1053 // get the transparency value
1054 float getTrans() const
1056 return m_template.m_fTrans;
1058 // test if it's a true shader, or a default shader created to wrap around a texture
1059 bool IsDefault() const
1061 return string_empty(m_filename);
1063 // get the alphaFunc
1064 void getAlphaFunc(EAlphaFunc *func, float *ref) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
1065 BlendFunc getBlendFunc() const
1069 // get the cull type
1072 return m_template.m_Cull;
1074 // get shader file name (ie the file where this one is defined)
1075 const char* getShaderFileName() const
1079 // -----------------------------------------
1083 m_pTexture = evaluateTexture(m_template.m_textureName, m_template.m_params, m_args);
1085 if(m_pTexture->texture_number == 0)
1087 m_notfound = m_pTexture;
1090 StringOutputStream name(256);
1091 name << GlobalRadiant().getAppPath() << "bitmaps/" << (IsDefault() ? "notex.bmp" : "shadernotex.bmp");
1092 m_pTexture = GlobalTexturesCache().capture(LoadImageCallback(0, loadBitmap), name.c_str());
1098 if(m_layers.size() == 1)
1100 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
1101 if(!string_empty(blendFunc.second.c_str()))
1103 m_blendFunc = BlendFunc(
1104 evaluateBlendFactor(blendFunc.first.c_str(), m_template.m_params, m_args),
1105 evaluateBlendFactor(blendFunc.second.c_str(), m_template.m_params, m_args)
1110 const char* blend = evaluateShaderValue(blendFunc.first.c_str(), m_template.m_params, m_args);
1112 if(string_equal_nocase(blend, "add"))
1114 m_blendFunc = BlendFunc(BLEND_ONE, BLEND_ONE);
1116 else if(string_equal_nocase(blend, "filter"))
1118 m_blendFunc = BlendFunc(BLEND_DST_COLOUR, BLEND_ZERO);
1120 else if(string_equal_nocase(blend, "blend"))
1122 m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA);
1126 globalErrorStream() << "parsing blend value failed: " << makeQuoted(blend) << "\n";
1134 GlobalTexturesCache().release(m_pTexture);
1138 GlobalTexturesCache().release(m_notfound);
1141 unrealiseLighting();
1144 void realiseLighting()
1146 if(m_lightingEnabled)
1148 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
1149 if(!string_empty(m_template.m_heightmapScale.c_str()))
1151 m_heightmapScale = evaluateFloat(m_template.m_heightmapScale, m_template.m_params, m_args);
1152 loader = LoadImageCallback(&m_heightmapScale, loadHeightmap);
1154 m_pDiffuse = evaluateTexture(m_template.m_diffuse, m_template.m_params, m_args);
1155 m_pBump = evaluateTexture(m_template.m_bump, m_template.m_params, m_args, loader);
1156 m_pSpecular = evaluateTexture(m_template.m_specular, m_template.m_params, m_args);
1157 m_pLightFalloffImage = evaluateTexture(m_template.m_lightFalloffImage, m_template.m_params, m_args);
1159 for(ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i)
1161 m_layers.push_back(evaluateLayer(*i, m_template.m_params, m_args));
1166 void unrealiseLighting()
1168 if(m_lightingEnabled)
1170 GlobalTexturesCache().release(m_pDiffuse);
1171 GlobalTexturesCache().release(m_pBump);
1172 GlobalTexturesCache().release(m_pSpecular);
1174 GlobalTexturesCache().release(m_pLightFalloffImage);
1176 for(MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i)
1178 GlobalTexturesCache().release((*i).texture());
1184 void setName(const char* name)
1189 class MapLayer : public ShaderLayer
1191 qtexture_t* m_texture;
1192 BlendFunc m_blendFunc;
1193 bool m_clampToBorder;
1196 MapLayer(qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) :
1198 m_blendFunc(blendFunc),
1199 m_clampToBorder(false),
1200 m_alphaTest(alphaTest)
1203 qtexture_t* texture() const
1207 BlendFunc blendFunc() const
1211 bool clampToBorder() const
1213 return m_clampToBorder;
1215 float alphaTest() const
1221 static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args)
1224 evaluateTexture(layerTemplate.texture(), params, args),
1225 evaluateBlendFunc(layerTemplate.blendFunc(), params, args),
1226 layerTemplate.clampToBorder(),
1227 evaluateFloat(layerTemplate.alphaTest(), params, args)
1231 typedef std::vector<MapLayer> MapLayers;
1234 const ShaderLayer* firstLayer() const
1236 if(m_layers.empty())
1240 return &m_layers.front();
1242 void forEachLayer(const ShaderLayerCallback& callback) const
1244 for(MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i)
1250 qtexture_t* lightFalloffImage() const
1252 if(!string_empty(m_template.m_lightFalloffImage.c_str()))
1254 return m_pLightFalloffImage;
1260 bool CShader::m_lightingEnabled = false;
1262 typedef SmartPointer<CShader> ShaderPointer;
1263 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1265 shaders_t g_ActiveShaders;
1267 static shaders_t::iterator g_ActiveShadersIterator;
1269 void ActiveShaders_IteratorBegin()
1271 g_ActiveShadersIterator = g_ActiveShaders.begin();
1274 bool ActiveShaders_IteratorAtEnd()
1276 return g_ActiveShadersIterator == g_ActiveShaders.end();
1279 IShader *ActiveShaders_IteratorCurrent()
1281 return static_cast<CShader*>(g_ActiveShadersIterator->second);
1284 void ActiveShaders_IteratorIncrement()
1286 ++g_ActiveShadersIterator;
1289 void debug_check_shaders(shaders_t& shaders)
1291 for(shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i)
1293 ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced");
1297 // will free all GL binded qtextures and shaders
1298 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1302 // empty the actives shaders list
1303 debug_check_shaders(g_ActiveShaders);
1304 g_ActiveShaders.clear();
1306 g_shaderTemplates.clear();
1307 g_shaderDefinitions.clear();
1308 g_ActiveShadersChangedNotify();
1311 bool ShaderTemplate::parseQuake3(Tokeniser& tokeniser)
1313 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1314 m_textureName = m_Name;
1316 tokeniser.nextLine();
1318 // we need to read until we hit a balanced }
1322 tokeniser.nextLine();
1323 const char* token = tokeniser.getToken();
1328 if(string_equal(token, "{"))
1333 else if(string_equal(token, "}"))
1336 if(depth < 0) // underflow
1340 if(depth == 0) // end of shader
1350 if (string_equal_nocase(token, "qer_nocarve"))
1352 m_nFlags |= QER_NOCARVE;
1354 else if (string_equal_nocase(token, "qer_trans"))
1356 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans));
1357 m_nFlags |= QER_TRANS;
1359 else if (string_equal_nocase(token, "qer_editorimage"))
1361 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
1363 else if (string_equal_nocase(token, "qer_alphafunc"))
1365 const char* alphafunc = tokeniser.getToken();
1369 Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc");
1373 if(string_equal_nocase(alphafunc, "equal"))
1375 m_AlphaFunc = IShader::eEqual;
1377 else if(string_equal_nocase(alphafunc, "greater"))
1379 m_AlphaFunc = IShader::eGreater;
1381 else if(string_equal_nocase(alphafunc, "less"))
1383 m_AlphaFunc = IShader::eLess;
1385 else if(string_equal_nocase(alphafunc, "gequal"))
1387 m_AlphaFunc = IShader::eGEqual;
1389 else if(string_equal_nocase(alphafunc, "lequal"))
1391 m_AlphaFunc = IShader::eLEqual;
1395 m_AlphaFunc = IShader::eAlways;
1398 m_nFlags |= QER_ALPHATEST;
1400 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef));
1402 else if (string_equal_nocase(token, "cull"))
1404 const char* cull = tokeniser.getToken();
1408 Tokeniser_unexpectedError(tokeniser, cull, "#cull");
1412 if(string_equal_nocase(cull, "none")
1413 || string_equal_nocase(cull, "twosided")
1414 || string_equal_nocase(cull, "disable"))
1416 m_Cull = IShader::eCullNone;
1418 else if(string_equal_nocase(cull, "back")
1419 || string_equal_nocase(cull, "backside")
1420 || string_equal_nocase(cull, "backsided"))
1422 m_Cull = IShader::eCullBack;
1426 m_Cull = IShader::eCullBack;
1429 m_nFlags |= QER_CULL;
1431 else if (string_equal_nocase(token, "surfaceparm"))
1433 const char* surfaceparm = tokeniser.getToken();
1435 if(surfaceparm == 0)
1437 Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm");
1441 if (string_equal_nocase(surfaceparm, "fog"))
1443 m_nFlags |= QER_FOG;
1444 if (m_fTrans == 1.0f) // has not been explicitly set by qer_trans
1449 else if (string_equal_nocase(surfaceparm, "nodraw"))
1451 m_nFlags |= QER_NODRAW;
1453 else if (string_equal_nocase(surfaceparm, "nonsolid"))
1455 m_nFlags |= QER_NONSOLID;
1457 else if (string_equal_nocase(surfaceparm, "water"))
1459 m_nFlags |= QER_WATER;
1461 else if (string_equal_nocase(surfaceparm, "lava"))
1463 m_nFlags |= QER_LAVA;
1465 else if (string_equal_nocase(surfaceparm, "areaportal"))
1467 m_nFlags |= QER_AREAPORTAL;
1469 else if (string_equal_nocase(surfaceparm, "playerclip"))
1471 m_nFlags |= QER_CLIP;
1473 else if (string_equal_nocase(surfaceparm, "botclip"))
1475 m_nFlags |= QER_BOTCLIP;
1488 CopiedString m_texture;
1489 BlendFunc m_blendFunc;
1490 bool m_clampToBorder;
1492 float m_heightmapScale;
1494 Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1), m_heightmapScale(0)
1499 std::list<CopiedString> g_shaderFilenames;
1501 void ParseShaderFile(Tokeniser& tokeniser, const char* filename)
1503 g_shaderFilenames.push_back(filename);
1504 filename = g_shaderFilenames.back().c_str();
1505 tokeniser.nextLine();
1508 const char* token = tokeniser.getToken();
1515 if(string_equal(token, "table"))
1517 if(tokeniser.getToken() == 0)
1519 Tokeniser_unexpectedError(tokeniser, 0, "#table-name");
1522 if(!Tokeniser_parseToken(tokeniser, "{"))
1528 const char* option = tokeniser.getToken();
1529 if(string_equal(option, "{"))
1533 const char* value = tokeniser.getToken();
1534 if(string_equal(value, "}"))
1540 if(!Tokeniser_parseToken(tokeniser, "}"))
1550 if(string_equal(token, "guide"))
1552 parseTemplateInstance(tokeniser, filename);
1556 if(!string_equal(token, "material")
1557 && !string_equal(token, "particle")
1558 && !string_equal(token, "skin"))
1560 tokeniser.ungetToken();
1562 // first token should be the path + name.. (from base)
1564 if(!Tokeniser_parseShaderName(tokeniser, name))
1567 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1568 shaderTemplate->setName(name.c_str());
1570 g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1572 bool result = (g_shaderLanguage == SHADERLANGUAGE_QUAKE3)
1573 ? shaderTemplate->parseQuake3(tokeniser)
1574 : shaderTemplate->parseDoom3(tokeniser);
1577 // do we already have this shader?
1578 if(!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(), ShaderDefinition(shaderTemplate.get(), ShaderArguments(), filename))).second)
1581 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1587 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1595 void parseGuideFile(Tokeniser& tokeniser, const char* filename)
1597 tokeniser.nextLine();
1600 const char* token = tokeniser.getToken();
1607 if(string_equal(token, "guide"))
1609 // first token should be the path + name.. (from base)
1610 ShaderTemplatePointer shaderTemplate(new ShaderTemplate);
1611 shaderTemplate->parseTemplate(tokeniser);
1612 if(!g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second)
1614 globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName()) << ": already defined, second definition ignored\n";
1617 else if(string_equal(token, "inlineGuide"))
1619 // skip entire inlineGuide definition
1620 std::size_t depth = 0;
1623 tokeniser.nextLine();
1624 token = tokeniser.getToken();
1625 if(string_equal(token, "{"))
1629 else if(string_equal(token, "}"))
1641 void LoadShaderFile(const char* filename)
1643 ArchiveTextFile* file = GlobalFileSystem().openTextFile(filename);
1647 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1649 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1651 ParseShaderFile(tokeniser, filename);
1653 tokeniser.release();
1658 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1662 typedef FreeCaller1<const char*, LoadShaderFile> LoadShaderFileCaller;
1665 void loadGuideFile(const char* filename)
1667 StringOutputStream fullname(256);
1668 fullname << "guides/" << filename;
1669 ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str());
1673 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1675 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1677 parseGuideFile(tokeniser, fullname.c_str());
1679 tokeniser.release();
1684 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1688 typedef FreeCaller1<const char*, loadGuideFile> LoadGuideFileCaller;
1691 CShader* Try_Shader_ForName(const char* name)
1694 shaders_t::iterator i = g_ActiveShaders.find(name);
1695 if(i != g_ActiveShaders.end())
1701 // not found, create it
1702 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find(name);
1703 if(i == g_shaderDefinitions.end())
1705 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1706 shaderTemplate->CreateDefault(name);
1707 g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1709 i = g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate.get(), ShaderArguments(), ""))).first;
1712 ShaderPointer pShader(new CShader((*i).second));
1713 pShader->setName(name);
1714 g_ActiveShaders.insert(shaders_t::value_type(name, pShader));
1715 g_ActiveShadersChangedNotify();
1719 IShader *Shader_ForName(const char *name)
1721 ASSERT_NOTNULL(name);
1723 IShader *pShader = Try_Shader_ForName(name);
1731 // the list of scripts/*.shader files we need to work with
1732 // those are listed in shaderlist file
1733 GSList *l_shaderfiles = 0;
1735 GSList* Shaders_getShaderFileList()
1737 return l_shaderfiles;
1742 DumpUnreferencedShaders
1743 usefull function: dumps the list of .shader files that are not referenced to the console
1746 void IfFound_dumpUnreferencedShader(bool& bFound, const char* filename)
1748 bool listed = false;
1750 for(GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh))
1752 if(!strcmp((char*)sh->data, filename))
1764 globalOutputStream() << "Following shader files are not referenced in shaderlist.txt:\n";
1766 globalOutputStream() << filename << "\n";
1769 typedef ReferenceCaller1<bool, const char*, IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1771 void DumpUnreferencedShaders()
1773 bool bFound = false;
1774 GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound));
1777 void ShaderList_addShaderFile(const char* dirstring)
1781 for(GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next)
1783 if(string_equal_nocase(dirstring, (char*)tmp->data))
1786 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1793 l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring));
1797 typedef FreeCaller1<const char*, ShaderList_addShaderFile> AddShaderFileCaller;
1803 build a CStringList of shader names
1806 void BuildShaderList(TextInputStream& shaderlist)
1808 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist);
1809 tokeniser.nextLine();
1810 const char* token = tokeniser.getToken();
1811 StringOutputStream shaderFile(64);
1814 // each token should be a shader filename
1815 shaderFile << token << "." << g_shadersExtension;
1817 ShaderList_addShaderFile(shaderFile.c_str());
1819 tokeniser.nextLine();
1820 token = tokeniser.getToken();
1824 tokeniser.release();
1827 void FreeShaderList()
1829 while(l_shaderfiles != 0)
1831 free(l_shaderfiles->data);
1832 l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data);
1836 #include "stream/filestream.h"
1838 bool shaderlist_findOrInstall(const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename)
1840 StringOutputStream absShaderList(256);
1841 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1842 if(file_exists(absShaderList.c_str()))
1847 StringOutputStream directory(256);
1848 directory << enginePath << gamename << '/' << shaderPath;
1849 if(!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str()))
1855 StringOutputStream defaultShaderList(256);
1856 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1857 if(file_exists(defaultShaderList.c_str()))
1859 return file_copy(defaultShaderList.c_str(), absShaderList.c_str());
1867 if(g_shaderLanguage == SHADERLANGUAGE_QUAKE4)
1869 GlobalFileSystem().forEachFile("guides/", "guide", LoadGuideFileCaller(), 0);
1872 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1873 if(!string_empty(shaderPath))
1875 StringOutputStream path(256);
1876 path << DirectoryCleaned(shaderPath);
1880 // preload shader files that have been listed in shaderlist.txt
1881 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame");
1882 const char* gamename = GlobalRadiant().getGameName();
1883 const char* enginePath = GlobalRadiant().getEnginePath();
1884 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1886 bool isMod = !string_equal(basegame, gamename);
1888 if(!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename))
1890 gamename = basegame;
1891 shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename);
1894 StringOutputStream absShaderList(256);
1895 absShaderList << enginePath << gamename << '/' << path.c_str() << "shaderlist.txt";
1898 globalOutputStream() << "Parsing shader files from " << absShaderList.c_str() << "\n";
1899 TextFileInputStream shaderlistFile(absShaderList.c_str());
1900 if(shaderlistFile.failed())
1902 globalErrorStream() << "Couldn't find '" << absShaderList.c_str() << "'\n";
1906 BuildShaderList(shaderlistFile);
1907 DumpUnreferencedShaders();
1913 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, AddShaderFileCaller(), 0);
1916 GSList *lst = l_shaderfiles;
1917 StringOutputStream shadername(256);
1920 shadername << path.c_str() << reinterpret_cast<const char*>(lst->data);
1921 LoadShaderFile(shadername.c_str());
1932 g_shaderFilenames.clear();
1935 ModuleObservers g_observers;
1937 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1938 bool Shaders_realised()
1940 return g_shaders_unrealised == 0;
1942 void Shaders_Realise()
1944 if(--g_shaders_unrealised == 0)
1947 g_observers.realise();
1950 void Shaders_Unrealise()
1952 if(++g_shaders_unrealised == 1)
1954 g_observers.unrealise();
1959 void Shaders_Refresh()
1961 Shaders_Unrealise();
1965 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1974 Shaders_Unrealise();
1981 IShader* getShaderForName(const char* name)
1983 return Shader_ForName(name);
1986 void foreachShaderName(const ShaderNameCallback& callback)
1988 for(ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i)
1990 callback((*i).first.c_str());
1994 void beginActiveShadersIterator()
1996 ActiveShaders_IteratorBegin();
1998 bool endActiveShadersIterator()
2000 return ActiveShaders_IteratorAtEnd();
2002 IShader* dereferenceActiveShadersIterator()
2004 return ActiveShaders_IteratorCurrent();
2006 void incrementActiveShadersIterator()
2008 ActiveShaders_IteratorIncrement();
2010 void setActiveShadersChangedNotify(const Callback& notify)
2012 g_ActiveShadersChangedNotify = notify;
2015 void attach(ModuleObserver& observer)
2017 g_observers.attach(observer);
2019 void detach(ModuleObserver& observer)
2021 g_observers.detach(observer);
2024 void setLightingEnabled(bool enabled)
2026 if(CShader::m_lightingEnabled != enabled)
2028 for(shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i)
2030 (*i).second->unrealiseLighting();
2032 CShader::m_lightingEnabled = enabled;
2033 for(shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i)
2035 (*i).second->realiseLighting();
2040 const char* getTexturePrefix() const
2042 return g_texturePrefix;
2046 Quake3ShaderSystem g_Quake3ShaderSystem;
2048 ShaderSystem& GetShaderSystem()
2050 return g_Quake3ShaderSystem;
2053 void Shaders_Construct()
2055 GlobalFileSystem().attach(g_Quake3ShaderSystem);
2057 void Shaders_Destroy()
2059 GlobalFileSystem().detach(g_Quake3ShaderSystem);
2061 if(Shaders_realised())