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();
1116 qtexture_t* lightFalloffImage() const {
1117 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1118 return m_pLightFalloffImage;
1124 bool CShader::m_lightingEnabled = false;
1126 typedef SmartPointer<CShader> ShaderPointer;
1127 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1129 shaders_t g_ActiveShaders;
1131 static shaders_t::iterator g_ActiveShadersIterator;
1133 void ActiveShaders_IteratorBegin(){
1134 g_ActiveShadersIterator = g_ActiveShaders.begin();
1137 bool ActiveShaders_IteratorAtEnd(){
1138 return g_ActiveShadersIterator == g_ActiveShaders.end();
1141 IShader *ActiveShaders_IteratorCurrent(){
1142 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1145 void ActiveShaders_IteratorIncrement(){
1146 ++g_ActiveShadersIterator;
1149 void debug_check_shaders( shaders_t& shaders ){
1150 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1152 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1156 // will free all GL binded qtextures and shaders
1157 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1160 // empty the actives shaders list
1161 debug_check_shaders( g_ActiveShaders );
1162 g_ActiveShaders.clear();
1164 g_shaderTemplates.clear();
1165 g_shaderDefinitions.clear();
1166 g_ActiveShadersChangedNotify();
1169 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1170 // name of the qtexture_t we'll use to represent this shader ( this one has the "textures\" before )
1171 m_textureName = m_Name.c_str();
1173 tokeniser.nextLine();
1175 // we need to read until we hit a balanced }
1179 tokeniser.nextLine();
1180 const char* token = tokeniser.getToken();
1186 if ( string_equal( token, "{" ) ) {
1190 else if ( string_equal( token, "}" ) ) {
1192 if ( depth < 0 ) { // underflow
1195 if ( depth == 0 ) { // end of shader
1203 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1204 m_nFlags |= QER_NOCARVE;
1206 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1207 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1208 m_nFlags |= QER_TRANS;
1210 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1211 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1213 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1214 const char* alphafunc = tokeniser.getToken();
1216 if ( alphafunc == 0 ) {
1217 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1221 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1222 m_AlphaFunc = IShader::eEqual;
1224 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1225 m_AlphaFunc = IShader::eGreater;
1227 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1228 m_AlphaFunc = IShader::eLess;
1230 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1231 m_AlphaFunc = IShader::eGEqual;
1233 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1234 m_AlphaFunc = IShader::eLEqual;
1238 m_AlphaFunc = IShader::eAlways;
1241 m_nFlags |= QER_ALPHATEST;
1243 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1245 else if ( string_equal_nocase( token, "cull" ) ) {
1246 const char* cull = tokeniser.getToken();
1249 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1253 if ( string_equal_nocase( cull, "none" )
1254 || string_equal_nocase( cull, "twosided" )
1255 || string_equal_nocase( cull, "disable" ) ) {
1256 m_Cull = IShader::eCullNone;
1258 else if ( string_equal_nocase( cull, "back" )
1259 || string_equal_nocase( cull, "backside" )
1260 || string_equal_nocase( cull, "backsided" ) ) {
1261 m_Cull = IShader::eCullBack;
1265 m_Cull = IShader::eCullBack;
1268 m_nFlags |= QER_CULL;
1270 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1271 const char* surfaceparm = tokeniser.getToken();
1273 if ( surfaceparm == 0 ) {
1274 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1278 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1279 m_nFlags |= QER_FOG;
1280 m_nFlags |= QER_TRANS;
1281 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1285 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1286 m_nFlags |= QER_NODRAW;
1288 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1289 m_nFlags |= QER_NONSOLID;
1291 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1292 m_nFlags |= QER_WATER;
1294 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1295 m_nFlags |= QER_LAVA;
1297 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1298 m_nFlags |= QER_AREAPORTAL;
1300 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1301 m_nFlags |= QER_CLIP;
1303 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1304 m_nFlags |= QER_BOTCLIP;
1317 TextureExpression m_texture;
1318 BlendFunc m_blendFunc;
1319 bool m_clampToBorder;
1321 float m_heightmapScale;
1323 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1327 std::list<CopiedString> g_shaderFilenames;
1329 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1330 g_shaderFilenames.push_back( filename );
1331 filename = g_shaderFilenames.back().c_str();
1332 tokeniser.nextLine();
1335 const char* token = tokeniser.getToken();
1341 if ( string_equal( token, "table" ) ) {
1342 if ( tokeniser.getToken() == 0 ) {
1343 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1346 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1351 const char* option = tokeniser.getToken();
1352 if ( string_equal( option, "{" ) ) {
1355 const char* value = tokeniser.getToken();
1356 if ( string_equal( value, "}" ) ) {
1361 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1370 if ( string_equal( token, "guide" ) ) {
1371 parseTemplateInstance( tokeniser, filename );
1375 if ( !string_equal( token, "material" )
1376 && !string_equal( token, "particle" )
1377 && !string_equal( token, "skin" ) ) {
1378 tokeniser.ungetToken();
1380 // first token should be the path + name.. ( from base )
1382 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1384 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1385 shaderTemplate->setName( name.c_str() );
1387 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1389 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1390 ? shaderTemplate->parseQuake3( tokeniser )
1391 : shaderTemplate->parseDoom3( tokeniser );
1393 // do we already have this shader?
1394 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1396 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1402 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1410 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1411 tokeniser.nextLine();
1414 const char* token = tokeniser.getToken();
1420 if ( string_equal( token, "guide" ) ) {
1421 // first token should be the path + name.. ( from base )
1422 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1423 shaderTemplate->parseTemplate( tokeniser );
1424 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1425 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1428 else if ( string_equal( token, "inlineGuide" ) ) {
1429 // skip entire inlineGuide definition
1430 std::size_t depth = 0;
1433 tokeniser.nextLine();
1434 token = tokeniser.getToken();
1435 if ( string_equal( token, "{" ) ) {
1438 else if ( string_equal( token, "}" ) ) {
1439 if ( --depth == 0 ) {
1448 void LoadShaderFile( const char* filename ){
1449 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1452 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1454 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1456 ParseShaderFile( tokeniser, filename );
1458 tokeniser.release();
1463 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1467 void loadGuideFile( const char* filename ){
1468 StringOutputStream fullname( 256 );
1469 fullname << "guides/" << filename;
1470 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1473 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1475 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1477 parseGuideFile( tokeniser, fullname.c_str() );
1479 tokeniser.release();
1484 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1488 CShader* Try_Shader_ForName( const char* name ){
1490 shaders_t::iterator i = g_ActiveShaders.find( name );
1491 if ( i != g_ActiveShaders.end() ) {
1492 return ( *i ).second;
1495 // active shader was not found
1497 // find matching shader definition
1498 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1499 if ( i == g_shaderDefinitions.end() ) {
1500 // shader definition was not found
1502 // create new shader definition from default shader template
1503 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1504 shaderTemplate->CreateDefault( name );
1505 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1507 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1510 // create shader from existing definition
1511 ShaderPointer pShader( new CShader( ( *i ).second ) );
1512 pShader->setName( name );
1513 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1514 g_ActiveShadersChangedNotify();
1518 IShader *Shader_ForName( const char *name ){
1519 ASSERT_NOTNULL( name );
1521 IShader *pShader = Try_Shader_ForName( name );
1527 // the list of scripts/*.shader files we need to work with
1528 // those are listed in shaderlist file
1529 GSList *l_shaderfiles = 0;
1531 GSList* Shaders_getShaderFileList(){
1532 return l_shaderfiles;
1537 DumpUnreferencedShaders
1538 usefull function: dumps the list of .shader files that are not referenced to the console
1541 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1542 bool listed = false;
1544 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1546 if ( !strcmp( (char*)sh->data, filename ) ) {
1555 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1557 globalOutputStream() << "\t" << filename << "\n";
1561 typedef ReferenceCaller<bool, void(const char*), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1563 void DumpUnreferencedShaders(){
1564 bool bFound = false;
1565 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1568 void ShaderList_addShaderFile( const char* dirstring ){
1571 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1573 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1575 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1581 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1588 build a CStringList of shader names
1591 void BuildShaderList( TextInputStream& shaderlist ){
1592 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1593 tokeniser.nextLine();
1594 const char* token = tokeniser.getToken();
1595 StringOutputStream shaderFile( 64 );
1596 while ( token != 0 )
1598 // each token should be a shader filename
1599 shaderFile << token << "." << g_shadersExtension;
1601 ShaderList_addShaderFile( shaderFile.c_str() );
1603 tokeniser.nextLine();
1604 token = tokeniser.getToken();
1608 tokeniser.release();
1611 void FreeShaderList(){
1612 while ( l_shaderfiles != 0 )
1614 free( l_shaderfiles->data );
1615 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1619 void ShaderList_addFromArchive( const char *archivename ){
1620 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1621 if ( string_empty( shaderpath ) ) {
1625 StringOutputStream shaderlist( 256 );
1626 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1628 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1630 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1632 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1633 BuildShaderList( file->getInputStream() );
1639 #include "stream/filestream.h"
1641 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1642 StringOutputStream absShaderList( 256 );
1643 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1644 if ( file_exists( absShaderList.c_str() ) ) {
1648 StringOutputStream directory( 256 );
1649 directory << enginePath << gamename << '/' << shaderPath;
1650 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1655 StringOutputStream defaultShaderList( 256 );
1656 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1657 if ( file_exists( defaultShaderList.c_str() ) ) {
1658 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1664 void Shaders_Load(){
1665 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1666 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1669 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1670 if ( !string_empty( shaderPath ) ) {
1671 StringOutputStream path( 256 );
1672 path << DirectoryCleaned( shaderPath );
1674 if ( g_useShaderList ) {
1675 // preload shader files that have been listed in shaderlist.txt
1676 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1677 const char* gamename = GlobalRadiant().getGameName();
1678 const char* enginePath = GlobalRadiant().getEnginePath();
1679 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1681 bool isMod = !string_equal( basegame, gamename );
1683 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1684 gamename = basegame;
1685 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1688 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1689 DumpUnreferencedShaders();
1693 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0);
1696 GSList *lst = l_shaderfiles;
1697 StringOutputStream shadername( 256 );
1700 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1701 LoadShaderFile( shadername.c_str() );
1707 //StringPool_analyse( ShaderPool::instance() );
1710 void Shaders_Free(){
1713 g_shaderFilenames.clear();
1716 ModuleObservers g_observers;
1718 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1719 bool Shaders_realised(){
1720 return g_shaders_unrealised == 0;
1723 void Shaders_Realise(){
1724 if ( --g_shaders_unrealised == 0 ) {
1726 g_observers.realise();
1730 void Shaders_Unrealise(){
1731 if ( ++g_shaders_unrealised == 1 ) {
1732 g_observers.unrealise();
1737 void Shaders_Refresh(){
1738 Shaders_Unrealise();
1742 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1750 Shaders_Unrealise();
1757 IShader* getShaderForName( const char* name ){
1758 return Shader_ForName( name );
1761 void foreachShaderName( const ShaderNameCallback& callback ){
1762 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1764 callback( ( *i ).first.c_str() );
1768 void beginActiveShadersIterator(){
1769 ActiveShaders_IteratorBegin();
1772 bool endActiveShadersIterator(){
1773 return ActiveShaders_IteratorAtEnd();
1776 IShader* dereferenceActiveShadersIterator(){
1777 return ActiveShaders_IteratorCurrent();
1780 void incrementActiveShadersIterator(){
1781 ActiveShaders_IteratorIncrement();
1784 void setActiveShadersChangedNotify( const Callback<void()>& notify ){
1785 g_ActiveShadersChangedNotify = notify;
1788 void attach( ModuleObserver& observer ){
1789 g_observers.attach( observer );
1792 void detach( ModuleObserver& observer ){
1793 g_observers.detach( observer );
1796 void setLightingEnabled( bool enabled ){
1797 if ( CShader::m_lightingEnabled != enabled ) {
1798 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1800 ( *i ).second->unrealiseLighting();
1802 CShader::m_lightingEnabled = enabled;
1803 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1805 ( *i ).second->realiseLighting();
1810 const char* getTexturePrefix() const {
1811 return g_texturePrefix;
1815 Quake3ShaderSystem g_Quake3ShaderSystem;
1817 ShaderSystem& GetShaderSystem(){
1818 return g_Quake3ShaderSystem;
1821 void Shaders_Construct(){
1822 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1825 void Shaders_Destroy(){
1826 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1828 if ( Shaders_realised() ) {