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)
44 #include "ifilesystem.h"
46 #include "iscriplib.h"
47 #include "itextures.h"
48 #include "qerplugin.h"
53 #include "debugging/debugging.h"
54 #include "string/pooledstring.h"
55 #include "math/vector.h"
56 #include "generic/callback.h"
57 #include "generic/referencecounted.h"
58 #include "stream/memstream.h"
59 #include "stream/stringstream.h"
60 #include "stream/textfilestream.h"
65 #include "shaderlib.h"
66 #include "texturelib.h"
68 #include "moduleobservers.h"
69 #include "archivelib.h"
72 const char* g_shadersExtension = "";
73 const char* g_shadersDirectory = "";
74 bool g_enableDefaultShaders = true;
75 ShaderLanguage g_shaderLanguage = SHADERLANGUAGE_QUAKE3;
76 bool g_useShaderList = true;
77 _QERPlugImageTable* g_bitmapModule = 0;
78 const char* g_texturePrefix = "textures/";
80 void ActiveShaders_IteratorBegin();
81 bool ActiveShaders_IteratorAtEnd();
82 IShader *ActiveShaders_IteratorCurrent();
83 void ActiveShaders_IteratorIncrement();
84 Callback g_ActiveShadersChangedNotify;
87 void LoadShaderFile( const char *filename );
88 qtexture_t *Texture_ForName( const char *filename );
92 NOTE TTimo: there is an important distinction between SHADER_NOT_FOUND and SHADER_NOTEX:
93 SHADER_NOT_FOUND means we didn't find the raw texture or the shader for this
94 SHADER_NOTEX means we recognize this as a shader script, but we are missing the texture to represent it
95 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
98 Image* loadBitmap( void* environment, const char* name ){
99 DirectoryArchiveFile file( name, name );
100 if ( !file.failed() ) {
101 return g_bitmapModule->loadImage( file );
106 inline byte* getPixel( byte* pixels, int width, int height, int x, int y ){
107 return pixels + ( ( ( ( ( y + height ) % height ) * width ) + ( ( x + width ) % width ) ) * 4 );
117 Image& convertHeightmapToNormalmap( Image& heightmap, float scale ){
118 int w = heightmap.getWidth();
119 int h = heightmap.getHeight();
121 Image& normalmap = *( new RGBAImage( heightmap.getWidth(), heightmap.getHeight() ) );
123 byte* in = heightmap.getRGBAPixels();
124 byte* out = normalmap.getRGBAPixels();
128 const int kernelSize = 2;
129 KernelElement kernel_du[kernelSize] = {
133 KernelElement kernel_dv[kernelSize] = {
139 const int kernelSize = 6;
140 KernelElement kernel_du[kernelSize] = {
148 KernelElement kernel_dv[kernelSize] = {
165 for ( KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i )
167 du += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
170 for ( KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i )
172 dv += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
175 float nx = -du * scale;
176 float ny = -dv * scale;
180 float norm = 1.0 / sqrt( nx * nx + ny * ny + nz * nz );
181 out[0] = float_to_integer( ( ( nx * norm ) + 1 ) * 127.5 );
182 out[1] = float_to_integer( ( ( ny * norm ) + 1 ) * 127.5 );
183 out[2] = float_to_integer( ( ( nz * norm ) + 1 ) * 127.5 );
196 Image* loadHeightmap( void* environment, const char* name ){
197 Image* heightmap = GlobalTexturesCache().loadImage( name );
198 if ( heightmap != 0 ) {
199 Image& normalmap = convertHeightmapToNormalmap( *heightmap, *reinterpret_cast<float*>( environment ) );
200 heightmap->release();
207 Image* loadSpecial( void* environment, const char* name ){
208 if ( *name == '_' ) { // special image
209 StringOutputStream bitmapName( 256 );
210 bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".bmp";
211 Image* image = loadBitmap( environment, bitmapName.c_str() );
216 return GlobalTexturesCache().loadImage( name );
219 class ShaderPoolContext
222 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
223 typedef PooledString<ShaderPool> ShaderString;
224 typedef ShaderString ShaderVariable;
225 typedef ShaderString ShaderValue;
226 typedef CopiedString TextureExpression;
228 // clean a texture name to the qtexture_t name format we use internally
229 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
230 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
231 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
232 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
233 template<typename StringType>
234 void parseTextureName( StringType& name, const char* token ){
235 StringOutputStream cleaned( 256 );
236 cleaned << PathCleaned( token );
237 name = CopiedString( StringRange( cleaned.c_str(), path_get_filename_base_end( cleaned.c_str() ) ) ).c_str(); // remove extension
240 bool Tokeniser_parseTextureName( Tokeniser& tokeniser, TextureExpression& name ){
241 const char* token = tokeniser.getToken();
243 Tokeniser_unexpectedError( tokeniser, token, "#texture-name" );
246 parseTextureName( name, token );
250 bool Tokeniser_parseShaderName( Tokeniser& tokeniser, CopiedString& name ){
251 const char* token = tokeniser.getToken();
253 Tokeniser_unexpectedError( tokeniser, token, "#shader-name" );
256 parseTextureName( name, token );
260 bool Tokeniser_parseString( Tokeniser& tokeniser, ShaderString& string ){
261 const char* token = tokeniser.getToken();
263 Tokeniser_unexpectedError( tokeniser, token, "#string" );
272 typedef std::list<ShaderVariable> ShaderParameters;
273 typedef std::list<ShaderVariable> ShaderArguments;
275 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
279 std::size_t m_refcount;
283 ShaderParameters m_params;
285 TextureExpression m_textureName;
286 TextureExpression m_diffuse;
287 TextureExpression m_bump;
288 ShaderValue m_heightmapScale;
289 TextureExpression m_specular;
290 TextureExpression m_lightFalloffImage;
296 IShader::EAlphaFunc m_AlphaFunc;
299 IShader::ECull m_Cull;
311 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
312 if ( --m_refcount == 0 ) {
317 std::size_t refcount(){
321 const char* getName() const {
322 return m_Name.c_str();
324 void setName( const char* name ){
328 // -----------------------------------------
330 bool parseDoom3( Tokeniser& tokeniser );
331 bool parseQuake3( Tokeniser& tokeniser );
332 bool parseTemplate( Tokeniser& tokeniser );
335 void CreateDefault( const char *name ){
336 if ( g_enableDefaultShaders ) {
337 m_textureName = name;
347 class MapLayerTemplate
349 TextureExpression m_texture;
350 BlendFuncExpression m_blendFunc;
351 bool m_clampToBorder;
352 ShaderValue m_alphaTest;
354 MapLayerTemplate( const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest ) :
355 m_texture( texture ),
356 m_blendFunc( blendFunc ),
357 m_clampToBorder( false ),
358 m_alphaTest( alphaTest ){
360 const TextureExpression& texture() const {
363 const BlendFuncExpression& blendFunc() const {
366 bool clampToBorder() const {
367 return m_clampToBorder;
369 const ShaderValue& alphaTest() const {
373 typedef std::vector<MapLayerTemplate> MapLayers;
378 bool Doom3Shader_parseHeightmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
379 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
380 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
381 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
382 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, heightmapScale ) );
383 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
387 bool Doom3Shader_parseAddnormals( Tokeniser& tokeniser, TextureExpression& bump ){
388 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
389 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
390 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
391 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "heightmap" ) );
392 TextureExpression heightmapName;
393 ShaderValue heightmapScale;
394 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, heightmapName, heightmapScale ) );
395 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
399 bool Doom3Shader_parseBumpmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
400 const char* token = tokeniser.getToken();
402 Tokeniser_unexpectedError( tokeniser, token, "#bumpmap" );
405 if ( string_equal( token, "heightmap" ) ) {
406 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, bump, heightmapScale ) );
408 else if ( string_equal( token, "addnormals" ) ) {
409 RETURN_FALSE_IF_FAIL( Doom3Shader_parseAddnormals( tokeniser, bump ) );
413 parseTextureName( bump, token );
431 TextureExpression m_texture;
432 BlendFuncExpression m_blendFunc;
433 bool m_clampToBorder;
434 ShaderValue m_alphaTest;
435 ShaderValue m_heightmapScale;
437 LayerTemplate() : m_type( LAYER_NONE ), m_blendFunc( "GL_ONE", "GL_ZERO" ), m_clampToBorder( false ), m_alphaTest( "-1" ), m_heightmapScale( "0" ){
441 bool parseShaderParameters( Tokeniser& tokeniser, ShaderParameters& params ){
442 Tokeniser_parseToken( tokeniser, "(" );
445 const char* param = tokeniser.getToken();
446 if ( string_equal( param, ")" ) ) {
449 params.push_back( param );
450 const char* comma = tokeniser.getToken();
451 if ( string_equal( comma, ")" ) ) {
454 if ( !string_equal( comma, "," ) ) {
455 Tokeniser_unexpectedError( tokeniser, comma, "," );
462 bool ShaderTemplate::parseTemplate( Tokeniser& tokeniser ){
463 m_Name = tokeniser.getToken();
464 if ( !parseShaderParameters( tokeniser, m_params ) ) {
465 globalErrorStream() << "shader template: " << makeQuoted( m_Name.c_str() ) << ": parameter parse failed\n";
469 return parseDoom3( tokeniser );
472 bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){
473 LayerTemplate currentLayer;
476 // we need to read until we hit a balanced }
480 tokeniser.nextLine();
481 const char* token = tokeniser.getToken();
487 if ( string_equal( token, "{" ) ) {
491 else if ( string_equal( token, "}" ) ) {
493 if ( depth < 0 ) { // error
496 if ( depth == 0 ) { // end of shader
499 if ( depth == 1 ) { // end of layer
500 if ( currentLayer.m_type == LAYER_DIFFUSEMAP ) {
501 m_diffuse = currentLayer.m_texture;
503 else if ( currentLayer.m_type == LAYER_BUMPMAP ) {
504 m_bump = currentLayer.m_texture;
506 else if ( currentLayer.m_type == LAYER_SPECULARMAP ) {
507 m_specular = currentLayer.m_texture;
509 else if ( !string_empty( currentLayer.m_texture.c_str() ) ) {
510 m_layers.push_back( MapLayerTemplate(
511 currentLayer.m_texture.c_str(),
512 currentLayer.m_blendFunc,
513 currentLayer.m_clampToBorder,
514 currentLayer.m_alphaTest
517 currentLayer.m_type = LAYER_NONE;
518 currentLayer.m_texture = "";
523 if ( depth == 2 ) { // in layer
524 if ( string_equal_nocase( token, "blend" ) ) {
525 const char* blend = tokeniser.getToken();
528 Tokeniser_unexpectedError( tokeniser, blend, "#blend" );
532 if ( string_equal_nocase( blend, "diffusemap" ) ) {
533 currentLayer.m_type = LAYER_DIFFUSEMAP;
535 else if ( string_equal_nocase( blend, "bumpmap" ) ) {
536 currentLayer.m_type = LAYER_BUMPMAP;
538 else if ( string_equal_nocase( blend, "specularmap" ) ) {
539 currentLayer.m_type = LAYER_SPECULARMAP;
543 currentLayer.m_blendFunc.first = blend;
545 const char* comma = tokeniser.getToken();
548 Tokeniser_unexpectedError( tokeniser, comma, "#comma" );
552 if ( string_equal( comma, "," ) ) {
553 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, currentLayer.m_blendFunc.second ) );
557 currentLayer.m_blendFunc.second = "";
558 tokeniser.ungetToken();
562 else if ( string_equal_nocase( token, "map" ) ) {
563 if ( currentLayer.m_type == LAYER_BUMPMAP ) {
564 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale ) );
568 const char* map = tokeniser.getToken();
571 Tokeniser_unexpectedError( tokeniser, map, "#map" );
575 if ( string_equal( map, "makealpha" ) ) {
576 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
577 const char* texture = tokeniser.getToken();
578 if ( texture == 0 ) {
579 Tokeniser_unexpectedError( tokeniser, texture, "#texture" );
582 currentLayer.m_texture = texture;
583 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
587 parseTextureName( currentLayer.m_texture, map );
591 else if ( string_equal_nocase( token, "zeroclamp" ) ) {
592 currentLayer.m_clampToBorder = true;
595 else if ( string_equal_nocase( token, "alphaTest" ) ) {
596 Tokeniser_getFloat( tokeniser, currentLayer.m_alphaTest );
600 else if ( depth == 1 ) {
601 if ( string_equal_nocase( token, "qer_editorimage" ) ) {
602 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
604 else if ( string_equal_nocase( token, "qer_trans" ) ) {
605 m_fTrans = string_read_float( tokeniser.getToken() );
606 m_nFlags |= QER_TRANS;
608 else if ( string_equal_nocase( token, "translucent" ) ) {
610 m_nFlags |= QER_TRANS;
612 else if ( string_equal( token, "DECAL_MACRO" ) ) {
614 m_nFlags |= QER_TRANS;
616 else if ( string_equal_nocase( token, "bumpmap" ) ) {
617 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) );
619 else if ( string_equal_nocase( token, "diffusemap" ) ) {
620 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) );
622 else if ( string_equal_nocase( token, "specularmap" ) ) {
623 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) );
625 else if ( string_equal_nocase( token, "twosided" ) ) {
626 m_Cull = IShader::eCullNone;
627 m_nFlags |= QER_CULL;
629 else if ( string_equal_nocase( token, "nodraw" ) ) {
630 m_nFlags |= QER_NODRAW;
632 else if ( string_equal_nocase( token, "nonsolid" ) ) {
633 m_nFlags |= QER_NONSOLID;
635 else if ( string_equal_nocase( token, "liquid" ) ) {
636 m_nFlags |= QER_WATER;
638 else if ( string_equal_nocase( token, "areaportal" ) ) {
639 m_nFlags |= QER_AREAPORTAL;
641 else if ( string_equal_nocase( token, "playerclip" )
642 || string_equal_nocase( token, "monsterclip" )
643 || string_equal_nocase( token, "ikclip" )
644 || string_equal_nocase( token, "moveableclip" ) ) {
645 m_nFlags |= QER_CLIP;
647 if ( string_equal_nocase( token, "fogLight" ) ) {
650 else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) {
651 const char* lightFalloffImage = tokeniser.getToken();
652 if ( lightFalloffImage == 0 ) {
653 Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" );
656 if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) {
657 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
658 TextureExpression name;
659 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) );
660 m_lightFalloffImage = name;
661 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
665 m_lightFalloffImage = lightFalloffImage;
671 if ( string_empty( m_textureName.c_str() ) ) {
672 m_textureName = m_diffuse;
678 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
679 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
681 ShaderTemplateMap g_shaders;
682 ShaderTemplateMap g_shaderTemplates;
684 ShaderTemplate* findTemplate( const char* name ){
685 ShaderTemplateMap::iterator i = g_shaderTemplates.find( name );
686 if ( i != g_shaderTemplates.end() ) {
687 return ( *i ).second.get();
692 class ShaderDefinition
695 ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename )
696 : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){
698 ShaderTemplate* shaderTemplate;
699 ShaderArguments args;
700 const char* filename;
703 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
705 ShaderDefinitionMap g_shaderDefinitions;
707 bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){
709 RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) );
710 const char* templateName = tokeniser.getToken();
711 ShaderTemplate* shaderTemplate = findTemplate( templateName );
712 if ( shaderTemplate == 0 ) {
713 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n";
716 ShaderArguments args;
717 if ( !parseShaderParameters( tokeniser, args ) ) {
718 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n";
722 if ( shaderTemplate != 0 ) {
723 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) {
724 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n";
731 const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){
732 ShaderArguments::const_iterator j = args.begin();
733 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
735 const char* other = ( *i ).c_str();
736 if ( string_equal( value, other ) ) {
737 return ( *j ).c_str();
743 ///\todo BlendFunc parsing
744 BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){
745 return BlendFunc( BLEND_ONE, BLEND_ZERO );
748 qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){
749 StringOutputStream result( 64 );
750 const char* expression = texture.c_str();
751 const char* end = expression + string_length( expression );
752 if ( !string_empty( expression ) ) {
755 const char* best = end;
756 const char* bestParam = 0;
757 const char* bestArg = 0;
758 ShaderArguments::const_iterator j = args.begin();
759 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
761 const char* found = strstr( expression, ( *i ).c_str() );
762 if ( found != 0 && found < best ) {
764 bestParam = ( *i ).c_str();
765 bestArg = ( *j ).c_str();
769 result << StringRange( expression, best );
770 result << PathCleaned( bestArg );
771 expression = best + string_length( bestParam );
778 result << expression;
780 return GlobalTexturesCache().capture( loader, result.c_str() );
783 float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
784 const char* result = evaluateShaderValue( value.c_str(), params, args );
786 if ( !string_parse_float( result, f ) ) {
787 globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n";
792 BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
793 const char* result = evaluateShaderValue( value.c_str(), params, args );
795 if ( string_equal_nocase( result, "gl_zero" ) ) {
798 if ( string_equal_nocase( result, "gl_one" ) ) {
801 if ( string_equal_nocase( result, "gl_src_color" ) ) {
802 return BLEND_SRC_COLOUR;
804 if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) {
805 return BLEND_ONE_MINUS_SRC_COLOUR;
807 if ( string_equal_nocase( result, "gl_src_alpha" ) ) {
808 return BLEND_SRC_ALPHA;
810 if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) {
811 return BLEND_ONE_MINUS_SRC_ALPHA;
813 if ( string_equal_nocase( result, "gl_dst_color" ) ) {
814 return BLEND_DST_COLOUR;
816 if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) {
817 return BLEND_ONE_MINUS_DST_COLOUR;
819 if ( string_equal_nocase( result, "gl_dst_alpha" ) ) {
820 return BLEND_DST_ALPHA;
822 if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) {
823 return BLEND_ONE_MINUS_DST_ALPHA;
825 if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) {
826 return BLEND_SRC_ALPHA_SATURATE;
829 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n";
833 class CShader : public IShader
835 std::size_t m_refcount;
837 const ShaderTemplate& m_template;
838 const ShaderArguments& m_args;
839 const char* m_filename;
840 // name is shader-name, otherwise texture-name (if not a real shader)
843 qtexture_t* m_pTexture;
844 qtexture_t* m_notfound;
845 qtexture_t* m_pDiffuse;
846 float m_heightmapScale;
848 qtexture_t* m_pSpecular;
849 qtexture_t* m_pLightFalloffImage;
850 BlendFunc m_blendFunc;
856 static bool m_lightingEnabled;
858 CShader( const ShaderDefinition& definition ) :
860 m_template( *definition.shaderTemplate ),
861 m_args( definition.args ),
862 m_filename( definition.filename ),
863 m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
877 ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" );
880 // IShaders implementation -----------------
885 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
886 if ( --m_refcount == 0 ) {
891 std::size_t refcount(){
895 // get/set the qtexture_t* Radiant uses to represent this shader object
896 qtexture_t* getTexture() const {
899 qtexture_t* getDiffuse() const {
902 qtexture_t* getBump() const {
905 qtexture_t* getSpecular() const {
909 const char* getName() const {
910 return m_Name.c_str();
912 bool IsInUse() const {
915 void SetInUse( bool bInUse ){
917 g_ActiveShadersChangedNotify();
919 // get the shader flags
920 int getFlags() const {
921 return m_template.m_nFlags;
923 // get the transparency value
924 float getTrans() const {
925 return m_template.m_fTrans;
927 // test if it's a true shader, or a default shader created to wrap around a texture
928 bool IsDefault() const {
929 return string_empty( m_filename );
932 void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
933 BlendFunc getBlendFunc() const {
938 return m_template.m_Cull;
940 // get shader file name (ie the file where this one is defined)
941 const char* getShaderFileName() const {
944 // -----------------------------------------
947 m_pTexture = evaluateTexture( m_template.m_textureName, m_template.m_params, m_args );
949 if ( m_pTexture->texture_number == 0 ) {
950 m_notfound = m_pTexture;
953 StringOutputStream name( 256 );
954 name << GlobalRadiant().getAppPath() << "bitmaps/" << ( IsDefault() ? "notex.bmp" : "shadernotex.bmp" );
955 m_pTexture = GlobalTexturesCache().capture( LoadImageCallback( 0, loadBitmap ), name.c_str() );
963 GlobalTexturesCache().release( m_pTexture );
965 if ( m_notfound != 0 ) {
966 GlobalTexturesCache().release( m_notfound );
972 void realiseLighting(){
973 if ( m_lightingEnabled ) {
974 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
975 if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) {
976 m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args );
977 loader = LoadImageCallback( &m_heightmapScale, loadHeightmap );
979 m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args );
980 m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader );
981 m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args );
982 m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args );
984 for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i )
986 m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) );
989 if ( m_layers.size() == 1 ) {
990 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
991 if ( !string_empty( blendFunc.second.c_str() ) ) {
992 m_blendFunc = BlendFunc(
993 evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ),
994 evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args )
999 const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args );
1001 if ( string_equal_nocase( blend, "add" ) ) {
1002 m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE );
1004 else if ( string_equal_nocase( blend, "filter" ) ) {
1005 m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO );
1007 else if ( string_equal_nocase( blend, "blend" ) ) {
1008 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1012 globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n";
1019 void unrealiseLighting(){
1020 if ( m_lightingEnabled ) {
1021 GlobalTexturesCache().release( m_pDiffuse );
1022 GlobalTexturesCache().release( m_pBump );
1023 GlobalTexturesCache().release( m_pSpecular );
1025 GlobalTexturesCache().release( m_pLightFalloffImage );
1027 for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1029 GlobalTexturesCache().release( ( *i ).texture() );
1033 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1038 void setName( const char* name ){
1042 class MapLayer : public ShaderLayer
1044 qtexture_t* m_texture;
1045 BlendFunc m_blendFunc;
1046 bool m_clampToBorder;
1049 MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) :
1050 m_texture( texture ),
1051 m_blendFunc( blendFunc ),
1052 m_clampToBorder( false ),
1053 m_alphaTest( alphaTest ){
1055 qtexture_t* texture() const {
1058 BlendFunc blendFunc() const {
1061 bool clampToBorder() const {
1062 return m_clampToBorder;
1064 float alphaTest() const {
1069 static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){
1071 evaluateTexture( layerTemplate.texture(), params, args ),
1072 evaluateBlendFunc( layerTemplate.blendFunc(), params, args ),
1073 layerTemplate.clampToBorder(),
1074 evaluateFloat( layerTemplate.alphaTest(), params, args )
1078 typedef std::vector<MapLayer> MapLayers;
1081 const ShaderLayer* firstLayer() const {
1082 if ( m_layers.empty() ) {
1085 return &m_layers.front();
1087 void forEachLayer( const ShaderLayerCallback& callback ) const {
1088 for ( MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1094 qtexture_t* lightFalloffImage() const {
1095 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1096 return m_pLightFalloffImage;
1102 bool CShader::m_lightingEnabled = false;
1104 typedef SmartPointer<CShader> ShaderPointer;
1105 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1107 shaders_t g_ActiveShaders;
1109 static shaders_t::iterator g_ActiveShadersIterator;
1111 void ActiveShaders_IteratorBegin(){
1112 g_ActiveShadersIterator = g_ActiveShaders.begin();
1115 bool ActiveShaders_IteratorAtEnd(){
1116 return g_ActiveShadersIterator == g_ActiveShaders.end();
1119 IShader *ActiveShaders_IteratorCurrent(){
1120 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1123 void ActiveShaders_IteratorIncrement(){
1124 ++g_ActiveShadersIterator;
1127 void debug_check_shaders( shaders_t& shaders ){
1128 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1130 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1134 // will free all GL binded qtextures and shaders
1135 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1138 // empty the actives shaders list
1139 debug_check_shaders( g_ActiveShaders );
1140 g_ActiveShaders.clear();
1142 g_shaderTemplates.clear();
1143 g_shaderDefinitions.clear();
1144 g_ActiveShadersChangedNotify();
1147 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1148 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1149 m_textureName = m_Name.c_str();
1151 tokeniser.nextLine();
1153 // we need to read until we hit a balanced }
1157 tokeniser.nextLine();
1158 const char* token = tokeniser.getToken();
1164 if ( string_equal( token, "{" ) ) {
1168 else if ( string_equal( token, "}" ) ) {
1170 if ( depth < 0 ) { // underflow
1173 if ( depth == 0 ) { // end of shader
1181 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1182 m_nFlags |= QER_NOCARVE;
1184 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1185 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1186 m_nFlags |= QER_TRANS;
1188 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1189 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1191 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1192 const char* alphafunc = tokeniser.getToken();
1194 if ( alphafunc == 0 ) {
1195 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1199 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1200 m_AlphaFunc = IShader::eEqual;
1202 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1203 m_AlphaFunc = IShader::eGreater;
1205 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1206 m_AlphaFunc = IShader::eLess;
1208 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1209 m_AlphaFunc = IShader::eGEqual;
1211 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1212 m_AlphaFunc = IShader::eLEqual;
1216 m_AlphaFunc = IShader::eAlways;
1219 m_nFlags |= QER_ALPHATEST;
1221 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1223 else if ( string_equal_nocase( token, "cull" ) ) {
1224 const char* cull = tokeniser.getToken();
1227 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1231 if ( string_equal_nocase( cull, "none" )
1232 || string_equal_nocase( cull, "twosided" )
1233 || string_equal_nocase( cull, "disable" ) ) {
1234 m_Cull = IShader::eCullNone;
1236 else if ( string_equal_nocase( cull, "back" )
1237 || string_equal_nocase( cull, "backside" )
1238 || string_equal_nocase( cull, "backsided" ) ) {
1239 m_Cull = IShader::eCullBack;
1243 m_Cull = IShader::eCullBack;
1246 m_nFlags |= QER_CULL;
1248 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1249 const char* surfaceparm = tokeniser.getToken();
1251 if ( surfaceparm == 0 ) {
1252 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1256 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1257 m_nFlags |= QER_FOG;
1258 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1262 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1263 m_nFlags |= QER_NODRAW;
1265 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1266 m_nFlags |= QER_NONSOLID;
1268 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1269 m_nFlags |= QER_WATER;
1271 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1272 m_nFlags |= QER_LAVA;
1274 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1275 m_nFlags |= QER_AREAPORTAL;
1277 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1278 m_nFlags |= QER_CLIP;
1280 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1281 m_nFlags |= QER_BOTCLIP;
1294 TextureExpression m_texture;
1295 BlendFunc m_blendFunc;
1296 bool m_clampToBorder;
1298 float m_heightmapScale;
1300 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1304 std::list<CopiedString> g_shaderFilenames;
1306 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1307 g_shaderFilenames.push_back( filename );
1308 filename = g_shaderFilenames.back().c_str();
1309 tokeniser.nextLine();
1312 const char* token = tokeniser.getToken();
1318 if ( string_equal( token, "table" ) ) {
1319 if ( tokeniser.getToken() == 0 ) {
1320 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1323 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1328 const char* option = tokeniser.getToken();
1329 if ( string_equal( option, "{" ) ) {
1332 const char* value = tokeniser.getToken();
1333 if ( string_equal( value, "}" ) ) {
1338 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1347 if ( string_equal( token, "guide" ) ) {
1348 parseTemplateInstance( tokeniser, filename );
1352 if ( !string_equal( token, "material" )
1353 && !string_equal( token, "particle" )
1354 && !string_equal( token, "skin" ) ) {
1355 tokeniser.ungetToken();
1357 // first token should be the path + name.. (from base)
1359 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1361 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1362 shaderTemplate->setName( name.c_str() );
1364 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1366 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1367 ? shaderTemplate->parseQuake3( tokeniser )
1368 : shaderTemplate->parseDoom3( tokeniser );
1370 // do we already have this shader?
1371 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1373 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1379 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1387 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1388 tokeniser.nextLine();
1391 const char* token = tokeniser.getToken();
1397 if ( string_equal( token, "guide" ) ) {
1398 // first token should be the path + name.. (from base)
1399 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1400 shaderTemplate->parseTemplate( tokeniser );
1401 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1402 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1405 else if ( string_equal( token, "inlineGuide" ) ) {
1406 // skip entire inlineGuide definition
1407 std::size_t depth = 0;
1410 tokeniser.nextLine();
1411 token = tokeniser.getToken();
1412 if ( string_equal( token, "{" ) ) {
1415 else if ( string_equal( token, "}" ) ) {
1416 if ( --depth == 0 ) {
1425 void LoadShaderFile( const char* filename ){
1426 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1429 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1431 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1433 ParseShaderFile( tokeniser, filename );
1435 tokeniser.release();
1440 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1444 typedef FreeCaller1<const char*, LoadShaderFile> LoadShaderFileCaller;
1447 void loadGuideFile( const char* filename ){
1448 StringOutputStream fullname( 256 );
1449 fullname << "guides/" << filename;
1450 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1453 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1455 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1457 parseGuideFile( tokeniser, fullname.c_str() );
1459 tokeniser.release();
1464 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1468 typedef FreeCaller1<const char*, loadGuideFile> LoadGuideFileCaller;
1471 CShader* Try_Shader_ForName( const char* name ){
1473 shaders_t::iterator i = g_ActiveShaders.find( name );
1474 if ( i != g_ActiveShaders.end() ) {
1475 return ( *i ).second;
1478 // active shader was not found
1480 // find matching shader definition
1481 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1482 if ( i == g_shaderDefinitions.end() ) {
1483 // shader definition was not found
1485 // create new shader definition from default shader template
1486 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1487 shaderTemplate->CreateDefault( name );
1488 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1490 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1493 // create shader from existing definition
1494 ShaderPointer pShader( new CShader( ( *i ).second ) );
1495 pShader->setName( name );
1496 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1497 g_ActiveShadersChangedNotify();
1501 IShader *Shader_ForName( const char *name ){
1502 ASSERT_NOTNULL( name );
1504 IShader *pShader = Try_Shader_ForName( name );
1512 // the list of scripts/*.shader files we need to work with
1513 // those are listed in shaderlist file
1514 GSList *l_shaderfiles = 0;
1516 GSList* Shaders_getShaderFileList(){
1517 return l_shaderfiles;
1522 DumpUnreferencedShaders
1523 usefull function: dumps the list of .shader files that are not referenced to the console
1526 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1527 bool listed = false;
1529 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1531 if ( !strcmp( (char*)sh->data, filename ) ) {
1540 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1542 globalOutputStream() << "\t" << filename << "\n";
1545 typedef ReferenceCaller1<bool, const char*, IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1547 void DumpUnreferencedShaders(){
1548 bool bFound = false;
1549 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1552 void ShaderList_addShaderFile( const char* dirstring ){
1555 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1557 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1559 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1565 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1569 typedef FreeCaller1<const char*, ShaderList_addShaderFile> AddShaderFileCaller;
1575 build a CStringList of shader names
1578 void BuildShaderList( TextInputStream& shaderlist ){
1579 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1580 tokeniser.nextLine();
1581 const char* token = tokeniser.getToken();
1582 StringOutputStream shaderFile( 64 );
1583 while ( token != 0 )
1585 // each token should be a shader filename
1586 shaderFile << token << "." << g_shadersExtension;
1588 ShaderList_addShaderFile( shaderFile.c_str() );
1590 tokeniser.nextLine();
1591 token = tokeniser.getToken();
1595 tokeniser.release();
1598 void FreeShaderList(){
1599 while ( l_shaderfiles != 0 )
1601 free( l_shaderfiles->data );
1602 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1606 void ShaderList_addFromArchive( const char *archivename ){
1607 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1608 if ( string_empty( shaderpath ) ) {
1612 StringOutputStream shaderlist( 256 );
1613 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1615 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1617 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1619 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1620 BuildShaderList( file->getInputStream() );
1626 typedef FreeCaller1<const char *, ShaderList_addFromArchive> AddShaderListFromArchiveCaller;
1628 #include "stream/filestream.h"
1630 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1631 StringOutputStream absShaderList( 256 );
1632 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1633 if ( file_exists( absShaderList.c_str() ) ) {
1637 StringOutputStream directory( 256 );
1638 directory << enginePath << gamename << '/' << shaderPath;
1639 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1644 StringOutputStream defaultShaderList( 256 );
1645 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1646 if ( file_exists( defaultShaderList.c_str() ) ) {
1647 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1653 void Shaders_Load(){
1654 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1655 GlobalFileSystem().forEachFile( "guides/", "guide", LoadGuideFileCaller(), 0 );
1658 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1659 if ( !string_empty( shaderPath ) ) {
1660 StringOutputStream path( 256 );
1661 path << DirectoryCleaned( shaderPath );
1663 if ( g_useShaderList ) {
1664 // preload shader files that have been listed in shaderlist.txt
1665 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1666 const char* gamename = GlobalRadiant().getGameName();
1667 const char* enginePath = GlobalRadiant().getEnginePath();
1668 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1670 bool isMod = !string_equal( basegame, gamename );
1672 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1673 gamename = basegame;
1674 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1677 GlobalFileSystem().forEachArchive( AddShaderListFromArchiveCaller(), false, true );
1678 DumpUnreferencedShaders();
1682 GlobalFileSystem().forEachFile( path.c_str(), g_shadersExtension, AddShaderFileCaller(), 0 );
1685 GSList *lst = l_shaderfiles;
1686 StringOutputStream shadername( 256 );
1689 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1690 LoadShaderFile( shadername.c_str() );
1696 //StringPool_analyse(ShaderPool::instance());
1699 void Shaders_Free(){
1702 g_shaderFilenames.clear();
1705 ModuleObservers g_observers;
1707 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1708 bool Shaders_realised(){
1709 return g_shaders_unrealised == 0;
1711 void Shaders_Realise(){
1712 if ( --g_shaders_unrealised == 0 ) {
1714 g_observers.realise();
1717 void Shaders_Unrealise(){
1718 if ( ++g_shaders_unrealised == 1 ) {
1719 g_observers.unrealise();
1724 void Shaders_Refresh(){
1725 Shaders_Unrealise();
1729 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1736 Shaders_Unrealise();
1742 IShader* getShaderForName( const char* name ){
1743 return Shader_ForName( name );
1746 void foreachShaderName( const ShaderNameCallback& callback ){
1747 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1749 callback( ( *i ).first.c_str() );
1753 void beginActiveShadersIterator(){
1754 ActiveShaders_IteratorBegin();
1756 bool endActiveShadersIterator(){
1757 return ActiveShaders_IteratorAtEnd();
1759 IShader* dereferenceActiveShadersIterator(){
1760 return ActiveShaders_IteratorCurrent();
1762 void incrementActiveShadersIterator(){
1763 ActiveShaders_IteratorIncrement();
1765 void setActiveShadersChangedNotify( const Callback& notify ){
1766 g_ActiveShadersChangedNotify = notify;
1769 void attach( ModuleObserver& observer ){
1770 g_observers.attach( observer );
1772 void detach( ModuleObserver& observer ){
1773 g_observers.detach( observer );
1776 void setLightingEnabled( bool enabled ){
1777 if ( CShader::m_lightingEnabled != enabled ) {
1778 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1780 ( *i ).second->unrealiseLighting();
1782 CShader::m_lightingEnabled = enabled;
1783 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1785 ( *i ).second->realiseLighting();
1790 const char* getTexturePrefix() const {
1791 return g_texturePrefix;
1795 Quake3ShaderSystem g_Quake3ShaderSystem;
1797 ShaderSystem& GetShaderSystem(){
1798 return g_Quake3ShaderSystem;
1801 void Shaders_Construct(){
1802 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1804 void Shaders_Destroy(){
1805 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1807 if ( Shaders_realised() ) {