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_LIQUID;
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 string_equal_nocase( surfaceparm, "lava" ) ||
1292 string_equal_nocase( surfaceparm, "slime") ){
1293 m_nFlags |= QER_LIQUID;
1295 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1296 m_nFlags |= QER_AREAPORTAL;
1298 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1299 m_nFlags |= QER_CLIP;
1301 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1302 m_nFlags |= QER_BOTCLIP;
1315 TextureExpression m_texture;
1316 BlendFunc m_blendFunc;
1317 bool m_clampToBorder;
1319 float m_heightmapScale;
1321 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1325 std::list<CopiedString> g_shaderFilenames;
1327 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1328 g_shaderFilenames.push_back( filename );
1329 filename = g_shaderFilenames.back().c_str();
1330 tokeniser.nextLine();
1333 const char* token = tokeniser.getToken();
1339 if ( string_equal( token, "table" ) ) {
1340 if ( tokeniser.getToken() == 0 ) {
1341 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1344 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1349 const char* option = tokeniser.getToken();
1350 if ( string_equal( option, "{" ) ) {
1353 const char* value = tokeniser.getToken();
1354 if ( string_equal( value, "}" ) ) {
1359 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1368 if ( string_equal( token, "guide" ) ) {
1369 parseTemplateInstance( tokeniser, filename );
1373 if ( !string_equal( token, "material" )
1374 && !string_equal( token, "particle" )
1375 && !string_equal( token, "skin" ) ) {
1376 tokeniser.ungetToken();
1378 // first token should be the path + name.. ( from base )
1380 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1382 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1383 shaderTemplate->setName( name.c_str() );
1385 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1387 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1388 ? shaderTemplate->parseQuake3( tokeniser )
1389 : shaderTemplate->parseDoom3( tokeniser );
1391 // do we already have this shader?
1392 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1394 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " 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 ){
1409 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( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1423 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1426 else if ( string_equal( token, "inlineGuide" ) ) {
1427 // skip entire inlineGuide definition
1428 std::size_t depth = 0;
1431 tokeniser.nextLine();
1432 token = tokeniser.getToken();
1433 if ( string_equal( token, "{" ) ) {
1436 else if ( string_equal( token, "}" ) ) {
1437 if ( --depth == 0 ) {
1446 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();
1461 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1465 void loadGuideFile( const char* filename ){
1466 StringOutputStream fullname( 256 );
1467 fullname << "guides/" << filename;
1468 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1471 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1473 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1475 parseGuideFile( tokeniser, fullname.c_str() );
1477 tokeniser.release();
1482 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1486 CShader* Try_Shader_ForName( const char* name ){
1488 shaders_t::iterator i = g_ActiveShaders.find( name );
1489 if ( i != g_ActiveShaders.end() ) {
1490 return ( *i ).second;
1493 // active shader was not found
1495 // find matching shader definition
1496 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1497 if ( i == g_shaderDefinitions.end() ) {
1498 // shader definition was not found
1500 // create new shader definition from default shader template
1501 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1502 shaderTemplate->CreateDefault( name );
1503 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1505 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
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 ){
1517 ASSERT_NOTNULL( name );
1519 IShader *pShader = Try_Shader_ForName( name );
1525 // the list of scripts/*.shader files we need to work with
1526 // those are listed in shaderlist file
1527 GSList *l_shaderfiles = 0;
1529 GSList* Shaders_getShaderFileList(){
1530 return l_shaderfiles;
1535 DumpUnreferencedShaders
1536 usefull function: dumps the list of .shader files that are not referenced to the console
1539 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1540 bool listed = false;
1542 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1544 if ( !strcmp( (char*)sh->data, filename ) ) {
1553 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1555 globalOutputStream() << "\t" << filename << "\n";
1559 typedef ReferenceCaller<bool, void(const char*), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1561 void DumpUnreferencedShaders(){
1562 bool bFound = false;
1563 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1566 void ShaderList_addShaderFile( const char* dirstring ){
1569 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1571 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1573 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1579 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1586 build a CStringList of shader names
1589 void BuildShaderList( TextInputStream& shaderlist ){
1590 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1591 tokeniser.nextLine();
1592 const char* token = tokeniser.getToken();
1593 StringOutputStream shaderFile( 64 );
1594 while ( token != 0 )
1596 // each token should be a shader filename
1597 shaderFile << token << "." << g_shadersExtension;
1599 ShaderList_addShaderFile( shaderFile.c_str() );
1601 tokeniser.nextLine();
1602 token = tokeniser.getToken();
1606 tokeniser.release();
1609 void FreeShaderList(){
1610 while ( l_shaderfiles != 0 )
1612 free( l_shaderfiles->data );
1613 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1617 void ShaderList_addFromArchive( const char *archivename ){
1618 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1619 if ( string_empty( shaderpath ) ) {
1623 StringOutputStream shaderlist( 256 );
1624 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1626 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1628 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1630 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1631 BuildShaderList( file->getInputStream() );
1637 #include "stream/filestream.h"
1639 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1640 StringOutputStream absShaderList( 256 );
1641 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1642 if ( file_exists( absShaderList.c_str() ) ) {
1646 StringOutputStream directory( 256 );
1647 directory << enginePath << gamename << '/' << shaderPath;
1648 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1653 StringOutputStream defaultShaderList( 256 );
1654 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1655 if ( file_exists( defaultShaderList.c_str() ) ) {
1656 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1662 void Shaders_Load(){
1663 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1664 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1667 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1668 if ( !string_empty( shaderPath ) ) {
1669 StringOutputStream path( 256 );
1670 path << DirectoryCleaned( shaderPath );
1672 if ( g_useShaderList ) {
1673 // preload shader files that have been listed in shaderlist.txt
1674 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1675 const char* gamename = GlobalRadiant().getGameName();
1676 const char* enginePath = GlobalRadiant().getEnginePath();
1677 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1679 bool isMod = !string_equal( basegame, gamename );
1681 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1682 gamename = basegame;
1683 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1686 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1687 DumpUnreferencedShaders();
1691 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0);
1694 GSList *lst = l_shaderfiles;
1695 StringOutputStream shadername( 256 );
1698 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1699 LoadShaderFile( shadername.c_str() );
1705 //StringPool_analyse( ShaderPool::instance() );
1708 void Shaders_Free(){
1711 g_shaderFilenames.clear();
1714 ModuleObservers g_observers;
1716 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1717 bool Shaders_realised(){
1718 return g_shaders_unrealised == 0;
1721 void Shaders_Realise(){
1722 if ( --g_shaders_unrealised == 0 ) {
1724 g_observers.realise();
1728 void Shaders_Unrealise(){
1729 if ( ++g_shaders_unrealised == 1 ) {
1730 g_observers.unrealise();
1735 void Shaders_Refresh(){
1736 Shaders_Unrealise();
1740 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1748 Shaders_Unrealise();
1755 IShader* getShaderForName( const char* name ){
1756 return Shader_ForName( name );
1759 void foreachShaderName( const ShaderNameCallback& callback ){
1760 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1762 callback( ( *i ).first.c_str() );
1766 void beginActiveShadersIterator(){
1767 ActiveShaders_IteratorBegin();
1770 bool endActiveShadersIterator(){
1771 return ActiveShaders_IteratorAtEnd();
1774 IShader* dereferenceActiveShadersIterator(){
1775 return ActiveShaders_IteratorCurrent();
1778 void incrementActiveShadersIterator(){
1779 ActiveShaders_IteratorIncrement();
1782 void setActiveShadersChangedNotify( const Callback<void()>& notify ){
1783 g_ActiveShadersChangedNotify = notify;
1786 void attach( ModuleObserver& observer ){
1787 g_observers.attach( observer );
1790 void detach( ModuleObserver& observer ){
1791 g_observers.detach( observer );
1794 void setLightingEnabled( bool enabled ){
1795 if ( CShader::m_lightingEnabled != enabled ) {
1796 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1798 ( *i ).second->unrealiseLighting();
1800 CShader::m_lightingEnabled = enabled;
1801 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1803 ( *i ).second->realiseLighting();
1808 const char* getTexturePrefix() const {
1809 return g_texturePrefix;
1813 Quake3ShaderSystem g_Quake3ShaderSystem;
1815 ShaderSystem& GetShaderSystem(){
1816 return g_Quake3ShaderSystem;
1819 void Shaders_Construct(){
1820 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1823 void Shaders_Destroy(){
1824 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1826 if ( Shaders_realised() ) {