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)
39 #include "globaldefs.h"
46 #include "ifilesystem.h"
48 #include "iscriplib.h"
49 #include "itextures.h"
50 #include "qerplugin.h"
55 #include "debugging/debugging.h"
56 #include "string/pooledstring.h"
57 #include "math/vector.h"
58 #include "generic/callback.h"
59 #include "generic/referencecounted.h"
60 #include "stream/memstream.h"
61 #include "stream/stringstream.h"
62 #include "stream/textfilestream.h"
67 #include "shaderlib.h"
68 #include "texturelib.h"
70 #include "moduleobservers.h"
71 #include "archivelib.h"
74 const char *g_shadersExtension = "";
75 const char *g_shadersDirectory = "";
76 bool g_enableDefaultShaders = true;
77 ShaderLanguage g_shaderLanguage = SHADERLANGUAGE_QUAKE3;
78 bool g_useShaderList = true;
79 _QERPlugImageTable *g_bitmapModule = 0;
80 const char *g_texturePrefix = DEFAULT_TEXTURE_DIRNAME;
82 void ActiveShaders_IteratorBegin();
84 bool ActiveShaders_IteratorAtEnd();
86 IShader *ActiveShaders_IteratorCurrent();
88 void ActiveShaders_IteratorIncrement();
90 Callback<void()> g_ActiveShadersChangedNotify;
94 void LoadShaderFile(const char *filename);
96 qtexture_t *Texture_ForName(const char *filename);
100 NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX:
101 SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this
102 SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it
103 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
106 Image *loadBitmap(void *environment, const char *name)
108 DirectoryArchiveFile file(name, name);
109 if (!file.failed()) {
110 return g_bitmapModule->loadImage(file);
115 inline byte *getPixel(byte *pixels, int width, int height, int x, int y)
117 return pixels + (((((y + height) % height) * width) + ((x + width) % width)) * 4);
120 class KernelElement {
126 Image &convertHeightmapToNormalmap(Image &heightmap, float scale)
128 int w = heightmap.getWidth();
129 int h = heightmap.getHeight();
131 Image &normalmap = *(new RGBAImage(heightmap.getWidth(), heightmap.getHeight()));
133 byte *in = heightmap.getRGBAPixels();
134 byte *out = normalmap.getRGBAPixels();
138 const int kernelSize = 2;
139 KernelElement kernel_du[kernelSize] = {
143 KernelElement kernel_dv[kernelSize] = {
149 const int kernelSize = 6;
150 KernelElement kernel_du[kernelSize] = {
158 KernelElement kernel_dv[kernelSize] = {
173 for (KernelElement *i = kernel_du; i != kernel_du + kernelSize; ++i) {
174 du += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w;
177 for (KernelElement *i = kernel_dv; i != kernel_dv + kernelSize; ++i) {
178 dv += (getPixel(in, w, h, x + (*i).x, y + (*i).y)[0] / 255.0) * (*i).w;
181 float nx = -du * scale;
182 float ny = -dv * scale;
186 float norm = 1.0 / sqrt(nx * nx + ny * ny + nz * nz);
187 out[0] = float_to_integer(((nx * norm) + 1) * 127.5);
188 out[1] = float_to_integer(((ny * norm) + 1) * 127.5);
189 out[2] = float_to_integer(((nz * norm) + 1) * 127.5);
202 Image *loadHeightmap(void *environment, const char *name)
204 Image *heightmap = GlobalTexturesCache().loadImage(name);
205 if (heightmap != 0) {
206 Image &normalmap = convertHeightmapToNormalmap(*heightmap, *reinterpret_cast<float *>( environment ));
207 heightmap->release();
214 Image *loadSpecial(void *environment, const char *name)
216 if (*name == '_') { // special image
217 StringOutputStream bitmapName(256);
218 bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".png";
219 Image *image = loadBitmap(environment, bitmapName.c_str());
224 return GlobalTexturesCache().loadImage(name);
227 class ShaderPoolContext {
230 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
231 typedef PooledString<ShaderPool> ShaderString;
232 typedef ShaderString ShaderVariable;
233 typedef ShaderString ShaderValue;
234 typedef CopiedString TextureExpression;
236 // clean a texture name to the qtexture_t name format we use internally
237 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
238 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
239 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
240 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
241 template<typename StringType>
242 void parseTextureName(StringType &name, const char *token)
244 StringOutputStream cleaned(256);
245 cleaned << PathCleaned(token);
247 StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str()))).c_str(); // remove extension
250 bool Tokeniser_parseTextureName(Tokeniser &tokeniser, TextureExpression &name)
252 const char *token = tokeniser.getToken();
254 Tokeniser_unexpectedError(tokeniser, token, "#texture-name");
257 parseTextureName(name, token);
261 bool Tokeniser_parseShaderName(Tokeniser &tokeniser, CopiedString &name)
263 const char *token = tokeniser.getToken();
265 Tokeniser_unexpectedError(tokeniser, token, "#shader-name");
268 parseTextureName(name, token);
272 bool Tokeniser_parseString(Tokeniser &tokeniser, ShaderString &string)
274 const char *token = tokeniser.getToken();
276 Tokeniser_unexpectedError(tokeniser, token, "#string");
284 typedef std::list<ShaderVariable> ShaderParameters;
285 typedef std::list<ShaderVariable> ShaderArguments;
287 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
289 class ShaderTemplate {
290 std::size_t m_refcount;
294 ShaderParameters m_params;
296 TextureExpression m_textureName;
297 TextureExpression m_diffuse;
298 TextureExpression m_bump;
299 ShaderValue m_heightmapScale;
300 TextureExpression m_specular;
301 TextureExpression m_lightFalloffImage;
307 IShader::EAlphaFunc m_AlphaFunc;
310 IShader::ECull m_Cull;
326 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
327 if (--m_refcount == 0) {
332 std::size_t refcount()
337 const char *getName() const
339 return m_Name.c_str();
342 void setName(const char *name)
347 // -----------------------------------------
349 bool parseDoom3(Tokeniser &tokeniser);
351 bool parseQuake3(Tokeniser &tokeniser);
353 bool parseTemplate(Tokeniser &tokeniser);
356 void CreateDefault(const char *name)
358 if (g_enableDefaultShaders) {
359 m_textureName = name;
367 class MapLayerTemplate {
368 TextureExpression m_texture;
369 BlendFuncExpression m_blendFunc;
370 bool m_clampToBorder;
371 ShaderValue m_alphaTest;
373 MapLayerTemplate(const TextureExpression &texture, const BlendFuncExpression &blendFunc, bool clampToBorder,
374 const ShaderValue &alphaTest) :
376 m_blendFunc(blendFunc),
377 m_clampToBorder(false),
378 m_alphaTest(alphaTest)
382 const TextureExpression &texture() const
387 const BlendFuncExpression &blendFunc() const
392 bool clampToBorder() const
394 return m_clampToBorder;
397 const ShaderValue &alphaTest() const
403 typedef std::vector<MapLayerTemplate> MapLayers;
408 bool Doom3Shader_parseHeightmap(Tokeniser &tokeniser, TextureExpression &bump, ShaderValue &heightmapScale)
410 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
411 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
412 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
413 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, heightmapScale));
414 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
418 bool Doom3Shader_parseAddnormals(Tokeniser &tokeniser, TextureExpression &bump)
420 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
421 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
422 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
423 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "heightmap"));
424 TextureExpression heightmapName;
425 ShaderValue heightmapScale;
426 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, heightmapName, heightmapScale));
427 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
431 bool Doom3Shader_parseBumpmap(Tokeniser &tokeniser, TextureExpression &bump, ShaderValue &heightmapScale)
433 const char *token = tokeniser.getToken();
435 Tokeniser_unexpectedError(tokeniser, token, "#bumpmap");
438 if (string_equal(token, "heightmap")) {
439 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, bump, heightmapScale));
440 } else if (string_equal(token, "addnormals")) {
441 RETURN_FALSE_IF_FAIL(Doom3Shader_parseAddnormals(tokeniser, bump));
443 parseTextureName(bump, token);
456 class LayerTemplate {
459 TextureExpression m_texture;
460 BlendFuncExpression m_blendFunc;
461 bool m_clampToBorder;
462 ShaderValue m_alphaTest;
463 ShaderValue m_heightmapScale;
465 LayerTemplate() : m_type(LAYER_NONE), m_blendFunc("GL_ONE", "GL_ZERO"), m_clampToBorder(false), m_alphaTest("-1"),
466 m_heightmapScale("0")
471 bool parseShaderParameters(Tokeniser &tokeniser, ShaderParameters ¶ms)
473 Tokeniser_parseToken(tokeniser, "(");
475 const char *param = tokeniser.getToken();
476 if (string_equal(param, ")")) {
479 params.push_back(param);
480 const char *comma = tokeniser.getToken();
481 if (string_equal(comma, ")")) {
484 if (!string_equal(comma, ",")) {
485 Tokeniser_unexpectedError(tokeniser, comma, ",");
492 bool ShaderTemplate::parseTemplate(Tokeniser &tokeniser)
494 m_Name = tokeniser.getToken();
495 if (!parseShaderParameters(tokeniser, m_params)) {
496 globalErrorStream() << "shader template: " << makeQuoted(m_Name.c_str()) << ": parameter parse failed\n";
500 return parseDoom3(tokeniser);
503 bool ShaderTemplate::parseDoom3(Tokeniser &tokeniser)
505 LayerTemplate currentLayer;
508 // we need to read until we hit a balanced }
511 tokeniser.nextLine();
512 const char *token = tokeniser.getToken();
518 if (string_equal(token, "{")) {
521 } else if (string_equal(token, "}")) {
523 if (depth < 0) { // error
526 if (depth == 0) { // end of shader
529 if (depth == 1) { // end of layer
530 if (currentLayer.m_type == LAYER_DIFFUSEMAP) {
531 m_diffuse = currentLayer.m_texture;
532 } else if (currentLayer.m_type == LAYER_BUMPMAP) {
533 m_bump = currentLayer.m_texture;
534 } else if (currentLayer.m_type == LAYER_SPECULARMAP) {
535 m_specular = currentLayer.m_texture;
536 } else if (!string_empty(currentLayer.m_texture.c_str())) {
537 m_layers.push_back(MapLayerTemplate(
538 currentLayer.m_texture.c_str(),
539 currentLayer.m_blendFunc,
540 currentLayer.m_clampToBorder,
541 currentLayer.m_alphaTest
544 currentLayer.m_type = LAYER_NONE;
545 currentLayer.m_texture = "";
550 if (depth == 2) { // in layer
551 if (string_equal_nocase(token, "blend")) {
552 const char *blend = tokeniser.getToken();
555 Tokeniser_unexpectedError(tokeniser, blend, "#blend");
559 if (string_equal_nocase(blend, "diffusemap")) {
560 currentLayer.m_type = LAYER_DIFFUSEMAP;
561 } else if (string_equal_nocase(blend, "bumpmap")) {
562 currentLayer.m_type = LAYER_BUMPMAP;
563 } else if (string_equal_nocase(blend, "specularmap")) {
564 currentLayer.m_type = LAYER_SPECULARMAP;
566 currentLayer.m_blendFunc.first = blend;
568 const char *comma = tokeniser.getToken();
571 Tokeniser_unexpectedError(tokeniser, comma, "#comma");
575 if (string_equal(comma, ",")) {
576 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, currentLayer.m_blendFunc.second));
578 currentLayer.m_blendFunc.second = "";
579 tokeniser.ungetToken();
582 } else if (string_equal_nocase(token, "map")) {
583 if (currentLayer.m_type == LAYER_BUMPMAP) {
584 RETURN_FALSE_IF_FAIL(
585 Doom3Shader_parseBumpmap(tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale));
587 const char *map = tokeniser.getToken();
590 Tokeniser_unexpectedError(tokeniser, map, "#map");
594 if (string_equal(map, "makealpha")) {
595 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
596 const char *texture = tokeniser.getToken();
598 Tokeniser_unexpectedError(tokeniser, texture, "#texture");
601 currentLayer.m_texture = texture;
602 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
604 parseTextureName(currentLayer.m_texture, map);
607 } else if (string_equal_nocase(token, "zeroclamp")) {
608 currentLayer.m_clampToBorder = true;
611 else if ( string_equal_nocase( token, "alphaTest" ) ) {
612 Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest );
615 } else if (depth == 1) {
616 if (string_equal_nocase(token, "qer_editorimage")) {
617 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
618 } else if (string_equal_nocase(token, "qer_trans")) {
619 m_fTrans = string_read_float(tokeniser.getToken());
620 m_nFlags |= QER_TRANS;
621 } else if (string_equal_nocase(token, "translucent")) {
623 m_nFlags |= QER_TRANS;
624 } else if (string_equal(token, "DECAL_MACRO")) {
626 m_nFlags |= QER_TRANS;
627 } else if (string_equal_nocase(token, "bumpmap")) {
628 RETURN_FALSE_IF_FAIL(Doom3Shader_parseBumpmap(tokeniser, m_bump, m_heightmapScale));
629 } else if (string_equal_nocase(token, "diffusemap")) {
630 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_diffuse));
631 } else if (string_equal_nocase(token, "specularmap")) {
632 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_specular));
633 } else if (string_equal_nocase(token, "twosided")) {
634 m_Cull = IShader::eCullNone;
635 m_nFlags |= QER_CULL;
636 } else if (string_equal_nocase(token, "nodraw")) {
637 m_nFlags |= QER_NODRAW;
638 } else if (string_equal_nocase(token, "nonsolid")) {
639 m_nFlags |= QER_NONSOLID;
640 } else if (string_equal_nocase(token, "liquid")) {
641 m_nFlags |= QER_WATER;
642 } else if (string_equal_nocase(token, "areaportal")) {
643 m_nFlags |= QER_AREAPORTAL;
644 } else if (string_equal_nocase(token, "playerclip")
645 || string_equal_nocase(token, "monsterclip")
646 || string_equal_nocase(token, "ikclip")
647 || string_equal_nocase(token, "moveableclip")) {
648 m_nFlags |= QER_CLIP;
650 if (string_equal_nocase(token, "fogLight")) {
652 } else if (!isFog && string_equal_nocase(token, "lightFalloffImage")) {
653 const char *lightFalloffImage = tokeniser.getToken();
654 if (lightFalloffImage == 0) {
655 Tokeniser_unexpectedError(tokeniser, lightFalloffImage, "#lightFalloffImage");
658 if (string_equal_nocase(lightFalloffImage, "makeintensity")) {
659 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
660 TextureExpression name;
661 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, name));
662 m_lightFalloffImage = name;
663 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
665 m_lightFalloffImage = lightFalloffImage;
671 if (string_empty(m_textureName.c_str())) {
672 m_textureName = m_diffuse;
678 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
679 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
681 ShaderTemplateMap g_shaders;
682 ShaderTemplateMap g_shaderTemplates;
684 ShaderTemplate *findTemplate(const char *name)
686 ShaderTemplateMap::iterator i = g_shaderTemplates.find(name);
687 if (i != g_shaderTemplates.end()) {
688 return (*i).second.get();
693 class ShaderDefinition {
695 ShaderDefinition(ShaderTemplate *shaderTemplate, const ShaderArguments &args, const char *filename)
696 : shaderTemplate(shaderTemplate), args(args), filename(filename)
700 ShaderTemplate *shaderTemplate;
701 ShaderArguments args;
702 const char *filename;
705 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
707 ShaderDefinitionMap g_shaderDefinitions;
709 bool parseTemplateInstance(Tokeniser &tokeniser, const char *filename)
712 RETURN_FALSE_IF_FAIL(Tokeniser_parseShaderName(tokeniser, name));
713 const char *templateName = tokeniser.getToken();
714 ShaderTemplate *shaderTemplate = findTemplate(templateName);
715 if (shaderTemplate == 0) {
716 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": shader template not found: "
717 << makeQuoted(templateName) << "\n";
720 ShaderArguments args;
721 if (!parseShaderParameters(tokeniser, args)) {
722 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": argument parse failed\n";
726 if (shaderTemplate != 0) {
727 if (!g_shaderDefinitions.insert(
728 ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate, args, filename))).second) {
729 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str())
730 << ": already exists, second definition ignored\n";
737 const char *evaluateShaderValue(const char *value, const ShaderParameters ¶ms, const ShaderArguments &args)
739 ShaderArguments::const_iterator j = args.begin();
740 for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) {
741 const char *other = (*i).c_str();
742 if (string_equal(value, other)) {
749 ///\todo BlendFunc parsing
751 evaluateBlendFunc(const BlendFuncExpression &blendFunc, const ShaderParameters ¶ms, const ShaderArguments &args)
753 return BlendFunc(BLEND_ONE, BLEND_ZERO);
757 evaluateTexture(const TextureExpression &texture, const ShaderParameters ¶ms, const ShaderArguments &args,
758 const LoadImageCallback &loader = GlobalTexturesCache().defaultLoader())
760 StringOutputStream result(64);
761 const char *expression = texture.c_str();
762 const char *end = expression + string_length(expression);
763 if (!string_empty(expression)) {
765 const char *best = end;
766 const char *bestParam = 0;
767 const char *bestArg = 0;
768 ShaderArguments::const_iterator j = args.begin();
769 for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) {
770 const char *found = strstr(expression, (*i).c_str());
771 if (found != 0 && found < best) {
773 bestParam = (*i).c_str();
774 bestArg = (*j).c_str();
778 result << StringRange(expression, best);
779 result << PathCleaned(bestArg);
780 expression = best + string_length(bestParam);
785 result << expression;
787 return GlobalTexturesCache().capture(loader, result.c_str());
790 float evaluateFloat(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args)
792 const char *result = evaluateShaderValue(value.c_str(), params, args);
794 if (!string_parse_float(result, f)) {
795 globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n";
800 BlendFactor evaluateBlendFactor(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args)
802 const char *result = evaluateShaderValue(value.c_str(), params, args);
804 if (string_equal_nocase(result, "gl_zero")) {
807 if (string_equal_nocase(result, "gl_one")) {
810 if (string_equal_nocase(result, "gl_src_color")) {
811 return BLEND_SRC_COLOUR;
813 if (string_equal_nocase(result, "gl_one_minus_src_color")) {
814 return BLEND_ONE_MINUS_SRC_COLOUR;
816 if (string_equal_nocase(result, "gl_src_alpha")) {
817 return BLEND_SRC_ALPHA;
819 if (string_equal_nocase(result, "gl_one_minus_src_alpha")) {
820 return BLEND_ONE_MINUS_SRC_ALPHA;
822 if (string_equal_nocase(result, "gl_dst_color")) {
823 return BLEND_DST_COLOUR;
825 if (string_equal_nocase(result, "gl_one_minus_dst_color")) {
826 return BLEND_ONE_MINUS_DST_COLOUR;
828 if (string_equal_nocase(result, "gl_dst_alpha")) {
829 return BLEND_DST_ALPHA;
831 if (string_equal_nocase(result, "gl_one_minus_dst_alpha")) {
832 return BLEND_ONE_MINUS_DST_ALPHA;
834 if (string_equal_nocase(result, "gl_src_alpha_saturate")) {
835 return BLEND_SRC_ALPHA_SATURATE;
838 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted(result) << "\n";
842 class CShader : public IShader {
843 std::size_t m_refcount;
845 const ShaderTemplate &m_template;
846 const ShaderArguments &m_args;
847 const char *m_filename;
848 // name is shader-name, otherwise texture-name (if not a real shader)
851 qtexture_t *m_pTexture;
852 qtexture_t *m_notfound;
853 qtexture_t *m_pDiffuse;
854 float m_heightmapScale;
856 qtexture_t *m_pSpecular;
857 qtexture_t *m_pLightFalloffImage;
858 BlendFunc m_blendFunc;
864 static bool m_lightingEnabled;
866 CShader(const ShaderDefinition &definition) :
868 m_template(*definition.shaderTemplate),
869 m_args(definition.args),
870 m_filename(definition.filename),
871 m_blendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA),
888 ASSERT_MESSAGE(m_refcount == 0, "deleting active shader");
891 // IShaders implementation -----------------
899 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
900 if (--m_refcount == 0) {
905 std::size_t refcount()
910 // get/set the qtexture_t* Radiant uses to represent this shader object
911 qtexture_t *getTexture() const
916 qtexture_t *getDiffuse() const
921 qtexture_t *getBump() const
926 qtexture_t *getSpecular() const
932 const char *getName() const
934 return m_Name.c_str();
942 void SetInUse(bool bInUse)
945 g_ActiveShadersChangedNotify();
948 // get the shader flags
951 return m_template.m_nFlags;
954 // get the transparency value
955 float getTrans() const
957 return m_template.m_fTrans;
960 // test if it's a true shader, or a default shader created to wrap around a texture
961 bool IsDefault() const
963 return string_empty(m_filename);
967 void getAlphaFunc(EAlphaFunc *func, float *ref)
969 *func = m_template.m_AlphaFunc;
970 *ref = m_template.m_AlphaRef;
973 BlendFunc getBlendFunc() const
981 return m_template.m_Cull;
984 // get shader file name (ie the file where this one is defined)
985 const char *getShaderFileName() const
989 // -----------------------------------------
993 m_pTexture = evaluateTexture(m_template.m_textureName, m_template.m_params, m_args);
995 if (m_pTexture->texture_number == 0) {
996 m_notfound = m_pTexture;
999 m_pTexture = GlobalTexturesCache().capture(IsDefault() ? DEFAULT_NOTEX_NAME : DEFAULT_SHADERNOTEX_NAME);
1008 GlobalTexturesCache().release(m_pTexture);
1010 if (m_notfound != 0) {
1011 GlobalTexturesCache().release(m_notfound);
1014 unrealiseLighting();
1017 void realiseLighting()
1019 if (m_lightingEnabled) {
1020 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
1021 if (!string_empty(m_template.m_heightmapScale.c_str())) {
1022 m_heightmapScale = evaluateFloat(m_template.m_heightmapScale, m_template.m_params, m_args);
1023 loader = LoadImageCallback(&m_heightmapScale, loadHeightmap);
1025 m_pDiffuse = evaluateTexture(m_template.m_diffuse, m_template.m_params, m_args);
1026 m_pBump = evaluateTexture(m_template.m_bump, m_template.m_params, m_args, loader);
1027 m_pSpecular = evaluateTexture(m_template.m_specular, m_template.m_params, m_args);
1028 m_pLightFalloffImage = evaluateTexture(m_template.m_lightFalloffImage, m_template.m_params, m_args);
1030 for (ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin();
1031 i != m_template.m_layers.end(); ++i) {
1032 m_layers.push_back(evaluateLayer(*i, m_template.m_params, m_args));
1035 if (m_layers.size() == 1) {
1036 const BlendFuncExpression &blendFunc = m_template.m_layers.front().blendFunc();
1037 if (!string_empty(blendFunc.second.c_str())) {
1038 m_blendFunc = BlendFunc(
1039 evaluateBlendFactor(blendFunc.first.c_str(), m_template.m_params, m_args),
1040 evaluateBlendFactor(blendFunc.second.c_str(), m_template.m_params, m_args)
1043 const char *blend = evaluateShaderValue(blendFunc.first.c_str(), m_template.m_params, m_args);
1045 if (string_equal_nocase(blend, "add")) {
1046 m_blendFunc = BlendFunc(BLEND_ONE, BLEND_ONE);
1047 } else if (string_equal_nocase(blend, "filter")) {
1048 m_blendFunc = BlendFunc(BLEND_DST_COLOUR, BLEND_ZERO);
1049 } else if (string_equal_nocase(blend, "blend")) {
1050 m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA);
1052 globalErrorStream() << "parsing blend value failed: " << makeQuoted(blend) << "\n";
1059 void unrealiseLighting()
1061 if (m_lightingEnabled) {
1062 GlobalTexturesCache().release(m_pDiffuse);
1063 GlobalTexturesCache().release(m_pBump);
1064 GlobalTexturesCache().release(m_pSpecular);
1066 GlobalTexturesCache().release(m_pLightFalloffImage);
1068 for (MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
1069 GlobalTexturesCache().release((*i).texture());
1073 m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA);
1078 void setName(const char *name)
1083 class MapLayer : public ShaderLayer {
1084 qtexture_t *m_texture;
1085 BlendFunc m_blendFunc;
1086 bool m_clampToBorder;
1089 MapLayer(qtexture_t *texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) :
1091 m_blendFunc(blendFunc),
1092 m_clampToBorder(false),
1093 m_alphaTest(alphaTest)
1097 qtexture_t *texture() const
1102 BlendFunc blendFunc() const
1107 bool clampToBorder() const
1109 return m_clampToBorder;
1112 float alphaTest() const
1118 static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate &layerTemplate, const ShaderParameters ¶ms,
1119 const ShaderArguments &args)
1122 evaluateTexture(layerTemplate.texture(), params, args),
1123 evaluateBlendFunc(layerTemplate.blendFunc(), params, args),
1124 layerTemplate.clampToBorder(),
1125 evaluateFloat(layerTemplate.alphaTest(), params, args)
1129 typedef std::vector<MapLayer> MapLayers;
1132 const ShaderLayer *firstLayer() const
1134 if (m_layers.empty()) {
1137 return &m_layers.front();
1140 void forEachLayer(const ShaderLayerCallback &callback) const
1142 for (MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
1147 qtexture_t *lightFalloffImage() const
1149 if (!string_empty(m_template.m_lightFalloffImage.c_str())) {
1150 return m_pLightFalloffImage;
1156 bool CShader::m_lightingEnabled = false;
1158 typedef SmartPointer<CShader> ShaderPointer;
1159 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1161 shaders_t g_ActiveShaders;
1163 static shaders_t::iterator g_ActiveShadersIterator;
1165 void ActiveShaders_IteratorBegin()
1167 g_ActiveShadersIterator = g_ActiveShaders.begin();
1170 bool ActiveShaders_IteratorAtEnd()
1172 return g_ActiveShadersIterator == g_ActiveShaders.end();
1175 IShader *ActiveShaders_IteratorCurrent()
1177 return static_cast<CShader *>( g_ActiveShadersIterator->second );
1180 void ActiveShaders_IteratorIncrement()
1182 ++g_ActiveShadersIterator;
1185 void debug_check_shaders(shaders_t &shaders)
1187 for (shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i) {
1188 ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced");
1192 // will free all GL binded qtextures and shaders
1193 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1197 // empty the actives shaders list
1198 debug_check_shaders(g_ActiveShaders);
1199 g_ActiveShaders.clear();
1201 g_shaderTemplates.clear();
1202 g_shaderDefinitions.clear();
1203 g_ActiveShadersChangedNotify();
1206 bool ShaderTemplate::parseQuake3(Tokeniser &tokeniser)
1208 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1209 m_textureName = m_Name.c_str();
1211 tokeniser.nextLine();
1213 // we need to read until we hit a balanced }
1216 tokeniser.nextLine();
1217 const char *token = tokeniser.getToken();
1223 if (string_equal(token, "{")) {
1226 } else if (string_equal(token, "}")) {
1228 if (depth < 0) { // underflow
1231 if (depth == 0) { // end of shader
1239 if (string_equal_nocase(token, "qer_nocarve")) {
1240 m_nFlags |= QER_NOCARVE;
1241 } else if (string_equal_nocase(token, "qer_trans")) {
1242 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans));
1243 m_nFlags |= QER_TRANS;
1244 } else if (string_equal_nocase(token, "qer_editorimage")) {
1245 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
1246 } else if (string_equal_nocase(token, "qer_alphafunc")) {
1247 const char *alphafunc = tokeniser.getToken();
1249 if (alphafunc == 0) {
1250 Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc");
1254 if (string_equal_nocase(alphafunc, "equal")) {
1255 m_AlphaFunc = IShader::eEqual;
1256 } else if (string_equal_nocase(alphafunc, "greater")) {
1257 m_AlphaFunc = IShader::eGreater;
1258 } else if (string_equal_nocase(alphafunc, "less")) {
1259 m_AlphaFunc = IShader::eLess;
1260 } else if (string_equal_nocase(alphafunc, "gequal")) {
1261 m_AlphaFunc = IShader::eGEqual;
1262 } else if (string_equal_nocase(alphafunc, "lequal")) {
1263 m_AlphaFunc = IShader::eLEqual;
1265 m_AlphaFunc = IShader::eAlways;
1268 m_nFlags |= QER_ALPHATEST;
1270 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef));
1271 } else if (string_equal_nocase(token, "cull")) {
1272 const char *cull = tokeniser.getToken();
1275 Tokeniser_unexpectedError(tokeniser, cull, "#cull");
1279 if (string_equal_nocase(cull, "none")
1280 || string_equal_nocase(cull, "twosided")
1281 || string_equal_nocase(cull, "disable")) {
1282 m_Cull = IShader::eCullNone;
1283 } else if (string_equal_nocase(cull, "back")
1284 || string_equal_nocase(cull, "backside")
1285 || string_equal_nocase(cull, "backsided")) {
1286 m_Cull = IShader::eCullBack;
1288 m_Cull = IShader::eCullBack;
1291 m_nFlags |= QER_CULL;
1292 } else if (string_equal_nocase(token, "surfaceparm")) {
1293 const char *surfaceparm = tokeniser.getToken();
1295 if (surfaceparm == 0) {
1296 Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm");
1300 if (string_equal_nocase(surfaceparm, "fog")) {
1301 m_nFlags |= QER_FOG;
1302 if (m_fTrans == 1.0f) { // has not been explicitly set by qer_trans
1305 } else if (string_equal_nocase(surfaceparm, "nodraw")) {
1306 m_nFlags |= QER_NODRAW;
1307 } else if (string_equal_nocase(surfaceparm, "nonsolid")) {
1308 m_nFlags |= QER_NONSOLID;
1309 } else if (string_equal_nocase(surfaceparm, "water")) {
1310 m_nFlags |= QER_WATER;
1311 } else if (string_equal_nocase(surfaceparm, "lava")) {
1312 m_nFlags |= QER_LAVA;
1313 } else if (string_equal_nocase(surfaceparm, "areaportal")) {
1314 m_nFlags |= QER_AREAPORTAL;
1315 } else if (string_equal_nocase(surfaceparm, "playerclip")) {
1316 m_nFlags |= QER_CLIP;
1317 } else if (string_equal_nocase(surfaceparm, "botclip")) {
1318 m_nFlags |= QER_BOTCLIP;
1330 TextureExpression m_texture;
1331 BlendFunc m_blendFunc;
1332 bool m_clampToBorder;
1334 float m_heightmapScale;
1336 Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1),
1342 std::list<CopiedString> g_shaderFilenames;
1344 void ParseShaderFile(Tokeniser &tokeniser, const char *filename)
1346 g_shaderFilenames.push_back(filename);
1347 filename = g_shaderFilenames.back().c_str();
1348 tokeniser.nextLine();
1350 const char *token = tokeniser.getToken();
1356 if (string_equal(token, "table")) {
1357 if (tokeniser.getToken() == 0) {
1358 Tokeniser_unexpectedError(tokeniser, 0, "#table-name");
1361 if (!Tokeniser_parseToken(tokeniser, "{")) {
1365 const char *option = tokeniser.getToken();
1366 if (string_equal(option, "{")) {
1368 const char *value = tokeniser.getToken();
1369 if (string_equal(value, "}")) {
1374 if (!Tokeniser_parseToken(tokeniser, "}")) {
1381 if (string_equal(token, "guide")) {
1382 parseTemplateInstance(tokeniser, filename);
1384 if (!string_equal(token, "material")
1385 && !string_equal(token, "particle")
1386 && !string_equal(token, "skin")) {
1387 tokeniser.ungetToken();
1389 // first token should be the path + name.. (from base)
1391 if (!Tokeniser_parseShaderName(tokeniser, name)) {
1393 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1394 shaderTemplate->setName(name.c_str());
1396 g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1398 bool result = (g_shaderLanguage == SHADERLANGUAGE_QUAKE3)
1399 ? shaderTemplate->parseQuake3(tokeniser)
1400 : shaderTemplate->parseDoom3(tokeniser);
1402 // do we already have this shader?
1403 if (!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(),
1405 shaderTemplate.get(),
1407 filename))).second) {
1409 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName()
1410 << " is already in memory, definition in " << filename << " ignored.\n";
1414 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1422 void parseGuideFile(Tokeniser &tokeniser, const char *filename)
1424 tokeniser.nextLine();
1426 const char *token = tokeniser.getToken();
1432 if (string_equal(token, "guide")) {
1433 // first token should be the path + name.. (from base)
1434 ShaderTemplatePointer shaderTemplate(new ShaderTemplate);
1435 shaderTemplate->parseTemplate(tokeniser);
1436 if (!g_shaderTemplates.insert(
1437 ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second) {
1438 globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName())
1439 << ": already defined, second definition ignored\n";
1441 } else if (string_equal(token, "inlineGuide")) {
1442 // skip entire inlineGuide definition
1443 std::size_t depth = 0;
1445 tokeniser.nextLine();
1446 token = tokeniser.getToken();
1447 if (string_equal(token, "{")) {
1449 } else if (string_equal(token, "}")) {
1459 void LoadShaderFile(const char *filename)
1461 ArchiveTextFile *file = GlobalFileSystem().openTextFile(filename);
1464 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1466 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1468 ParseShaderFile(tokeniser, filename);
1470 tokeniser.release();
1473 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1477 void loadGuideFile(const char *filename)
1479 StringOutputStream fullname(256);
1480 fullname << "guides/" << filename;
1481 ArchiveTextFile *file = GlobalFileSystem().openTextFile(fullname.c_str());
1484 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1486 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1488 parseGuideFile(tokeniser, fullname.c_str());
1490 tokeniser.release();
1493 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1497 CShader *Try_Shader_ForName(const char *name)
1500 shaders_t::iterator i = g_ActiveShaders.find(name);
1501 if (i != g_ActiveShaders.end()) {
1505 // active shader was not found
1507 // find matching shader definition
1508 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find(name);
1509 if (i == g_shaderDefinitions.end()) {
1510 // shader definition was not found
1512 // create new shader definition from default shader template
1513 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1514 shaderTemplate->CreateDefault(name);
1515 g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1517 i = g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate.get(),
1522 // create shader from existing definition
1523 ShaderPointer pShader(new CShader((*i).second));
1524 pShader->setName(name);
1525 g_ActiveShaders.insert(shaders_t::value_type(name, pShader));
1526 g_ActiveShadersChangedNotify();
1530 IShader *Shader_ForName(const char *name)
1532 ASSERT_NOTNULL(name);
1534 IShader *pShader = Try_Shader_ForName(name);
1540 // the list of scripts/*.shader files we need to work with
1541 // those are listed in shaderlist file
1542 GSList *l_shaderfiles = 0;
1544 GSList *Shaders_getShaderFileList()
1546 return l_shaderfiles;
1551 DumpUnreferencedShaders
1552 usefull function: dumps the list of .shader files that are not referenced to the console
1555 void IfFound_dumpUnreferencedShader(bool &bFound, const char *filename)
1557 bool listed = false;
1559 for (GSList *sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh)) {
1560 if (!strcmp((char *) sh->data, filename)) {
1569 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1571 globalOutputStream() << "\t" << filename << "\n";
1575 typedef ReferenceCaller<bool, void(const char *), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1577 void DumpUnreferencedShaders()
1579 bool bFound = false;
1580 GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound));
1583 void ShaderList_addShaderFile(const char *dirstring)
1587 for (GSList *tmp = l_shaderfiles; tmp != 0; tmp = tmp->next) {
1588 if (string_equal_nocase(dirstring, (char *) tmp->data)) {
1590 globalOutputStream() << "duplicate entry \"" << (char *) tmp->data << "\" in shaderlist.txt\n";
1596 l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring));
1603 build a CStringList of shader names
1606 void BuildShaderList(TextInputStream &shaderlist)
1608 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist);
1609 tokeniser.nextLine();
1610 const char *token = tokeniser.getToken();
1611 StringOutputStream shaderFile(64);
1612 while (token != 0) {
1613 // each token should be a shader filename
1614 shaderFile << token << "." << g_shadersExtension;
1616 ShaderList_addShaderFile(shaderFile.c_str());
1618 tokeniser.nextLine();
1619 token = tokeniser.getToken();
1623 tokeniser.release();
1626 void FreeShaderList()
1628 while (l_shaderfiles != 0) {
1629 free(l_shaderfiles->data);
1630 l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data);
1634 void ShaderList_addFromArchive(const char *archivename)
1636 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1637 if (string_empty(shaderpath)) {
1641 StringOutputStream shaderlist(256);
1642 shaderlist << DirectoryCleaned(shaderpath) << "shaderlist.txt";
1644 Archive *archive = GlobalFileSystem().getArchive(archivename, false);
1646 ArchiveTextFile *file = archive->openTextFile(shaderlist.c_str());
1648 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1649 BuildShaderList(file->getInputStream());
1655 #include "stream/filestream.h"
1658 shaderlist_findOrInstall(const char *enginePath, const char *toolsPath, const char *shaderPath, const char *gamename)
1660 StringOutputStream absShaderList(256);
1661 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1662 if (file_exists(absShaderList.c_str())) {
1666 StringOutputStream directory(256);
1667 directory << enginePath << gamename << '/' << shaderPath;
1668 if (!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str())) {
1673 StringOutputStream defaultShaderList(256);
1674 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1675 if (file_exists(defaultShaderList.c_str())) {
1676 return file_copy(defaultShaderList.c_str(), absShaderList.c_str());
1684 if (g_shaderLanguage == SHADERLANGUAGE_QUAKE4) {
1685 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1688 const char *shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1689 if (!string_empty(shaderPath)) {
1690 StringOutputStream path(256);
1691 path << DirectoryCleaned(shaderPath);
1693 if (g_useShaderList) {
1694 // preload shader files that have been listed in shaderlist.txt
1695 const char *basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame");
1696 const char *gamename = GlobalRadiant().getGameName();
1697 const char *enginePath = GlobalRadiant().getEnginePath();
1698 const char *toolsPath = GlobalRadiant().getGameToolsPath();
1700 bool isMod = !string_equal(basegame, gamename);
1702 if (!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename)) {
1703 gamename = basegame;
1704 shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename);
1707 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1708 DumpUnreferencedShaders();
1710 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile),
1714 GSList *lst = l_shaderfiles;
1715 StringOutputStream shadername(256);
1717 shadername << path.c_str() << reinterpret_cast<const char *>( lst->data );
1718 LoadShaderFile(shadername.c_str());
1724 //StringPool_analyse(ShaderPool::instance());
1731 g_shaderFilenames.clear();
1734 ModuleObservers g_observers;
1736 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1737 bool Shaders_realised()
1739 return g_shaders_unrealised == 0;
1742 void Shaders_Realise()
1744 if (--g_shaders_unrealised == 0) {
1746 g_observers.realise();
1750 void Shaders_Unrealise()
1752 if (++g_shaders_unrealised == 1) {
1753 g_observers.unrealise();
1758 void Shaders_Refresh()
1760 Shaders_Unrealise();
1764 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver {
1773 Shaders_Unrealise();
1781 IShader *getShaderForName(const char *name)
1783 return Shader_ForName(name);
1786 void foreachShaderName(const ShaderNameCallback &callback)
1788 for (ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i) {
1789 callback((*i).first.c_str());
1793 void beginActiveShadersIterator()
1795 ActiveShaders_IteratorBegin();
1798 bool endActiveShadersIterator()
1800 return ActiveShaders_IteratorAtEnd();
1803 IShader *dereferenceActiveShadersIterator()
1805 return ActiveShaders_IteratorCurrent();
1808 void incrementActiveShadersIterator()
1810 ActiveShaders_IteratorIncrement();
1813 void setActiveShadersChangedNotify(const Callback<void()> ¬ify)
1815 g_ActiveShadersChangedNotify = notify;
1818 void attach(ModuleObserver &observer)
1820 g_observers.attach(observer);
1823 void detach(ModuleObserver &observer)
1825 g_observers.detach(observer);
1828 void setLightingEnabled(bool enabled)
1830 if (CShader::m_lightingEnabled != enabled) {
1831 for (shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) {
1832 (*i).second->unrealiseLighting();
1834 CShader::m_lightingEnabled = enabled;
1835 for (shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) {
1836 (*i).second->realiseLighting();
1841 const char *getTexturePrefix() const
1843 return g_texturePrefix;
1847 Quake3ShaderSystem g_Quake3ShaderSystem;
1849 ShaderSystem &GetShaderSystem()
1851 return g_Quake3ShaderSystem;
1854 void Shaders_Construct()
1856 GlobalFileSystem().attach(g_Quake3ShaderSystem);
1859 void Shaders_Destroy()
1861 GlobalFileSystem().detach(g_Quake3ShaderSystem);
1863 if (Shaders_realised()) {