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)
38 #include "globaldefs.h"
45 #include "ifilesystem.h"
47 #include "iscriplib.h"
48 #include "itextures.h"
49 #include "qerplugin.h"
54 #include "debugging/debugging.h"
55 #include "string/pooledstring.h"
56 #include "math/vector.h"
57 #include "generic/callback.h"
58 #include "generic/referencecounted.h"
59 #include "stream/memstream.h"
60 #include "stream/stringstream.h"
61 #include "stream/textfilestream.h"
66 #include "shaderlib.h"
67 #include "texturelib.h"
69 #include "moduleobservers.h"
70 #include "archivelib.h"
73 const char* g_shadersExtension = "";
74 const char* g_shadersDirectory = "";
75 bool g_enableDefaultShaders = true;
76 ShaderLanguage g_shaderLanguage = SHADERLANGUAGE_QUAKE3;
77 bool g_useShaderList = true;
78 _QERPlugImageTable* g_bitmapModule = 0;
79 const char* g_texturePrefix = "textures/";
81 void ActiveShaders_IteratorBegin();
82 bool ActiveShaders_IteratorAtEnd();
83 IShader *ActiveShaders_IteratorCurrent();
84 void ActiveShaders_IteratorIncrement();
85 Callback g_ActiveShadersChangedNotify;
88 void LoadShaderFile( const char *filename );
89 qtexture_t *Texture_ForName( const char *filename );
93 NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX:
94 SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this
95 SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it
96 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
99 Image* loadBitmap( void* environment, const char* name ){
100 DirectoryArchiveFile file( name, name );
101 if ( !file.failed() ) {
102 return g_bitmapModule->loadImage( file );
107 inline byte* getPixel( byte* pixels, int width, int height, int x, int y ){
108 return pixels + ( ( ( ( ( y + height ) % height ) * width ) + ( ( x + width ) % width ) ) * 4 );
118 Image& convertHeightmapToNormalmap( Image& heightmap, float scale ){
119 int w = heightmap.getWidth();
120 int h = heightmap.getHeight();
122 Image& normalmap = *( new RGBAImage( heightmap.getWidth(), heightmap.getHeight() ) );
124 byte* in = heightmap.getRGBAPixels();
125 byte* out = normalmap.getRGBAPixels();
129 const int kernelSize = 2;
130 KernelElement kernel_du[kernelSize] = {
134 KernelElement kernel_dv[kernelSize] = {
140 const int kernelSize = 6;
141 KernelElement kernel_du[kernelSize] = {
149 KernelElement kernel_dv[kernelSize] = {
166 for ( KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i )
168 du += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
171 for ( KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i )
173 dv += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
176 float nx = -du * scale;
177 float ny = -dv * scale;
181 float norm = 1.0 / sqrt( nx * nx + ny * ny + nz * nz );
182 out[0] = float_to_integer( ( ( nx * norm ) + 1 ) * 127.5 );
183 out[1] = float_to_integer( ( ( ny * norm ) + 1 ) * 127.5 );
184 out[2] = float_to_integer( ( ( nz * norm ) + 1 ) * 127.5 );
197 Image* loadHeightmap( void* environment, const char* name ){
198 Image* heightmap = GlobalTexturesCache().loadImage( name );
199 if ( heightmap != 0 ) {
200 Image& normalmap = convertHeightmapToNormalmap( *heightmap, *reinterpret_cast<float*>( environment ) );
201 heightmap->release();
208 Image* loadSpecial( void* environment, const char* name ){
209 if ( *name == '_' ) { // special image
210 StringOutputStream bitmapName( 256 );
211 bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".png";
212 Image* image = loadBitmap( environment, bitmapName.c_str() );
217 return GlobalTexturesCache().loadImage( name );
220 class ShaderPoolContext
223 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
224 typedef PooledString<ShaderPool> ShaderString;
225 typedef ShaderString ShaderVariable;
226 typedef ShaderString ShaderValue;
227 typedef CopiedString TextureExpression;
229 // clean a texture name to the qtexture_t name format we use internally
230 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
231 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
232 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
233 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
234 template<typename StringType>
235 void parseTextureName( StringType& name, const char* token ){
236 StringOutputStream cleaned( 256 );
237 cleaned << PathCleaned( token );
238 name = CopiedString( StringRange( cleaned.c_str(), path_get_filename_base_end( cleaned.c_str() ) ) ).c_str(); // remove extension
241 bool Tokeniser_parseTextureName( Tokeniser& tokeniser, TextureExpression& name ){
242 const char* token = tokeniser.getToken();
244 Tokeniser_unexpectedError( tokeniser, token, "#texture-name" );
247 parseTextureName( name, token );
251 bool Tokeniser_parseShaderName( Tokeniser& tokeniser, CopiedString& name ){
252 const char* token = tokeniser.getToken();
254 Tokeniser_unexpectedError( tokeniser, token, "#shader-name" );
257 parseTextureName( name, token );
261 bool Tokeniser_parseString( Tokeniser& tokeniser, ShaderString& string ){
262 const char* token = tokeniser.getToken();
264 Tokeniser_unexpectedError( tokeniser, token, "#string" );
273 typedef std::list<ShaderVariable> ShaderParameters;
274 typedef std::list<ShaderVariable> ShaderArguments;
276 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
280 std::size_t m_refcount;
284 ShaderParameters m_params;
286 TextureExpression m_textureName;
287 TextureExpression m_diffuse;
288 TextureExpression m_bump;
289 ShaderValue m_heightmapScale;
290 TextureExpression m_specular;
291 TextureExpression m_lightFalloffImage;
297 IShader::EAlphaFunc m_AlphaFunc;
300 IShader::ECull m_Cull;
312 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
313 if ( --m_refcount == 0 ) {
318 std::size_t refcount(){
322 const char* getName() const {
323 return m_Name.c_str();
325 void setName( const char* name ){
329 // -----------------------------------------
331 bool parseDoom3( Tokeniser& tokeniser );
332 bool parseQuake3( Tokeniser& tokeniser );
333 bool parseTemplate( Tokeniser& tokeniser );
336 void CreateDefault( const char *name ){
337 if ( g_enableDefaultShaders ) {
338 m_textureName = name;
348 class MapLayerTemplate
350 TextureExpression m_texture;
351 BlendFuncExpression m_blendFunc;
352 bool m_clampToBorder;
353 ShaderValue m_alphaTest;
355 MapLayerTemplate( const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest ) :
356 m_texture( texture ),
357 m_blendFunc( blendFunc ),
358 m_clampToBorder( false ),
359 m_alphaTest( alphaTest ){
361 const TextureExpression& texture() const {
364 const BlendFuncExpression& blendFunc() const {
367 bool clampToBorder() const {
368 return m_clampToBorder;
370 const ShaderValue& alphaTest() const {
374 typedef std::vector<MapLayerTemplate> MapLayers;
379 bool Doom3Shader_parseHeightmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
380 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
381 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
382 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
383 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, heightmapScale ) );
384 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
388 bool Doom3Shader_parseAddnormals( Tokeniser& tokeniser, TextureExpression& bump ){
389 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
390 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
391 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
392 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "heightmap" ) );
393 TextureExpression heightmapName;
394 ShaderValue heightmapScale;
395 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, heightmapName, heightmapScale ) );
396 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
400 bool Doom3Shader_parseBumpmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
401 const char* token = tokeniser.getToken();
403 Tokeniser_unexpectedError( tokeniser, token, "#bumpmap" );
406 if ( string_equal( token, "heightmap" ) ) {
407 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, bump, heightmapScale ) );
409 else if ( string_equal( token, "addnormals" ) ) {
410 RETURN_FALSE_IF_FAIL( Doom3Shader_parseAddnormals( tokeniser, bump ) );
414 parseTextureName( bump, token );
432 TextureExpression m_texture;
433 BlendFuncExpression m_blendFunc;
434 bool m_clampToBorder;
435 ShaderValue m_alphaTest;
436 ShaderValue m_heightmapScale;
438 LayerTemplate() : m_type( LAYER_NONE ), m_blendFunc( "GL_ONE", "GL_ZERO" ), m_clampToBorder( false ), m_alphaTest( "-1" ), m_heightmapScale( "0" ){
442 bool parseShaderParameters( Tokeniser& tokeniser, ShaderParameters& params ){
443 Tokeniser_parseToken( tokeniser, "(" );
446 const char* param = tokeniser.getToken();
447 if ( string_equal( param, ")" ) ) {
450 params.push_back( param );
451 const char* comma = tokeniser.getToken();
452 if ( string_equal( comma, ")" ) ) {
455 if ( !string_equal( comma, "," ) ) {
456 Tokeniser_unexpectedError( tokeniser, comma, "," );
463 bool ShaderTemplate::parseTemplate( Tokeniser& tokeniser ){
464 m_Name = tokeniser.getToken();
465 if ( !parseShaderParameters( tokeniser, m_params ) ) {
466 globalErrorStream() << "shader template: " << makeQuoted( m_Name.c_str() ) << ": parameter parse failed\n";
470 return parseDoom3( tokeniser );
473 bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){
474 LayerTemplate currentLayer;
477 // we need to read until we hit a balanced }
481 tokeniser.nextLine();
482 const char* token = tokeniser.getToken();
488 if ( string_equal( token, "{" ) ) {
492 else if ( string_equal( token, "}" ) ) {
494 if ( depth < 0 ) { // error
497 if ( depth == 0 ) { // end of shader
500 if ( depth == 1 ) { // end of layer
501 if ( currentLayer.m_type == LAYER_DIFFUSEMAP ) {
502 m_diffuse = currentLayer.m_texture;
504 else if ( currentLayer.m_type == LAYER_BUMPMAP ) {
505 m_bump = currentLayer.m_texture;
507 else if ( currentLayer.m_type == LAYER_SPECULARMAP ) {
508 m_specular = currentLayer.m_texture;
510 else if ( !string_empty( currentLayer.m_texture.c_str() ) ) {
511 m_layers.push_back( MapLayerTemplate(
512 currentLayer.m_texture.c_str(),
513 currentLayer.m_blendFunc,
514 currentLayer.m_clampToBorder,
515 currentLayer.m_alphaTest
518 currentLayer.m_type = LAYER_NONE;
519 currentLayer.m_texture = "";
524 if ( depth == 2 ) { // in layer
525 if ( string_equal_nocase( token, "blend" ) ) {
526 const char* blend = tokeniser.getToken();
529 Tokeniser_unexpectedError( tokeniser, blend, "#blend" );
533 if ( string_equal_nocase( blend, "diffusemap" ) ) {
534 currentLayer.m_type = LAYER_DIFFUSEMAP;
536 else if ( string_equal_nocase( blend, "bumpmap" ) ) {
537 currentLayer.m_type = LAYER_BUMPMAP;
539 else if ( string_equal_nocase( blend, "specularmap" ) ) {
540 currentLayer.m_type = LAYER_SPECULARMAP;
544 currentLayer.m_blendFunc.first = blend;
546 const char* comma = tokeniser.getToken();
549 Tokeniser_unexpectedError( tokeniser, comma, "#comma" );
553 if ( string_equal( comma, "," ) ) {
554 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, currentLayer.m_blendFunc.second ) );
558 currentLayer.m_blendFunc.second = "";
559 tokeniser.ungetToken();
563 else if ( string_equal_nocase( token, "map" ) ) {
564 if ( currentLayer.m_type == LAYER_BUMPMAP ) {
565 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale ) );
569 const char* map = tokeniser.getToken();
572 Tokeniser_unexpectedError( tokeniser, map, "#map" );
576 if ( string_equal( map, "makealpha" ) ) {
577 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
578 const char* texture = tokeniser.getToken();
579 if ( texture == 0 ) {
580 Tokeniser_unexpectedError( tokeniser, texture, "#texture" );
583 currentLayer.m_texture = texture;
584 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
588 parseTextureName( currentLayer.m_texture, map );
592 else if ( string_equal_nocase( token, "zeroclamp" ) ) {
593 currentLayer.m_clampToBorder = true;
596 else if ( string_equal_nocase( token, "alphaTest" ) ) {
597 Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest );
601 else if ( depth == 1 ) {
602 if ( string_equal_nocase( token, "qer_editorimage" ) ) {
603 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
605 else if ( string_equal_nocase( token, "qer_trans" ) ) {
606 m_fTrans = string_read_float( tokeniser.getToken() );
607 m_nFlags |= QER_TRANS;
609 else if ( string_equal_nocase( token, "translucent" ) ) {
611 m_nFlags |= QER_TRANS;
613 else if ( string_equal( token, "DECAL_MACRO" ) ) {
615 m_nFlags |= QER_TRANS;
617 else if ( string_equal_nocase( token, "bumpmap" ) ) {
618 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) );
620 else if ( string_equal_nocase( token, "diffusemap" ) ) {
621 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) );
623 else if ( string_equal_nocase( token, "specularmap" ) ) {
624 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) );
626 else if ( string_equal_nocase( token, "twosided" ) ) {
627 m_Cull = IShader::eCullNone;
628 m_nFlags |= QER_CULL;
630 else if ( string_equal_nocase( token, "nodraw" ) ) {
631 m_nFlags |= QER_NODRAW;
633 else if ( string_equal_nocase( token, "nonsolid" ) ) {
634 m_nFlags |= QER_NONSOLID;
636 else if ( string_equal_nocase( token, "liquid" ) ) {
637 m_nFlags |= QER_WATER;
639 else if ( string_equal_nocase( token, "areaportal" ) ) {
640 m_nFlags |= QER_AREAPORTAL;
642 else if ( string_equal_nocase( token, "playerclip" )
643 || string_equal_nocase( token, "monsterclip" )
644 || string_equal_nocase( token, "ikclip" )
645 || string_equal_nocase( token, "moveableclip" ) ) {
646 m_nFlags |= QER_CLIP;
648 if ( string_equal_nocase( token, "fogLight" ) ) {
651 else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) {
652 const char* lightFalloffImage = tokeniser.getToken();
653 if ( lightFalloffImage == 0 ) {
654 Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" );
657 if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) {
658 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
659 TextureExpression name;
660 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) );
661 m_lightFalloffImage = name;
662 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
666 m_lightFalloffImage = lightFalloffImage;
672 if ( string_empty( m_textureName.c_str() ) ) {
673 m_textureName = m_diffuse;
679 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
680 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
682 ShaderTemplateMap g_shaders;
683 ShaderTemplateMap g_shaderTemplates;
685 ShaderTemplate* findTemplate( const char* name ){
686 ShaderTemplateMap::iterator i = g_shaderTemplates.find( name );
687 if ( i != g_shaderTemplates.end() ) {
688 return ( *i ).second.get();
693 class ShaderDefinition
696 ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename )
697 : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){
699 ShaderTemplate* shaderTemplate;
700 ShaderArguments args;
701 const char* filename;
704 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
706 ShaderDefinitionMap g_shaderDefinitions;
708 bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){
710 RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) );
711 const char* templateName = tokeniser.getToken();
712 ShaderTemplate* shaderTemplate = findTemplate( templateName );
713 if ( shaderTemplate == 0 ) {
714 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n";
717 ShaderArguments args;
718 if ( !parseShaderParameters( tokeniser, args ) ) {
719 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n";
723 if ( shaderTemplate != 0 ) {
724 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) {
725 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n";
732 const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){
733 ShaderArguments::const_iterator j = args.begin();
734 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
736 const char* other = ( *i ).c_str();
737 if ( string_equal( value, other ) ) {
738 return ( *j ).c_str();
744 ///\todo BlendFunc parsing
745 BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){
746 return BlendFunc( BLEND_ONE, BLEND_ZERO );
749 qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){
750 StringOutputStream result( 64 );
751 const char* expression = texture.c_str();
752 const char* end = expression + string_length( expression );
753 if ( !string_empty( expression ) ) {
756 const char* best = end;
757 const char* bestParam = 0;
758 const char* bestArg = 0;
759 ShaderArguments::const_iterator j = args.begin();
760 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
762 const char* found = strstr( expression, ( *i ).c_str() );
763 if ( found != 0 && found < best ) {
765 bestParam = ( *i ).c_str();
766 bestArg = ( *j ).c_str();
770 result << StringRange( expression, best );
771 result << PathCleaned( bestArg );
772 expression = best + string_length( bestParam );
779 result << expression;
781 return GlobalTexturesCache().capture( loader, result.c_str() );
784 float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
785 const char* result = evaluateShaderValue( value.c_str(), params, args );
787 if ( !string_parse_float( result, f ) ) {
788 globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n";
793 BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
794 const char* result = evaluateShaderValue( value.c_str(), params, args );
796 if ( string_equal_nocase( result, "gl_zero" ) ) {
799 if ( string_equal_nocase( result, "gl_one" ) ) {
802 if ( string_equal_nocase( result, "gl_src_color" ) ) {
803 return BLEND_SRC_COLOUR;
805 if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) {
806 return BLEND_ONE_MINUS_SRC_COLOUR;
808 if ( string_equal_nocase( result, "gl_src_alpha" ) ) {
809 return BLEND_SRC_ALPHA;
811 if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) {
812 return BLEND_ONE_MINUS_SRC_ALPHA;
814 if ( string_equal_nocase( result, "gl_dst_color" ) ) {
815 return BLEND_DST_COLOUR;
817 if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) {
818 return BLEND_ONE_MINUS_DST_COLOUR;
820 if ( string_equal_nocase( result, "gl_dst_alpha" ) ) {
821 return BLEND_DST_ALPHA;
823 if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) {
824 return BLEND_ONE_MINUS_DST_ALPHA;
826 if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) {
827 return BLEND_SRC_ALPHA_SATURATE;
830 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n";
834 class CShader : public IShader
836 std::size_t m_refcount;
838 const ShaderTemplate& m_template;
839 const ShaderArguments& m_args;
840 const char* m_filename;
841 // name is shader-name, otherwise texture-name (if not a real shader)
844 qtexture_t* m_pTexture;
845 qtexture_t* m_notfound;
846 qtexture_t* m_pDiffuse;
847 float m_heightmapScale;
849 qtexture_t* m_pSpecular;
850 qtexture_t* m_pLightFalloffImage;
851 BlendFunc m_blendFunc;
857 static bool m_lightingEnabled;
859 CShader( const ShaderDefinition& definition ) :
861 m_template( *definition.shaderTemplate ),
862 m_args( definition.args ),
863 m_filename( definition.filename ),
864 m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
878 ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" );
881 // IShaders implementation -----------------
886 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
887 if ( --m_refcount == 0 ) {
892 std::size_t refcount(){
896 // get/set the qtexture_t* Radiant uses to represent this shader object
897 qtexture_t* getTexture() const {
900 qtexture_t* getDiffuse() const {
903 qtexture_t* getBump() const {
906 qtexture_t* getSpecular() const {
910 const char* getName() const {
911 return m_Name.c_str();
913 bool IsInUse() const {
916 void SetInUse( bool bInUse ){
918 g_ActiveShadersChangedNotify();
920 // get the shader flags
921 int getFlags() const {
922 return m_template.m_nFlags;
924 // get the transparency value
925 float getTrans() const {
926 return m_template.m_fTrans;
928 // test if it's a true shader, or a default shader created to wrap around a texture
929 bool IsDefault() const {
930 return string_empty( m_filename );
933 void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
934 BlendFunc getBlendFunc() const {
939 return m_template.m_Cull;
941 // get shader file name (ie the file where this one is defined)
942 const char* getShaderFileName() const {
945 // -----------------------------------------
948 m_pTexture = evaluateTexture( m_template.m_textureName, m_template.m_params, m_args );
950 if ( m_pTexture->texture_number == 0 ) {
951 m_notfound = m_pTexture;
954 StringOutputStream name( 256 );
955 name << GlobalRadiant().getAppPath() << "bitmaps/" << ( IsDefault() ? "notex.png" : "shadernotex.png" );
956 m_pTexture = GlobalTexturesCache().capture( LoadImageCallback( 0, loadBitmap ), name.c_str() );
964 GlobalTexturesCache().release( m_pTexture );
966 if ( m_notfound != 0 ) {
967 GlobalTexturesCache().release( m_notfound );
973 void realiseLighting(){
974 if ( m_lightingEnabled ) {
975 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
976 if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) {
977 m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args );
978 loader = LoadImageCallback( &m_heightmapScale, loadHeightmap );
980 m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args );
981 m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader );
982 m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args );
983 m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args );
985 for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i )
987 m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) );
990 if ( m_layers.size() == 1 ) {
991 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
992 if ( !string_empty( blendFunc.second.c_str() ) ) {
993 m_blendFunc = BlendFunc(
994 evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ),
995 evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args )
1000 const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args );
1002 if ( string_equal_nocase( blend, "add" ) ) {
1003 m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE );
1005 else if ( string_equal_nocase( blend, "filter" ) ) {
1006 m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO );
1008 else if ( string_equal_nocase( blend, "blend" ) ) {
1009 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1013 globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n";
1020 void unrealiseLighting(){
1021 if ( m_lightingEnabled ) {
1022 GlobalTexturesCache().release( m_pDiffuse );
1023 GlobalTexturesCache().release( m_pBump );
1024 GlobalTexturesCache().release( m_pSpecular );
1026 GlobalTexturesCache().release( m_pLightFalloffImage );
1028 for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1030 GlobalTexturesCache().release( ( *i ).texture() );
1034 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1039 void setName( const char* name ){
1043 class MapLayer : public ShaderLayer
1045 qtexture_t* m_texture;
1046 BlendFunc m_blendFunc;
1047 bool m_clampToBorder;
1050 MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) :
1051 m_texture( texture ),
1052 m_blendFunc( blendFunc ),
1053 m_clampToBorder( false ),
1054 m_alphaTest( alphaTest ){
1056 qtexture_t* texture() const {
1059 BlendFunc blendFunc() const {
1062 bool clampToBorder() const {
1063 return m_clampToBorder;
1065 float alphaTest() const {
1070 static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){
1072 evaluateTexture( layerTemplate.texture(), params, args ),
1073 evaluateBlendFunc( layerTemplate.blendFunc(), params, args ),
1074 layerTemplate.clampToBorder(),
1075 evaluateFloat( layerTemplate.alphaTest(), params, args )
1079 typedef std::vector<MapLayer> MapLayers;
1082 const ShaderLayer* firstLayer() const {
1083 if ( m_layers.empty() ) {
1086 return &m_layers.front();
1088 void forEachLayer( const ShaderLayerCallback& callback ) const {
1089 for ( MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1095 qtexture_t* lightFalloffImage() const {
1096 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1097 return m_pLightFalloffImage;
1103 bool CShader::m_lightingEnabled = false;
1105 typedef SmartPointer<CShader> ShaderPointer;
1106 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1108 shaders_t g_ActiveShaders;
1110 static shaders_t::iterator g_ActiveShadersIterator;
1112 void ActiveShaders_IteratorBegin(){
1113 g_ActiveShadersIterator = g_ActiveShaders.begin();
1116 bool ActiveShaders_IteratorAtEnd(){
1117 return g_ActiveShadersIterator == g_ActiveShaders.end();
1120 IShader *ActiveShaders_IteratorCurrent(){
1121 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1124 void ActiveShaders_IteratorIncrement(){
1125 ++g_ActiveShadersIterator;
1128 void debug_check_shaders( shaders_t& shaders ){
1129 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1131 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1135 // will free all GL binded qtextures and shaders
1136 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1139 // empty the actives shaders list
1140 debug_check_shaders( g_ActiveShaders );
1141 g_ActiveShaders.clear();
1143 g_shaderTemplates.clear();
1144 g_shaderDefinitions.clear();
1145 g_ActiveShadersChangedNotify();
1148 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1149 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1150 m_textureName = m_Name.c_str();
1152 tokeniser.nextLine();
1154 // we need to read until we hit a balanced }
1158 tokeniser.nextLine();
1159 const char* token = tokeniser.getToken();
1165 if ( string_equal( token, "{" ) ) {
1169 else if ( string_equal( token, "}" ) ) {
1171 if ( depth < 0 ) { // underflow
1174 if ( depth == 0 ) { // end of shader
1182 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1183 m_nFlags |= QER_NOCARVE;
1185 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1186 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1187 m_nFlags |= QER_TRANS;
1189 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1190 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1192 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1193 const char* alphafunc = tokeniser.getToken();
1195 if ( alphafunc == 0 ) {
1196 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1200 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1201 m_AlphaFunc = IShader::eEqual;
1203 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1204 m_AlphaFunc = IShader::eGreater;
1206 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1207 m_AlphaFunc = IShader::eLess;
1209 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1210 m_AlphaFunc = IShader::eGEqual;
1212 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1213 m_AlphaFunc = IShader::eLEqual;
1217 m_AlphaFunc = IShader::eAlways;
1220 m_nFlags |= QER_ALPHATEST;
1222 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1224 else if ( string_equal_nocase( token, "cull" ) ) {
1225 const char* cull = tokeniser.getToken();
1228 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1232 if ( string_equal_nocase( cull, "none" )
1233 || string_equal_nocase( cull, "twosided" )
1234 || string_equal_nocase( cull, "disable" ) ) {
1235 m_Cull = IShader::eCullNone;
1237 else if ( string_equal_nocase( cull, "back" )
1238 || string_equal_nocase( cull, "backside" )
1239 || string_equal_nocase( cull, "backsided" ) ) {
1240 m_Cull = IShader::eCullBack;
1244 m_Cull = IShader::eCullBack;
1247 m_nFlags |= QER_CULL;
1249 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1250 const char* surfaceparm = tokeniser.getToken();
1252 if ( surfaceparm == 0 ) {
1253 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1257 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1258 m_nFlags |= QER_FOG;
1259 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1263 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1264 m_nFlags |= QER_NODRAW;
1266 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1267 m_nFlags |= QER_NONSOLID;
1269 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1270 m_nFlags |= QER_WATER;
1272 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1273 m_nFlags |= QER_LAVA;
1275 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1276 m_nFlags |= QER_AREAPORTAL;
1278 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1279 m_nFlags |= QER_CLIP;
1281 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1282 m_nFlags |= QER_BOTCLIP;
1295 TextureExpression m_texture;
1296 BlendFunc m_blendFunc;
1297 bool m_clampToBorder;
1299 float m_heightmapScale;
1301 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1305 std::list<CopiedString> g_shaderFilenames;
1307 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1308 g_shaderFilenames.push_back( filename );
1309 filename = g_shaderFilenames.back().c_str();
1310 tokeniser.nextLine();
1313 const char* token = tokeniser.getToken();
1319 if ( string_equal( token, "table" ) ) {
1320 if ( tokeniser.getToken() == 0 ) {
1321 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1324 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1329 const char* option = tokeniser.getToken();
1330 if ( string_equal( option, "{" ) ) {
1333 const char* value = tokeniser.getToken();
1334 if ( string_equal( value, "}" ) ) {
1339 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1348 if ( string_equal( token, "guide" ) ) {
1349 parseTemplateInstance( tokeniser, filename );
1353 if ( !string_equal( token, "material" )
1354 && !string_equal( token, "particle" )
1355 && !string_equal( token, "skin" ) ) {
1356 tokeniser.ungetToken();
1358 // first token should be the path + name.. (from base)
1360 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1362 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1363 shaderTemplate->setName( name.c_str() );
1365 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1367 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1368 ? shaderTemplate->parseQuake3( tokeniser )
1369 : shaderTemplate->parseDoom3( tokeniser );
1371 // do we already have this shader?
1372 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1374 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1380 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1388 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1389 tokeniser.nextLine();
1392 const char* token = tokeniser.getToken();
1398 if ( string_equal( token, "guide" ) ) {
1399 // first token should be the path + name.. (from base)
1400 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1401 shaderTemplate->parseTemplate( tokeniser );
1402 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1403 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1406 else if ( string_equal( token, "inlineGuide" ) ) {
1407 // skip entire inlineGuide definition
1408 std::size_t depth = 0;
1411 tokeniser.nextLine();
1412 token = tokeniser.getToken();
1413 if ( string_equal( token, "{" ) ) {
1416 else if ( string_equal( token, "}" ) ) {
1417 if ( --depth == 0 ) {
1426 void LoadShaderFile( const char* filename ){
1427 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1430 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1432 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1434 ParseShaderFile( tokeniser, filename );
1436 tokeniser.release();
1441 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1445 typedef FreeCaller1<const char*, LoadShaderFile> LoadShaderFileCaller;
1448 void loadGuideFile( const char* filename ){
1449 StringOutputStream fullname( 256 );
1450 fullname << "guides/" << filename;
1451 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1454 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1456 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1458 parseGuideFile( tokeniser, fullname.c_str() );
1460 tokeniser.release();
1465 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1469 typedef FreeCaller1<const char*, loadGuideFile> LoadGuideFileCaller;
1472 CShader* Try_Shader_ForName( const char* name ){
1474 shaders_t::iterator i = g_ActiveShaders.find( name );
1475 if ( i != g_ActiveShaders.end() ) {
1476 return ( *i ).second;
1479 // active shader was not found
1481 // find matching shader definition
1482 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1483 if ( i == g_shaderDefinitions.end() ) {
1484 // shader definition was not found
1486 // create new shader definition from default shader template
1487 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1488 shaderTemplate->CreateDefault( name );
1489 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1491 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1494 // create shader from existing definition
1495 ShaderPointer pShader( new CShader( ( *i ).second ) );
1496 pShader->setName( name );
1497 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1498 g_ActiveShadersChangedNotify();
1502 IShader *Shader_ForName( const char *name ){
1503 ASSERT_NOTNULL( name );
1505 IShader *pShader = Try_Shader_ForName( name );
1513 // the list of scripts/*.shader files we need to work with
1514 // those are listed in shaderlist file
1515 GSList *l_shaderfiles = 0;
1517 GSList* Shaders_getShaderFileList(){
1518 return l_shaderfiles;
1523 DumpUnreferencedShaders
1524 usefull function: dumps the list of .shader files that are not referenced to the console
1527 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1528 bool listed = false;
1530 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1532 if ( !strcmp( (char*)sh->data, filename ) ) {
1541 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1543 globalOutputStream() << "\t" << filename << "\n";
1546 typedef ReferenceCaller1<bool, const char*, IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1548 void DumpUnreferencedShaders(){
1549 bool bFound = false;
1550 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1553 void ShaderList_addShaderFile( const char* dirstring ){
1556 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1558 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1560 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1566 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1570 typedef FreeCaller1<const char*, ShaderList_addShaderFile> AddShaderFileCaller;
1576 build a CStringList of shader names
1579 void BuildShaderList( TextInputStream& shaderlist ){
1580 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1581 tokeniser.nextLine();
1582 const char* token = tokeniser.getToken();
1583 StringOutputStream shaderFile( 64 );
1584 while ( token != 0 )
1586 // each token should be a shader filename
1587 shaderFile << token << "." << g_shadersExtension;
1589 ShaderList_addShaderFile( shaderFile.c_str() );
1591 tokeniser.nextLine();
1592 token = tokeniser.getToken();
1596 tokeniser.release();
1599 void FreeShaderList(){
1600 while ( l_shaderfiles != 0 )
1602 free( l_shaderfiles->data );
1603 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1607 void ShaderList_addFromArchive( const char *archivename ){
1608 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1609 if ( string_empty( shaderpath ) ) {
1613 StringOutputStream shaderlist( 256 );
1614 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1616 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1618 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1620 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1621 BuildShaderList( file->getInputStream() );
1627 typedef FreeCaller1<const char *, ShaderList_addFromArchive> AddShaderListFromArchiveCaller;
1629 #include "stream/filestream.h"
1631 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1632 StringOutputStream absShaderList( 256 );
1633 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1634 if ( file_exists( absShaderList.c_str() ) ) {
1638 StringOutputStream directory( 256 );
1639 directory << enginePath << gamename << '/' << shaderPath;
1640 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1645 StringOutputStream defaultShaderList( 256 );
1646 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1647 if ( file_exists( defaultShaderList.c_str() ) ) {
1648 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1654 void Shaders_Load(){
1655 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1656 GlobalFileSystem().forEachFile( "guides/", "guide", LoadGuideFileCaller(), 0 );
1659 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1660 if ( !string_empty( shaderPath ) ) {
1661 StringOutputStream path( 256 );
1662 path << DirectoryCleaned( shaderPath );
1664 if ( g_useShaderList ) {
1665 // preload shader files that have been listed in shaderlist.txt
1666 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1667 const char* gamename = GlobalRadiant().getGameName();
1668 const char* enginePath = GlobalRadiant().getEnginePath();
1669 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1671 bool isMod = !string_equal( basegame, gamename );
1673 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1674 gamename = basegame;
1675 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1678 GlobalFileSystem().forEachArchive( AddShaderListFromArchiveCaller(), false, true );
1679 DumpUnreferencedShaders();
1683 GlobalFileSystem().forEachFile( path.c_str(), g_shadersExtension, AddShaderFileCaller(), 0 );
1686 GSList *lst = l_shaderfiles;
1687 StringOutputStream shadername( 256 );
1690 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1691 LoadShaderFile( shadername.c_str() );
1697 //StringPool_analyse(ShaderPool::instance());
1700 void Shaders_Free(){
1703 g_shaderFilenames.clear();
1706 ModuleObservers g_observers;
1708 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1709 bool Shaders_realised(){
1710 return g_shaders_unrealised == 0;
1712 void Shaders_Realise(){
1713 if ( --g_shaders_unrealised == 0 ) {
1715 g_observers.realise();
1718 void Shaders_Unrealise(){
1719 if ( ++g_shaders_unrealised == 1 ) {
1720 g_observers.unrealise();
1725 void Shaders_Refresh(){
1726 Shaders_Unrealise();
1730 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1737 Shaders_Unrealise();
1743 IShader* getShaderForName( const char* name ){
1744 return Shader_ForName( name );
1747 void foreachShaderName( const ShaderNameCallback& callback ){
1748 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1750 callback( ( *i ).first.c_str() );
1754 void beginActiveShadersIterator(){
1755 ActiveShaders_IteratorBegin();
1757 bool endActiveShadersIterator(){
1758 return ActiveShaders_IteratorAtEnd();
1760 IShader* dereferenceActiveShadersIterator(){
1761 return ActiveShaders_IteratorCurrent();
1763 void incrementActiveShadersIterator(){
1764 ActiveShaders_IteratorIncrement();
1766 void setActiveShadersChangedNotify( const Callback& notify ){
1767 g_ActiveShadersChangedNotify = notify;
1770 void attach( ModuleObserver& observer ){
1771 g_observers.attach( observer );
1773 void detach( ModuleObserver& observer ){
1774 g_observers.detach( observer );
1777 void setLightingEnabled( bool enabled ){
1778 if ( CShader::m_lightingEnabled != enabled ) {
1779 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1781 ( *i ).second->unrealiseLighting();
1783 CShader::m_lightingEnabled = enabled;
1784 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1786 ( *i ).second->realiseLighting();
1791 const char* getTexturePrefix() const {
1792 return g_texturePrefix;
1796 Quake3ShaderSystem g_Quake3ShaderSystem;
1798 ShaderSystem& GetShaderSystem(){
1799 return g_Quake3ShaderSystem;
1802 void Shaders_Construct(){
1803 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1805 void Shaders_Destroy(){
1806 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1808 if ( Shaders_realised() ) {