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 ){
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 ){
115 return pixels + ( ( ( ( ( y + height ) % height ) * width ) + ( ( x + width ) % width ) ) * 4 );
125 Image& convertHeightmapToNormalmap( Image& heightmap, float scale ){
126 int w = heightmap.getWidth();
127 int h = heightmap.getHeight();
129 Image& normalmap = *( new RGBAImage( heightmap.getWidth(), heightmap.getHeight() ) );
131 byte* in = heightmap.getRGBAPixels();
132 byte* out = normalmap.getRGBAPixels();
136 const int kernelSize = 2;
137 KernelElement kernel_du[kernelSize] = {
141 KernelElement kernel_dv[kernelSize] = {
147 const int kernelSize = 6;
148 KernelElement kernel_du[kernelSize] = {
156 KernelElement kernel_dv[kernelSize] = {
173 for ( KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i )
175 du += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
178 for ( KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i )
180 dv += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
183 float nx = -du * scale;
184 float ny = -dv * scale;
188 float norm = 1.0 / sqrt( nx * nx + ny * ny + nz * nz );
189 out[0] = float_to_integer( ( ( nx * norm ) + 1 ) * 127.5 );
190 out[1] = float_to_integer( ( ( ny * norm ) + 1 ) * 127.5 );
191 out[2] = float_to_integer( ( ( nz * norm ) + 1 ) * 127.5 );
204 Image* loadHeightmap( void* environment, const char* name ){
205 Image* heightmap = GlobalTexturesCache().loadImage( name );
206 if ( heightmap != 0 ) {
207 Image& normalmap = convertHeightmapToNormalmap( *heightmap, *reinterpret_cast<float*>( environment ) );
208 heightmap->release();
214 class ShaderPoolContext
218 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
219 typedef PooledString<ShaderPool> ShaderString;
220 typedef ShaderString ShaderVariable;
221 typedef ShaderString ShaderValue;
222 typedef CopiedString TextureExpression;
224 // clean a texture name to the qtexture_t name format we use internally
225 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
226 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
227 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
228 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
229 template<typename StringType>
230 void parseTextureName( StringType& name, const char* token ){
231 StringOutputStream cleaned( 256 );
232 cleaned << PathCleaned( token );
233 name = CopiedString( StringRange( cleaned.c_str(), path_get_filename_base_end( cleaned.c_str() ) ) ).c_str(); // remove extension
236 bool Tokeniser_parseTextureName( Tokeniser& tokeniser, TextureExpression& name ){
237 const char* token = tokeniser.getToken();
239 Tokeniser_unexpectedError( tokeniser, token, "#texture-name" );
242 parseTextureName( name, token );
246 bool Tokeniser_parseShaderName( Tokeniser& tokeniser, CopiedString& name ){
247 const char* token = tokeniser.getToken();
249 Tokeniser_unexpectedError( tokeniser, token, "#shader-name" );
252 parseTextureName( name, token );
256 bool Tokeniser_parseString( Tokeniser& tokeniser, ShaderString& string ){
257 const char* token = tokeniser.getToken();
259 Tokeniser_unexpectedError( tokeniser, token, "#string" );
267 typedef std::list<ShaderVariable> ShaderParameters;
268 typedef std::list<ShaderVariable> ShaderArguments;
270 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
274 std::size_t m_refcount;
278 ShaderParameters m_params;
280 TextureExpression m_textureName;
281 TextureExpression m_diffuse;
282 TextureExpression m_bump;
283 ShaderValue m_heightmapScale;
284 TextureExpression m_specular;
285 TextureExpression m_lightFalloffImage;
291 IShader::EAlphaFunc m_AlphaFunc;
294 IShader::ECull m_Cull;
307 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
308 if ( --m_refcount == 0 ) {
313 std::size_t refcount(){
317 const char* getName() const {
318 return m_Name.c_str();
321 void setName( const char* name ){
325 // -----------------------------------------
327 bool parseDoom3( Tokeniser& tokeniser );
329 bool parseQuake3( Tokeniser& tokeniser );
331 bool parseTemplate( Tokeniser& tokeniser );
334 void CreateDefault( const char *name ){
335 if ( g_enableDefaultShaders ) {
336 m_textureName = name;
346 class MapLayerTemplate
348 TextureExpression m_texture;
349 BlendFuncExpression m_blendFunc;
350 bool m_clampToBorder;
351 ShaderValue m_alphaTest;
353 MapLayerTemplate( const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest ) :
354 m_texture( texture ),
355 m_blendFunc( blendFunc ),
356 m_clampToBorder( false ),
357 m_alphaTest( alphaTest ){
360 const TextureExpression& texture() const {
364 const BlendFuncExpression& blendFunc() const {
368 bool clampToBorder() const {
369 return m_clampToBorder;
372 const ShaderValue& alphaTest() const {
377 typedef std::vector<MapLayerTemplate> MapLayers;
382 bool Doom3Shader_parseHeightmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
383 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
384 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
385 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
386 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, heightmapScale ) );
387 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
391 bool Doom3Shader_parseAddnormals( Tokeniser& tokeniser, TextureExpression& bump ){
392 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
393 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
394 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
395 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "heightmap" ) );
396 TextureExpression heightmapName;
397 ShaderValue heightmapScale;
398 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, heightmapName, heightmapScale ) );
399 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
403 bool Doom3Shader_parseBumpmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
404 const char* token = tokeniser.getToken();
406 Tokeniser_unexpectedError( tokeniser, token, "#bumpmap" );
409 if ( string_equal( token, "heightmap" ) ) {
410 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, bump, heightmapScale ) );
412 else if ( string_equal( token, "addnormals" ) ) {
413 RETURN_FALSE_IF_FAIL( Doom3Shader_parseAddnormals( tokeniser, bump ) );
417 parseTextureName( bump, token );
435 TextureExpression m_texture;
436 BlendFuncExpression m_blendFunc;
437 bool m_clampToBorder;
438 ShaderValue m_alphaTest;
439 ShaderValue m_heightmapScale;
441 LayerTemplate() : m_type( LAYER_NONE ), m_blendFunc( "GL_ONE", "GL_ZERO" ), m_clampToBorder( false ), m_alphaTest( "-1" ), m_heightmapScale( "0" ){
445 bool parseShaderParameters( Tokeniser& tokeniser, ShaderParameters& params ){
446 Tokeniser_parseToken( tokeniser, "(" );
449 const char* param = tokeniser.getToken();
450 if ( string_equal( param, ")" ) ) {
453 params.push_back( param );
454 const char* comma = tokeniser.getToken();
455 if ( string_equal( comma, ")" ) ) {
458 if ( !string_equal( comma, "," ) ) {
459 Tokeniser_unexpectedError( tokeniser, comma, "," );
466 bool ShaderTemplate::parseTemplate( Tokeniser& tokeniser ){
467 m_Name = tokeniser.getToken();
468 if ( !parseShaderParameters( tokeniser, m_params ) ) {
469 globalErrorStream() << "shader template: " << makeQuoted( m_Name.c_str() ) << ": parameter parse failed\n";
473 return parseDoom3( tokeniser );
476 bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){
477 LayerTemplate currentLayer;
480 // we need to read until we hit a balanced }
484 tokeniser.nextLine();
485 const char* token = tokeniser.getToken();
491 if ( string_equal( token, "{" ) ) {
495 else if ( string_equal( token, "}" ) ) {
497 if ( depth < 0 ) { // error
500 if ( depth == 0 ) { // end of shader
503 if ( depth == 1 ) { // end of layer
504 if ( currentLayer.m_type == LAYER_DIFFUSEMAP ) {
505 m_diffuse = currentLayer.m_texture;
507 else if ( currentLayer.m_type == LAYER_BUMPMAP ) {
508 m_bump = currentLayer.m_texture;
510 else if ( currentLayer.m_type == LAYER_SPECULARMAP ) {
511 m_specular = currentLayer.m_texture;
513 else if ( !string_empty( currentLayer.m_texture.c_str() ) ) {
514 m_layers.push_back( MapLayerTemplate(
515 currentLayer.m_texture.c_str(),
516 currentLayer.m_blendFunc,
517 currentLayer.m_clampToBorder,
518 currentLayer.m_alphaTest
521 currentLayer.m_type = LAYER_NONE;
522 currentLayer.m_texture = "";
527 if ( depth == 2 ) { // in layer
528 if ( string_equal_nocase( token, "blend" ) ) {
529 const char* blend = tokeniser.getToken();
532 Tokeniser_unexpectedError( tokeniser, blend, "#blend" );
536 if ( string_equal_nocase( blend, "diffusemap" ) ) {
537 currentLayer.m_type = LAYER_DIFFUSEMAP;
539 else if ( string_equal_nocase( blend, "bumpmap" ) ) {
540 currentLayer.m_type = LAYER_BUMPMAP;
542 else if ( string_equal_nocase( blend, "specularmap" ) ) {
543 currentLayer.m_type = LAYER_SPECULARMAP;
547 currentLayer.m_blendFunc.first = blend;
549 const char* comma = tokeniser.getToken();
552 Tokeniser_unexpectedError( tokeniser, comma, "#comma" );
556 if ( string_equal( comma, "," ) ) {
557 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, currentLayer.m_blendFunc.second ) );
561 currentLayer.m_blendFunc.second = "";
562 tokeniser.ungetToken();
566 else if ( string_equal_nocase( token, "map" ) ) {
567 if ( currentLayer.m_type == LAYER_BUMPMAP ) {
568 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale ) );
572 const char* map = tokeniser.getToken();
575 Tokeniser_unexpectedError( tokeniser, map, "#map" );
579 if ( string_equal( map, "makealpha" ) ) {
580 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
581 const char* texture = tokeniser.getToken();
582 if ( texture == 0 ) {
583 Tokeniser_unexpectedError( tokeniser, texture, "#texture" );
586 currentLayer.m_texture = texture;
587 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
591 parseTextureName( currentLayer.m_texture, map );
595 else if ( string_equal_nocase( token, "zeroclamp" ) ) {
596 currentLayer.m_clampToBorder = true;
599 else if ( string_equal_nocase( token, "alphaTest" ) ) {
600 Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest );
604 else if ( depth == 1 ) {
605 if ( string_equal_nocase( token, "qer_editorimage" ) ) {
606 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
608 else if ( string_equal_nocase( token, "qer_trans" ) ) {
609 m_fTrans = string_read_float( tokeniser.getToken() );
610 m_nFlags |= QER_TRANS;
612 else if ( string_equal_nocase( token, "translucent" ) ) {
614 m_nFlags |= QER_TRANS;
616 else if ( string_equal( token, "DECAL_MACRO" ) ) {
618 m_nFlags |= QER_TRANS;
620 else if ( string_equal_nocase( token, "bumpmap" ) ) {
621 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) );
623 else if ( string_equal_nocase( token, "diffusemap" ) ) {
624 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) );
626 else if ( string_equal_nocase( token, "specularmap" ) ) {
627 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) );
629 else if ( string_equal_nocase( token, "twosided" ) ) {
630 m_Cull = IShader::eCullNone;
631 m_nFlags |= QER_CULL;
633 else if ( string_equal_nocase( token, "nodraw" ) ) {
634 m_nFlags |= QER_NODRAW;
636 else if ( string_equal_nocase( token, "nonsolid" ) ) {
637 m_nFlags |= QER_NONSOLID;
639 else if ( string_equal_nocase( token, "liquid" ) ) {
640 m_nFlags |= QER_WATER;
642 else if ( string_equal_nocase( token, "areaportal" ) ) {
643 m_nFlags |= QER_AREAPORTAL;
645 else if ( string_equal_nocase( token, "playerclip" )
646 || string_equal_nocase( token, "monsterclip" )
647 || string_equal_nocase( token, "ikclip" )
648 || string_equal_nocase( token, "moveableclip" ) ) {
649 m_nFlags |= QER_CLIP;
651 if ( string_equal_nocase( token, "fogLight" ) ) {
654 else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) {
655 const char* lightFalloffImage = tokeniser.getToken();
656 if ( lightFalloffImage == 0 ) {
657 Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" );
660 if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) {
661 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
662 TextureExpression name;
663 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) );
664 m_lightFalloffImage = name;
665 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
669 m_lightFalloffImage = lightFalloffImage;
675 if ( string_empty( m_textureName.c_str() ) ) {
676 m_textureName = m_diffuse;
682 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
683 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
685 ShaderTemplateMap g_shaders;
686 ShaderTemplateMap g_shaderTemplates;
688 ShaderTemplate* findTemplate( const char* name ){
689 ShaderTemplateMap::iterator i = g_shaderTemplates.find( name );
690 if ( i != g_shaderTemplates.end() ) {
691 return ( *i ).second.get();
696 class ShaderDefinition
699 ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename )
700 : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){
703 ShaderTemplate* shaderTemplate;
704 ShaderArguments args;
705 const char* filename;
708 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
710 ShaderDefinitionMap g_shaderDefinitions;
712 bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){
714 RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) );
715 const char* templateName = tokeniser.getToken();
716 ShaderTemplate* shaderTemplate = findTemplate( templateName );
717 if ( shaderTemplate == 0 ) {
718 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n";
721 ShaderArguments args;
722 if ( !parseShaderParameters( tokeniser, args ) ) {
723 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n";
727 if ( shaderTemplate != 0 ) {
728 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) {
729 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n";
736 const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){
737 ShaderArguments::const_iterator j = args.begin();
738 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 ) ) {
742 return ( *j ).c_str();
748 ///\todo BlendFunc parsing
749 BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){
750 return BlendFunc( BLEND_ONE, BLEND_ZERO );
753 qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){
754 StringOutputStream result( 64 );
755 const char* expression = texture.c_str();
756 const char* end = expression + string_length( expression );
757 if ( !string_empty( expression ) ) {
760 const char* best = end;
761 const char* bestParam = 0;
762 const char* bestArg = 0;
763 ShaderArguments::const_iterator j = args.begin();
764 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
766 const char* found = strstr( expression, ( *i ).c_str() );
767 if ( found != 0 && found < best ) {
769 bestParam = ( *i ).c_str();
770 bestArg = ( *j ).c_str();
774 result << StringRange( expression, best );
775 result << PathCleaned( bestArg );
776 expression = best + string_length( bestParam );
783 result << expression;
785 return GlobalTexturesCache().capture( loader, result.c_str() );
788 float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
789 const char* result = evaluateShaderValue( value.c_str(), params, args );
791 if ( !string_parse_float( result, f ) ) {
792 globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n";
797 BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
798 const char* result = evaluateShaderValue( value.c_str(), params, args );
800 if ( string_equal_nocase( result, "gl_zero" ) ) {
803 if ( string_equal_nocase( result, "gl_one" ) ) {
806 if ( string_equal_nocase( result, "gl_src_color" ) ) {
807 return BLEND_SRC_COLOUR;
809 if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) {
810 return BLEND_ONE_MINUS_SRC_COLOUR;
812 if ( string_equal_nocase( result, "gl_src_alpha" ) ) {
813 return BLEND_SRC_ALPHA;
815 if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) {
816 return BLEND_ONE_MINUS_SRC_ALPHA;
818 if ( string_equal_nocase( result, "gl_dst_color" ) ) {
819 return BLEND_DST_COLOUR;
821 if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) {
822 return BLEND_ONE_MINUS_DST_COLOUR;
824 if ( string_equal_nocase( result, "gl_dst_alpha" ) ) {
825 return BLEND_DST_ALPHA;
827 if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) {
828 return BLEND_ONE_MINUS_DST_ALPHA;
830 if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) {
831 return BLEND_SRC_ALPHA_SATURATE;
834 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n";
838 class CShader : public IShader
840 std::size_t m_refcount;
842 const ShaderTemplate& m_template;
843 const ShaderArguments& m_args;
844 const char* m_filename;
845 // name is shader-name, otherwise texture-name ( if not a real shader )
848 qtexture_t* m_pTexture;
849 qtexture_t* m_notfound;
850 qtexture_t* m_pDiffuse;
851 float m_heightmapScale;
853 qtexture_t* m_pSpecular;
854 qtexture_t* m_pLightFalloffImage;
855 BlendFunc m_blendFunc;
861 static bool m_lightingEnabled;
863 CShader( const ShaderDefinition& definition ) :
865 m_template( *definition.shaderTemplate ),
866 m_args( definition.args ),
867 m_filename( definition.filename ),
868 m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
883 ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" );
886 // IShaders implementation -----------------
892 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
893 if ( --m_refcount == 0 ) {
898 std::size_t refcount(){
902 // get/set the qtexture_t* Radiant uses to represent this shader object
903 qtexture_t* getTexture() const {
907 qtexture_t* getDiffuse() const {
911 qtexture_t* getBump() const {
915 qtexture_t* getSpecular() const {
920 const char* getName() const {
921 return m_Name.c_str();
924 bool IsInUse() const {
928 void SetInUse( bool bInUse ){
930 g_ActiveShadersChangedNotify();
933 // get the shader flags
934 int getFlags() const {
935 return m_template.m_nFlags;
938 // get the transparency value
939 float getTrans() const {
940 return m_template.m_fTrans;
943 // test if it's a true shader, or a default shader created to wrap around a texture
944 bool IsDefault() const {
945 return string_empty( m_filename );
949 void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
950 BlendFunc getBlendFunc() const {
956 return m_template.m_Cull;
959 // get shader file name ( ie the file where this one is defined )
960 const char* getShaderFileName() const {
963 // -----------------------------------------
966 m_pTexture = evaluateTexture( m_template.m_textureName, m_template.m_params, m_args );
968 if ( m_pTexture->texture_number == 0 ) {
969 m_notfound = m_pTexture;
972 m_pTexture = GlobalTexturesCache().capture( IsDefault() ? DEFAULT_NOTEX_NAME : DEFAULT_SHADERNOTEX_NAME );
980 GlobalTexturesCache().release( m_pTexture );
982 if ( m_notfound != 0 ) {
983 GlobalTexturesCache().release( m_notfound );
989 void realiseLighting(){
990 if ( m_lightingEnabled ) {
991 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
992 if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) {
993 m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args );
994 loader = LoadImageCallback( &m_heightmapScale, loadHeightmap );
996 m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args );
997 m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader );
998 m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args );
999 m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args );
1001 for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i )
1003 m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) );
1006 if ( m_layers.size() == 1 ) {
1007 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
1008 if ( !string_empty( blendFunc.second.c_str() ) ) {
1009 m_blendFunc = BlendFunc(
1010 evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ),
1011 evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args )
1016 const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args );
1018 if ( string_equal_nocase( blend, "add" ) ) {
1019 m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE );
1021 else if ( string_equal_nocase( blend, "filter" ) ) {
1022 m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO );
1024 else if ( string_equal_nocase( blend, "blend" ) ) {
1025 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1029 globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n";
1036 void unrealiseLighting(){
1037 if ( m_lightingEnabled ) {
1038 GlobalTexturesCache().release( m_pDiffuse );
1039 GlobalTexturesCache().release( m_pBump );
1040 GlobalTexturesCache().release( m_pSpecular );
1042 GlobalTexturesCache().release( m_pLightFalloffImage );
1044 for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1046 GlobalTexturesCache().release( ( *i ).texture() );
1050 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1055 void setName( const char* name ){
1059 class MapLayer : public ShaderLayer
1061 qtexture_t* m_texture;
1062 BlendFunc m_blendFunc;
1063 bool m_clampToBorder;
1066 MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) :
1067 m_texture( texture ),
1068 m_blendFunc( blendFunc ),
1069 m_clampToBorder( false ),
1070 m_alphaTest( alphaTest ){
1073 qtexture_t* texture() const {
1077 BlendFunc blendFunc() const {
1081 bool clampToBorder() const {
1082 return m_clampToBorder;
1085 float alphaTest() const {
1090 static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){
1092 evaluateTexture( layerTemplate.texture(), params, args ),
1093 evaluateBlendFunc( layerTemplate.blendFunc(), params, args ),
1094 layerTemplate.clampToBorder(),
1095 evaluateFloat( layerTemplate.alphaTest(), params, args )
1099 typedef std::vector<MapLayer> MapLayers;
1102 const ShaderLayer* firstLayer() const {
1103 if ( m_layers.empty() ) {
1106 return &m_layers.front();
1108 void forEachLayer( const ShaderLayerCallback& callback ) const {
1109 for ( MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1115 qtexture_t* lightFalloffImage() const {
1116 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1117 return m_pLightFalloffImage;
1123 bool CShader::m_lightingEnabled = false;
1125 typedef SmartPointer<CShader> ShaderPointer;
1126 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1128 shaders_t g_ActiveShaders;
1130 static shaders_t::iterator g_ActiveShadersIterator;
1132 void ActiveShaders_IteratorBegin(){
1133 g_ActiveShadersIterator = g_ActiveShaders.begin();
1136 bool ActiveShaders_IteratorAtEnd(){
1137 return g_ActiveShadersIterator == g_ActiveShaders.end();
1140 IShader *ActiveShaders_IteratorCurrent(){
1141 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1144 void ActiveShaders_IteratorIncrement(){
1145 ++g_ActiveShadersIterator;
1148 void debug_check_shaders( shaders_t& shaders ){
1149 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1151 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1155 // will free all GL binded qtextures and shaders
1156 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1159 // empty the actives shaders list
1160 debug_check_shaders( g_ActiveShaders );
1161 g_ActiveShaders.clear();
1163 g_shaderTemplates.clear();
1164 g_shaderDefinitions.clear();
1165 g_ActiveShadersChangedNotify();
1168 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1169 // name of the qtexture_t we'll use to represent this shader ( this one has the "textures\" before )
1170 m_textureName = m_Name.c_str();
1172 tokeniser.nextLine();
1174 // we need to read until we hit a balanced }
1178 tokeniser.nextLine();
1179 const char* token = tokeniser.getToken();
1185 if ( string_equal( token, "{" ) ) {
1189 else if ( string_equal( token, "}" ) ) {
1191 if ( depth < 0 ) { // underflow
1194 if ( depth == 0 ) { // end of shader
1202 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1203 m_nFlags |= QER_NOCARVE;
1205 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1206 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1207 m_nFlags |= QER_TRANS;
1209 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1210 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1212 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1213 const char* alphafunc = tokeniser.getToken();
1215 if ( alphafunc == 0 ) {
1216 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1220 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1221 m_AlphaFunc = IShader::eEqual;
1223 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1224 m_AlphaFunc = IShader::eGreater;
1226 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1227 m_AlphaFunc = IShader::eLess;
1229 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1230 m_AlphaFunc = IShader::eGEqual;
1232 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1233 m_AlphaFunc = IShader::eLEqual;
1237 m_AlphaFunc = IShader::eAlways;
1240 m_nFlags |= QER_ALPHATEST;
1242 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1244 else if ( string_equal_nocase( token, "cull" ) ) {
1245 const char* cull = tokeniser.getToken();
1248 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1252 if ( string_equal_nocase( cull, "none" )
1253 || string_equal_nocase( cull, "twosided" )
1254 || string_equal_nocase( cull, "disable" ) ) {
1255 m_Cull = IShader::eCullNone;
1257 else if ( string_equal_nocase( cull, "back" )
1258 || string_equal_nocase( cull, "backside" )
1259 || string_equal_nocase( cull, "backsided" ) ) {
1260 m_Cull = IShader::eCullBack;
1264 m_Cull = IShader::eCullBack;
1267 m_nFlags |= QER_CULL;
1269 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1270 const char* surfaceparm = tokeniser.getToken();
1272 if ( surfaceparm == 0 ) {
1273 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1277 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1278 m_nFlags |= QER_FOG;
1279 m_nFlags |= QER_TRANS;
1280 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1284 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1285 m_nFlags |= QER_NODRAW;
1287 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1288 m_nFlags |= QER_NONSOLID;
1290 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1291 m_nFlags |= QER_WATER;
1293 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1294 m_nFlags |= QER_LAVA;
1296 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1297 m_nFlags |= QER_AREAPORTAL;
1299 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1300 m_nFlags |= QER_CLIP;
1302 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1303 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 ), m_heightmapScale( 0 ){
1326 std::list<CopiedString> g_shaderFilenames;
1328 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1329 g_shaderFilenames.push_back( filename );
1330 filename = g_shaderFilenames.back().c_str();
1331 tokeniser.nextLine();
1334 const char* token = tokeniser.getToken();
1340 if ( string_equal( token, "table" ) ) {
1341 if ( tokeniser.getToken() == 0 ) {
1342 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1345 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1350 const char* option = tokeniser.getToken();
1351 if ( string_equal( option, "{" ) ) {
1354 const char* value = tokeniser.getToken();
1355 if ( string_equal( value, "}" ) ) {
1360 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1369 if ( string_equal( token, "guide" ) ) {
1370 parseTemplateInstance( tokeniser, filename );
1374 if ( !string_equal( token, "material" )
1375 && !string_equal( token, "particle" )
1376 && !string_equal( token, "skin" ) ) {
1377 tokeniser.ungetToken();
1379 // first token should be the path + name.. ( from base )
1381 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1383 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1384 shaderTemplate->setName( name.c_str() );
1386 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1388 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1389 ? shaderTemplate->parseQuake3( tokeniser )
1390 : shaderTemplate->parseDoom3( tokeniser );
1392 // do we already have this shader?
1393 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1395 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1401 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1409 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1410 tokeniser.nextLine();
1413 const char* token = tokeniser.getToken();
1419 if ( string_equal( token, "guide" ) ) {
1420 // first token should be the path + name.. ( from base )
1421 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1422 shaderTemplate->parseTemplate( tokeniser );
1423 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1424 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1427 else if ( string_equal( token, "inlineGuide" ) ) {
1428 // skip entire inlineGuide definition
1429 std::size_t depth = 0;
1432 tokeniser.nextLine();
1433 token = tokeniser.getToken();
1434 if ( string_equal( token, "{" ) ) {
1437 else if ( string_equal( token, "}" ) ) {
1438 if ( --depth == 0 ) {
1447 void LoadShaderFile( const char* filename ){
1448 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1451 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1453 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1455 ParseShaderFile( tokeniser, filename );
1457 tokeniser.release();
1462 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1466 void loadGuideFile( const char* filename ){
1467 StringOutputStream fullname( 256 );
1468 fullname << "guides/" << filename;
1469 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1472 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1474 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1476 parseGuideFile( tokeniser, fullname.c_str() );
1478 tokeniser.release();
1483 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1487 CShader* Try_Shader_ForName( const char* name ){
1489 shaders_t::iterator i = g_ActiveShaders.find( name );
1490 if ( i != g_ActiveShaders.end() ) {
1491 return ( *i ).second;
1494 // active shader was not found
1496 // find matching shader definition
1497 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1498 if ( i == g_shaderDefinitions.end() ) {
1499 // shader definition was not found
1501 // create new shader definition from default shader template
1502 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1503 shaderTemplate->CreateDefault( name );
1504 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1506 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1509 // create shader from existing definition
1510 ShaderPointer pShader( new CShader( ( *i ).second ) );
1511 pShader->setName( name );
1512 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1513 g_ActiveShadersChangedNotify();
1517 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(){
1531 return l_shaderfiles;
1536 DumpUnreferencedShaders
1537 usefull function: dumps the list of .shader files that are not referenced to the console
1540 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1541 bool listed = false;
1543 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1545 if ( !strcmp( (char*)sh->data, filename ) ) {
1554 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1556 globalOutputStream() << "\t" << filename << "\n";
1560 typedef ReferenceCaller<bool, void(const char*), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1562 void DumpUnreferencedShaders(){
1563 bool bFound = false;
1564 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1567 void ShaderList_addShaderFile( const char* dirstring ){
1570 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1572 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1574 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1580 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1587 build a CStringList of shader names
1590 void BuildShaderList( TextInputStream& shaderlist ){
1591 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1592 tokeniser.nextLine();
1593 const char* token = tokeniser.getToken();
1594 StringOutputStream shaderFile( 64 );
1595 while ( token != 0 )
1597 // each token should be a shader filename
1598 shaderFile << token << "." << g_shadersExtension;
1600 ShaderList_addShaderFile( shaderFile.c_str() );
1602 tokeniser.nextLine();
1603 token = tokeniser.getToken();
1607 tokeniser.release();
1610 void FreeShaderList(){
1611 while ( l_shaderfiles != 0 )
1613 free( l_shaderfiles->data );
1614 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1618 void ShaderList_addFromArchive( const char *archivename ){
1619 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1620 if ( string_empty( shaderpath ) ) {
1624 StringOutputStream shaderlist( 256 );
1625 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1627 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1629 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1631 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1632 BuildShaderList( file->getInputStream() );
1638 #include "stream/filestream.h"
1640 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1641 StringOutputStream absShaderList( 256 );
1642 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1643 if ( file_exists( absShaderList.c_str() ) ) {
1647 StringOutputStream directory( 256 );
1648 directory << enginePath << gamename << '/' << shaderPath;
1649 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1654 StringOutputStream defaultShaderList( 256 );
1655 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1656 if ( file_exists( defaultShaderList.c_str() ) ) {
1657 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1663 void Shaders_Load(){
1664 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1665 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1668 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1669 if ( !string_empty( shaderPath ) ) {
1670 StringOutputStream path( 256 );
1671 path << DirectoryCleaned( shaderPath );
1673 if ( g_useShaderList ) {
1674 // preload shader files that have been listed in shaderlist.txt
1675 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1676 const char* gamename = GlobalRadiant().getGameName();
1677 const char* enginePath = GlobalRadiant().getEnginePath();
1678 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1680 bool isMod = !string_equal( basegame, gamename );
1682 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1683 gamename = basegame;
1684 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1687 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1688 DumpUnreferencedShaders();
1692 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0);
1695 GSList *lst = l_shaderfiles;
1696 StringOutputStream shadername( 256 );
1699 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1700 LoadShaderFile( shadername.c_str() );
1706 //StringPool_analyse( ShaderPool::instance() );
1709 void Shaders_Free(){
1712 g_shaderFilenames.clear();
1715 ModuleObservers g_observers;
1717 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1718 bool Shaders_realised(){
1719 return g_shaders_unrealised == 0;
1722 void Shaders_Realise(){
1723 if ( --g_shaders_unrealised == 0 ) {
1725 g_observers.realise();
1729 void Shaders_Unrealise(){
1730 if ( ++g_shaders_unrealised == 1 ) {
1731 g_observers.unrealise();
1736 void Shaders_Refresh(){
1737 Shaders_Unrealise();
1741 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1749 Shaders_Unrealise();
1756 IShader* getShaderForName( const char* name ){
1757 return Shader_ForName( name );
1760 void foreachShaderName( const ShaderNameCallback& callback ){
1761 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1763 callback( ( *i ).first.c_str() );
1767 void beginActiveShadersIterator(){
1768 ActiveShaders_IteratorBegin();
1771 bool endActiveShadersIterator(){
1772 return ActiveShaders_IteratorAtEnd();
1775 IShader* dereferenceActiveShadersIterator(){
1776 return ActiveShaders_IteratorCurrent();
1779 void incrementActiveShadersIterator(){
1780 ActiveShaders_IteratorIncrement();
1783 void setActiveShadersChangedNotify( const Callback<void()>& notify ){
1784 g_ActiveShadersChangedNotify = notify;
1787 void attach( ModuleObserver& observer ){
1788 g_observers.attach( observer );
1791 void detach( ModuleObserver& observer ){
1792 g_observers.detach( observer );
1795 void setLightingEnabled( bool enabled ){
1796 if ( CShader::m_lightingEnabled != enabled ) {
1797 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1799 ( *i ).second->unrealiseLighting();
1801 CShader::m_lightingEnabled = enabled;
1802 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1804 ( *i ).second->realiseLighting();
1809 const char* getTexturePrefix() const {
1810 return g_texturePrefix;
1814 Quake3ShaderSystem g_Quake3ShaderSystem;
1816 ShaderSystem& GetShaderSystem(){
1817 return g_Quake3ShaderSystem;
1820 void Shaders_Construct(){
1821 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1824 void Shaders_Destroy(){
1825 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1827 if ( Shaders_realised() ) {