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 );
97 NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX:
98 SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this
99 SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it
100 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
103 Image* loadBitmap( void* environment, const char* name ){
104 DirectoryArchiveFile file( name, name );
105 if ( !file.failed() ) {
106 return g_bitmapModule->loadImage( file );
111 inline byte* getPixel( byte* pixels, int width, int height, int x, int y ){
112 return pixels + ( ( ( ( ( y + height ) % height ) * width ) + ( ( x + width ) % width ) ) * 4 );
122 Image& convertHeightmapToNormalmap( Image& heightmap, float scale ){
123 int w = heightmap.getWidth();
124 int h = heightmap.getHeight();
126 Image& normalmap = *( new RGBAImage( heightmap.getWidth(), heightmap.getHeight() ) );
128 byte* in = heightmap.getRGBAPixels();
129 byte* out = normalmap.getRGBAPixels();
133 const int kernelSize = 2;
134 KernelElement kernel_du[kernelSize] = {
138 KernelElement kernel_dv[kernelSize] = {
144 const int kernelSize = 6;
145 KernelElement kernel_du[kernelSize] = {
153 KernelElement kernel_dv[kernelSize] = {
170 for ( KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i )
172 du += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
175 for ( KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i )
177 dv += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
180 float nx = -du * scale;
181 float ny = -dv * scale;
185 float norm = 1.0 / sqrt( nx * nx + ny * ny + nz * nz );
186 out[0] = float_to_integer( ( ( nx * norm ) + 1 ) * 127.5 );
187 out[1] = float_to_integer( ( ( ny * norm ) + 1 ) * 127.5 );
188 out[2] = float_to_integer( ( ( nz * norm ) + 1 ) * 127.5 );
201 Image* loadHeightmap( void* environment, const char* name ){
202 Image* heightmap = GlobalTexturesCache().loadImage( name );
203 if ( heightmap != 0 ) {
204 Image& normalmap = convertHeightmapToNormalmap( *heightmap, *reinterpret_cast<float*>( environment ) );
205 heightmap->release();
211 class ShaderPoolContext
215 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
216 typedef PooledString<ShaderPool> ShaderString;
217 typedef ShaderString ShaderVariable;
218 typedef ShaderString ShaderValue;
219 typedef CopiedString TextureExpression;
221 // clean a texture name to the qtexture_t name format we use internally
222 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
223 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
224 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
225 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
226 template<typename StringType>
227 void parseTextureName( StringType& name, const char* token ){
228 StringOutputStream cleaned( 256 );
229 cleaned << PathCleaned( token );
230 name = CopiedString( StringRange( cleaned.c_str(), path_get_filename_base_end( cleaned.c_str() ) ) ).c_str(); // remove extension
233 bool Tokeniser_parseTextureName( Tokeniser& tokeniser, TextureExpression& name ){
234 const char* token = tokeniser.getToken();
236 Tokeniser_unexpectedError( tokeniser, token, "#texture-name" );
239 parseTextureName( name, token );
243 bool Tokeniser_parseShaderName( Tokeniser& tokeniser, CopiedString& name ){
244 const char* token = tokeniser.getToken();
246 Tokeniser_unexpectedError( tokeniser, token, "#shader-name" );
249 parseTextureName( name, token );
253 bool Tokeniser_parseString( Tokeniser& tokeniser, ShaderString& string ){
254 const char* token = tokeniser.getToken();
256 Tokeniser_unexpectedError( tokeniser, token, "#string" );
264 typedef std::list<ShaderVariable> ShaderParameters;
265 typedef std::list<ShaderVariable> ShaderArguments;
267 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
271 std::size_t m_refcount;
273 CopiedString m_WadName;
276 ShaderParameters m_params;
278 TextureExpression m_textureName;
279 TextureExpression m_diffuse;
280 TextureExpression m_bump;
281 ShaderValue m_heightmapScale;
282 TextureExpression m_specular;
283 TextureExpression m_lightFalloffImage;
289 IShader::EAlphaFunc m_AlphaFunc;
292 IShader::ECull m_Cull;
305 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
306 if ( --m_refcount == 0 ) {
311 std::size_t refcount(){
315 const char* getName() const {
316 return m_Name.c_str();
319 void setName( const char* name ){
323 // -----------------------------------------
325 bool parseDoom3( Tokeniser& tokeniser );
327 bool parseQuake3( Tokeniser& tokeniser );
329 bool parseTemplate( Tokeniser& tokeniser );
332 void CreateDefault( const char *name ){
333 if ( g_enableDefaultShaders ) {
334 m_textureName = name;
344 class MapLayerTemplate
346 TextureExpression m_texture;
347 BlendFuncExpression m_blendFunc;
348 bool m_clampToBorder;
349 ShaderValue m_alphaTest;
351 MapLayerTemplate( const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest ) :
352 m_texture( texture ),
353 m_blendFunc( blendFunc ),
354 m_clampToBorder( false ),
355 m_alphaTest( alphaTest ){
358 const TextureExpression& texture() const {
362 const BlendFuncExpression& blendFunc() const {
366 bool clampToBorder() const {
367 return m_clampToBorder;
370 const ShaderValue& alphaTest() const {
375 typedef std::vector<MapLayerTemplate> MapLayers;
380 bool Doom3Shader_parseHeightmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
381 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
382 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
383 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
384 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, heightmapScale ) );
385 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
389 bool Doom3Shader_parseAddnormals( Tokeniser& tokeniser, TextureExpression& bump ){
390 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
391 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
392 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
393 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "heightmap" ) );
394 TextureExpression heightmapName;
395 ShaderValue heightmapScale;
396 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, heightmapName, heightmapScale ) );
397 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
401 bool Doom3Shader_parseBumpmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
402 const char* token = tokeniser.getToken();
404 Tokeniser_unexpectedError( tokeniser, token, "#bumpmap" );
407 if ( string_equal( token, "heightmap" ) ) {
408 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, bump, heightmapScale ) );
410 else if ( string_equal( token, "addnormals" ) ) {
411 RETURN_FALSE_IF_FAIL( Doom3Shader_parseAddnormals( tokeniser, bump ) );
415 parseTextureName( bump, token );
433 TextureExpression m_texture;
434 BlendFuncExpression m_blendFunc;
435 bool m_clampToBorder;
436 ShaderValue m_alphaTest;
437 ShaderValue m_heightmapScale;
439 LayerTemplate() : m_type( LAYER_NONE ), m_blendFunc( "GL_ONE", "GL_ZERO" ), m_clampToBorder( false ), m_alphaTest( "-1" ), m_heightmapScale( "0" ){
443 bool parseShaderParameters( Tokeniser& tokeniser, ShaderParameters& params ){
444 Tokeniser_parseToken( tokeniser, "(" );
447 const char* param = tokeniser.getToken();
448 if ( string_equal( param, ")" ) ) {
451 params.push_back( param );
452 const char* comma = tokeniser.getToken();
453 if ( string_equal( comma, ")" ) ) {
456 if ( !string_equal( comma, "," ) ) {
457 Tokeniser_unexpectedError( tokeniser, comma, "," );
464 bool ShaderTemplate::parseTemplate( Tokeniser& tokeniser ){
465 m_Name = tokeniser.getToken();
466 if ( !parseShaderParameters( tokeniser, m_params ) ) {
467 globalErrorStream() << "shader template: " << makeQuoted( m_Name.c_str() ) << ": parameter parse failed\n";
471 return parseDoom3( tokeniser );
474 bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){
475 LayerTemplate currentLayer;
478 // we need to read until we hit a balanced }
482 tokeniser.nextLine();
483 const char* token = tokeniser.getToken();
489 if ( string_equal( token, "{" ) ) {
493 else if ( string_equal( token, "}" ) ) {
495 if ( depth < 0 ) { // error
498 if ( depth == 0 ) { // end of shader
501 if ( depth == 1 ) { // end of layer
502 if ( currentLayer.m_type == LAYER_DIFFUSEMAP ) {
503 m_diffuse = currentLayer.m_texture;
505 else if ( currentLayer.m_type == LAYER_BUMPMAP ) {
506 m_bump = currentLayer.m_texture;
508 else if ( currentLayer.m_type == LAYER_SPECULARMAP ) {
509 m_specular = currentLayer.m_texture;
511 else if ( !string_empty( currentLayer.m_texture.c_str() ) ) {
512 m_layers.push_back( MapLayerTemplate(
513 currentLayer.m_texture.c_str(),
514 currentLayer.m_blendFunc,
515 currentLayer.m_clampToBorder,
516 currentLayer.m_alphaTest
519 currentLayer.m_type = LAYER_NONE;
520 currentLayer.m_texture = "";
525 if ( depth == 2 ) { // in layer
526 if ( string_equal_nocase( token, "blend" ) ) {
527 const char* blend = tokeniser.getToken();
530 Tokeniser_unexpectedError( tokeniser, blend, "#blend" );
534 if ( string_equal_nocase( blend, "diffusemap" ) ) {
535 currentLayer.m_type = LAYER_DIFFUSEMAP;
537 else if ( string_equal_nocase( blend, "bumpmap" ) ) {
538 currentLayer.m_type = LAYER_BUMPMAP;
540 else if ( string_equal_nocase( blend, "specularmap" ) ) {
541 currentLayer.m_type = LAYER_SPECULARMAP;
545 currentLayer.m_blendFunc.first = blend;
547 const char* comma = tokeniser.getToken();
550 Tokeniser_unexpectedError( tokeniser, comma, "#comma" );
554 if ( string_equal( comma, "," ) ) {
555 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, currentLayer.m_blendFunc.second ) );
559 currentLayer.m_blendFunc.second = "";
560 tokeniser.ungetToken();
564 else if ( string_equal_nocase( token, "map" ) ) {
565 if ( currentLayer.m_type == LAYER_BUMPMAP ) {
566 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale ) );
570 const char* map = tokeniser.getToken();
573 Tokeniser_unexpectedError( tokeniser, map, "#map" );
577 if ( string_equal( map, "makealpha" ) ) {
578 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
579 const char* texture = tokeniser.getToken();
580 if ( texture == 0 ) {
581 Tokeniser_unexpectedError( tokeniser, texture, "#texture" );
584 currentLayer.m_texture = texture;
585 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
589 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 );
602 else if ( depth == 1 ) {
603 if ( string_equal_nocase( token, "qer_editorimage" ) ) {
604 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
606 else if ( string_equal_nocase( token, "qer_trans" ) ) {
607 m_fTrans = string_read_float( tokeniser.getToken() );
608 m_nFlags |= QER_TRANS;
610 else if ( string_equal_nocase( token, "translucent" ) ) {
612 m_nFlags |= QER_TRANS;
614 else if ( string_equal( token, "DECAL_MACRO" ) ) {
616 m_nFlags |= QER_TRANS;
618 else if ( string_equal_nocase( token, "bumpmap" ) ) {
619 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) );
621 else if ( string_equal_nocase( token, "diffusemap" ) ) {
622 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) );
624 else if ( string_equal_nocase( token, "specularmap" ) ) {
625 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) );
627 else if ( string_equal_nocase( token, "twosided" ) ) {
628 m_Cull = IShader::eCullNone;
629 m_nFlags |= QER_CULL;
631 else if ( string_equal_nocase( token, "nodraw" ) ) {
632 m_nFlags |= QER_NODRAW;
634 else if ( string_equal_nocase( token, "nonsolid" ) ) {
635 m_nFlags |= QER_NONSOLID;
637 else if ( string_equal_nocase( token, "liquid" ) ) {
638 m_nFlags |= QER_WATER;
640 else if ( string_equal_nocase( token, "areaportal" ) ) {
641 m_nFlags |= QER_AREAPORTAL;
643 else if ( string_equal_nocase( token, "playerclip" )
644 || string_equal_nocase( token, "monsterclip" )
645 || string_equal_nocase( token, "ikclip" )
646 || string_equal_nocase( token, "moveableclip" ) ) {
647 m_nFlags |= QER_CLIP;
649 if ( string_equal_nocase( token, "fogLight" ) ) {
652 else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) {
653 const char* lightFalloffImage = tokeniser.getToken();
654 if ( lightFalloffImage == 0 ) {
655 Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" );
658 if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) {
659 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
660 TextureExpression name;
661 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) );
662 m_lightFalloffImage = name;
663 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
667 m_lightFalloffImage = lightFalloffImage;
673 if ( string_empty( m_textureName.c_str() ) ) {
674 m_textureName = m_diffuse;
680 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
681 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
683 ShaderTemplateMap g_shaders;
684 ShaderTemplateMap g_shaderTemplates;
686 ShaderTemplate* findTemplate( const char* name ){
687 ShaderTemplateMap::iterator i = g_shaderTemplates.find( name );
688 if ( i != g_shaderTemplates.end() ) {
689 return ( *i ).second.get();
694 class ShaderDefinition
697 ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename )
698 : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){
701 ShaderTemplate* shaderTemplate;
702 ShaderArguments args;
703 const char* filename;
706 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
708 ShaderDefinitionMap g_shaderDefinitions;
710 bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){
712 RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) );
713 const char* templateName = tokeniser.getToken();
714 ShaderTemplate* shaderTemplate = findTemplate( templateName );
715 if ( shaderTemplate == 0 ) {
716 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n";
719 ShaderArguments args;
720 if ( !parseShaderParameters( tokeniser, args ) ) {
721 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n";
725 if ( shaderTemplate != 0 ) {
726 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) {
727 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n";
734 const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){
735 ShaderArguments::const_iterator j = args.begin();
736 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
738 const char* other = ( *i ).c_str();
739 if ( string_equal( value, other ) ) {
740 return ( *j ).c_str();
746 ///\todo BlendFunc parsing
747 BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){
748 return BlendFunc( BLEND_ONE, BLEND_ZERO );
751 qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){
752 StringOutputStream result( 64 );
753 const char* expression = texture.c_str();
754 const char* end = expression + string_length( expression );
755 if ( !string_empty( expression ) ) {
758 const char* best = end;
759 const char* bestParam = 0;
760 const char* bestArg = 0;
761 ShaderArguments::const_iterator j = args.begin();
762 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
764 const char* found = strstr( expression, ( *i ).c_str() );
765 if ( found != 0 && found < best ) {
767 bestParam = ( *i ).c_str();
768 bestArg = ( *j ).c_str();
772 result << StringRange( expression, best );
773 result << PathCleaned( bestArg );
774 expression = best + string_length( bestParam );
781 result << expression;
783 return GlobalTexturesCache().capture( loader, result.c_str() );
786 float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
787 const char* result = evaluateShaderValue( value.c_str(), params, args );
789 if ( !string_parse_float( result, f ) ) {
790 globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n";
795 BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
796 const char* result = evaluateShaderValue( value.c_str(), params, args );
798 if ( string_equal_nocase( result, "gl_zero" ) ) {
801 if ( string_equal_nocase( result, "gl_one" ) ) {
804 if ( string_equal_nocase( result, "gl_src_color" ) ) {
805 return BLEND_SRC_COLOUR;
807 if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) {
808 return BLEND_ONE_MINUS_SRC_COLOUR;
810 if ( string_equal_nocase( result, "gl_src_alpha" ) ) {
811 return BLEND_SRC_ALPHA;
813 if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) {
814 return BLEND_ONE_MINUS_SRC_ALPHA;
816 if ( string_equal_nocase( result, "gl_dst_color" ) ) {
817 return BLEND_DST_COLOUR;
819 if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) {
820 return BLEND_ONE_MINUS_DST_COLOUR;
822 if ( string_equal_nocase( result, "gl_dst_alpha" ) ) {
823 return BLEND_DST_ALPHA;
825 if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) {
826 return BLEND_ONE_MINUS_DST_ALPHA;
828 if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) {
829 return BLEND_SRC_ALPHA_SATURATE;
832 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n";
836 class CShader : public IShader
838 std::size_t m_refcount;
840 const ShaderTemplate& m_template;
841 const ShaderArguments& m_args;
842 const char* m_filename;
843 // name is shader-name, otherwise texture-name ( if not a real shader )
845 CopiedString m_WadName;
847 qtexture_t* m_pTexture;
848 qtexture_t* m_notfound;
849 qtexture_t* m_pDiffuse;
850 float m_heightmapScale;
852 qtexture_t* m_pSpecular;
853 qtexture_t* m_pLightFalloffImage;
854 BlendFunc m_blendFunc;
860 static bool m_lightingEnabled;
862 CShader( const ShaderDefinition& definition ) :
864 m_template( *definition.shaderTemplate ),
865 m_args( definition.args ),
866 m_filename( definition.filename ),
867 m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
882 ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" );
885 // IShaders implementation -----------------
891 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
892 if ( --m_refcount == 0 ) {
897 std::size_t refcount(){
901 // get/set the qtexture_t* Radiant uses to represent this shader object
902 qtexture_t* getTexture() const {
906 qtexture_t* getDiffuse() const {
910 qtexture_t* getBump() const {
914 qtexture_t* getSpecular() const {
919 const char* getName() const {
920 return m_Name.c_str();
923 const char* getWadName() const {
924 return m_WadName.c_str();
927 bool IsInUse() const {
931 void SetInUse( bool bInUse ){
933 g_ActiveShadersChangedNotify();
936 // get the shader flags
937 int getFlags() const {
938 return m_template.m_nFlags;
941 // get the transparency value
942 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 {
948 return string_empty( m_filename );
952 void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
953 BlendFunc getBlendFunc() const {
959 return m_template.m_Cull;
962 // get shader file name ( ie the file where this one is defined )
963 const char* getShaderFileName() const {
966 // -----------------------------------------
969 m_pTexture = evaluateTexture( m_template.m_textureName, m_template.m_params, m_args );
971 if ( m_pTexture->texture_number == 0 ) {
972 m_notfound = m_pTexture;
975 m_pTexture = GlobalTexturesCache().capture( IsDefault() ? DEFAULT_NOTEX_NAME : DEFAULT_SHADERNOTEX_NAME );
983 GlobalTexturesCache().release( m_pTexture );
985 if ( m_notfound != 0 ) {
986 GlobalTexturesCache().release( m_notfound );
992 void realiseLighting(){
993 if ( m_lightingEnabled ) {
994 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
995 if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) {
996 m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args );
997 loader = LoadImageCallback( &m_heightmapScale, loadHeightmap );
999 m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args );
1000 m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader );
1001 m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args );
1002 m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args );
1004 for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i )
1006 m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) );
1009 if ( m_layers.size() == 1 ) {
1010 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
1011 if ( !string_empty( blendFunc.second.c_str() ) ) {
1012 m_blendFunc = BlendFunc(
1013 evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ),
1014 evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args )
1019 const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args );
1021 if ( string_equal_nocase( blend, "add" ) ) {
1022 m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE );
1024 else if ( string_equal_nocase( blend, "filter" ) ) {
1025 m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO );
1027 else if ( string_equal_nocase( blend, "blend" ) ) {
1028 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1032 globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n";
1039 void unrealiseLighting(){
1040 if ( m_lightingEnabled ) {
1041 GlobalTexturesCache().release( m_pDiffuse );
1042 GlobalTexturesCache().release( m_pBump );
1043 GlobalTexturesCache().release( m_pSpecular );
1045 GlobalTexturesCache().release( m_pLightFalloffImage );
1047 for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1049 GlobalTexturesCache().release( ( *i ).texture() );
1053 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1058 void setName( const char* name ){
1062 void setWadName( const char* name ){
1066 class MapLayer : public ShaderLayer
1068 qtexture_t* m_texture;
1069 BlendFunc m_blendFunc;
1070 bool m_clampToBorder;
1073 MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) :
1074 m_texture( texture ),
1075 m_blendFunc( blendFunc ),
1076 m_clampToBorder( false ),
1077 m_alphaTest( alphaTest ){
1080 qtexture_t* texture() const {
1084 BlendFunc blendFunc() const {
1088 bool clampToBorder() const {
1089 return m_clampToBorder;
1092 float alphaTest() const {
1097 static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){
1099 evaluateTexture( layerTemplate.texture(), params, args ),
1100 evaluateBlendFunc( layerTemplate.blendFunc(), params, args ),
1101 layerTemplate.clampToBorder(),
1102 evaluateFloat( layerTemplate.alphaTest(), params, args )
1106 typedef std::vector<MapLayer> MapLayers;
1109 const ShaderLayer* firstLayer() const {
1110 if ( m_layers.empty() ) {
1113 return &m_layers.front();
1115 void forEachLayer( const ShaderLayerCallback& callback ) const {
1116 for ( MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1122 qtexture_t* lightFalloffImage() const {
1123 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1124 return m_pLightFalloffImage;
1130 bool CShader::m_lightingEnabled = false;
1132 typedef SmartPointer<CShader> ShaderPointer;
1133 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1135 shaders_t g_ActiveShaders;
1137 static shaders_t::iterator g_ActiveShadersIterator;
1139 void ActiveShaders_IteratorBegin(){
1140 g_ActiveShadersIterator = g_ActiveShaders.begin();
1143 bool ActiveShaders_IteratorAtEnd(){
1144 return g_ActiveShadersIterator == g_ActiveShaders.end();
1147 IShader *ActiveShaders_IteratorCurrent(){
1148 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1151 void ActiveShaders_IteratorIncrement(){
1152 ++g_ActiveShadersIterator;
1155 void debug_check_shaders( shaders_t& shaders ){
1156 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1158 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1162 // will free all GL binded qtextures and shaders
1163 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1166 // empty the actives shaders list
1167 debug_check_shaders( g_ActiveShaders );
1168 g_ActiveShaders.clear();
1170 g_shaderTemplates.clear();
1171 g_shaderDefinitions.clear();
1172 g_ActiveShadersChangedNotify();
1175 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1176 // name of the qtexture_t we'll use to represent this shader ( this one has the "textures\" before )
1177 m_textureName = m_Name.c_str();
1179 tokeniser.nextLine();
1181 // we need to read until we hit a balanced }
1185 tokeniser.nextLine();
1186 const char* token = tokeniser.getToken();
1192 if ( string_equal( token, "{" ) ) {
1196 else if ( string_equal( token, "}" ) ) {
1198 if ( depth < 0 ) { // underflow
1201 if ( depth == 0 ) { // end of shader
1209 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1210 m_nFlags |= QER_NOCARVE;
1212 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1213 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1214 m_nFlags |= QER_TRANS;
1216 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1217 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1219 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1220 const char* alphafunc = tokeniser.getToken();
1222 if ( alphafunc == 0 ) {
1223 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1227 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1228 m_AlphaFunc = IShader::eEqual;
1230 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1231 m_AlphaFunc = IShader::eGreater;
1233 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1234 m_AlphaFunc = IShader::eLess;
1236 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1237 m_AlphaFunc = IShader::eGEqual;
1239 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1240 m_AlphaFunc = IShader::eLEqual;
1244 m_AlphaFunc = IShader::eAlways;
1247 m_nFlags |= QER_ALPHATEST;
1249 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1251 else if ( string_equal_nocase( token, "cull" ) ) {
1252 const char* cull = tokeniser.getToken();
1255 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1259 if ( string_equal_nocase( cull, "none" )
1260 || string_equal_nocase( cull, "twosided" )
1261 || string_equal_nocase( cull, "disable" ) ) {
1262 m_Cull = IShader::eCullNone;
1264 else if ( string_equal_nocase( cull, "back" )
1265 || string_equal_nocase( cull, "backside" )
1266 || string_equal_nocase( cull, "backsided" ) ) {
1267 m_Cull = IShader::eCullBack;
1271 m_Cull = IShader::eCullBack;
1274 m_nFlags |= QER_CULL;
1276 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1277 const char* surfaceparm = tokeniser.getToken();
1279 if ( surfaceparm == 0 ) {
1280 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1284 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1285 m_nFlags |= QER_FOG;
1286 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1290 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1291 m_nFlags |= QER_NODRAW;
1293 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1294 m_nFlags |= QER_NONSOLID;
1296 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1297 m_nFlags |= QER_WATER;
1299 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1300 m_nFlags |= QER_LAVA;
1302 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1303 m_nFlags |= QER_AREAPORTAL;
1305 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1306 m_nFlags |= QER_CLIP;
1308 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1309 m_nFlags |= QER_BOTCLIP;
1322 TextureExpression m_texture;
1323 BlendFunc m_blendFunc;
1324 bool m_clampToBorder;
1326 float m_heightmapScale;
1328 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1332 std::list<CopiedString> g_shaderFilenames;
1334 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1335 g_shaderFilenames.push_back( filename );
1336 filename = g_shaderFilenames.back().c_str();
1337 tokeniser.nextLine();
1340 const char* token = tokeniser.getToken();
1346 if ( string_equal( token, "table" ) ) {
1347 if ( tokeniser.getToken() == 0 ) {
1348 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1351 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1356 const char* option = tokeniser.getToken();
1357 if ( string_equal( option, "{" ) ) {
1360 const char* value = tokeniser.getToken();
1361 if ( string_equal( value, "}" ) ) {
1366 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1375 if ( string_equal( token, "guide" ) ) {
1376 parseTemplateInstance( tokeniser, filename );
1380 if ( !string_equal( token, "material" )
1381 && !string_equal( token, "particle" )
1382 && !string_equal( token, "skin" ) ) {
1383 tokeniser.ungetToken();
1385 // first token should be the path + name.. ( from base )
1387 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1389 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1390 shaderTemplate->setName( name.c_str() );
1392 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1394 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1395 ? shaderTemplate->parseQuake3( tokeniser )
1396 : shaderTemplate->parseDoom3( tokeniser );
1398 // do we already have this shader?
1399 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1401 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1407 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1415 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1416 tokeniser.nextLine();
1419 const char* token = tokeniser.getToken();
1425 if ( string_equal( token, "guide" ) ) {
1426 // first token should be the path + name.. ( from base )
1427 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1428 shaderTemplate->parseTemplate( tokeniser );
1429 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1430 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1433 else if ( string_equal( token, "inlineGuide" ) ) {
1434 // skip entire inlineGuide definition
1435 std::size_t depth = 0;
1438 tokeniser.nextLine();
1439 token = tokeniser.getToken();
1440 if ( string_equal( token, "{" ) ) {
1443 else if ( string_equal( token, "}" ) ) {
1444 if ( --depth == 0 ) {
1453 void LoadShaderFile( const char* filename ){
1454 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1457 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1459 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1461 ParseShaderFile( tokeniser, filename );
1463 tokeniser.release();
1468 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1472 void loadGuideFile( const char* filename ){
1473 StringOutputStream fullname( 256 );
1474 fullname << "guides/" << filename;
1475 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1478 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1480 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1482 parseGuideFile( tokeniser, fullname.c_str() );
1484 tokeniser.release();
1489 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1493 CShader* Try_Shader_ForName( const char* name ){
1495 shaders_t::iterator i = g_ActiveShaders.find( name );
1496 if ( i != g_ActiveShaders.end() ) {
1497 return ( *i ).second;
1500 // active shader was not found
1502 // find matching shader definition
1503 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1504 if ( i == g_shaderDefinitions.end() ) {
1505 // shader definition was not found
1507 // create new shader definition from default shader template
1508 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1509 shaderTemplate->CreateDefault( name );
1510 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1512 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1515 // create shader from existing definition
1516 ShaderPointer pShader( new CShader( ( *i ).second ) );
1517 pShader->setName( name );
1518 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1519 g_ActiveShadersChangedNotify();
1523 IShader *Shader_ForName( const char *name ){
1524 ASSERT_NOTNULL( name );
1526 IShader *pShader = Try_Shader_ForName( name );
1532 // the list of scripts/*.shader files we need to work with
1533 // those are listed in shaderlist file
1534 GSList *l_shaderfiles = 0;
1536 GSList* Shaders_getShaderFileList(){
1537 return l_shaderfiles;
1542 DumpUnreferencedShaders
1543 usefull function: dumps the list of .shader files that are not referenced to the console
1546 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1547 bool listed = false;
1549 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1551 if ( !strcmp( (char*)sh->data, filename ) ) {
1560 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1562 globalOutputStream() << "\t" << filename << "\n";
1566 typedef ReferenceCaller<bool, void(const char*), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1568 void DumpUnreferencedShaders(){
1569 bool bFound = false;
1570 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1573 void ShaderList_addShaderFile( const char* dirstring ){
1576 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1578 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1580 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1586 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1593 build a CStringList of shader names
1596 void BuildShaderList( TextInputStream& shaderlist ){
1597 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1598 tokeniser.nextLine();
1599 const char* token = tokeniser.getToken();
1600 StringOutputStream shaderFile( 64 );
1601 while ( token != 0 )
1603 // each token should be a shader filename
1604 shaderFile << token << "." << g_shadersExtension;
1606 ShaderList_addShaderFile( shaderFile.c_str() );
1608 tokeniser.nextLine();
1609 token = tokeniser.getToken();
1613 tokeniser.release();
1616 void FreeShaderList(){
1617 while ( l_shaderfiles != 0 )
1619 free( l_shaderfiles->data );
1620 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1624 void ShaderList_addFromArchive( const char *archivename ){
1625 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1626 if ( string_empty( shaderpath ) ) {
1630 StringOutputStream shaderlist( 256 );
1631 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1633 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1635 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1637 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1638 BuildShaderList( file->getInputStream() );
1644 #include "stream/filestream.h"
1646 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1647 StringOutputStream absShaderList( 256 );
1648 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1649 if ( file_exists( absShaderList.c_str() ) ) {
1653 StringOutputStream directory( 256 );
1654 directory << enginePath << gamename << '/' << shaderPath;
1655 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1660 StringOutputStream defaultShaderList( 256 );
1661 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1662 if ( file_exists( defaultShaderList.c_str() ) ) {
1663 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1669 void Shaders_Load(){
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();
1698 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0);
1701 GSList *lst = l_shaderfiles;
1702 StringOutputStream shadername( 256 );
1705 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1706 LoadShaderFile( shadername.c_str() );
1712 //StringPool_analyse( ShaderPool::instance() );
1715 void Shaders_Free(){
1718 g_shaderFilenames.clear();
1721 ModuleObservers g_observers;
1723 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1724 bool Shaders_realised(){
1725 return g_shaders_unrealised == 0;
1728 void Shaders_Realise(){
1729 if ( --g_shaders_unrealised == 0 ) {
1731 g_observers.realise();
1735 void Shaders_Unrealise(){
1736 if ( ++g_shaders_unrealised == 1 ) {
1737 g_observers.unrealise();
1742 void Shaders_Refresh(){
1743 Shaders_Unrealise();
1747 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1755 Shaders_Unrealise();
1762 IShader* getShaderForName( const char* name ){
1763 return Shader_ForName( name );
1766 void foreachShaderName( const ShaderNameCallback& callback ){
1767 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1769 callback( ( *i ).first.c_str() );
1773 void beginActiveShadersIterator(){
1774 ActiveShaders_IteratorBegin();
1777 bool endActiveShadersIterator(){
1778 return ActiveShaders_IteratorAtEnd();
1781 IShader* dereferenceActiveShadersIterator(){
1782 return ActiveShaders_IteratorCurrent();
1785 void incrementActiveShadersIterator(){
1786 ActiveShaders_IteratorIncrement();
1789 void setActiveShadersChangedNotify( const Callback<void()>& notify ){
1790 g_ActiveShadersChangedNotify = notify;
1793 void attach( ModuleObserver& observer ){
1794 g_observers.attach( observer );
1797 void detach( ModuleObserver& observer ){
1798 g_observers.detach( observer );
1801 void setLightingEnabled( bool enabled ){
1802 if ( CShader::m_lightingEnabled != enabled ) {
1803 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1805 ( *i ).second->unrealiseLighting();
1807 CShader::m_lightingEnabled = enabled;
1808 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1810 ( *i ).second->realiseLighting();
1815 const char* getTexturePrefix() const {
1816 return g_texturePrefix;
1820 Quake3ShaderSystem g_Quake3ShaderSystem;
1822 ShaderSystem& GetShaderSystem(){
1823 return g_Quake3ShaderSystem;
1826 void Shaders_Construct(){
1827 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1830 void Shaders_Destroy(){
1831 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1833 if ( Shaders_realised() ) {