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();
213 class ShaderPoolContext {
216 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
217 typedef PooledString<ShaderPool> ShaderString;
218 typedef ShaderString ShaderVariable;
219 typedef ShaderString ShaderValue;
220 typedef CopiedString TextureExpression;
222 // clean a texture name to the qtexture_t name format we use internally
223 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
224 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
225 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
226 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
227 template<typename StringType>
228 void parseTextureName(StringType &name, const char *token)
230 StringOutputStream cleaned(256);
231 cleaned << PathCleaned(token);
233 StringRange(cleaned.c_str(), path_get_filename_base_end(cleaned.c_str()))).c_str(); // remove extension
236 bool Tokeniser_parseTextureName(Tokeniser &tokeniser, TextureExpression &name)
238 const char *token = tokeniser.getToken();
240 Tokeniser_unexpectedError(tokeniser, token, "#texture-name");
243 parseTextureName(name, token);
247 bool Tokeniser_parseShaderName(Tokeniser &tokeniser, CopiedString &name)
249 const char *token = tokeniser.getToken();
251 Tokeniser_unexpectedError(tokeniser, token, "#shader-name");
254 parseTextureName(name, token);
258 bool Tokeniser_parseString(Tokeniser &tokeniser, ShaderString &string)
260 const char *token = tokeniser.getToken();
262 Tokeniser_unexpectedError(tokeniser, token, "#string");
270 typedef std::list<ShaderVariable> ShaderParameters;
271 typedef std::list<ShaderVariable> ShaderArguments;
273 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
275 class ShaderTemplate {
276 std::size_t m_refcount;
280 ShaderParameters m_params;
282 TextureExpression m_textureName;
283 TextureExpression m_diffuse;
284 TextureExpression m_bump;
285 ShaderValue m_heightmapScale;
286 TextureExpression m_specular;
287 TextureExpression m_lightFalloffImage;
293 IShader::EAlphaFunc m_AlphaFunc;
296 IShader::ECull m_Cull;
312 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
313 if (--m_refcount == 0) {
318 std::size_t refcount()
323 const char *getName() const
325 return m_Name.c_str();
328 void setName(const char *name)
333 // -----------------------------------------
335 bool parseDoom3(Tokeniser &tokeniser);
337 bool parseQuake3(Tokeniser &tokeniser);
339 bool parseTemplate(Tokeniser &tokeniser);
342 void CreateDefault(const char *name)
344 if (g_enableDefaultShaders) {
345 m_textureName = name;
353 class MapLayerTemplate {
354 TextureExpression m_texture;
355 BlendFuncExpression m_blendFunc;
356 bool m_clampToBorder;
357 ShaderValue m_alphaTest;
359 MapLayerTemplate(const TextureExpression &texture, const BlendFuncExpression &blendFunc, bool clampToBorder,
360 const ShaderValue &alphaTest) :
362 m_blendFunc(blendFunc),
363 m_clampToBorder(false),
364 m_alphaTest(alphaTest)
368 const TextureExpression &texture() const
373 const BlendFuncExpression &blendFunc() const
378 bool clampToBorder() const
380 return m_clampToBorder;
383 const ShaderValue &alphaTest() const
389 typedef std::vector<MapLayerTemplate> MapLayers;
394 bool Doom3Shader_parseHeightmap(Tokeniser &tokeniser, TextureExpression &bump, ShaderValue &heightmapScale)
396 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
397 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
398 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
399 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, heightmapScale));
400 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
404 bool Doom3Shader_parseAddnormals(Tokeniser &tokeniser, TextureExpression &bump)
406 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
407 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, bump));
408 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ","));
409 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "heightmap"));
410 TextureExpression heightmapName;
411 ShaderValue heightmapScale;
412 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, heightmapName, heightmapScale));
413 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
417 bool Doom3Shader_parseBumpmap(Tokeniser &tokeniser, TextureExpression &bump, ShaderValue &heightmapScale)
419 const char *token = tokeniser.getToken();
421 Tokeniser_unexpectedError(tokeniser, token, "#bumpmap");
424 if (string_equal(token, "heightmap")) {
425 RETURN_FALSE_IF_FAIL(Doom3Shader_parseHeightmap(tokeniser, bump, heightmapScale));
426 } else if (string_equal(token, "addnormals")) {
427 RETURN_FALSE_IF_FAIL(Doom3Shader_parseAddnormals(tokeniser, bump));
429 parseTextureName(bump, token);
442 class LayerTemplate {
445 TextureExpression m_texture;
446 BlendFuncExpression m_blendFunc;
447 bool m_clampToBorder;
448 ShaderValue m_alphaTest;
449 ShaderValue m_heightmapScale;
451 LayerTemplate() : m_type(LAYER_NONE), m_blendFunc("GL_ONE", "GL_ZERO"), m_clampToBorder(false), m_alphaTest("-1"),
452 m_heightmapScale("0")
457 bool parseShaderParameters(Tokeniser &tokeniser, ShaderParameters ¶ms)
459 Tokeniser_parseToken(tokeniser, "(");
461 const char *param = tokeniser.getToken();
462 if (string_equal(param, ")")) {
465 params.push_back(param);
466 const char *comma = tokeniser.getToken();
467 if (string_equal(comma, ")")) {
470 if (!string_equal(comma, ",")) {
471 Tokeniser_unexpectedError(tokeniser, comma, ",");
478 bool ShaderTemplate::parseTemplate(Tokeniser &tokeniser)
480 m_Name = tokeniser.getToken();
481 if (!parseShaderParameters(tokeniser, m_params)) {
482 globalErrorStream() << "shader template: " << makeQuoted(m_Name.c_str()) << ": parameter parse failed\n";
486 return parseDoom3(tokeniser);
489 bool ShaderTemplate::parseDoom3(Tokeniser &tokeniser)
491 LayerTemplate currentLayer;
494 // we need to read until we hit a balanced }
497 tokeniser.nextLine();
498 const char *token = tokeniser.getToken();
504 if (string_equal(token, "{")) {
507 } else if (string_equal(token, "}")) {
509 if (depth < 0) { // error
512 if (depth == 0) { // end of shader
515 if (depth == 1) { // end of layer
516 if (currentLayer.m_type == LAYER_DIFFUSEMAP) {
517 m_diffuse = currentLayer.m_texture;
518 } else if (currentLayer.m_type == LAYER_BUMPMAP) {
519 m_bump = currentLayer.m_texture;
520 } else if (currentLayer.m_type == LAYER_SPECULARMAP) {
521 m_specular = currentLayer.m_texture;
522 } else if (!string_empty(currentLayer.m_texture.c_str())) {
523 m_layers.push_back(MapLayerTemplate(
524 currentLayer.m_texture.c_str(),
525 currentLayer.m_blendFunc,
526 currentLayer.m_clampToBorder,
527 currentLayer.m_alphaTest
530 currentLayer.m_type = LAYER_NONE;
531 currentLayer.m_texture = "";
536 if (depth == 2) { // in layer
537 if (string_equal_nocase(token, "blend")) {
538 const char *blend = tokeniser.getToken();
541 Tokeniser_unexpectedError(tokeniser, blend, "#blend");
545 if (string_equal_nocase(blend, "diffusemap")) {
546 currentLayer.m_type = LAYER_DIFFUSEMAP;
547 } else if (string_equal_nocase(blend, "bumpmap")) {
548 currentLayer.m_type = LAYER_BUMPMAP;
549 } else if (string_equal_nocase(blend, "specularmap")) {
550 currentLayer.m_type = LAYER_SPECULARMAP;
552 currentLayer.m_blendFunc.first = blend;
554 const char *comma = tokeniser.getToken();
557 Tokeniser_unexpectedError(tokeniser, comma, "#comma");
561 if (string_equal(comma, ",")) {
562 RETURN_FALSE_IF_FAIL(Tokeniser_parseString(tokeniser, currentLayer.m_blendFunc.second));
564 currentLayer.m_blendFunc.second = "";
565 tokeniser.ungetToken();
568 } else if (string_equal_nocase(token, "map")) {
569 if (currentLayer.m_type == LAYER_BUMPMAP) {
570 RETURN_FALSE_IF_FAIL(
571 Doom3Shader_parseBumpmap(tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale));
573 const char *map = tokeniser.getToken();
576 Tokeniser_unexpectedError(tokeniser, map, "#map");
580 if (string_equal(map, "makealpha")) {
581 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
582 const char *texture = tokeniser.getToken();
584 Tokeniser_unexpectedError(tokeniser, texture, "#texture");
587 currentLayer.m_texture = texture;
588 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
590 parseTextureName(currentLayer.m_texture, map);
593 } else if (string_equal_nocase(token, "zeroclamp")) {
594 currentLayer.m_clampToBorder = true;
597 else if ( string_equal_nocase( token, "alphaTest" ) ) {
598 Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest );
601 } else if (depth == 1) {
602 if (string_equal_nocase(token, "qer_editorimage")) {
603 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
604 } else if (string_equal_nocase(token, "qer_trans")) {
605 m_fTrans = string_read_float(tokeniser.getToken());
606 m_nFlags |= QER_TRANS;
607 } else if (string_equal_nocase(token, "translucent")) {
609 m_nFlags |= QER_TRANS;
610 } else if (string_equal(token, "DECAL_MACRO")) {
612 m_nFlags |= QER_TRANS;
613 } else if (string_equal_nocase(token, "bumpmap")) {
614 RETURN_FALSE_IF_FAIL(Doom3Shader_parseBumpmap(tokeniser, m_bump, m_heightmapScale));
615 } else if (string_equal_nocase(token, "diffusemap")) {
616 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_diffuse));
617 } else if (string_equal_nocase(token, "specularmap")) {
618 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_specular));
619 } else if (string_equal_nocase(token, "twosided")) {
620 m_Cull = IShader::eCullNone;
621 m_nFlags |= QER_CULL;
622 } else if (string_equal_nocase(token, "nodraw")) {
623 m_nFlags |= QER_NODRAW;
624 } else if (string_equal_nocase(token, "nonsolid")) {
625 m_nFlags |= QER_NONSOLID;
626 } else if (string_equal_nocase(token, "liquid")) {
627 m_nFlags |= QER_WATER;
628 } else if (string_equal_nocase(token, "areaportal")) {
629 m_nFlags |= QER_AREAPORTAL;
630 } else if (string_equal_nocase(token, "playerclip")
631 || string_equal_nocase(token, "monsterclip")
632 || string_equal_nocase(token, "ikclip")
633 || string_equal_nocase(token, "moveableclip")) {
634 m_nFlags |= QER_CLIP;
636 if (string_equal_nocase(token, "fogLight")) {
638 } else if (!isFog && string_equal_nocase(token, "lightFalloffImage")) {
639 const char *lightFalloffImage = tokeniser.getToken();
640 if (lightFalloffImage == 0) {
641 Tokeniser_unexpectedError(tokeniser, lightFalloffImage, "#lightFalloffImage");
644 if (string_equal_nocase(lightFalloffImage, "makeintensity")) {
645 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, "("));
646 TextureExpression name;
647 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, name));
648 m_lightFalloffImage = name;
649 RETURN_FALSE_IF_FAIL(Tokeniser_parseToken(tokeniser, ")"));
651 m_lightFalloffImage = lightFalloffImage;
657 if (string_empty(m_textureName.c_str())) {
658 m_textureName = m_diffuse;
664 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
665 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
667 ShaderTemplateMap g_shaders;
668 ShaderTemplateMap g_shaderTemplates;
670 ShaderTemplate *findTemplate(const char *name)
672 ShaderTemplateMap::iterator i = g_shaderTemplates.find(name);
673 if (i != g_shaderTemplates.end()) {
674 return (*i).second.get();
679 class ShaderDefinition {
681 ShaderDefinition(ShaderTemplate *shaderTemplate, const ShaderArguments &args, const char *filename)
682 : shaderTemplate(shaderTemplate), args(args), filename(filename)
686 ShaderTemplate *shaderTemplate;
687 ShaderArguments args;
688 const char *filename;
691 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
693 ShaderDefinitionMap g_shaderDefinitions;
695 bool parseTemplateInstance(Tokeniser &tokeniser, const char *filename)
698 RETURN_FALSE_IF_FAIL(Tokeniser_parseShaderName(tokeniser, name));
699 const char *templateName = tokeniser.getToken();
700 ShaderTemplate *shaderTemplate = findTemplate(templateName);
701 if (shaderTemplate == 0) {
702 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": shader template not found: "
703 << makeQuoted(templateName) << "\n";
706 ShaderArguments args;
707 if (!parseShaderParameters(tokeniser, args)) {
708 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str()) << ": argument parse failed\n";
712 if (shaderTemplate != 0) {
713 if (!g_shaderDefinitions.insert(
714 ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate, args, filename))).second) {
715 globalErrorStream() << "shader instance: " << makeQuoted(name.c_str())
716 << ": already exists, second definition ignored\n";
723 const char *evaluateShaderValue(const char *value, const ShaderParameters ¶ms, const ShaderArguments &args)
725 ShaderArguments::const_iterator j = args.begin();
726 for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) {
727 const char *other = (*i).c_str();
728 if (string_equal(value, other)) {
735 ///\todo BlendFunc parsing
737 evaluateBlendFunc(const BlendFuncExpression &blendFunc, const ShaderParameters ¶ms, const ShaderArguments &args)
739 return BlendFunc(BLEND_ONE, BLEND_ZERO);
743 evaluateTexture(const TextureExpression &texture, const ShaderParameters ¶ms, const ShaderArguments &args,
744 const LoadImageCallback &loader = GlobalTexturesCache().defaultLoader())
746 StringOutputStream result(64);
747 const char *expression = texture.c_str();
748 const char *end = expression + string_length(expression);
749 if (!string_empty(expression)) {
751 const char *best = end;
752 const char *bestParam = 0;
753 const char *bestArg = 0;
754 ShaderArguments::const_iterator j = args.begin();
755 for (ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j) {
756 const char *found = strstr(expression, (*i).c_str());
757 if (found != 0 && found < best) {
759 bestParam = (*i).c_str();
760 bestArg = (*j).c_str();
764 result << StringRange(expression, best);
765 result << PathCleaned(bestArg);
766 expression = best + string_length(bestParam);
771 result << expression;
773 return GlobalTexturesCache().capture(loader, result.c_str());
776 float evaluateFloat(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args)
778 const char *result = evaluateShaderValue(value.c_str(), params, args);
780 if (!string_parse_float(result, f)) {
781 globalErrorStream() << "parsing float value failed: " << makeQuoted(result) << "\n";
786 BlendFactor evaluateBlendFactor(const ShaderValue &value, const ShaderParameters ¶ms, const ShaderArguments &args)
788 const char *result = evaluateShaderValue(value.c_str(), params, args);
790 if (string_equal_nocase(result, "gl_zero")) {
793 if (string_equal_nocase(result, "gl_one")) {
796 if (string_equal_nocase(result, "gl_src_color")) {
797 return BLEND_SRC_COLOUR;
799 if (string_equal_nocase(result, "gl_one_minus_src_color")) {
800 return BLEND_ONE_MINUS_SRC_COLOUR;
802 if (string_equal_nocase(result, "gl_src_alpha")) {
803 return BLEND_SRC_ALPHA;
805 if (string_equal_nocase(result, "gl_one_minus_src_alpha")) {
806 return BLEND_ONE_MINUS_SRC_ALPHA;
808 if (string_equal_nocase(result, "gl_dst_color")) {
809 return BLEND_DST_COLOUR;
811 if (string_equal_nocase(result, "gl_one_minus_dst_color")) {
812 return BLEND_ONE_MINUS_DST_COLOUR;
814 if (string_equal_nocase(result, "gl_dst_alpha")) {
815 return BLEND_DST_ALPHA;
817 if (string_equal_nocase(result, "gl_one_minus_dst_alpha")) {
818 return BLEND_ONE_MINUS_DST_ALPHA;
820 if (string_equal_nocase(result, "gl_src_alpha_saturate")) {
821 return BLEND_SRC_ALPHA_SATURATE;
824 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted(result) << "\n";
828 class CShader : public IShader {
829 std::size_t m_refcount;
831 const ShaderTemplate &m_template;
832 const ShaderArguments &m_args;
833 const char *m_filename;
834 // name is shader-name, otherwise texture-name (if not a real shader)
837 qtexture_t *m_pTexture;
838 qtexture_t *m_notfound;
839 qtexture_t *m_pDiffuse;
840 float m_heightmapScale;
842 qtexture_t *m_pSpecular;
843 qtexture_t *m_pLightFalloffImage;
844 BlendFunc m_blendFunc;
850 static bool m_lightingEnabled;
852 CShader(const ShaderDefinition &definition) :
854 m_template(*definition.shaderTemplate),
855 m_args(definition.args),
856 m_filename(definition.filename),
857 m_blendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA),
874 ASSERT_MESSAGE(m_refcount == 0, "deleting active shader");
877 // IShaders implementation -----------------
885 ASSERT_MESSAGE(m_refcount != 0, "shader reference-count going below zero");
886 if (--m_refcount == 0) {
891 std::size_t refcount()
896 // get/set the qtexture_t* Radiant uses to represent this shader object
897 qtexture_t *getTexture() const
902 qtexture_t *getDiffuse() const
907 qtexture_t *getBump() const
912 qtexture_t *getSpecular() const
918 const char *getName() const
920 return m_Name.c_str();
928 void SetInUse(bool bInUse)
931 g_ActiveShadersChangedNotify();
934 // get the shader flags
937 return m_template.m_nFlags;
940 // get the transparency value
941 float getTrans() const
943 return m_template.m_fTrans;
946 // test if it's a true shader, or a default shader created to wrap around a texture
947 bool IsDefault() const
949 return string_empty(m_filename);
953 void getAlphaFunc(EAlphaFunc *func, float *ref)
955 *func = m_template.m_AlphaFunc;
956 *ref = m_template.m_AlphaRef;
959 BlendFunc getBlendFunc() const
967 return m_template.m_Cull;
970 // get shader file name (ie the file where this one is defined)
971 const char *getShaderFileName() const
975 // -----------------------------------------
979 m_pTexture = evaluateTexture(m_template.m_textureName, m_template.m_params, m_args);
981 if (m_pTexture->texture_number == 0) {
982 m_notfound = m_pTexture;
985 m_pTexture = GlobalTexturesCache().capture(IsDefault() ? DEFAULT_NOTEX_NAME : DEFAULT_SHADERNOTEX_NAME);
994 GlobalTexturesCache().release(m_pTexture);
996 if (m_notfound != 0) {
997 GlobalTexturesCache().release(m_notfound);
1000 unrealiseLighting();
1003 void realiseLighting()
1005 if (m_lightingEnabled) {
1006 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
1007 if (!string_empty(m_template.m_heightmapScale.c_str())) {
1008 m_heightmapScale = evaluateFloat(m_template.m_heightmapScale, m_template.m_params, m_args);
1009 loader = LoadImageCallback(&m_heightmapScale, loadHeightmap);
1011 m_pDiffuse = evaluateTexture(m_template.m_diffuse, m_template.m_params, m_args);
1012 m_pBump = evaluateTexture(m_template.m_bump, m_template.m_params, m_args, loader);
1013 m_pSpecular = evaluateTexture(m_template.m_specular, m_template.m_params, m_args);
1014 m_pLightFalloffImage = evaluateTexture(m_template.m_lightFalloffImage, m_template.m_params, m_args);
1016 for (ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin();
1017 i != m_template.m_layers.end(); ++i) {
1018 m_layers.push_back(evaluateLayer(*i, m_template.m_params, m_args));
1021 if (m_layers.size() == 1) {
1022 const BlendFuncExpression &blendFunc = m_template.m_layers.front().blendFunc();
1023 if (!string_empty(blendFunc.second.c_str())) {
1024 m_blendFunc = BlendFunc(
1025 evaluateBlendFactor(blendFunc.first.c_str(), m_template.m_params, m_args),
1026 evaluateBlendFactor(blendFunc.second.c_str(), m_template.m_params, m_args)
1029 const char *blend = evaluateShaderValue(blendFunc.first.c_str(), m_template.m_params, m_args);
1031 if (string_equal_nocase(blend, "add")) {
1032 m_blendFunc = BlendFunc(BLEND_ONE, BLEND_ONE);
1033 } else if (string_equal_nocase(blend, "filter")) {
1034 m_blendFunc = BlendFunc(BLEND_DST_COLOUR, BLEND_ZERO);
1035 } else if (string_equal_nocase(blend, "blend")) {
1036 m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA);
1038 globalErrorStream() << "parsing blend value failed: " << makeQuoted(blend) << "\n";
1045 void unrealiseLighting()
1047 if (m_lightingEnabled) {
1048 GlobalTexturesCache().release(m_pDiffuse);
1049 GlobalTexturesCache().release(m_pBump);
1050 GlobalTexturesCache().release(m_pSpecular);
1052 GlobalTexturesCache().release(m_pLightFalloffImage);
1054 for (MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
1055 GlobalTexturesCache().release((*i).texture());
1059 m_blendFunc = BlendFunc(BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA);
1064 void setName(const char *name)
1069 class MapLayer : public ShaderLayer {
1070 qtexture_t *m_texture;
1071 BlendFunc m_blendFunc;
1072 bool m_clampToBorder;
1075 MapLayer(qtexture_t *texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest) :
1077 m_blendFunc(blendFunc),
1078 m_clampToBorder(false),
1079 m_alphaTest(alphaTest)
1083 qtexture_t *texture() const
1088 BlendFunc blendFunc() const
1093 bool clampToBorder() const
1095 return m_clampToBorder;
1098 float alphaTest() const
1104 static MapLayer evaluateLayer(const ShaderTemplate::MapLayerTemplate &layerTemplate, const ShaderParameters ¶ms,
1105 const ShaderArguments &args)
1108 evaluateTexture(layerTemplate.texture(), params, args),
1109 evaluateBlendFunc(layerTemplate.blendFunc(), params, args),
1110 layerTemplate.clampToBorder(),
1111 evaluateFloat(layerTemplate.alphaTest(), params, args)
1115 typedef std::vector<MapLayer> MapLayers;
1118 const ShaderLayer *firstLayer() const
1120 if (m_layers.empty()) {
1123 return &m_layers.front();
1126 void forEachLayer(const ShaderLayerCallback &callback) const
1128 for (MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i) {
1133 qtexture_t *lightFalloffImage() const
1135 if (!string_empty(m_template.m_lightFalloffImage.c_str())) {
1136 return m_pLightFalloffImage;
1142 bool CShader::m_lightingEnabled = false;
1144 typedef SmartPointer<CShader> ShaderPointer;
1145 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1147 shaders_t g_ActiveShaders;
1149 static shaders_t::iterator g_ActiveShadersIterator;
1151 void ActiveShaders_IteratorBegin()
1153 g_ActiveShadersIterator = g_ActiveShaders.begin();
1156 bool ActiveShaders_IteratorAtEnd()
1158 return g_ActiveShadersIterator == g_ActiveShaders.end();
1161 IShader *ActiveShaders_IteratorCurrent()
1163 return static_cast<CShader *>( g_ActiveShadersIterator->second );
1166 void ActiveShaders_IteratorIncrement()
1168 ++g_ActiveShadersIterator;
1171 void debug_check_shaders(shaders_t &shaders)
1173 for (shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i) {
1174 ASSERT_MESSAGE(i->second->refcount() == 1, "orphan shader still referenced");
1178 // will free all GL binded qtextures and shaders
1179 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1183 // empty the actives shaders list
1184 debug_check_shaders(g_ActiveShaders);
1185 g_ActiveShaders.clear();
1187 g_shaderTemplates.clear();
1188 g_shaderDefinitions.clear();
1189 g_ActiveShadersChangedNotify();
1192 bool ShaderTemplate::parseQuake3(Tokeniser &tokeniser)
1194 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1195 m_textureName = m_Name.c_str();
1197 tokeniser.nextLine();
1199 // we need to read until we hit a balanced }
1202 tokeniser.nextLine();
1203 const char *token = tokeniser.getToken();
1209 if (string_equal(token, "{")) {
1212 } else if (string_equal(token, "}")) {
1214 if (depth < 0) { // underflow
1217 if (depth == 0) { // end of shader
1225 if (string_equal_nocase(token, "qer_nocarve")) {
1226 m_nFlags |= QER_NOCARVE;
1227 } else if (string_equal_nocase(token, "qer_trans")) {
1228 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_fTrans));
1229 m_nFlags |= QER_TRANS;
1230 } else if (string_equal_nocase(token, "qer_editorimage")) {
1231 RETURN_FALSE_IF_FAIL(Tokeniser_parseTextureName(tokeniser, m_textureName));
1232 } else if (string_equal_nocase(token, "qer_alphafunc")) {
1233 const char *alphafunc = tokeniser.getToken();
1235 if (alphafunc == 0) {
1236 Tokeniser_unexpectedError(tokeniser, alphafunc, "#alphafunc");
1240 if (string_equal_nocase(alphafunc, "equal")) {
1241 m_AlphaFunc = IShader::eEqual;
1242 } else if (string_equal_nocase(alphafunc, "greater")) {
1243 m_AlphaFunc = IShader::eGreater;
1244 } else if (string_equal_nocase(alphafunc, "less")) {
1245 m_AlphaFunc = IShader::eLess;
1246 } else if (string_equal_nocase(alphafunc, "gequal")) {
1247 m_AlphaFunc = IShader::eGEqual;
1248 } else if (string_equal_nocase(alphafunc, "lequal")) {
1249 m_AlphaFunc = IShader::eLEqual;
1251 m_AlphaFunc = IShader::eAlways;
1254 m_nFlags |= QER_ALPHATEST;
1256 RETURN_FALSE_IF_FAIL(Tokeniser_getFloat(tokeniser, m_AlphaRef));
1257 } else if (string_equal_nocase(token, "cull")) {
1258 const char *cull = tokeniser.getToken();
1261 Tokeniser_unexpectedError(tokeniser, cull, "#cull");
1265 if (string_equal_nocase(cull, "none")
1266 || string_equal_nocase(cull, "twosided")
1267 || string_equal_nocase(cull, "disable")) {
1268 m_Cull = IShader::eCullNone;
1269 } else if (string_equal_nocase(cull, "back")
1270 || string_equal_nocase(cull, "backside")
1271 || string_equal_nocase(cull, "backsided")) {
1272 m_Cull = IShader::eCullBack;
1274 m_Cull = IShader::eCullBack;
1277 m_nFlags |= QER_CULL;
1278 } else if (string_equal_nocase(token, "surfaceparm")) {
1279 const char *surfaceparm = tokeniser.getToken();
1281 if (surfaceparm == 0) {
1282 Tokeniser_unexpectedError(tokeniser, surfaceparm, "#surfaceparm");
1286 if (string_equal_nocase(surfaceparm, "fog")) {
1287 m_nFlags |= QER_FOG;
1288 if (m_fTrans == 1.0f) { // has not been explicitly set by qer_trans
1291 } else if (string_equal_nocase(surfaceparm, "nodraw")) {
1292 m_nFlags |= QER_NODRAW;
1293 } else if (string_equal_nocase(surfaceparm, "nonsolid")) {
1294 m_nFlags |= QER_NONSOLID;
1295 } else if (string_equal_nocase(surfaceparm, "water")) {
1296 m_nFlags |= QER_WATER;
1297 } else if (string_equal_nocase(surfaceparm, "lava")) {
1298 m_nFlags |= QER_LAVA;
1299 } else if (string_equal_nocase(surfaceparm, "areaportal")) {
1300 m_nFlags |= QER_AREAPORTAL;
1301 } else if (string_equal_nocase(surfaceparm, "playerclip")) {
1302 m_nFlags |= QER_CLIP;
1303 } else if (string_equal_nocase(surfaceparm, "botclip")) {
1304 m_nFlags |= QER_BOTCLIP;
1316 TextureExpression m_texture;
1317 BlendFunc m_blendFunc;
1318 bool m_clampToBorder;
1320 float m_heightmapScale;
1322 Layer() : m_type(LAYER_NONE), m_blendFunc(BLEND_ONE, BLEND_ZERO), m_clampToBorder(false), m_alphaTest(-1),
1328 std::list<CopiedString> g_shaderFilenames;
1330 void ParseShaderFile(Tokeniser &tokeniser, const char *filename)
1332 g_shaderFilenames.push_back(filename);
1333 filename = g_shaderFilenames.back().c_str();
1334 tokeniser.nextLine();
1336 const char *token = tokeniser.getToken();
1342 if (string_equal(token, "table")) {
1343 if (tokeniser.getToken() == 0) {
1344 Tokeniser_unexpectedError(tokeniser, 0, "#table-name");
1347 if (!Tokeniser_parseToken(tokeniser, "{")) {
1351 const char *option = tokeniser.getToken();
1352 if (string_equal(option, "{")) {
1354 const char *value = tokeniser.getToken();
1355 if (string_equal(value, "}")) {
1360 if (!Tokeniser_parseToken(tokeniser, "}")) {
1367 if (string_equal(token, "guide")) {
1368 parseTemplateInstance(tokeniser, filename);
1370 if (!string_equal(token, "material")
1371 && !string_equal(token, "particle")
1372 && !string_equal(token, "skin")) {
1373 tokeniser.ungetToken();
1375 // first token should be the path + name.. (from base)
1377 if (!Tokeniser_parseShaderName(tokeniser, name)) {
1379 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1380 shaderTemplate->setName(name.c_str());
1382 g_shaders.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1384 bool result = (g_shaderLanguage == SHADERLANGUAGE_QUAKE3)
1385 ? shaderTemplate->parseQuake3(tokeniser)
1386 : shaderTemplate->parseDoom3(tokeniser);
1388 // do we already have this shader?
1389 if (!g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(shaderTemplate->getName(),
1391 shaderTemplate.get(),
1393 filename))).second) {
1395 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName()
1396 << " is already in memory, definition in " << filename << " ignored.\n";
1400 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1408 void parseGuideFile(Tokeniser &tokeniser, const char *filename)
1410 tokeniser.nextLine();
1412 const char *token = tokeniser.getToken();
1418 if (string_equal(token, "guide")) {
1419 // first token should be the path + name.. (from base)
1420 ShaderTemplatePointer shaderTemplate(new ShaderTemplate);
1421 shaderTemplate->parseTemplate(tokeniser);
1422 if (!g_shaderTemplates.insert(
1423 ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate)).second) {
1424 globalErrorStream() << "guide " << makeQuoted(shaderTemplate->getName())
1425 << ": already defined, second definition ignored\n";
1427 } else if (string_equal(token, "inlineGuide")) {
1428 // skip entire inlineGuide definition
1429 std::size_t depth = 0;
1431 tokeniser.nextLine();
1432 token = tokeniser.getToken();
1433 if (string_equal(token, "{")) {
1435 } else if (string_equal(token, "}")) {
1445 void LoadShaderFile(const char *filename)
1447 ArchiveTextFile *file = GlobalFileSystem().openTextFile(filename);
1450 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1452 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1454 ParseShaderFile(tokeniser, filename);
1456 tokeniser.release();
1459 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1463 void loadGuideFile(const char *filename)
1465 StringOutputStream fullname(256);
1466 fullname << "guides/" << filename;
1467 ArchiveTextFile *file = GlobalFileSystem().openTextFile(fullname.c_str());
1470 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1472 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser(file->getInputStream());
1474 parseGuideFile(tokeniser, fullname.c_str());
1476 tokeniser.release();
1479 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1483 CShader *Try_Shader_ForName(const char *name)
1486 shaders_t::iterator i = g_ActiveShaders.find(name);
1487 if (i != g_ActiveShaders.end()) {
1491 // active shader was not found
1493 // find matching shader definition
1494 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find(name);
1495 if (i == g_shaderDefinitions.end()) {
1496 // shader definition was not found
1498 // create new shader definition from default shader template
1499 ShaderTemplatePointer shaderTemplate(new ShaderTemplate());
1500 shaderTemplate->CreateDefault(name);
1501 g_shaderTemplates.insert(ShaderTemplateMap::value_type(shaderTemplate->getName(), shaderTemplate));
1503 i = g_shaderDefinitions.insert(ShaderDefinitionMap::value_type(name, ShaderDefinition(shaderTemplate.get(),
1508 // create shader from existing definition
1509 ShaderPointer pShader(new CShader((*i).second));
1510 pShader->setName(name);
1511 g_ActiveShaders.insert(shaders_t::value_type(name, pShader));
1512 g_ActiveShadersChangedNotify();
1516 IShader *Shader_ForName(const char *name)
1518 ASSERT_NOTNULL(name);
1520 IShader *pShader = Try_Shader_ForName(name);
1526 // the list of scripts/*.shader files we need to work with
1527 // those are listed in shaderlist file
1528 GSList *l_shaderfiles = 0;
1530 GSList *Shaders_getShaderFileList()
1532 return l_shaderfiles;
1537 DumpUnreferencedShaders
1538 usefull function: dumps the list of .shader files that are not referenced to the console
1541 void IfFound_dumpUnreferencedShader(bool &bFound, const char *filename)
1543 bool listed = false;
1545 for (GSList *sh = l_shaderfiles; sh != 0; sh = g_slist_next(sh)) {
1546 if (!strcmp((char *) sh->data, filename)) {
1555 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1557 globalOutputStream() << "\t" << filename << "\n";
1561 typedef ReferenceCaller<bool, void(const char *), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1563 void DumpUnreferencedShaders()
1565 bool bFound = false;
1566 GlobalFileSystem().forEachFile(g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller(bFound));
1569 void ShaderList_addShaderFile(const char *dirstring)
1573 for (GSList *tmp = l_shaderfiles; tmp != 0; tmp = tmp->next) {
1574 if (string_equal_nocase(dirstring, (char *) tmp->data)) {
1576 globalOutputStream() << "duplicate entry \"" << (char *) tmp->data << "\" in shaderlist.txt\n";
1582 l_shaderfiles = g_slist_append(l_shaderfiles, strdup(dirstring));
1589 build a CStringList of shader names
1592 void BuildShaderList(TextInputStream &shaderlist)
1594 Tokeniser &tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser(shaderlist);
1595 tokeniser.nextLine();
1596 const char *token = tokeniser.getToken();
1597 StringOutputStream shaderFile(64);
1598 while (token != 0) {
1599 // each token should be a shader filename
1600 shaderFile << token << "." << g_shadersExtension;
1602 ShaderList_addShaderFile(shaderFile.c_str());
1604 tokeniser.nextLine();
1605 token = tokeniser.getToken();
1609 tokeniser.release();
1612 void FreeShaderList()
1614 while (l_shaderfiles != 0) {
1615 free(l_shaderfiles->data);
1616 l_shaderfiles = g_slist_remove(l_shaderfiles, l_shaderfiles->data);
1620 void ShaderList_addFromArchive(const char *archivename)
1622 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1623 if (string_empty(shaderpath)) {
1627 StringOutputStream shaderlist(256);
1628 shaderlist << DirectoryCleaned(shaderpath) << "shaderlist.txt";
1630 Archive *archive = GlobalFileSystem().getArchive(archivename, false);
1632 ArchiveTextFile *file = archive->openTextFile(shaderlist.c_str());
1634 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1635 BuildShaderList(file->getInputStream());
1641 #include "stream/filestream.h"
1644 shaderlist_findOrInstall(const char *enginePath, const char *toolsPath, const char *shaderPath, const char *gamename)
1646 StringOutputStream absShaderList(256);
1647 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1648 if (file_exists(absShaderList.c_str())) {
1652 StringOutputStream directory(256);
1653 directory << enginePath << gamename << '/' << shaderPath;
1654 if (!file_exists(directory.c_str()) && !Q_mkdir(directory.c_str())) {
1659 StringOutputStream defaultShaderList(256);
1660 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1661 if (file_exists(defaultShaderList.c_str())) {
1662 return file_copy(defaultShaderList.c_str(), absShaderList.c_str());
1670 if (g_shaderLanguage == SHADERLANGUAGE_QUAKE4) {
1671 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1674 const char *shaderPath = GlobalRadiant().getGameDescriptionKeyValue("shaderpath");
1675 if (!string_empty(shaderPath)) {
1676 StringOutputStream path(256);
1677 path << DirectoryCleaned(shaderPath);
1679 if (g_useShaderList) {
1680 // preload shader files that have been listed in shaderlist.txt
1681 const char *basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue("basegame");
1682 const char *gamename = GlobalRadiant().getGameName();
1683 const char *enginePath = GlobalRadiant().getEnginePath();
1684 const char *toolsPath = GlobalRadiant().getGameToolsPath();
1686 bool isMod = !string_equal(basegame, gamename);
1688 if (!isMod || !shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename)) {
1689 gamename = basegame;
1690 shaderlist_findOrInstall(enginePath, toolsPath, path.c_str(), gamename);
1693 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1694 DumpUnreferencedShaders();
1696 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile),
1700 GSList *lst = l_shaderfiles;
1701 StringOutputStream shadername(256);
1703 shadername << path.c_str() << reinterpret_cast<const char *>( lst->data );
1704 LoadShaderFile(shadername.c_str());
1710 //StringPool_analyse(ShaderPool::instance());
1717 g_shaderFilenames.clear();
1720 ModuleObservers g_observers;
1722 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1723 bool Shaders_realised()
1725 return g_shaders_unrealised == 0;
1728 void Shaders_Realise()
1730 if (--g_shaders_unrealised == 0) {
1732 g_observers.realise();
1736 void Shaders_Unrealise()
1738 if (++g_shaders_unrealised == 1) {
1739 g_observers.unrealise();
1744 void Shaders_Refresh()
1746 Shaders_Unrealise();
1750 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver {
1759 Shaders_Unrealise();
1767 IShader *getShaderForName(const char *name)
1769 return Shader_ForName(name);
1772 void foreachShaderName(const ShaderNameCallback &callback)
1774 for (ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i) {
1775 callback((*i).first.c_str());
1779 void beginActiveShadersIterator()
1781 ActiveShaders_IteratorBegin();
1784 bool endActiveShadersIterator()
1786 return ActiveShaders_IteratorAtEnd();
1789 IShader *dereferenceActiveShadersIterator()
1791 return ActiveShaders_IteratorCurrent();
1794 void incrementActiveShadersIterator()
1796 ActiveShaders_IteratorIncrement();
1799 void setActiveShadersChangedNotify(const Callback<void()> ¬ify)
1801 g_ActiveShadersChangedNotify = notify;
1804 void attach(ModuleObserver &observer)
1806 g_observers.attach(observer);
1809 void detach(ModuleObserver &observer)
1811 g_observers.detach(observer);
1814 void setLightingEnabled(bool enabled)
1816 if (CShader::m_lightingEnabled != enabled) {
1817 for (shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) {
1818 (*i).second->unrealiseLighting();
1820 CShader::m_lightingEnabled = enabled;
1821 for (shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i) {
1822 (*i).second->realiseLighting();
1827 const char *getTexturePrefix() const
1829 return g_texturePrefix;
1833 Quake3ShaderSystem g_Quake3ShaderSystem;
1835 ShaderSystem &GetShaderSystem()
1837 return g_Quake3ShaderSystem;
1840 void Shaders_Construct()
1842 GlobalFileSystem().attach(g_Quake3ShaderSystem);
1845 void Shaders_Destroy()
1847 GlobalFileSystem().detach(g_Quake3ShaderSystem);
1849 if (Shaders_realised()) {