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)
38 #include "globaldefs.h"
45 #include "ifilesystem.h"
47 #include "iscriplib.h"
48 #include "itextures.h"
49 #include "qerplugin.h"
54 #include "debugging/debugging.h"
55 #include "string/pooledstring.h"
56 #include "math/vector.h"
57 #include "generic/callback.h"
58 #include "generic/referencecounted.h"
59 #include "stream/memstream.h"
60 #include "stream/stringstream.h"
61 #include "stream/textfilestream.h"
66 #include "shaderlib.h"
67 #include "texturelib.h"
69 #include "moduleobservers.h"
70 #include "archivelib.h"
73 const char *g_shadersExtension = "";
74 const char *g_shadersDirectory = "";
75 bool g_enableDefaultShaders = true;
76 ShaderLanguage g_shaderLanguage = SHADERLANGUAGE_QUAKE3;
77 bool g_useShaderList = true;
78 _QERPlugImageTable *g_bitmapModule = 0;
79 const char *g_texturePrefix = "textures/";
81 void ActiveShaders_IteratorBegin();
83 bool ActiveShaders_IteratorAtEnd();
85 IShader *ActiveShaders_IteratorCurrent();
87 void ActiveShaders_IteratorIncrement();
89 Callback<void()> g_ActiveShadersChangedNotify;
93 void LoadShaderFile(const char *filename);
95 qtexture_t *Texture_ForName(const char *filename);
99 NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX:
100 SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this
101 SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it
102 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
105 Image *loadBitmap(void *environment, const char *name)
107 DirectoryArchiveFile file(name, name);
108 if (!file.failed()) {
109 return g_bitmapModule->loadImage(file);
114 inline byte *getPixel(byte *pixels, int width, int height, int x, int y)
116 return pixels + (((((y + height) % height) * width) + ((x + width) % width)) * 4);
119 class KernelElement {
125 Image &convertHeightmapToNormalmap(Image &heightmap, float scale)
127 int w = heightmap.getWidth();
128 int h = heightmap.getHeight();
130 Image &normalmap = *(new RGBAImage(heightmap.getWidth(), heightmap.getHeight()));
132 byte *in = heightmap.getRGBAPixels();
133 byte *out = normalmap.getRGBAPixels();
137 const int kernelSize = 2;
138 KernelElement kernel_du[kernelSize] = {
142 KernelElement kernel_dv[kernelSize] = {
148 const int kernelSize = 6;
149 KernelElement kernel_du[kernelSize] = {
157 KernelElement kernel_dv[kernelSize] = {
172 for (KernelElement *i = kernel_du; i != kernel_du + kernelSize; ++i) {
173 du += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w;
176 for (KernelElement *i = kernel_dv; i != kernel_dv + kernelSize; ++i) {
177 dv += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w;
180 float nx = -du * scale;
181 float ny = -dv * scale;
185 float norm = 1.0 / sqrt(nx * nx + ny * ny + nz * nz);
186 out[0] = float_to_integer(((nx * norm) + 1) * 127.5);
187 out[1] = float_to_integer(((ny * norm) + 1) * 127.5);
188 out[2] = float_to_integer(((nz * norm) + 1) * 127.5);
201 Image *loadHeightmap(void *environment, const char *name)
203 Image *heightmap = GlobalTexturesCache().loadImage(name);
204 if (heightmap != 0) {
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
216 StringOutputStream bitmapName(256);
217 bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".png";
218 Image *image = loadBitmap(environment, bitmapName.c_str());
223 return GlobalTexturesCache().loadImage(name);
226 class ShaderPoolContext {
229 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
230 typedef PooledString<ShaderPool> ShaderString;
231 typedef ShaderString ShaderVariable;
232 typedef ShaderString ShaderValue;
233 typedef CopiedString TextureExpression;
235 // clean a texture name to the qtexture_t name format we use internally
236 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
237 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
238 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
239 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
240 template<typename StringType>
241 void parseTextureName(StringType &name, const char *token)
243 StringOutputStream cleaned(256);
244 cleaned << PathCleaned(token);
246 StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str()))).c_str(); // remove extension
249 bool Tokeniser_parseTextureName(Tokeniser &tokeniser, TextureExpression &name)
251 const char *token = tokeniser.getToken();
253 Tokeniser_unexpectedError(tokeniser, token, "#texture-name");
256 parseTextureName(name, token);
260 bool Tokeniser_parseShaderName(Tokeniser &tokeniser, CopiedString &name)
262 const char *token = tokeniser.getToken();
264 Tokeniser_unexpectedError(tokeniser, token, "#shader-name");
267 parseTextureName(name, token);
271 bool Tokeniser_parseString(Tokeniser &tokeniser, ShaderString &string)
273 const char *token = tokeniser.getToken();
275 Tokeniser_unexpectedError(tokeniser, token, "#string");
283 typedef std::list<ShaderVariable> ShaderParameters;
284 typedef std::list<ShaderVariable> ShaderArguments;
286 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
288 class ShaderTemplate {
289 std::size_t m_refcount;
293 ShaderParameters m_params;
295 TextureExpression m_textureName;
296 TextureExpression m_diffuse;
297 TextureExpression m_bump;
298 ShaderValue m_heightmapScale;
299 TextureExpression m_specular;
300 TextureExpression m_lightFalloffImage;
306 IShader::EAlphaFunc m_AlphaFunc;
309 IShader::ECull m_Cull;
325 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
326 if (--m_refcount == 0) {
331 std::size_t refcount()
336 const char *getName() const
338 return m_Name.c_str();
341 void setName(const char *name)
346 // -----------------------------------------
348 bool parseDoom3(Tokeniser &tokeniser);
350 bool parseQuake3(Tokeniser &tokeniser);
352 bool parseTemplate(Tokeniser &tokeniser);
355 void CreateDefault(const char *name)
357 if (g_enableDefaultShaders) {
358 m_textureName = name;
366 class MapLayerTemplate {
367 TextureExpression m_texture;
368 BlendFuncExpression m_blendFunc;
369 bool m_clampToBorder;
370 ShaderValue m_alphaTest;
372 MapLayerTemplate(const TextureExpression &texture, const BlendFuncExpression &blendFunc, bool clampToBorder,
373 const ShaderValue &alphaTest) :
375 m_blendFunc(blendFunc),
376 m_clampToBorder(false),
377 m_alphaTest(alphaTest)
381 const TextureExpression &texture() const
386 const BlendFuncExpression &blendFunc() const
391 bool clampToBorder() const
393 return m_clampToBorder;
396 const ShaderValue &alphaTest() const
402 typedef std::vector<MapLayerTemplate> MapLayers;
407 bool Doom3Shader_parseHeightmap(Tokeniser &tokeniser, TextureExpression &bump, ShaderValue &heightmapScale)
409 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
410 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
411 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
412 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, heightmapScale));
413 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
417 bool Doom3Shader_parseAddnormals(Tokeniser &tokeniser, TextureExpression &bump)
419 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
420 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
421 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
422 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "heightmap"));
423 TextureExpression heightmapName;
424 ShaderValue heightmapScale;
425 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, heightmapName, heightmapScale));
426 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
430 bool Doom3Shader_parseBumpmap(Tokeniser &tokeniser, TextureExpression &bump, ShaderValue &heightmapScale)
432 const char *token = tokeniser.getToken();
434 Tokeniser_unexpectedError(tokeniser, token, "#bumpmap");
437 if (string_equal(token, "heightmap")) {
438 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, bump, heightmapScale));
439 } else if (string_equal(token, "addnormals")) {
440 RETURN_FALSE_IF_FAIL(Doom3Shader_parseAddnormals(tokeniser, bump));
442 parseTextureName(bump, token);
455 class LayerTemplate {
458 TextureExpression m_texture;
459 BlendFuncExpression m_blendFunc;
460 bool m_clampToBorder;
461 ShaderValue m_alphaTest;
462 ShaderValue m_heightmapScale;
464 LayerTemplate() : m_type(LAYER_NONE), m_blendFunc("GL_ONE", "GL_ZERO"), m_clampToBorder(false), m_alphaTest("-1"),
465 m_heightmapScale("0")
470 bool parseShaderParameters(Tokeniser &tokeniser, ShaderParameters ¶ms)
472 Tokeniser_parseToken(tokeniser, "(");
474 const char *param = tokeniser.getToken();
475 if (string_equal(param, ")")) {
478 params.push_back(param);
479 const char *comma = tokeniser.getToken();
480 if (string_equal(comma, ")")) {
483 if (!string_equal(comma, ",")) {
484 Tokeniser_unexpectedError(tokeniser, comma, ",");
491 bool ShaderTemplate::parseTemplate(Tokeniser &tokeniser)
493 m_Name = tokeniser.getToken();
494 if (!parseShaderParameters(tokeniser, m_params)) {
495 globalErrorStream() << "shader template: " << makeQuoted(m_Name.c_str()) << ": parameter parse failed\n";
499 return parseDoom3(tokeniser);
502 bool ShaderTemplate::parseDoom3(Tokeniser &tokeniser)
504 LayerTemplate currentLayer;
507 // we need to read until we hit a balanced }
510 tokeniser.nextLine();
511 const char *token = tokeniser.getToken();
517 if (string_equal(token, "{")) {
520 } else if (string_equal(token, "}")) {
522 if (depth < 0) { // error
525 if (depth == 0) { // end of shader
528 if (depth == 1) { // end of layer
529 if (currentLayer.m_type == LAYER_DIFFUSEMAP) {
530 m_diffuse = currentLayer.m_texture;
531 } else if (currentLayer.m_type == LAYER_BUMPMAP) {
532 m_bump = currentLayer.m_texture;
533 } else if (currentLayer.m_type == LAYER_SPECULARMAP) {
534 m_specular = currentLayer.m_texture;
535 } else if (!string_empty(currentLayer.m_texture.c_str())) {
536 m_layers.push_back(MapLayerTemplate(
537 currentLayer.m_texture.c_str(),
538 currentLayer.m_blendFunc,
539 currentLayer.m_clampToBorder,
540 currentLayer.m_alphaTest
543 currentLayer.m_type = LAYER_NONE;
544 currentLayer.m_texture = "";
549 if (depth == 2) { // in layer
550 if (string_equal_nocase(token, "blend")) {
551 const char *blend = tokeniser.getToken();
554 Tokeniser_unexpectedError(tokeniser, blend, "#blend");
558 if (string_equal_nocase(blend, "diffusemap")) {
559 currentLayer.m_type = LAYER_DIFFUSEMAP;
560 } else if (string_equal_nocase(blend, "bumpmap")) {
561 currentLayer.m_type = LAYER_BUMPMAP;
562 } else if (string_equal_nocase(blend, "specularmap")) {
563 currentLayer.m_type = LAYER_SPECULARMAP;
565 currentLayer.m_blendFunc.first = blend;
567 const char *comma = tokeniser.getToken();
570 Tokeniser_unexpectedError(tokeniser, comma, "#comma");
574 if (string_equal(comma, ",")) {
575 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, currentLayer.m_blendFunc.second));
577 currentLayer.m_blendFunc.second = "";
578 tokeniser.ungetToken();
581 } else if (string_equal_nocase(token, "map")) {
582 if (currentLayer.m_type == LAYER_BUMPMAP) {
583 RETURN_FALSE_IF_FAIL(
584 Doom3Shader_parseBumpmap(tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale));
586 const char *map = tokeniser.getToken();
589 Tokeniser_unexpectedError(tokeniser, map, "#map");
593 if (string_equal(map, "makealpha")) {
594 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
595 const char *texture = tokeniser.getToken();
597 Tokeniser_unexpectedError(tokeniser, texture, "#texture");
600 currentLayer.m_texture = texture;
601 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
603 parseTextureName(currentLayer.m_texture, map);
606 } else if (string_equal_nocase(token, "zeroclamp")) {
607 currentLayer.m_clampToBorder = true;
610 else if ( string_equal_nocase( token, "alphaTest" ) ) {
611 Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest );
614 } else if (depth == 1) {
615 if (string_equal_nocase(token, "qer_editorimage")) {
616 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
617 } else if (string_equal_nocase(token, "qer_trans")) {
618 m_fTrans = string_read_float(tokeniser.getToken());
619 m_nFlags |= QER_TRANS;
620 } else if (string_equal_nocase(token, "translucent")) {
622 m_nFlags |= QER_TRANS;
623 } else if (string_equal(token, "DECAL_MACRO")) {
625 m_nFlags |= QER_TRANS;
626 } else if (string_equal_nocase(token, "bumpmap")) {
627 RETURN_FALSE_IF_FAIL(Doom3Shader_parseBumpmap(tokeniser, m_bump, m_heightmapScale));
628 } else if (string_equal_nocase(token, "diffusemap")) {
629 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_diffuse));
630 } else if (string_equal_nocase(token, "specularmap")) {
631 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_specular));
632 } else if (string_equal_nocase(token, "twosided")) {
633 m_Cull = IShader::eCullNone;
634 m_nFlags |= QER_CULL;
635 } else if (string_equal_nocase(token, "nodraw")) {
636 m_nFlags |= QER_NODRAW;
637 } else if (string_equal_nocase(token, "nonsolid")) {
638 m_nFlags |= QER_NONSOLID;
639 } else if (string_equal_nocase(token, "liquid")) {
640 m_nFlags |= QER_WATER;
641 } else if (string_equal_nocase(token, "areaportal")) {
642 m_nFlags |= QER_AREAPORTAL;
643 } else if (string_equal_nocase(token, "playerclip")
644 || string_equal_nocase(token, "monsterclip")
645 || string_equal_nocase(token, "ikclip")
646 || string_equal_nocase(token, "moveableclip")) {
647 m_nFlags |= QER_CLIP;
649 if (string_equal_nocase(token, "fogLight")) {
651 } else if (!isFog && string_equal_nocase(token, "lightFalloffImage")) {
652 const char *lightFalloffImage = tokeniser.getToken();
653 if (lightFalloffImage == 0) {
654 Tokeniser_unexpectedError(tokeniser, lightFalloffImage, "#lightFalloffImage");
657 if (string_equal_nocase(lightFalloffImage, "makeintensity")) {
658 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
659 TextureExpression name;
660 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, name));
661 m_lightFalloffImage = name;
662 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
664 m_lightFalloffImage = lightFalloffImage;
670 if (string_empty(m_textureName.c_str())) {
671 m_textureName = m_diffuse;
677 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
678 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
680 ShaderTemplateMap g_shaders;
681 ShaderTemplateMap g_shaderTemplates;
683 ShaderTemplate *findTemplate(const char *name)
685 ShaderTemplateMap::iterator i = g_shaderTemplates.find(name);
686 if (i != g_shaderTemplates.end()) {
687 return (*i).second.get();
692 class ShaderDefinition {
694 ShaderDefinition(ShaderTemplate *shaderTemplate, const ShaderArguments &args, const char *filename)
695 : shaderTemplate(shaderTemplate), args(args), filename(filename)
699 ShaderTemplate *shaderTemplate;
700 ShaderArguments args;
701 const char *filename;
704 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
706 ShaderDefinitionMap g_shaderDefinitions;
708 bool parseTemplateInstance(Tokeniser &tokeniser, const char *filename)
711 RETURN_FALSE_IF_FAIL(Tokeniser_parseShaderName(tokeniser, name));
712 const char *templateName = tokeniser.getToken();
713 ShaderTemplate *shaderTemplate = findTemplate(templateName);
714 if (shaderTemplate == 0) {
715 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": shader template not found: "
716 << makeQuoted(templateName) << "\n";
719 ShaderArguments args;
720 if (!parseShaderParameters(tokeniser, args)) {
721 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": argument parse failed\n";
725 if (shaderTemplate != 0) {
726 if (!g_shaderDefinitions.insert(
727 ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate, args, filename))).second) {
728 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str())
729 << ": already exists, second definition ignored\n";
736 const char *evaluateShaderValue(const char *value, const ShaderParameters ¶ms, const ShaderArguments &args)
738 ShaderArguments::const_iterator j = args.begin();
739 for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) {
740 const char *other = (*i).c_str();
741 if (string_equal(value, other)) {
748 ///\todo BlendFunc parsing
750 evaluateBlendFunc(const BlendFuncExpression &blendFunc, const ShaderParameters ¶ms, const ShaderArguments &args)
752 return BlendFunc(BLEND_ONE, BLEND_ZERO);
756 evaluateTexture(const TextureExpression &texture, const ShaderParameters ¶ms, const ShaderArguments &args,
757 const LoadImageCallback &loader = GlobalTexturesCache().defaultLoader())
759 StringOutputStream result(64);
760 const char *expression = texture.c_str();
761 const char *end = expression + string_length(expression);
762 if (!string_empty(expression)) {
764 const char *best = end;
765 const char *bestParam = 0;
766 const char *bestArg = 0;
767 ShaderArguments::const_iterator j = args.begin();
768 for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) {
769 const char *found = strstr(expression, (*i).c_str());
770 if (found != 0 && found < best) {
772 bestParam = (*i).c_str();
773 bestArg = (*j).c_str();
777 result << StringRange(expression, best);
778 result << PathCleaned(bestArg);
779 expression = best + string_length(bestParam);
784 result << expression;
786 return GlobalTexturesCache().capture(loader, result.c_str());
789 float evaluateFloat(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args)
791 const char *result = evaluateShaderValue(value.c_str(), params, args);
793 if (!string_parse_float(result, f)) {
794 globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n";
799 BlendFactor evaluateBlendFactor(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args)
801 const char *result = evaluateShaderValue(value.c_str(), params, args);
803 if (string_equal_nocase(result, "gl_zero")) {
806 if (string_equal_nocase(result, "gl_one")) {
809 if (string_equal_nocase(result, "gl_src_color")) {
810 return BLEND_SRC_COLOUR;
812 if (string_equal_nocase(result, "gl_one_minus_src_color")) {
813 return BLEND_ONE_MINUS_SRC_COLOUR;
815 if (string_equal_nocase(result, "gl_src_alpha")) {
816 return BLEND_SRC_ALPHA;
818 if (string_equal_nocase(result, "gl_one_minus_src_alpha")) {
819 return BLEND_ONE_MINUS_SRC_ALPHA;
821 if (string_equal_nocase(result, "gl_dst_color")) {
822 return BLEND_DST_COLOUR;
824 if (string_equal_nocase(result, "gl_one_minus_dst_color")) {
825 return BLEND_ONE_MINUS_DST_COLOUR;
827 if (string_equal_nocase(result, "gl_dst_alpha")) {
828 return BLEND_DST_ALPHA;
830 if (string_equal_nocase(result, "gl_one_minus_dst_alpha")) {
831 return BLEND_ONE_MINUS_DST_ALPHA;
833 if (string_equal_nocase(result, "gl_src_alpha_saturate")) {
834 return BLEND_SRC_ALPHA_SATURATE;
837 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted(result) << "\n";
841 class CShader : public IShader {
842 std::size_t m_refcount;
844 const ShaderTemplate &m_template;
845 const ShaderArguments &m_args;
846 const char *m_filename;
847 // name is shader-name, otherwise texture-name (if not a real shader)
850 qtexture_t *m_pTexture;
851 qtexture_t *m_notfound;
852 qtexture_t *m_pDiffuse;
853 float m_heightmapScale;
855 qtexture_t *m_pSpecular;
856 qtexture_t *m_pLightFalloffImage;
857 BlendFunc m_blendFunc;
863 static bool m_lightingEnabled;
865 CShader(const ShaderDefinition &definition) :
867 m_template(*definition.shaderTemplate),
868 m_args(definition.args),
869 m_filename(definition.filename),
870 m_blendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA),
887 ASSERT_MESSAGE(m_refcount == 0, "deleting active shader");
890 // IShaders implementation -----------------
898 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
899 if (--m_refcount == 0) {
904 std::size_t refcount()
909 // get/set the qtexture_t* Radiant uses to represent this shader object
910 qtexture_t *getTexture() const
915 qtexture_t *getDiffuse() const
920 qtexture_t *getBump() const
925 qtexture_t *getSpecular() const
931 const char *getName() const
933 return m_Name.c_str();
941 void SetInUse(bool bInUse)
944 g_ActiveShadersChangedNotify();
947 // get the shader flags
950 return m_template.m_nFlags;
953 // get the transparency value
954 float getTrans() const
956 return m_template.m_fTrans;
959 // test if it's a true shader, or a default shader created to wrap around a texture
960 bool IsDefault() const
962 return string_empty(m_filename);
966 void getAlphaFunc(EAlphaFunc *func, float *ref)
968 *func = m_template.m_AlphaFunc;
969 *ref = m_template.m_AlphaRef;
972 BlendFunc getBlendFunc() const
980 return m_template.m_Cull;
983 // get shader file name (ie the file where this one is defined)
984 const char *getShaderFileName() const
988 // -----------------------------------------
992 m_pTexture = evaluateTexture(m_template.m_textureName, m_template.m_params, m_args);
994 if (m_pTexture->texture_number == 0) {
995 m_notfound = m_pTexture;
998 StringOutputStream name(256);
999 name << GlobalRadiant().getAppPath() << "bitmaps/" << (IsDefault() ? "notex.png" : "shadernotex.png");
1000 m_pTexture = GlobalTexturesCache().capture(LoadImageCallback(0, loadBitmap), name.c_str());
1009 GlobalTexturesCache().release(m_pTexture);
1011 if (m_notfound != 0) {
1012 GlobalTexturesCache().release(m_notfound);
1015 unrealiseLighting();
1018 void realiseLighting()
1020 if (m_lightingEnabled) {
1021 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
1022 if (!string_empty(m_template.m_heightmapScale.c_str())) {
1023 m_heightmapScale = evaluateFloat(m_template.m_heightmapScale, m_template.m_params, m_args);
1024 loader = LoadImageCallback(&m_heightmapScale, loadHeightmap);
1026 m_pDiffuse = evaluateTexture(m_template.m_diffuse, m_template.m_params, m_args);
1027 m_pBump = evaluateTexture(m_template.m_bump, m_template.m_params, m_args, loader);
1028 m_pSpecular = evaluateTexture(m_template.m_specular, m_template.m_params, m_args);
1029 m_pLightFalloffImage = evaluateTexture(m_template.m_lightFalloffImage, m_template.m_params, m_args);
1031 for (ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin();
1032 i != m_template.m_layers.end(); ++i) {
1033 m_layers.push_back(evaluateLayer(*i, m_template.m_params, m_args));
1036 if (m_layers.size() == 1) {
1037 const BlendFuncExpression &blendFunc = m_template.m_layers.front().blendFunc();
1038 if (!string_empty(blendFunc.second.c_str())) {
1039 m_blendFunc = BlendFunc(
1040 evaluateBlendFactor(blendFunc.first.c_str(), m_template.m_params, m_args),
1041 evaluateBlendFactor(blendFunc.second.c_str(), m_template.m_params, m_args)
1044 const char *blend = evaluateShaderValue(blendFunc.first.c_str(), m_template.m_params, m_args);
1046 if (string_equal_nocase(blend, "add")) {
1047 m_blendFunc = BlendFunc(BLEND_ONE, BLEND_ONE);
1048 } else if (string_equal_nocase(blend, "filter")) {
1049 m_blendFunc = BlendFunc(BLEND_DST_COLOUR, BLEND_ZERO);
1050 } else if (string_equal_nocase(blend, "blend")) {
1051 m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA);
1053 globalErrorStream() << "parsing blend value failed: " << makeQuoted(blend) << "\n";
1060 void unrealiseLighting()
1062 if (m_lightingEnabled) {
1063 GlobalTexturesCache().release(m_pDiffuse);
1064 GlobalTexturesCache().release(m_pBump);
1065 GlobalTexturesCache().release(m_pSpecular);
1067 GlobalTexturesCache().release(m_pLightFalloffImage);
1069 for (MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
1070 GlobalTexturesCache().release((*i).texture());
1074 m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA);
1079 void setName(const char *name)
1084 class MapLayer : public ShaderLayer {
1085 qtexture_t *m_texture;
1086 BlendFunc m_blendFunc;
1087 bool m_clampToBorder;
1090 MapLayer(qtexture_t *texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) :
1092 m_blendFunc(blendFunc),
1093 m_clampToBorder(false),
1094 m_alphaTest(alphaTest)
1098 qtexture_t *texture() const
1103 BlendFunc blendFunc() const
1108 bool clampToBorder() const
1110 return m_clampToBorder;
1113 float alphaTest() const
1119 static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate &layerTemplate, const ShaderParameters ¶ms,
1120 const ShaderArguments &args)
1123 evaluateTexture(layerTemplate.texture(), params, args),
1124 evaluateBlendFunc(layerTemplate.blendFunc(), params, args),
1125 layerTemplate.clampToBorder(),
1126 evaluateFloat(layerTemplate.alphaTest(), params, args)
1130 typedef std::vector<MapLayer> MapLayers;
1133 const ShaderLayer *firstLayer() const
1135 if (m_layers.empty()) {
1138 return &m_layers.front();
1141 void forEachLayer(const ShaderLayerCallback &callback) const
1143 for (MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
1148 qtexture_t *lightFalloffImage() const
1150 if (!string_empty(m_template.m_lightFalloffImage.c_str())) {
1151 return m_pLightFalloffImage;
1157 bool CShader::m_lightingEnabled = false;
1159 typedef SmartPointer<CShader> ShaderPointer;
1160 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1162 shaders_t g_ActiveShaders;
1164 static shaders_t::iterator g_ActiveShadersIterator;
1166 void ActiveShaders_IteratorBegin()
1168 g_ActiveShadersIterator = g_ActiveShaders.begin();
1171 bool ActiveShaders_IteratorAtEnd()
1173 return g_ActiveShadersIterator == g_ActiveShaders.end();
1176 IShader *ActiveShaders_IteratorCurrent()
1178 return static_cast<CShader *>( g_ActiveShadersIterator->second );
1181 void ActiveShaders_IteratorIncrement()
1183 ++g_ActiveShadersIterator;
1186 void debug_check_shaders(shaders_t &shaders)
1188 for (shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i) {
1189 ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced");
1193 // will free all GL binded qtextures and shaders
1194 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1198 // empty the actives shaders list
1199 debug_check_shaders(g_ActiveShaders);
1200 g_ActiveShaders.clear();
1202 g_shaderTemplates.clear();
1203 g_shaderDefinitions.clear();
1204 g_ActiveShadersChangedNotify();
1207 bool ShaderTemplate::parseQuake3(Tokeniser &tokeniser)
1209 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1210 m_textureName = m_Name.c_str();
1212 tokeniser.nextLine();
1214 // we need to read until we hit a balanced }
1217 tokeniser.nextLine();
1218 const char *token = tokeniser.getToken();
1224 if (string_equal(token, "{")) {
1227 } else if (string_equal(token, "}")) {
1229 if (depth < 0) { // underflow
1232 if (depth == 0) { // end of shader
1240 if (string_equal_nocase(token, "qer_nocarve")) {
1241 m_nFlags |= QER_NOCARVE;
1242 } else if (string_equal_nocase(token, "qer_trans")) {
1243 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans));
1244 m_nFlags |= QER_TRANS;
1245 } else if (string_equal_nocase(token, "qer_editorimage")) {
1246 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
1247 } else if (string_equal_nocase(token, "qer_alphafunc")) {
1248 const char *alphafunc = tokeniser.getToken();
1250 if (alphafunc == 0) {
1251 Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc");
1255 if (string_equal_nocase(alphafunc, "equal")) {
1256 m_AlphaFunc = IShader::eEqual;
1257 } else if (string_equal_nocase(alphafunc, "greater")) {
1258 m_AlphaFunc = IShader::eGreater;
1259 } else if (string_equal_nocase(alphafunc, "less")) {
1260 m_AlphaFunc = IShader::eLess;
1261 } else if (string_equal_nocase(alphafunc, "gequal")) {
1262 m_AlphaFunc = IShader::eGEqual;
1263 } else if (string_equal_nocase(alphafunc, "lequal")) {
1264 m_AlphaFunc = IShader::eLEqual;
1266 m_AlphaFunc = IShader::eAlways;
1269 m_nFlags |= QER_ALPHATEST;
1271 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef));
1272 } else if (string_equal_nocase(token, "cull")) {
1273 const char *cull = tokeniser.getToken();
1276 Tokeniser_unexpectedError(tokeniser, cull, "#cull");
1280 if (string_equal_nocase(cull, "none")
1281 || string_equal_nocase(cull, "twosided")
1282 || string_equal_nocase(cull, "disable")) {
1283 m_Cull = IShader::eCullNone;
1284 } else if (string_equal_nocase(cull, "back")
1285 || string_equal_nocase(cull, "backside")
1286 || string_equal_nocase(cull, "backsided")) {
1287 m_Cull = IShader::eCullBack;
1289 m_Cull = IShader::eCullBack;
1292 m_nFlags |= QER_CULL;
1293 } else if (string_equal_nocase(token, "surfaceparm")) {
1294 const char *surfaceparm = tokeniser.getToken();
1296 if (surfaceparm == 0) {
1297 Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm");
1301 if (string_equal_nocase(surfaceparm, "fog")) {
1302 m_nFlags |= QER_FOG;
1303 if (m_fTrans == 1.0f) { // has not been explicitly set by qer_trans
1306 } else if (string_equal_nocase(surfaceparm, "nodraw")) {
1307 m_nFlags |= QER_NODRAW;
1308 } else if (string_equal_nocase(surfaceparm, "nonsolid")) {
1309 m_nFlags |= QER_NONSOLID;
1310 } else if (string_equal_nocase(surfaceparm, "water")) {
1311 m_nFlags |= QER_WATER;
1312 } else if (string_equal_nocase(surfaceparm, "lava")) {
1313 m_nFlags |= QER_LAVA;
1314 } else if (string_equal_nocase(surfaceparm, "areaportal")) {
1315 m_nFlags |= QER_AREAPORTAL;
1316 } else if (string_equal_nocase(surfaceparm, "playerclip")) {
1317 m_nFlags |= QER_CLIP;
1318 } else if (string_equal_nocase(surfaceparm, "botclip")) {
1319 m_nFlags |= QER_BOTCLIP;
1331 TextureExpression m_texture;
1332 BlendFunc m_blendFunc;
1333 bool m_clampToBorder;
1335 float m_heightmapScale;
1337 Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1),
1343 std::list<CopiedString> g_shaderFilenames;
1345 void ParseShaderFile(Tokeniser &tokeniser, const char *filename)
1347 g_shaderFilenames.push_back(filename);
1348 filename = g_shaderFilenames.back().c_str();
1349 tokeniser.nextLine();
1351 const char *token = tokeniser.getToken();
1357 if (string_equal(token, "table")) {
1358 if (tokeniser.getToken() == 0) {
1359 Tokeniser_unexpectedError(tokeniser, 0, "#table-name");
1362 if (!Tokeniser_parseToken(tokeniser, "{")) {
1366 const char *option = tokeniser.getToken();
1367 if (string_equal(option, "{")) {
1369 const char *value = tokeniser.getToken();
1370 if (string_equal(value, "}")) {
1375 if (!Tokeniser_parseToken(tokeniser, "}")) {
1382 if (string_equal(token, "guide")) {
1383 parseTemplateInstance(tokeniser, filename);
1385 if (!string_equal(token, "material")
1386 && !string_equal(token, "particle")
1387 && !string_equal(token, "skin")) {
1388 tokeniser.ungetToken();
1390 // first token should be the path + name.. (from base)
1392 if (!Tokeniser_parseShaderName(tokeniser, name)) {
1394 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1395 shaderTemplate->setName(name.c_str());
1397 g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1399 bool result = (g_shaderLanguage == SHADERLANGUAGE_QUAKE3)
1400 ? shaderTemplate->parseQuake3(tokeniser)
1401 : shaderTemplate->parseDoom3(tokeniser);
1403 // do we already have this shader?
1404 if (!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(),
1406 shaderTemplate.get(),
1408 filename))).second) {
1410 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName()
1411 << " is already in memory, definition in " << filename << " ignored.\n";
1415 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1423 void parseGuideFile(Tokeniser &tokeniser, const char *filename)
1425 tokeniser.nextLine();
1427 const char *token = tokeniser.getToken();
1433 if (string_equal(token, "guide")) {
1434 // first token should be the path + name.. (from base)
1435 ShaderTemplatePointer shaderTemplate(new ShaderTemplate);
1436 shaderTemplate->parseTemplate(tokeniser);
1437 if (!g_shaderTemplates.insert(
1438 ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second) {
1439 globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName())
1440 << ": already defined, second definition ignored\n";
1442 } else if (string_equal(token, "inlineGuide")) {
1443 // skip entire inlineGuide definition
1444 std::size_t depth = 0;
1446 tokeniser.nextLine();
1447 token = tokeniser.getToken();
1448 if (string_equal(token, "{")) {
1450 } else if (string_equal(token, "}")) {
1460 void LoadShaderFile(const char *filename)
1462 ArchiveTextFile *file = GlobalFileSystem().openTextFile(filename);
1465 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1467 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1469 ParseShaderFile(tokeniser, filename);
1471 tokeniser.release();
1474 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1478 void loadGuideFile(const char *filename)
1480 StringOutputStream fullname(256);
1481 fullname << "guides/" << filename;
1482 ArchiveTextFile *file = GlobalFileSystem().openTextFile(fullname.c_str());
1485 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1487 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1489 parseGuideFile(tokeniser, fullname.c_str());
1491 tokeniser.release();
1494 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1498 CShader *Try_Shader_ForName(const char *name)
1501 shaders_t::iterator i = g_ActiveShaders.find(name);
1502 if (i != g_ActiveShaders.end()) {
1506 // active shader was not found
1508 // find matching shader definition
1509 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find(name);
1510 if (i == g_shaderDefinitions.end()) {
1511 // shader definition was not found
1513 // create new shader definition from default shader template
1514 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1515 shaderTemplate->CreateDefault(name);
1516 g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1518 i = g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate.get(),
1523 // create shader from existing definition
1524 ShaderPointer pShader(new CShader((*i).second));
1525 pShader->setName(name);
1526 g_ActiveShaders.insert(shaders_t::value_type(name, pShader));
1527 g_ActiveShadersChangedNotify();
1531 IShader *Shader_ForName(const char *name)
1533 ASSERT_NOTNULL(name);
1535 IShader *pShader = Try_Shader_ForName(name);
1541 // the list of scripts/*.shader files we need to work with
1542 // those are listed in shaderlist file
1543 GSList *l_shaderfiles = 0;
1545 GSList *Shaders_getShaderFileList()
1547 return l_shaderfiles;
1552 DumpUnreferencedShaders
1553 usefull function: dumps the list of .shader files that are not referenced to the console
1556 void IfFound_dumpUnreferencedShader(bool &bFound, const char *filename)
1558 bool listed = false;
1560 for (GSList *sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh)) {
1561 if (!strcmp((char *) sh->data, filename)) {
1570 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1572 globalOutputStream() << "\t" << filename << "\n";
1576 typedef ReferenceCaller<bool, void(const char *), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1578 void DumpUnreferencedShaders()
1580 bool bFound = false;
1581 GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound));
1584 void ShaderList_addShaderFile(const char *dirstring)
1588 for (GSList *tmp = l_shaderfiles; tmp != 0; tmp = tmp->next) {
1589 if (string_equal_nocase(dirstring, (char *) tmp->data)) {
1591 globalOutputStream() << "duplicate entry \"" << (char *) tmp->data << "\" in shaderlist.txt\n";
1597 l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring));
1604 build a CStringList of shader names
1607 void BuildShaderList(TextInputStream &shaderlist)
1609 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist);
1610 tokeniser.nextLine();
1611 const char *token = tokeniser.getToken();
1612 StringOutputStream shaderFile(64);
1613 while (token != 0) {
1614 // each token should be a shader filename
1615 shaderFile << token << "." << g_shadersExtension;
1617 ShaderList_addShaderFile(shaderFile.c_str());
1619 tokeniser.nextLine();
1620 token = tokeniser.getToken();
1624 tokeniser.release();
1627 void FreeShaderList()
1629 while (l_shaderfiles != 0) {
1630 free(l_shaderfiles->data);
1631 l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data);
1635 void ShaderList_addFromArchive(const char *archivename)
1637 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1638 if (string_empty(shaderpath)) {
1642 StringOutputStream shaderlist(256);
1643 shaderlist << DirectoryCleaned(shaderpath) << "shaderlist.txt";
1645 Archive *archive = GlobalFileSystem().getArchive(archivename, false);
1647 ArchiveTextFile *file = archive->openTextFile(shaderlist.c_str());
1649 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1650 BuildShaderList(file->getInputStream());
1656 #include "stream/filestream.h"
1659 shaderlist_findOrInstall(const char *enginePath, const char *toolsPath, const char *shaderPath, const char *gamename)
1661 StringOutputStream absShaderList(256);
1662 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1663 if (file_exists(absShaderList.c_str())) {
1667 StringOutputStream directory(256);
1668 directory << enginePath << gamename << '/' << shaderPath;
1669 if (!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str())) {
1674 StringOutputStream defaultShaderList(256);
1675 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1676 if (file_exists(defaultShaderList.c_str())) {
1677 return file_copy(defaultShaderList.c_str(), absShaderList.c_str());
1685 if (g_shaderLanguage == SHADERLANGUAGE_QUAKE4) {
1686 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1689 const char *shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1690 if (!string_empty(shaderPath)) {
1691 StringOutputStream path(256);
1692 path << DirectoryCleaned(shaderPath);
1694 if (g_useShaderList) {
1695 // preload shader files that have been listed in shaderlist.txt
1696 const char *basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame");
1697 const char *gamename = GlobalRadiant().getGameName();
1698 const char *enginePath = GlobalRadiant().getEnginePath();
1699 const char *toolsPath = GlobalRadiant().getGameToolsPath();
1701 bool isMod = !string_equal(basegame, gamename);
1703 if (!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename)) {
1704 gamename = basegame;
1705 shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename);
1708 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1709 DumpUnreferencedShaders();
1711 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile),
1715 GSList *lst = l_shaderfiles;
1716 StringOutputStream shadername(256);
1718 shadername << path.c_str() << reinterpret_cast<const char *>( lst->data );
1719 LoadShaderFile(shadername.c_str());
1725 //StringPool_analyse(ShaderPool::instance());
1732 g_shaderFilenames.clear();
1735 ModuleObservers g_observers;
1737 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1738 bool Shaders_realised()
1740 return g_shaders_unrealised == 0;
1743 void Shaders_Realise()
1745 if (--g_shaders_unrealised == 0) {
1747 g_observers.realise();
1751 void Shaders_Unrealise()
1753 if (++g_shaders_unrealised == 1) {
1754 g_observers.unrealise();
1759 void Shaders_Refresh()
1761 Shaders_Unrealise();
1765 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver {
1774 Shaders_Unrealise();
1782 IShader *getShaderForName(const char *name)
1784 return Shader_ForName(name);
1787 void foreachShaderName(const ShaderNameCallback &callback)
1789 for (ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i) {
1790 callback((*i).first.c_str());
1794 void beginActiveShadersIterator()
1796 ActiveShaders_IteratorBegin();
1799 bool endActiveShadersIterator()
1801 return ActiveShaders_IteratorAtEnd();
1804 IShader *dereferenceActiveShadersIterator()
1806 return ActiveShaders_IteratorCurrent();
1809 void incrementActiveShadersIterator()
1811 ActiveShaders_IteratorIncrement();
1814 void setActiveShadersChangedNotify(const Callback<void()> ¬ify)
1816 g_ActiveShadersChangedNotify = notify;
1819 void attach(ModuleObserver &observer)
1821 g_observers.attach(observer);
1824 void detach(ModuleObserver &observer)
1826 g_observers.detach(observer);
1829 void setLightingEnabled(bool enabled)
1831 if (CShader::m_lightingEnabled != enabled) {
1832 for (shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) {
1833 (*i).second->unrealiseLighting();
1835 CShader::m_lightingEnabled = enabled;
1836 for (shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) {
1837 (*i).second->realiseLighting();
1842 const char *getTexturePrefix() const
1844 return g_texturePrefix;
1848 Quake3ShaderSystem g_Quake3ShaderSystem;
1850 ShaderSystem &GetShaderSystem()
1852 return g_Quake3ShaderSystem;
1855 void Shaders_Construct()
1857 GlobalFileSystem().attach(g_Quake3ShaderSystem);
1860 void Shaders_Destroy()
1862 GlobalFileSystem().detach(g_Quake3ShaderSystem);
1864 if (Shaders_realised()) {