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 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1284 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1285 m_nFlags |= QER_NODRAW;
1287 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1288 m_nFlags |= QER_NONSOLID;
1290 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1291 m_nFlags |= QER_WATER;
1293 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1294 m_nFlags |= QER_LAVA;
1296 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1297 m_nFlags |= QER_AREAPORTAL;
1299 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1300 m_nFlags |= QER_CLIP;
1302 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1303 m_nFlags |= QER_BOTCLIP;
1316 TextureExpression m_texture;
1317 BlendFunc m_blendFunc;
1318 bool m_clampToBorder;
1320 float m_heightmapScale;
1322 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1326 std::list<CopiedString> g_shaderFilenames;
1328 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1329 g_shaderFilenames.push_back( filename );
1330 filename = g_shaderFilenames.back().c_str();
1331 tokeniser.nextLine();
1334 const char* token = tokeniser.getToken();
1340 if ( string_equal( token, "table" ) ) {
1341 if ( tokeniser.getToken() == 0 ) {
1342 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1345 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1350 const char* option = tokeniser.getToken();
1351 if ( string_equal( option, "{" ) ) {
1354 const char* value = tokeniser.getToken();
1355 if ( string_equal( value, "}" ) ) {
1360 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1369 if ( string_equal( token, "guide" ) ) {
1370 parseTemplateInstance( tokeniser, filename );
1374 if ( !string_equal( token, "material" )
1375 && !string_equal( token, "particle" )
1376 && !string_equal( token, "skin" ) ) {
1377 tokeniser.ungetToken();
1379 // first token should be the path + name.. ( from base )
1381 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1383 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1384 shaderTemplate->setName( name.c_str() );
1386 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1388 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1389 ? shaderTemplate->parseQuake3( tokeniser )
1390 : shaderTemplate->parseDoom3( tokeniser );
1392 // do we already have this shader?
1393 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1395 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1401 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1409 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1410 tokeniser.nextLine();
1413 const char* token = tokeniser.getToken();
1419 if ( string_equal( token, "guide" ) ) {
1420 // first token should be the path + name.. ( from base )
1421 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1422 shaderTemplate->parseTemplate( tokeniser );
1423 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1424 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1427 else if ( string_equal( token, "inlineGuide" ) ) {
1428 // skip entire inlineGuide definition
1429 std::size_t depth = 0;
1432 tokeniser.nextLine();
1433 token = tokeniser.getToken();
1434 if ( string_equal( token, "{" ) ) {
1437 else if ( string_equal( token, "}" ) ) {
1438 if ( --depth == 0 ) {
1447 void LoadShaderFile( const char* filename ){
1448 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1451 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1453 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1455 ParseShaderFile( tokeniser, filename );
1457 tokeniser.release();
1462 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1466 void loadGuideFile( const char* filename ){
1467 StringOutputStream fullname( 256 );
1468 fullname << "guides/" << filename;
1469 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1472 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1474 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1476 parseGuideFile( tokeniser, fullname.c_str() );
1478 tokeniser.release();
1483 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1487 CShader* Try_Shader_ForName( const char* name ){
1489 shaders_t::iterator i = g_ActiveShaders.find( name );
1490 if ( i != g_ActiveShaders.end() ) {
1491 return ( *i ).second;
1494 // active shader was not found
1496 // find matching shader definition
1497 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1498 if ( i == g_shaderDefinitions.end() ) {
1499 // shader definition was not found
1501 // create new shader definition from default shader template
1502 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1503 shaderTemplate->CreateDefault( name );
1504 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1506 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1509 // create shader from existing definition
1510 ShaderPointer pShader( new CShader( ( *i ).second ) );
1511 pShader->setName( name );
1512 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1513 g_ActiveShadersChangedNotify();
1517 IShader *Shader_ForName( const char *name ){
1518 ASSERT_NOTNULL( name );
1520 IShader *pShader = Try_Shader_ForName( name );
1526 // the list of scripts/*.shader files we need to work with
1527 // those are listed in shaderlist file
1528 GSList *l_shaderfiles = 0;
1530 GSList* Shaders_getShaderFileList(){
1531 return l_shaderfiles;
1536 DumpUnreferencedShaders
1537 usefull function: dumps the list of .shader files that are not referenced to the console
1540 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1541 bool listed = false;
1543 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1545 if ( !strcmp( (char*)sh->data, filename ) ) {
1554 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1556 globalOutputStream() << "\t" << filename << "\n";
1560 typedef ReferenceCaller<bool, void(const char*), IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1562 void DumpUnreferencedShaders(){
1563 bool bFound = false;
1564 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1567 void ShaderList_addShaderFile( const char* dirstring ){
1570 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1572 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1574 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1580 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1587 build a CStringList of shader names
1590 void BuildShaderList( TextInputStream& shaderlist ){
1591 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1592 tokeniser.nextLine();
1593 const char* token = tokeniser.getToken();
1594 StringOutputStream shaderFile( 64 );
1595 while ( token != 0 )
1597 // each token should be a shader filename
1598 shaderFile << token << "." << g_shadersExtension;
1600 ShaderList_addShaderFile( shaderFile.c_str() );
1602 tokeniser.nextLine();
1603 token = tokeniser.getToken();
1607 tokeniser.release();
1610 void FreeShaderList(){
1611 while ( l_shaderfiles != 0 )
1613 free( l_shaderfiles->data );
1614 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1618 void ShaderList_addFromArchive( const char *archivename ){
1619 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1620 if ( string_empty( shaderpath ) ) {
1624 StringOutputStream shaderlist( 256 );
1625 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1627 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1629 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1631 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1632 BuildShaderList( file->getInputStream() );
1638 #include "stream/filestream.h"
1640 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1641 StringOutputStream absShaderList( 256 );
1642 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1643 if ( file_exists( absShaderList.c_str() ) ) {
1647 StringOutputStream directory( 256 );
1648 directory << enginePath << gamename << '/' << shaderPath;
1649 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1654 StringOutputStream defaultShaderList( 256 );
1655 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1656 if ( file_exists( defaultShaderList.c_str() ) ) {
1657 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1663 void Shaders_Load(){
1664 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1665 GlobalFileSystem().forEachFile("guides/", "guide", makeCallbackF(loadGuideFile), 0);
1668 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1669 if ( !string_empty( shaderPath ) ) {
1670 StringOutputStream path( 256 );
1671 path << DirectoryCleaned( shaderPath );
1673 if ( g_useShaderList ) {
1674 // preload shader files that have been listed in shaderlist.txt
1675 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1676 const char* gamename = GlobalRadiant().getGameName();
1677 const char* enginePath = GlobalRadiant().getEnginePath();
1678 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1680 bool isMod = !string_equal( basegame, gamename );
1682 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1683 gamename = basegame;
1684 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1687 GlobalFileSystem().forEachArchive(makeCallbackF(ShaderList_addFromArchive), false, true);
1688 DumpUnreferencedShaders();
1692 GlobalFileSystem().forEachFile(path.c_str(), g_shadersExtension, makeCallbackF(ShaderList_addShaderFile), 0);
1695 GSList *lst = l_shaderfiles;
1696 StringOutputStream shadername( 256 );
1699 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1700 LoadShaderFile( shadername.c_str() );
1706 //StringPool_analyse( ShaderPool::instance() );
1709 void Shaders_Free(){
1712 g_shaderFilenames.clear();
1715 ModuleObservers g_observers;
1717 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1718 bool Shaders_realised(){
1719 return g_shaders_unrealised == 0;
1722 void Shaders_Realise(){
1723 if ( --g_shaders_unrealised == 0 ) {
1725 g_observers.realise();
1729 void Shaders_Unrealise(){
1730 if ( ++g_shaders_unrealised == 1 ) {
1731 g_observers.unrealise();
1736 void Shaders_Refresh(){
1737 Shaders_Unrealise();
1741 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1749 Shaders_Unrealise();
1756 IShader* getShaderForName( const char* name ){
1757 return Shader_ForName( name );
1760 void foreachShaderName( const ShaderNameCallback& callback ){
1761 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1763 callback( ( *i ).first.c_str() );
1767 void beginActiveShadersIterator(){
1768 ActiveShaders_IteratorBegin();
1771 bool endActiveShadersIterator(){
1772 return ActiveShaders_IteratorAtEnd();
1775 IShader* dereferenceActiveShadersIterator(){
1776 return ActiveShaders_IteratorCurrent();
1779 void incrementActiveShadersIterator(){
1780 ActiveShaders_IteratorIncrement();
1783 void setActiveShadersChangedNotify( const Callback<void()>& notify ){
1784 g_ActiveShadersChangedNotify = notify;
1787 void attach( ModuleObserver& observer ){
1788 g_observers.attach( observer );
1791 void detach( ModuleObserver& observer ){
1792 g_observers.detach( observer );
1795 void setLightingEnabled( bool enabled ){
1796 if ( CShader::m_lightingEnabled != enabled ) {
1797 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1799 ( *i ).second->unrealiseLighting();
1801 CShader::m_lightingEnabled = enabled;
1802 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1804 ( *i ).second->realiseLighting();
1809 const char* getTexturePrefix() const {
1810 return g_texturePrefix;
1814 Quake3ShaderSystem g_Quake3ShaderSystem;
1816 ShaderSystem& GetShaderSystem(){
1817 return g_Quake3ShaderSystem;
1820 void Shaders_Construct(){
1821 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1824 void Shaders_Destroy(){
1825 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1827 if ( Shaders_realised() ) {