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_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA),
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());
1185 void setName(const char* name)
1190 class MapLayer : public ShaderLayer
1192 qtexture_t* m_texture;
1193 BlendFunc m_blendFunc;
1194 bool m_clampToBorder;
1197 MapLayer(qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) :
1199 m_blendFunc(blendFunc),
1200 m_clampToBorder(false),
1201 m_alphaTest(alphaTest)
1204 qtexture_t* texture() const
1208 BlendFunc blendFunc() const
1212 bool clampToBorder() const
1214 return m_clampToBorder;
1216 float alphaTest() const
1222 static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args)
1225 evaluateTexture(layerTemplate.texture(), params, args),
1226 evaluateBlendFunc(layerTemplate.blendFunc(), params, args),
1227 layerTemplate.clampToBorder(),
1228 evaluateFloat(layerTemplate.alphaTest(), params, args)
1232 typedef std::vector<MapLayer> MapLayers;
1235 const ShaderLayer* firstLayer() const
1237 if(m_layers.empty())
1241 return &m_layers.front();
1243 void forEachLayer(const ShaderLayerCallback& callback) const
1245 for(MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i)
1251 qtexture_t* lightFalloffImage() const
1253 if(!string_empty(m_template.m_lightFalloffImage.c_str()))
1255 return m_pLightFalloffImage;
1261 bool CShader::m_lightingEnabled = false;
1263 typedef SmartPointer<CShader> ShaderPointer;
1264 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1266 shaders_t g_ActiveShaders;
1268 static shaders_t::iterator g_ActiveShadersIterator;
1270 void ActiveShaders_IteratorBegin()
1272 g_ActiveShadersIterator = g_ActiveShaders.begin();
1275 bool ActiveShaders_IteratorAtEnd()
1277 return g_ActiveShadersIterator == g_ActiveShaders.end();
1280 IShader *ActiveShaders_IteratorCurrent()
1282 return static_cast<CShader*>(g_ActiveShadersIterator->second);
1285 void ActiveShaders_IteratorIncrement()
1287 ++g_ActiveShadersIterator;
1290 void debug_check_shaders(shaders_t& shaders)
1292 for(shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i)
1294 ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced");
1298 // will free all GL binded qtextures and shaders
1299 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1303 // empty the actives shaders list
1304 debug_check_shaders(g_ActiveShaders);
1305 g_ActiveShaders.clear();
1307 g_shaderTemplates.clear();
1308 g_shaderDefinitions.clear();
1309 g_ActiveShadersChangedNotify();
1312 bool ShaderTemplate::parseQuake3(Tokeniser& tokeniser)
1314 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1315 m_textureName = m_Name;
1317 tokeniser.nextLine();
1319 // we need to read until we hit a balanced }
1323 tokeniser.nextLine();
1324 const char* token = tokeniser.getToken();
1329 if(string_equal(token, "{"))
1334 else if(string_equal(token, "}"))
1337 if(depth < 0) // underflow
1341 if(depth == 0) // end of shader
1351 if (string_equal_nocase(token, "qer_nocarve"))
1353 m_nFlags |= QER_NOCARVE;
1355 else if (string_equal_nocase(token, "qer_trans"))
1357 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans));
1358 m_nFlags |= QER_TRANS;
1360 else if (string_equal_nocase(token, "qer_editorimage"))
1362 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
1364 else if (string_equal_nocase(token, "qer_alphafunc"))
1366 const char* alphafunc = tokeniser.getToken();
1370 Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc");
1374 if(string_equal_nocase(alphafunc, "equal"))
1376 m_AlphaFunc = IShader::eEqual;
1378 else if(string_equal_nocase(alphafunc, "greater"))
1380 m_AlphaFunc = IShader::eGreater;
1382 else if(string_equal_nocase(alphafunc, "less"))
1384 m_AlphaFunc = IShader::eLess;
1386 else if(string_equal_nocase(alphafunc, "gequal"))
1388 m_AlphaFunc = IShader::eGEqual;
1390 else if(string_equal_nocase(alphafunc, "lequal"))
1392 m_AlphaFunc = IShader::eLEqual;
1396 m_AlphaFunc = IShader::eAlways;
1399 m_nFlags |= QER_ALPHATEST;
1401 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef));
1403 else if (string_equal_nocase(token, "cull"))
1405 const char* cull = tokeniser.getToken();
1409 Tokeniser_unexpectedError(tokeniser, cull, "#cull");
1413 if(string_equal_nocase(cull, "none")
1414 || string_equal_nocase(cull, "twosided")
1415 || string_equal_nocase(cull, "disable"))
1417 m_Cull = IShader::eCullNone;
1419 else if(string_equal_nocase(cull, "back")
1420 || string_equal_nocase(cull, "backside")
1421 || string_equal_nocase(cull, "backsided"))
1423 m_Cull = IShader::eCullBack;
1427 m_Cull = IShader::eCullBack;
1430 m_nFlags |= QER_CULL;
1432 else if (string_equal_nocase(token, "surfaceparm"))
1434 const char* surfaceparm = tokeniser.getToken();
1436 if(surfaceparm == 0)
1438 Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm");
1442 if (string_equal_nocase(surfaceparm, "fog"))
1444 m_nFlags |= QER_FOG;
1445 if (m_fTrans == 1.0f) // has not been explicitly set by qer_trans
1450 else if (string_equal_nocase(surfaceparm, "nodraw"))
1452 m_nFlags |= QER_NODRAW;
1454 else if (string_equal_nocase(surfaceparm, "nonsolid"))
1456 m_nFlags |= QER_NONSOLID;
1458 else if (string_equal_nocase(surfaceparm, "water"))
1460 m_nFlags |= QER_WATER;
1462 else if (string_equal_nocase(surfaceparm, "lava"))
1464 m_nFlags |= QER_LAVA;
1466 else if (string_equal_nocase(surfaceparm, "areaportal"))
1468 m_nFlags |= QER_AREAPORTAL;
1470 else if (string_equal_nocase(surfaceparm, "playerclip"))
1472 m_nFlags |= QER_CLIP;
1474 else if (string_equal_nocase(surfaceparm, "botclip"))
1476 m_nFlags |= QER_BOTCLIP;
1489 CopiedString m_texture;
1490 BlendFunc m_blendFunc;
1491 bool m_clampToBorder;
1493 float m_heightmapScale;
1495 Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1), m_heightmapScale(0)
1500 std::list<CopiedString> g_shaderFilenames;
1502 void ParseShaderFile(Tokeniser& tokeniser, const char* filename)
1504 g_shaderFilenames.push_back(filename);
1505 filename = g_shaderFilenames.back().c_str();
1506 tokeniser.nextLine();
1509 const char* token = tokeniser.getToken();
1516 if(string_equal(token, "table"))
1518 if(tokeniser.getToken() == 0)
1520 Tokeniser_unexpectedError(tokeniser, 0, "#table-name");
1523 if(!Tokeniser_parseToken(tokeniser, "{"))
1529 const char* option = tokeniser.getToken();
1530 if(string_equal(option, "{"))
1534 const char* value = tokeniser.getToken();
1535 if(string_equal(value, "}"))
1541 if(!Tokeniser_parseToken(tokeniser, "}"))
1551 if(string_equal(token, "guide"))
1553 parseTemplateInstance(tokeniser, filename);
1557 if(!string_equal(token, "material")
1558 && !string_equal(token, "particle")
1559 && !string_equal(token, "skin"))
1561 tokeniser.ungetToken();
1563 // first token should be the path + name.. (from base)
1565 if(!Tokeniser_parseShaderName(tokeniser, name))
1568 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1569 shaderTemplate->setName(name.c_str());
1571 g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1573 bool result = (g_shaderLanguage == SHADERLANGUAGE_QUAKE3)
1574 ? shaderTemplate->parseQuake3(tokeniser)
1575 : shaderTemplate->parseDoom3(tokeniser);
1578 // do we already have this shader?
1579 if(!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(), ShaderDefinition(shaderTemplate.get(), ShaderArguments(), filename))).second)
1582 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1588 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1596 void parseGuideFile(Tokeniser& tokeniser, const char* filename)
1598 tokeniser.nextLine();
1601 const char* token = tokeniser.getToken();
1608 if(string_equal(token, "guide"))
1610 // first token should be the path + name.. (from base)
1611 ShaderTemplatePointer shaderTemplate(new ShaderTemplate);
1612 shaderTemplate->parseTemplate(tokeniser);
1613 if(!g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second)
1615 globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName()) << ": already defined, second definition ignored\n";
1618 else if(string_equal(token, "inlineGuide"))
1620 // skip entire inlineGuide definition
1621 std::size_t depth = 0;
1624 tokeniser.nextLine();
1625 token = tokeniser.getToken();
1626 if(string_equal(token, "{"))
1630 else if(string_equal(token, "}"))
1642 void LoadShaderFile(const char* filename)
1644 ArchiveTextFile* file = GlobalFileSystem().openTextFile(filename);
1648 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1650 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1652 ParseShaderFile(tokeniser, filename);
1654 tokeniser.release();
1659 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1663 typedef FreeCaller1<const char*, LoadShaderFile> LoadShaderFileCaller;
1666 void loadGuideFile(const char* filename)
1668 StringOutputStream fullname(256);
1669 fullname << "guides/" << filename;
1670 ArchiveTextFile* file = GlobalFileSystem().openTextFile(fullname.c_str());
1674 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1676 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1678 parseGuideFile(tokeniser, fullname.c_str());
1680 tokeniser.release();
1685 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1689 typedef FreeCaller1<const char*, loadGuideFile> LoadGuideFileCaller;
1692 CShader* Try_Shader_ForName(const char* name)
1695 shaders_t::iterator i = g_ActiveShaders.find(name);
1696 if(i != g_ActiveShaders.end())
1702 // not found, create it
1703 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find(name);
1704 if(i == g_shaderDefinitions.end())
1706 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1707 shaderTemplate->CreateDefault(name);
1708 g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1710 i = g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate.get(), ShaderArguments(), ""))).first;
1713 ShaderPointer pShader(new CShader((*i).second));
1714 pShader->setName(name);
1715 g_ActiveShaders.insert(shaders_t::value_type(name, pShader));
1716 g_ActiveShadersChangedNotify();
1720 IShader *Shader_ForName(const char *name)
1722 ASSERT_NOTNULL(name);
1724 IShader *pShader = Try_Shader_ForName(name);
1732 // the list of scripts/*.shader files we need to work with
1733 // those are listed in shaderlist file
1734 GSList *l_shaderfiles = 0;
1736 GSList* Shaders_getShaderFileList()
1738 return l_shaderfiles;
1743 DumpUnreferencedShaders
1744 usefull function: dumps the list of .shader files that are not referenced to the console
1747 void IfFound_dumpUnreferencedShader(bool& bFound, const char* filename)
1749 bool listed = false;
1751 for(GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh))
1753 if(!strcmp((char*)sh->data, filename))
1765 globalOutputStream() << "Following shader files are not referenced in shaderlist.txt:\n";
1767 globalOutputStream() << filename << "\n";
1770 typedef ReferenceCaller1<bool, const char*, IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1772 void DumpUnreferencedShaders()
1774 bool bFound = false;
1775 GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound));
1778 void ShaderList_addShaderFile(const char* dirstring)
1782 for(GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next)
1784 if(string_equal_nocase(dirstring, (char*)tmp->data))
1787 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1794 l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring));
1798 typedef FreeCaller1<const char*, ShaderList_addShaderFile> AddShaderFileCaller;
1804 build a CStringList of shader names
1807 void BuildShaderList(TextInputStream& shaderlist)
1809 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist);
1810 tokeniser.nextLine();
1811 const char* token = tokeniser.getToken();
1812 StringOutputStream shaderFile(64);
1815 // each token should be a shader filename
1816 shaderFile << token << "." << g_shadersExtension;
1818 ShaderList_addShaderFile(shaderFile.c_str());
1820 tokeniser.nextLine();
1821 token = tokeniser.getToken();
1825 tokeniser.release();
1828 void FreeShaderList()
1830 while(l_shaderfiles != 0)
1832 free(l_shaderfiles->data);
1833 l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data);
1837 #include "stream/filestream.h"
1839 bool shaderlist_findOrInstall(const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename)
1841 StringOutputStream absShaderList(256);
1842 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1843 if(file_exists(absShaderList.c_str()))
1848 StringOutputStream directory(256);
1849 directory << enginePath << gamename << '/' << shaderPath;
1850 if(!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str()))
1856 StringOutputStream defaultShaderList(256);
1857 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1858 if(file_exists(defaultShaderList.c_str()))
1860 return file_copy(defaultShaderList.c_str(), absShaderList.c_str());
1868 if(g_shaderLanguage == SHADERLANGUAGE_QUAKE4)
1870 GlobalFileSystem().forEachFile("guides/", "guide", LoadGuideFileCaller(), 0);
1873 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1874 if(!string_empty(shaderPath))
1876 StringOutputStream path(256);
1877 path << DirectoryCleaned(shaderPath);
1881 // preload shader files that have been listed in shaderlist.txt
1882 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame");
1883 const char* gamename = GlobalRadiant().getGameName();
1884 const char* enginePath = GlobalRadiant().getEnginePath();
1885 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1887 bool isMod = !string_equal(basegame, gamename);
1889 if(!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename))
1891 gamename = basegame;
1892 shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename);
1895 StringOutputStream absShaderList(256);
1896 absShaderList << enginePath << gamename << '/' << path.c_str() << "shaderlist.txt";
1899 globalOutputStream() << "Parsing shader files from " << absShaderList.c_str() << "\n";
1900 TextFileInputStream shaderlistFile(absShaderList.c_str());
1901 if(shaderlistFile.failed())
1903 globalErrorStream() << "Couldn't find '" << absShaderList.c_str() << "'\n";
1907 BuildShaderList(shaderlistFile);
1908 DumpUnreferencedShaders();
1914 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, AddShaderFileCaller(), 0);
1917 GSList *lst = l_shaderfiles;
1918 StringOutputStream shadername(256);
1921 shadername << path.c_str() << reinterpret_cast<const char*>(lst->data);
1922 LoadShaderFile(shadername.c_str());
1933 g_shaderFilenames.clear();
1936 ModuleObservers g_observers;
1938 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1939 bool Shaders_realised()
1941 return g_shaders_unrealised == 0;
1943 void Shaders_Realise()
1945 if(--g_shaders_unrealised == 0)
1948 g_observers.realise();
1951 void Shaders_Unrealise()
1953 if(++g_shaders_unrealised == 1)
1955 g_observers.unrealise();
1960 void Shaders_Refresh()
1962 Shaders_Unrealise();
1966 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1975 Shaders_Unrealise();
1982 IShader* getShaderForName(const char* name)
1984 return Shader_ForName(name);
1987 void foreachShaderName(const ShaderNameCallback& callback)
1989 for(ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i)
1991 callback((*i).first.c_str());
1995 void beginActiveShadersIterator()
1997 ActiveShaders_IteratorBegin();
1999 bool endActiveShadersIterator()
2001 return ActiveShaders_IteratorAtEnd();
2003 IShader* dereferenceActiveShadersIterator()
2005 return ActiveShaders_IteratorCurrent();
2007 void incrementActiveShadersIterator()
2009 ActiveShaders_IteratorIncrement();
2011 void setActiveShadersChangedNotify(const Callback& notify)
2013 g_ActiveShadersChangedNotify = notify;
2016 void attach(ModuleObserver& observer)
2018 g_observers.attach(observer);
2020 void detach(ModuleObserver& observer)
2022 g_observers.detach(observer);
2025 void setLightingEnabled(bool enabled)
2027 if(CShader::m_lightingEnabled != enabled)
2029 for(shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i)
2031 (*i).second->unrealiseLighting();
2033 CShader::m_lightingEnabled = enabled;
2034 for(shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i)
2036 (*i).second->realiseLighting();
2041 const char* getTexturePrefix() const
2043 return g_texturePrefix;
2047 Quake3ShaderSystem g_Quake3ShaderSystem;
2049 ShaderSystem& GetShaderSystem()
2051 return g_Quake3ShaderSystem;
2054 void Shaders_Construct()
2056 GlobalFileSystem().attach(g_Quake3ShaderSystem);
2058 void Shaders_Destroy()
2060 GlobalFileSystem().detach(g_Quake3ShaderSystem);
2062 if(Shaders_realised())