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;
276 CopiedString m_WadName;
279 ShaderParameters m_params;
281 TextureExpression m_textureName;
282 TextureExpression m_diffuse;
283 TextureExpression m_bump;
284 ShaderValue m_heightmapScale;
285 TextureExpression m_specular;
286 TextureExpression m_lightFalloffImage;
292 IShader::EAlphaFunc m_AlphaFunc;
295 IShader::ECull m_Cull;
308 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
309 if ( --m_refcount == 0 ) {
314 std::size_t refcount(){
318 const char* getName() const {
319 return m_Name.c_str();
322 void setName( const char* name ){
326 // -----------------------------------------
328 bool parseDoom3( Tokeniser& tokeniser );
330 bool parseQuake3( Tokeniser& tokeniser );
332 bool parseTemplate( Tokeniser& tokeniser );
335 void CreateDefault( const char *name ){
336 if ( g_enableDefaultShaders ) {
337 m_textureName = name;
347 class MapLayerTemplate
349 TextureExpression m_texture;
350 BlendFuncExpression m_blendFunc;
351 bool m_clampToBorder;
352 ShaderValue m_alphaTest;
354 MapLayerTemplate( const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest ) :
355 m_texture( texture ),
356 m_blendFunc( blendFunc ),
357 m_clampToBorder( false ),
358 m_alphaTest( alphaTest ){
361 const TextureExpression& texture() const {
365 const BlendFuncExpression& blendFunc() const {
369 bool clampToBorder() const {
370 return m_clampToBorder;
373 const ShaderValue& alphaTest() const {
378 typedef std::vector<MapLayerTemplate> MapLayers;
383 bool Doom3Shader_parseHeightmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
384 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
385 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
386 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
387 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, heightmapScale ) );
388 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
392 bool Doom3Shader_parseAddnormals( Tokeniser& tokeniser, TextureExpression& bump ){
393 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
394 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
395 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
396 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "heightmap" ) );
397 TextureExpression heightmapName;
398 ShaderValue heightmapScale;
399 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, heightmapName, heightmapScale ) );
400 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
404 bool Doom3Shader_parseBumpmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
405 const char* token = tokeniser.getToken();
407 Tokeniser_unexpectedError( tokeniser, token, "#bumpmap" );
410 if ( string_equal( token, "heightmap" ) ) {
411 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, bump, heightmapScale ) );
413 else if ( string_equal( token, "addnormals" ) ) {
414 RETURN_FALSE_IF_FAIL( Doom3Shader_parseAddnormals( tokeniser, bump ) );
418 parseTextureName( bump, token );
436 TextureExpression m_texture;
437 BlendFuncExpression m_blendFunc;
438 bool m_clampToBorder;
439 ShaderValue m_alphaTest;
440 ShaderValue m_heightmapScale;
442 LayerTemplate() : m_type( LAYER_NONE ), m_blendFunc( "GL_ONE", "GL_ZERO" ), m_clampToBorder( false ), m_alphaTest( "-1" ), m_heightmapScale( "0" ){
446 bool parseShaderParameters( Tokeniser& tokeniser, ShaderParameters& params ){
447 Tokeniser_parseToken( tokeniser, "(" );
450 const char* param = tokeniser.getToken();
451 if ( string_equal( param, ")" ) ) {
454 params.push_back( param );
455 const char* comma = tokeniser.getToken();
456 if ( string_equal( comma, ")" ) ) {
459 if ( !string_equal( comma, "," ) ) {
460 Tokeniser_unexpectedError( tokeniser, comma, "," );
467 bool ShaderTemplate::parseTemplate( Tokeniser& tokeniser ){
468 m_Name = tokeniser.getToken();
469 if ( !parseShaderParameters( tokeniser, m_params ) ) {
470 globalErrorStream() << "shader template: " << makeQuoted( m_Name.c_str() ) << ": parameter parse failed\n";
474 return parseDoom3( tokeniser );
477 bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){
478 LayerTemplate currentLayer;
481 // we need to read until we hit a balanced }
485 tokeniser.nextLine();
486 const char* token = tokeniser.getToken();
492 if ( string_equal( token, "{" ) ) {
496 else if ( string_equal( token, "}" ) ) {
498 if ( depth < 0 ) { // error
501 if ( depth == 0 ) { // end of shader
504 if ( depth == 1 ) { // end of layer
505 if ( currentLayer.m_type == LAYER_DIFFUSEMAP ) {
506 m_diffuse = currentLayer.m_texture;
508 else if ( currentLayer.m_type == LAYER_BUMPMAP ) {
509 m_bump = currentLayer.m_texture;
511 else if ( currentLayer.m_type == LAYER_SPECULARMAP ) {
512 m_specular = currentLayer.m_texture;
514 else if ( !string_empty( currentLayer.m_texture.c_str() ) ) {
515 m_layers.push_back( MapLayerTemplate(
516 currentLayer.m_texture.c_str(),
517 currentLayer.m_blendFunc,
518 currentLayer.m_clampToBorder,
519 currentLayer.m_alphaTest
522 currentLayer.m_type = LAYER_NONE;
523 currentLayer.m_texture = "";
528 if ( depth == 2 ) { // in layer
529 if ( string_equal_nocase( token, "blend" ) ) {
530 const char* blend = tokeniser.getToken();
533 Tokeniser_unexpectedError( tokeniser, blend, "#blend" );
537 if ( string_equal_nocase( blend, "diffusemap" ) ) {
538 currentLayer.m_type = LAYER_DIFFUSEMAP;
540 else if ( string_equal_nocase( blend, "bumpmap" ) ) {
541 currentLayer.m_type = LAYER_BUMPMAP;
543 else if ( string_equal_nocase( blend, "specularmap" ) ) {
544 currentLayer.m_type = LAYER_SPECULARMAP;
548 currentLayer.m_blendFunc.first = blend;
550 const char* comma = tokeniser.getToken();
553 Tokeniser_unexpectedError( tokeniser, comma, "#comma" );
557 if ( string_equal( comma, "," ) ) {
558 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, currentLayer.m_blendFunc.second ) );
562 currentLayer.m_blendFunc.second = "";
563 tokeniser.ungetToken();
567 else if ( string_equal_nocase( token, "map" ) ) {
568 if ( currentLayer.m_type == LAYER_BUMPMAP ) {
569 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale ) );
573 const char* map = tokeniser.getToken();
576 Tokeniser_unexpectedError( tokeniser, map, "#map" );
580 if ( string_equal( map, "makealpha" ) ) {
581 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
582 const char* texture = tokeniser.getToken();
583 if ( texture == 0 ) {
584 Tokeniser_unexpectedError( tokeniser, texture, "#texture" );
587 currentLayer.m_texture = texture;
588 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
592 parseTextureName( currentLayer.m_texture, map );
596 else if ( string_equal_nocase( token, "zeroclamp" ) ) {
597 currentLayer.m_clampToBorder = true;
600 else if ( string_equal_nocase( token, "alphaTest" ) ) {
601 Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest );
605 else if ( depth == 1 ) {
606 if ( string_equal_nocase( token, "qer_editorimage" ) ) {
607 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
609 else if ( string_equal_nocase( token, "qer_trans" ) ) {
610 m_fTrans = string_read_float( tokeniser.getToken() );
611 m_nFlags |= QER_TRANS;
613 else if ( string_equal_nocase( token, "translucent" ) ) {
615 m_nFlags |= QER_TRANS;
617 else if ( string_equal( token, "DECAL_MACRO" ) ) {
619 m_nFlags |= QER_TRANS;
621 else if ( string_equal_nocase( token, "bumpmap" ) ) {
622 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) );
624 else if ( string_equal_nocase( token, "diffusemap" ) ) {
625 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) );
627 else if ( string_equal_nocase( token, "specularmap" ) ) {
628 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) );
630 else if ( string_equal_nocase( token, "twosided" ) ) {
631 m_Cull = IShader::eCullNone;
632 m_nFlags |= QER_CULL;
634 else if ( string_equal_nocase( token, "nodraw" ) ) {
635 m_nFlags |= QER_NODRAW;
637 else if ( string_equal_nocase( token, "nonsolid" ) ) {
638 m_nFlags |= QER_NONSOLID;
640 else if ( string_equal_nocase( token, "liquid" ) ) {
641 m_nFlags |= QER_WATER;
643 else if ( string_equal_nocase( token, "areaportal" ) ) {
644 m_nFlags |= QER_AREAPORTAL;
646 else if ( string_equal_nocase( token, "playerclip" )
647 || string_equal_nocase( token, "monsterclip" )
648 || string_equal_nocase( token, "ikclip" )
649 || string_equal_nocase( token, "moveableclip" ) ) {
650 m_nFlags |= QER_CLIP;
652 if ( string_equal_nocase( token, "fogLight" ) ) {
655 else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) {
656 const char* lightFalloffImage = tokeniser.getToken();
657 if ( lightFalloffImage == 0 ) {
658 Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" );
661 if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) {
662 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
663 TextureExpression name;
664 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) );
665 m_lightFalloffImage = name;
666 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
670 m_lightFalloffImage = lightFalloffImage;
676 if ( string_empty( m_textureName.c_str() ) ) {
677 m_textureName = m_diffuse;
683 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
684 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
686 ShaderTemplateMap g_shaders;
687 ShaderTemplateMap g_shaderTemplates;
689 ShaderTemplate* findTemplate( const char* name ){
690 ShaderTemplateMap::iterator i = g_shaderTemplates.find( name );
691 if ( i != g_shaderTemplates.end() ) {
692 return ( *i ).second.get();
697 class ShaderDefinition
700 ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename )
701 : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){
704 ShaderTemplate* shaderTemplate;
705 ShaderArguments args;
706 const char* filename;
709 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
711 ShaderDefinitionMap g_shaderDefinitions;
713 bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){
715 RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) );
716 const char* templateName = tokeniser.getToken();
717 ShaderTemplate* shaderTemplate = findTemplate( templateName );
718 if ( shaderTemplate == 0 ) {
719 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n";
722 ShaderArguments args;
723 if ( !parseShaderParameters( tokeniser, args ) ) {
724 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n";
728 if ( shaderTemplate != 0 ) {
729 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) {
730 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n";
737 const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){
738 ShaderArguments::const_iterator j = args.begin();
739 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
741 const char* other = ( *i ).c_str();
742 if ( string_equal( value, other ) ) {
743 return ( *j ).c_str();
749 ///\todo BlendFunc parsing
750 BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){
751 return BlendFunc( BLEND_ONE, BLEND_ZERO );
754 qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){
755 StringOutputStream result( 64 );
756 const char* expression = texture.c_str();
757 const char* end = expression + string_length( expression );
758 if ( !string_empty( expression ) ) {
761 const char* best = end;
762 const char* bestParam = 0;
763 const char* bestArg = 0;
764 ShaderArguments::const_iterator j = args.begin();
765 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
767 const char* found = strstr( expression, ( *i ).c_str() );
768 if ( found != 0 && found < best ) {
770 bestParam = ( *i ).c_str();
771 bestArg = ( *j ).c_str();
775 result << StringRange( expression, best );
776 result << PathCleaned( bestArg );
777 expression = best + string_length( bestParam );
784 result << expression;
786 return GlobalTexturesCache().capture( loader, result.c_str() );
789 float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
790 const char* result = evaluateShaderValue( value.c_str(), params, args );
792 if ( !string_parse_float( result, f ) ) {
793 globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n";
798 BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
799 const char* result = evaluateShaderValue( value.c_str(), params, args );
801 if ( string_equal_nocase( result, "gl_zero" ) ) {
804 if ( string_equal_nocase( result, "gl_one" ) ) {
807 if ( string_equal_nocase( result, "gl_src_color" ) ) {
808 return BLEND_SRC_COLOUR;
810 if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) {
811 return BLEND_ONE_MINUS_SRC_COLOUR;
813 if ( string_equal_nocase( result, "gl_src_alpha" ) ) {
814 return BLEND_SRC_ALPHA;
816 if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) {
817 return BLEND_ONE_MINUS_SRC_ALPHA;
819 if ( string_equal_nocase( result, "gl_dst_color" ) ) {
820 return BLEND_DST_COLOUR;
822 if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) {
823 return BLEND_ONE_MINUS_DST_COLOUR;
825 if ( string_equal_nocase( result, "gl_dst_alpha" ) ) {
826 return BLEND_DST_ALPHA;
828 if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) {
829 return BLEND_ONE_MINUS_DST_ALPHA;
831 if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) {
832 return BLEND_SRC_ALPHA_SATURATE;
835 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n";
839 class CShader : public IShader
841 std::size_t m_refcount;
843 const ShaderTemplate& m_template;
844 const ShaderArguments& m_args;
845 const char* m_filename;
846 // name is shader-name, otherwise texture-name ( if not a real shader )
848 CopiedString m_WadName;
850 qtexture_t* m_pTexture;
851 qtexture_t* m_notfound;
852 qtexture_t* m_pDiffuse;
853 float m_heightmapScale;
855 qtexture_t* m_pSpecular;
856 qtexture_t* m_pLightFalloffImage;
857 BlendFunc m_blendFunc;
863 static bool m_lightingEnabled;
865 CShader( const ShaderDefinition& definition ) :
867 m_template( *definition.shaderTemplate ),
868 m_args( definition.args ),
869 m_filename( definition.filename ),
870 m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
885 ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" );
888 // IShaders implementation -----------------
894 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
895 if ( --m_refcount == 0 ) {
900 std::size_t refcount(){
904 // get/set the qtexture_t* Radiant uses to represent this shader object
905 qtexture_t* getTexture() const {
909 qtexture_t* getDiffuse() const {
913 qtexture_t* getBump() const {
917 qtexture_t* getSpecular() const {
922 const char* getName() const {
923 return m_Name.c_str();
926 const char* getWadName() const {
927 return m_WadName.c_str();
930 bool IsInUse() const {
934 void SetInUse( bool bInUse ){
936 g_ActiveShadersChangedNotify();
939 // get the shader flags
940 int getFlags() const {
941 return m_template.m_nFlags;
944 // get the transparency value
945 float getTrans() const {
946 return m_template.m_fTrans;
949 // test if it's a true shader, or a default shader created to wrap around a texture
950 bool IsDefault() const {
951 return string_empty( m_filename );
955 void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
956 BlendFunc getBlendFunc() const {
962 return m_template.m_Cull;
965 // get shader file name ( ie the file where this one is defined )
966 const char* getShaderFileName() const {
969 // -----------------------------------------
972 m_pTexture = evaluateTexture( m_template.m_textureName, m_template.m_params, m_args );
974 if ( m_pTexture->texture_number == 0 ) {
975 m_notfound = m_pTexture;
978 m_pTexture = GlobalTexturesCache().capture( IsDefault() ? DEFAULT_NOTEX_NAME : DEFAULT_SHADERNOTEX_NAME );
986 GlobalTexturesCache().release( m_pTexture );
988 if ( m_notfound != 0 ) {
989 GlobalTexturesCache().release( m_notfound );
995 void realiseLighting(){
996 if ( m_lightingEnabled ) {
997 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
998 if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) {
999 m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args );
1000 loader = LoadImageCallback( &m_heightmapScale, loadHeightmap );
1002 m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args );
1003 m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader );
1004 m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args );
1005 m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args );
1007 for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i )
1009 m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) );
1012 if ( m_layers.size() == 1 ) {
1013 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
1014 if ( !string_empty( blendFunc.second.c_str() ) ) {
1015 m_blendFunc = BlendFunc(
1016 evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ),
1017 evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args )
1022 const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args );
1024 if ( string_equal_nocase( blend, "add" ) ) {
1025 m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE );
1027 else if ( string_equal_nocase( blend, "filter" ) ) {
1028 m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO );
1030 else if ( string_equal_nocase( blend, "blend" ) ) {
1031 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1035 globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n";
1042 void unrealiseLighting(){
1043 if ( m_lightingEnabled ) {
1044 GlobalTexturesCache().release( m_pDiffuse );
1045 GlobalTexturesCache().release( m_pBump );
1046 GlobalTexturesCache().release( m_pSpecular );
1048 GlobalTexturesCache().release( m_pLightFalloffImage );
1050 for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1052 GlobalTexturesCache().release( ( *i ).texture() );
1056 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1061 void setName( const char* name ){
1065 void setWadName( const char* name ){
1069 class MapLayer : public ShaderLayer
1071 qtexture_t* m_texture;
1072 BlendFunc m_blendFunc;
1073 bool m_clampToBorder;
1076 MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) :
1077 m_texture( texture ),
1078 m_blendFunc( blendFunc ),
1079 m_clampToBorder( false ),
1080 m_alphaTest( alphaTest ){
1083 qtexture_t* texture() const {
1087 BlendFunc blendFunc() const {
1091 bool clampToBorder() const {
1092 return m_clampToBorder;
1095 float alphaTest() const {
1100 static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){
1102 evaluateTexture( layerTemplate.texture(), params, args ),
1103 evaluateBlendFunc( layerTemplate.blendFunc(), params, args ),
1104 layerTemplate.clampToBorder(),
1105 evaluateFloat( layerTemplate.alphaTest(), params, args )
1109 typedef std::vector<MapLayer> MapLayers;
1112 const ShaderLayer* firstLayer() const {
1113 if ( m_layers.empty() ) {
1116 return &m_layers.front();
1118 void forEachLayer( const ShaderLayerCallback& callback ) const {
1119 for ( MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1125 qtexture_t* lightFalloffImage() const {
1126 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1127 return m_pLightFalloffImage;
1133 bool CShader::m_lightingEnabled = false;
1135 typedef SmartPointer<CShader> ShaderPointer;
1136 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1138 shaders_t g_ActiveShaders;
1140 static shaders_t::iterator g_ActiveShadersIterator;
1142 void ActiveShaders_IteratorBegin(){
1143 g_ActiveShadersIterator = g_ActiveShaders.begin();
1146 bool ActiveShaders_IteratorAtEnd(){
1147 return g_ActiveShadersIterator == g_ActiveShaders.end();
1150 IShader *ActiveShaders_IteratorCurrent(){
1151 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1154 void ActiveShaders_IteratorIncrement(){
1155 ++g_ActiveShadersIterator;
1158 void debug_check_shaders( shaders_t& shaders ){
1159 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1161 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1165 // will free all GL binded qtextures and shaders
1166 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1169 // empty the actives shaders list
1170 debug_check_shaders( g_ActiveShaders );
1171 g_ActiveShaders.clear();
1173 g_shaderTemplates.clear();
1174 g_shaderDefinitions.clear();
1175 g_ActiveShadersChangedNotify();
1178 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1179 // name of the qtexture_t we'll use to represent this shader ( this one has the "textures\" before )
1180 m_textureName = m_Name.c_str();
1182 tokeniser.nextLine();
1184 // we need to read until we hit a balanced }
1188 tokeniser.nextLine();
1189 const char* token = tokeniser.getToken();
1195 if ( string_equal( token, "{" ) ) {
1199 else if ( string_equal( token, "}" ) ) {
1201 if ( depth < 0 ) { // underflow
1204 if ( depth == 0 ) { // end of shader
1212 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1213 m_nFlags |= QER_NOCARVE;
1215 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1216 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1217 m_nFlags |= QER_TRANS;
1219 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1220 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1222 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1223 const char* alphafunc = tokeniser.getToken();
1225 if ( alphafunc == 0 ) {
1226 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1230 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1231 m_AlphaFunc = IShader::eEqual;
1233 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1234 m_AlphaFunc = IShader::eGreater;
1236 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1237 m_AlphaFunc = IShader::eLess;
1239 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1240 m_AlphaFunc = IShader::eGEqual;
1242 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1243 m_AlphaFunc = IShader::eLEqual;
1247 m_AlphaFunc = IShader::eAlways;
1250 m_nFlags |= QER_ALPHATEST;
1252 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1254 else if ( string_equal_nocase( token, "cull" ) ) {
1255 const char* cull = tokeniser.getToken();
1258 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1262 if ( string_equal_nocase( cull, "none" )
1263 || string_equal_nocase( cull, "twosided" )
1264 || string_equal_nocase( cull, "disable" ) ) {
1265 m_Cull = IShader::eCullNone;
1267 else if ( string_equal_nocase( cull, "back" )
1268 || string_equal_nocase( cull, "backside" )
1269 || string_equal_nocase( cull, "backsided" ) ) {
1270 m_Cull = IShader::eCullBack;
1274 m_Cull = IShader::eCullBack;
1277 m_nFlags |= QER_CULL;
1279 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1280 const char* surfaceparm = tokeniser.getToken();
1282 if ( surfaceparm == 0 ) {
1283 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1287 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1288 m_nFlags |= QER_FOG;
1289 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1293 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1294 m_nFlags |= QER_NODRAW;
1296 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1297 m_nFlags |= QER_NONSOLID;
1299 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1300 m_nFlags |= QER_WATER;
1302 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1303 m_nFlags |= QER_LAVA;
1305 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1306 m_nFlags |= QER_AREAPORTAL;
1308 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1309 m_nFlags |= QER_CLIP;
1311 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1312 m_nFlags |= QER_BOTCLIP;
1325 TextureExpression m_texture;
1326 BlendFunc m_blendFunc;
1327 bool m_clampToBorder;
1329 float m_heightmapScale;
1331 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1335 std::list<CopiedString> g_shaderFilenames;
1337 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1338 g_shaderFilenames.push_back( filename );
1339 filename = g_shaderFilenames.back().c_str();
1340 tokeniser.nextLine();
1343 const char* token = tokeniser.getToken();
1349 if ( string_equal( token, "table" ) ) {
1350 if ( tokeniser.getToken() == 0 ) {
1351 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1354 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1359 const char* option = tokeniser.getToken();
1360 if ( string_equal( option, "{" ) ) {
1363 const char* value = tokeniser.getToken();
1364 if ( string_equal( value, "}" ) ) {
1369 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1378 if ( string_equal( token, "guide" ) ) {
1379 parseTemplateInstance( tokeniser, filename );
1383 if ( !string_equal( token, "material" )
1384 && !string_equal( token, "particle" )
1385 && !string_equal( token, "skin" ) ) {
1386 tokeniser.ungetToken();
1388 // first token should be the path + name.. ( from base )
1390 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1392 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1393 shaderTemplate->setName( name.c_str() );
1395 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1397 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1398 ? shaderTemplate->parseQuake3( tokeniser )
1399 : shaderTemplate->parseDoom3( tokeniser );
1401 // do we already have this shader?
1402 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1404 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1410 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1418 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1419 tokeniser.nextLine();
1422 const char* token = tokeniser.getToken();
1428 if ( string_equal( token, "guide" ) ) {
1429 // first token should be the path + name.. ( from base )
1430 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1431 shaderTemplate->parseTemplate( tokeniser );
1432 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1433 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1436 else if ( string_equal( token, "inlineGuide" ) ) {
1437 // skip entire inlineGuide definition
1438 std::size_t depth = 0;
1441 tokeniser.nextLine();
1442 token = tokeniser.getToken();
1443 if ( string_equal( token, "{" ) ) {
1446 else if ( string_equal( token, "}" ) ) {
1447 if ( --depth == 0 ) {
1456 void LoadShaderFile( const char* filename ){
1457 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1460 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1462 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1464 ParseShaderFile( tokeniser, filename );
1466 tokeniser.release();
1471 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1475 void loadGuideFile( const char* filename ){
1476 StringOutputStream fullname( 256 );
1477 fullname << "guides/" << filename;
1478 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1481 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1483 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1485 parseGuideFile( tokeniser, fullname.c_str() );
1487 tokeniser.release();
1492 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1496 CShader* Try_Shader_ForName( const char* name ){
1498 shaders_t::iterator i = g_ActiveShaders.find( name );
1499 if ( i != g_ActiveShaders.end() ) {
1500 return ( *i ).second;
1503 // active shader was not found
1505 // find matching shader definition
1506 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1507 if ( i == g_shaderDefinitions.end() ) {
1508 // shader definition was not found
1510 // create new shader definition from default shader template
1511 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1512 shaderTemplate->CreateDefault( name );
1513 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1515 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1518 // create shader from existing definition
1519 ShaderPointer pShader( new CShader( ( *i ).second ) );
1520 pShader->setName( name );
1521 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1522 g_ActiveShadersChangedNotify();
1526 IShader *Shader_ForName( const char *name ){
1527 ASSERT_NOTNULL( name );
1529 IShader *pShader = Try_Shader_ForName( name );
1535 // the list of scripts/*.shader files we need to work with
1536 // those are listed in shaderlist file
1537 GSList *l_shaderfiles = 0;
1539 GSList* Shaders_getShaderFileList(){
1540 return l_shaderfiles;
1545 DumpUnreferencedShaders
1546 usefull function: dumps the list of .shader files that are not referenced to the console
1549 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1550 bool listed = false;
1552 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1554 if ( !strcmp( (char*)sh->data, filename ) ) {
1563 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1565 globalOutputStream() << "\t" << filename << "\n";
1569 typedef ReferenceCaller<bool, void(const char*), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1571 void DumpUnreferencedShaders(){
1572 bool bFound = false;
1573 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1576 void ShaderList_addShaderFile( const char* dirstring ){
1579 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1581 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1583 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1589 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1596 build a CStringList of shader names
1599 void BuildShaderList( TextInputStream& shaderlist ){
1600 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1601 tokeniser.nextLine();
1602 const char* token = tokeniser.getToken();
1603 StringOutputStream shaderFile( 64 );
1604 while ( token != 0 )
1606 // each token should be a shader filename
1607 shaderFile << token << "." << g_shadersExtension;
1609 ShaderList_addShaderFile( shaderFile.c_str() );
1611 tokeniser.nextLine();
1612 token = tokeniser.getToken();
1616 tokeniser.release();
1619 void FreeShaderList(){
1620 while ( l_shaderfiles != 0 )
1622 free( l_shaderfiles->data );
1623 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1627 void ShaderList_addFromArchive( const char *archivename ){
1628 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1629 if ( string_empty( shaderpath ) ) {
1633 StringOutputStream shaderlist( 256 );
1634 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1636 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1638 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1640 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1641 BuildShaderList( file->getInputStream() );
1647 #include "stream/filestream.h"
1649 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1650 StringOutputStream absShaderList( 256 );
1651 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1652 if ( file_exists( absShaderList.c_str() ) ) {
1656 StringOutputStream directory( 256 );
1657 directory << enginePath << gamename << '/' << shaderPath;
1658 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1663 StringOutputStream defaultShaderList( 256 );
1664 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1665 if ( file_exists( defaultShaderList.c_str() ) ) {
1666 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1672 void Shaders_Load(){
1673 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1674 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1677 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1678 if ( !string_empty( shaderPath ) ) {
1679 StringOutputStream path( 256 );
1680 path << DirectoryCleaned( shaderPath );
1682 if ( g_useShaderList ) {
1683 // preload shader files that have been listed in shaderlist.txt
1684 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1685 const char* gamename = GlobalRadiant().getGameName();
1686 const char* enginePath = GlobalRadiant().getEnginePath();
1687 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1689 bool isMod = !string_equal( basegame, gamename );
1691 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1692 gamename = basegame;
1693 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1696 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1697 DumpUnreferencedShaders();
1701 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0);
1704 GSList *lst = l_shaderfiles;
1705 StringOutputStream shadername( 256 );
1708 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1709 LoadShaderFile( shadername.c_str() );
1715 //StringPool_analyse( ShaderPool::instance() );
1718 void Shaders_Free(){
1721 g_shaderFilenames.clear();
1724 ModuleObservers g_observers;
1726 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1727 bool Shaders_realised(){
1728 return g_shaders_unrealised == 0;
1731 void Shaders_Realise(){
1732 if ( --g_shaders_unrealised == 0 ) {
1734 g_observers.realise();
1738 void Shaders_Unrealise(){
1739 if ( ++g_shaders_unrealised == 1 ) {
1740 g_observers.unrealise();
1745 void Shaders_Refresh(){
1746 Shaders_Unrealise();
1750 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1758 Shaders_Unrealise();
1765 IShader* getShaderForName( const char* name ){
1766 return Shader_ForName( name );
1769 void foreachShaderName( const ShaderNameCallback& callback ){
1770 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1772 callback( ( *i ).first.c_str() );
1776 void beginActiveShadersIterator(){
1777 ActiveShaders_IteratorBegin();
1780 bool endActiveShadersIterator(){
1781 return ActiveShaders_IteratorAtEnd();
1784 IShader* dereferenceActiveShadersIterator(){
1785 return ActiveShaders_IteratorCurrent();
1788 void incrementActiveShadersIterator(){
1789 ActiveShaders_IteratorIncrement();
1792 void setActiveShadersChangedNotify( const Callback<void()>& notify ){
1793 g_ActiveShadersChangedNotify = notify;
1796 void attach( ModuleObserver& observer ){
1797 g_observers.attach( observer );
1800 void detach( ModuleObserver& observer ){
1801 g_observers.detach( observer );
1804 void setLightingEnabled( bool enabled ){
1805 if ( CShader::m_lightingEnabled != enabled ) {
1806 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1808 ( *i ).second->unrealiseLighting();
1810 CShader::m_lightingEnabled = enabled;
1811 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1813 ( *i ).second->realiseLighting();
1818 const char* getTexturePrefix() const {
1819 return g_texturePrefix;
1823 Quake3ShaderSystem g_Quake3ShaderSystem;
1825 ShaderSystem& GetShaderSystem(){
1826 return g_Quake3ShaderSystem;
1829 void Shaders_Construct(){
1830 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1833 void Shaders_Destroy(){
1834 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1836 if ( Shaders_realised() ) {