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 << ".png";
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 ( depth == 1 ) {
596 if ( string_equal_nocase( token, "qer_editorimage" ) ) {
597 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
599 else if ( string_equal_nocase( token, "qer_trans" ) ) {
600 m_fTrans = string_read_float( tokeniser.getToken() );
601 m_nFlags |= QER_TRANS;
603 else if ( string_equal_nocase( token, "translucent" ) ) {
605 m_nFlags |= QER_TRANS;
607 else if ( string_equal( token, "DECAL_MACRO" ) ) {
609 m_nFlags |= QER_TRANS;
611 else if ( string_equal_nocase( token, "bumpmap" ) ) {
612 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) );
614 else if ( string_equal_nocase( token, "diffusemap" ) ) {
615 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) );
617 else if ( string_equal_nocase( token, "specularmap" ) ) {
618 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) );
620 else if ( string_equal_nocase( token, "twosided" ) ) {
621 m_Cull = IShader::eCullNone;
622 m_nFlags |= QER_CULL;
624 else if ( string_equal_nocase( token, "nodraw" ) ) {
625 m_nFlags |= QER_NODRAW;
627 else if ( string_equal_nocase( token, "nonsolid" ) ) {
628 m_nFlags |= QER_NONSOLID;
630 else if ( string_equal_nocase( token, "liquid" ) ) {
631 m_nFlags |= QER_WATER;
633 else if ( string_equal_nocase( token, "areaportal" ) ) {
634 m_nFlags |= QER_AREAPORTAL;
636 else if ( string_equal_nocase( token, "playerclip" )
637 || string_equal_nocase( token, "monsterclip" )
638 || string_equal_nocase( token, "ikclip" )
639 || string_equal_nocase( token, "moveableclip" ) ) {
640 m_nFlags |= QER_CLIP;
642 if ( string_equal_nocase( token, "fogLight" ) ) {
645 else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) {
646 const char* lightFalloffImage = tokeniser.getToken();
647 if ( lightFalloffImage == 0 ) {
648 Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" );
651 if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) {
652 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
653 TextureExpression name;
654 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) );
655 m_lightFalloffImage = name;
656 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
660 m_lightFalloffImage = lightFalloffImage;
666 if ( string_empty( m_textureName.c_str() ) ) {
667 m_textureName = m_diffuse;
673 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
674 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
676 ShaderTemplateMap g_shaders;
677 ShaderTemplateMap g_shaderTemplates;
679 ShaderTemplate* findTemplate( const char* name ){
680 ShaderTemplateMap::iterator i = g_shaderTemplates.find( name );
681 if ( i != g_shaderTemplates.end() ) {
682 return ( *i ).second.get();
687 class ShaderDefinition
690 ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename )
691 : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){
693 ShaderTemplate* shaderTemplate;
694 ShaderArguments args;
695 const char* filename;
698 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
700 ShaderDefinitionMap g_shaderDefinitions;
702 bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){
704 RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) );
705 const char* templateName = tokeniser.getToken();
706 ShaderTemplate* shaderTemplate = findTemplate( templateName );
707 if ( shaderTemplate == 0 ) {
708 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n";
711 ShaderArguments args;
712 if ( !parseShaderParameters( tokeniser, args ) ) {
713 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n";
717 if ( shaderTemplate != 0 ) {
718 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) {
719 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n";
726 const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){
727 ShaderArguments::const_iterator j = args.begin();
728 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
730 const char* other = ( *i ).c_str();
731 if ( string_equal( value, other ) ) {
732 return ( *j ).c_str();
738 ///\todo BlendFunc parsing
739 BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){
740 return BlendFunc( BLEND_ONE, BLEND_ZERO );
743 qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){
744 StringOutputStream result( 64 );
745 const char* expression = texture.c_str();
746 const char* end = expression + string_length( expression );
747 if ( !string_empty( expression ) ) {
750 const char* best = end;
751 const char* bestParam = 0;
752 const char* bestArg = 0;
753 ShaderArguments::const_iterator j = args.begin();
754 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
756 const char* found = strstr( expression, ( *i ).c_str() );
757 if ( found != 0 && found < best ) {
759 bestParam = ( *i ).c_str();
760 bestArg = ( *j ).c_str();
764 result << StringRange( expression, best );
765 result << PathCleaned( bestArg );
766 expression = best + string_length( bestParam );
773 result << expression;
775 return GlobalTexturesCache().capture( loader, result.c_str() );
778 float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
779 const char* result = evaluateShaderValue( value.c_str(), params, args );
781 if ( !string_parse_float( result, f ) ) {
782 globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n";
787 BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
788 const char* result = evaluateShaderValue( value.c_str(), params, args );
790 if ( string_equal_nocase( result, "gl_zero" ) ) {
793 if ( string_equal_nocase( result, "gl_one" ) ) {
796 if ( string_equal_nocase( result, "gl_src_color" ) ) {
797 return BLEND_SRC_COLOUR;
799 if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) {
800 return BLEND_ONE_MINUS_SRC_COLOUR;
802 if ( string_equal_nocase( result, "gl_src_alpha" ) ) {
803 return BLEND_SRC_ALPHA;
805 if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) {
806 return BLEND_ONE_MINUS_SRC_ALPHA;
808 if ( string_equal_nocase( result, "gl_dst_color" ) ) {
809 return BLEND_DST_COLOUR;
811 if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) {
812 return BLEND_ONE_MINUS_DST_COLOUR;
814 if ( string_equal_nocase( result, "gl_dst_alpha" ) ) {
815 return BLEND_DST_ALPHA;
817 if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) {
818 return BLEND_ONE_MINUS_DST_ALPHA;
820 if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) {
821 return BLEND_SRC_ALPHA_SATURATE;
824 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n";
828 class CShader : public IShader
830 std::size_t m_refcount;
832 const ShaderTemplate& m_template;
833 const ShaderArguments& m_args;
834 const char* m_filename;
835 // name is shader-name, otherwise texture-name (if not a real shader)
838 qtexture_t* m_pTexture;
839 qtexture_t* m_notfound;
840 qtexture_t* m_pDiffuse;
841 float m_heightmapScale;
843 qtexture_t* m_pSpecular;
844 qtexture_t* m_pLightFalloffImage;
845 BlendFunc m_blendFunc;
851 static bool m_lightingEnabled;
853 CShader( const ShaderDefinition& definition ) :
855 m_template( *definition.shaderTemplate ),
856 m_args( definition.args ),
857 m_filename( definition.filename ),
858 m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
872 ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" );
875 // IShaders implementation -----------------
880 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
881 if ( --m_refcount == 0 ) {
886 std::size_t refcount(){
890 // get/set the qtexture_t* Radiant uses to represent this shader object
891 qtexture_t* getTexture() const {
894 qtexture_t* getDiffuse() const {
897 qtexture_t* getBump() const {
900 qtexture_t* getSpecular() const {
904 const char* getName() const {
905 return m_Name.c_str();
907 bool IsInUse() const {
910 void SetInUse( bool bInUse ){
912 g_ActiveShadersChangedNotify();
914 // get the shader flags
915 int getFlags() const {
916 return m_template.m_nFlags;
918 // get the transparency value
919 float getTrans() const {
920 return m_template.m_fTrans;
922 // test if it's a true shader, or a default shader created to wrap around a texture
923 bool IsDefault() const {
924 return string_empty( m_filename );
927 void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
928 BlendFunc getBlendFunc() const {
933 return m_template.m_Cull;
935 // get shader file name (ie the file where this one is defined)
936 const char* getShaderFileName() const {
939 // -----------------------------------------
942 m_pTexture = evaluateTexture( m_template.m_textureName, m_template.m_params, m_args );
944 if ( m_pTexture->texture_number == 0 ) {
945 m_notfound = m_pTexture;
948 StringOutputStream name( 256 );
949 name << GlobalRadiant().getAppPath() << "bitmaps/" << ( IsDefault() ? "notex.png" : "shadernotex.png" );
950 m_pTexture = GlobalTexturesCache().capture( LoadImageCallback( 0, loadBitmap ), name.c_str() );
958 GlobalTexturesCache().release( m_pTexture );
960 if ( m_notfound != 0 ) {
961 GlobalTexturesCache().release( m_notfound );
967 void realiseLighting(){
968 if ( m_lightingEnabled ) {
969 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
970 if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) {
971 m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args );
972 loader = LoadImageCallback( &m_heightmapScale, loadHeightmap );
974 m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args );
975 m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader );
976 m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args );
977 m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args );
979 for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i )
981 m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) );
984 if ( m_layers.size() == 1 ) {
985 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
986 if ( !string_empty( blendFunc.second.c_str() ) ) {
987 m_blendFunc = BlendFunc(
988 evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ),
989 evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args )
994 const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args );
996 if ( string_equal_nocase( blend, "add" ) ) {
997 m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE );
999 else if ( string_equal_nocase( blend, "filter" ) ) {
1000 m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO );
1002 else if ( string_equal_nocase( blend, "blend" ) ) {
1003 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1007 globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n";
1014 void unrealiseLighting(){
1015 if ( m_lightingEnabled ) {
1016 GlobalTexturesCache().release( m_pDiffuse );
1017 GlobalTexturesCache().release( m_pBump );
1018 GlobalTexturesCache().release( m_pSpecular );
1020 GlobalTexturesCache().release( m_pLightFalloffImage );
1022 for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1024 GlobalTexturesCache().release( ( *i ).texture() );
1028 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1033 void setName( const char* name ){
1037 class MapLayer : public ShaderLayer
1039 qtexture_t* m_texture;
1040 BlendFunc m_blendFunc;
1041 bool m_clampToBorder;
1044 MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) :
1045 m_texture( texture ),
1046 m_blendFunc( blendFunc ),
1047 m_clampToBorder( false ),
1048 m_alphaTest( alphaTest ){
1050 qtexture_t* texture() const {
1053 BlendFunc blendFunc() const {
1056 bool clampToBorder() const {
1057 return m_clampToBorder;
1059 float alphaTest() const {
1064 static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){
1066 evaluateTexture( layerTemplate.texture(), params, args ),
1067 evaluateBlendFunc( layerTemplate.blendFunc(), params, args ),
1068 layerTemplate.clampToBorder(),
1069 evaluateFloat( layerTemplate.alphaTest(), params, args )
1073 typedef std::vector<MapLayer> MapLayers;
1076 const ShaderLayer* firstLayer() const {
1077 if ( m_layers.empty() ) {
1080 return &m_layers.front();
1082 void forEachLayer( const ShaderLayerCallback& callback ) const {
1083 for ( MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1089 qtexture_t* lightFalloffImage() const {
1090 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1091 return m_pLightFalloffImage;
1097 bool CShader::m_lightingEnabled = false;
1099 typedef SmartPointer<CShader> ShaderPointer;
1100 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1102 shaders_t g_ActiveShaders;
1104 static shaders_t::iterator g_ActiveShadersIterator;
1106 void ActiveShaders_IteratorBegin(){
1107 g_ActiveShadersIterator = g_ActiveShaders.begin();
1110 bool ActiveShaders_IteratorAtEnd(){
1111 return g_ActiveShadersIterator == g_ActiveShaders.end();
1114 IShader *ActiveShaders_IteratorCurrent(){
1115 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1118 void ActiveShaders_IteratorIncrement(){
1119 ++g_ActiveShadersIterator;
1122 void debug_check_shaders( shaders_t& shaders ){
1123 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1125 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1129 // will free all GL binded qtextures and shaders
1130 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1133 // empty the actives shaders list
1134 debug_check_shaders( g_ActiveShaders );
1135 g_ActiveShaders.clear();
1137 g_shaderTemplates.clear();
1138 g_shaderDefinitions.clear();
1139 g_ActiveShadersChangedNotify();
1142 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1143 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1144 m_textureName = m_Name.c_str();
1146 tokeniser.nextLine();
1148 // we need to read until we hit a balanced }
1152 tokeniser.nextLine();
1153 const char* token = tokeniser.getToken();
1159 if ( string_equal( token, "{" ) ) {
1163 else if ( string_equal( token, "}" ) ) {
1165 if ( depth < 0 ) { // underflow
1168 if ( depth == 0 ) { // end of shader
1176 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1177 m_nFlags |= QER_NOCARVE;
1179 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1180 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1181 m_nFlags |= QER_TRANS;
1183 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1184 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1186 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1187 const char* alphafunc = tokeniser.getToken();
1189 if ( alphafunc == 0 ) {
1190 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1194 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1195 m_AlphaFunc = IShader::eEqual;
1197 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1198 m_AlphaFunc = IShader::eGreater;
1200 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1201 m_AlphaFunc = IShader::eLess;
1203 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1204 m_AlphaFunc = IShader::eGEqual;
1206 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1207 m_AlphaFunc = IShader::eLEqual;
1211 m_AlphaFunc = IShader::eAlways;
1214 m_nFlags |= QER_ALPHATEST;
1216 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1218 else if ( string_equal_nocase( token, "cull" ) ) {
1219 const char* cull = tokeniser.getToken();
1222 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1226 if ( string_equal_nocase( cull, "none" )
1227 || string_equal_nocase( cull, "twosided" )
1228 || string_equal_nocase( cull, "disable" ) ) {
1229 m_Cull = IShader::eCullNone;
1231 else if ( string_equal_nocase( cull, "back" )
1232 || string_equal_nocase( cull, "backside" )
1233 || string_equal_nocase( cull, "backsided" ) ) {
1234 m_Cull = IShader::eCullBack;
1238 m_Cull = IShader::eCullBack;
1241 m_nFlags |= QER_CULL;
1243 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1244 const char* surfaceparm = tokeniser.getToken();
1246 if ( surfaceparm == 0 ) {
1247 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1251 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1252 m_nFlags |= QER_FOG;
1253 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1257 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1258 m_nFlags |= QER_NODRAW;
1260 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1261 m_nFlags |= QER_NONSOLID;
1263 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1264 m_nFlags |= QER_WATER;
1266 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1267 m_nFlags |= QER_LAVA;
1269 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1270 m_nFlags |= QER_AREAPORTAL;
1272 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1273 m_nFlags |= QER_CLIP;
1275 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1276 m_nFlags |= QER_BOTCLIP;
1289 TextureExpression m_texture;
1290 BlendFunc m_blendFunc;
1291 bool m_clampToBorder;
1293 float m_heightmapScale;
1295 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1299 std::list<CopiedString> g_shaderFilenames;
1301 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1302 g_shaderFilenames.push_back( filename );
1303 filename = g_shaderFilenames.back().c_str();
1304 tokeniser.nextLine();
1307 const char* token = tokeniser.getToken();
1313 if ( string_equal( token, "table" ) ) {
1314 if ( tokeniser.getToken() == 0 ) {
1315 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1318 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1323 const char* option = tokeniser.getToken();
1324 if ( string_equal( option, "{" ) ) {
1327 const char* value = tokeniser.getToken();
1328 if ( string_equal( value, "}" ) ) {
1333 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1342 if ( string_equal( token, "guide" ) ) {
1343 parseTemplateInstance( tokeniser, filename );
1347 if ( !string_equal( token, "material" )
1348 && !string_equal( token, "particle" )
1349 && !string_equal( token, "skin" ) ) {
1350 tokeniser.ungetToken();
1352 // first token should be the path + name.. (from base)
1354 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1356 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1357 shaderTemplate->setName( name.c_str() );
1359 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1361 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1362 ? shaderTemplate->parseQuake3( tokeniser )
1363 : shaderTemplate->parseDoom3( tokeniser );
1365 // do we already have this shader?
1366 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1368 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1374 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1382 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1383 tokeniser.nextLine();
1386 const char* token = tokeniser.getToken();
1392 if ( string_equal( token, "guide" ) ) {
1393 // first token should be the path + name.. (from base)
1394 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1395 shaderTemplate->parseTemplate( tokeniser );
1396 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1397 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1400 else if ( string_equal( token, "inlineGuide" ) ) {
1401 // skip entire inlineGuide definition
1402 std::size_t depth = 0;
1405 tokeniser.nextLine();
1406 token = tokeniser.getToken();
1407 if ( string_equal( token, "{" ) ) {
1410 else if ( string_equal( token, "}" ) ) {
1411 if ( --depth == 0 ) {
1420 void LoadShaderFile( const char* filename ){
1421 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1424 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1426 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1428 ParseShaderFile( tokeniser, filename );
1430 tokeniser.release();
1435 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1439 typedef FreeCaller1<const char*, LoadShaderFile> LoadShaderFileCaller;
1442 void loadGuideFile( const char* filename ){
1443 StringOutputStream fullname( 256 );
1444 fullname << "guides/" << filename;
1445 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1448 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1450 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1452 parseGuideFile( tokeniser, fullname.c_str() );
1454 tokeniser.release();
1459 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1463 typedef FreeCaller1<const char*, loadGuideFile> LoadGuideFileCaller;
1466 CShader* Try_Shader_ForName( const char* name ){
1468 shaders_t::iterator i = g_ActiveShaders.find( name );
1469 if ( i != g_ActiveShaders.end() ) {
1470 return ( *i ).second;
1473 // active shader was not found
1475 // find matching shader definition
1476 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1477 if ( i == g_shaderDefinitions.end() ) {
1478 // shader definition was not found
1480 // create new shader definition from default shader template
1481 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1482 shaderTemplate->CreateDefault( name );
1483 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1485 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1488 // create shader from existing definition
1489 ShaderPointer pShader( new CShader( ( *i ).second ) );
1490 pShader->setName( name );
1491 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1492 g_ActiveShadersChangedNotify();
1496 IShader *Shader_ForName( const char *name ){
1497 ASSERT_NOTNULL( name );
1499 IShader *pShader = Try_Shader_ForName( name );
1507 // the list of scripts/*.shader files we need to work with
1508 // those are listed in shaderlist file
1509 GSList *l_shaderfiles = 0;
1511 GSList* Shaders_getShaderFileList(){
1512 return l_shaderfiles;
1517 DumpUnreferencedShaders
1518 usefull function: dumps the list of .shader files that are not referenced to the console
1521 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1522 bool listed = false;
1524 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1526 if ( !strcmp( (char*)sh->data, filename ) ) {
1535 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1537 globalOutputStream() << "\t" << filename << "\n";
1540 typedef ReferenceCaller1<bool, const char*, IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1542 void DumpUnreferencedShaders(){
1543 bool bFound = false;
1544 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1547 void ShaderList_addShaderFile( const char* dirstring ){
1550 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1552 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1554 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1560 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1564 typedef FreeCaller1<const char*, ShaderList_addShaderFile> AddShaderFileCaller;
1570 build a CStringList of shader names
1573 void BuildShaderList( TextInputStream& shaderlist ){
1574 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1575 tokeniser.nextLine();
1576 const char* token = tokeniser.getToken();
1577 StringOutputStream shaderFile( 64 );
1578 while ( token != 0 )
1580 // each token should be a shader filename
1581 shaderFile << token << "." << g_shadersExtension;
1583 ShaderList_addShaderFile( shaderFile.c_str() );
1585 tokeniser.nextLine();
1586 token = tokeniser.getToken();
1590 tokeniser.release();
1593 void FreeShaderList(){
1594 while ( l_shaderfiles != 0 )
1596 free( l_shaderfiles->data );
1597 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1601 void ShaderList_addFromArchive( const char *archivename ){
1602 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1603 if ( string_empty( shaderpath ) ) {
1607 StringOutputStream shaderlist( 256 );
1608 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1610 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1612 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1614 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1615 BuildShaderList( file->getInputStream() );
1621 typedef FreeCaller1<const char *, ShaderList_addFromArchive> AddShaderListFromArchiveCaller;
1623 #include "stream/filestream.h"
1625 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1626 StringOutputStream absShaderList( 256 );
1627 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1628 if ( file_exists( absShaderList.c_str() ) ) {
1632 StringOutputStream directory( 256 );
1633 directory << enginePath << gamename << '/' << shaderPath;
1634 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1639 StringOutputStream defaultShaderList( 256 );
1640 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1641 if ( file_exists( defaultShaderList.c_str() ) ) {
1642 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1648 void Shaders_Load(){
1649 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1650 GlobalFileSystem().forEachFile( "guides/", "guide", LoadGuideFileCaller(), 0 );
1653 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1654 if ( !string_empty( shaderPath ) ) {
1655 StringOutputStream path( 256 );
1656 path << DirectoryCleaned( shaderPath );
1658 if ( g_useShaderList ) {
1659 // preload shader files that have been listed in shaderlist.txt
1660 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1661 const char* gamename = GlobalRadiant().getGameName();
1662 const char* enginePath = GlobalRadiant().getEnginePath();
1663 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1665 bool isMod = !string_equal( basegame, gamename );
1667 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1668 gamename = basegame;
1669 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1672 GlobalFileSystem().forEachArchive( AddShaderListFromArchiveCaller(), false, true );
1673 DumpUnreferencedShaders();
1677 GlobalFileSystem().forEachFile( path.c_str(), g_shadersExtension, AddShaderFileCaller(), 0 );
1680 GSList *lst = l_shaderfiles;
1681 StringOutputStream shadername( 256 );
1684 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1685 LoadShaderFile( shadername.c_str() );
1691 //StringPool_analyse(ShaderPool::instance());
1694 void Shaders_Free(){
1697 g_shaderFilenames.clear();
1700 ModuleObservers g_observers;
1702 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1703 bool Shaders_realised(){
1704 return g_shaders_unrealised == 0;
1706 void Shaders_Realise(){
1707 if ( --g_shaders_unrealised == 0 ) {
1709 g_observers.realise();
1712 void Shaders_Unrealise(){
1713 if ( ++g_shaders_unrealised == 1 ) {
1714 g_observers.unrealise();
1719 void Shaders_Refresh(){
1720 Shaders_Unrealise();
1724 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1731 Shaders_Unrealise();
1737 IShader* getShaderForName( const char* name ){
1738 return Shader_ForName( name );
1741 void foreachShaderName( const ShaderNameCallback& callback ){
1742 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1744 callback( ( *i ).first.c_str() );
1748 void beginActiveShadersIterator(){
1749 ActiveShaders_IteratorBegin();
1751 bool endActiveShadersIterator(){
1752 return ActiveShaders_IteratorAtEnd();
1754 IShader* dereferenceActiveShadersIterator(){
1755 return ActiveShaders_IteratorCurrent();
1757 void incrementActiveShadersIterator(){
1758 ActiveShaders_IteratorIncrement();
1760 void setActiveShadersChangedNotify( const Callback& notify ){
1761 g_ActiveShadersChangedNotify = notify;
1764 void attach( ModuleObserver& observer ){
1765 g_observers.attach( observer );
1767 void detach( ModuleObserver& observer ){
1768 g_observers.detach( observer );
1771 void setLightingEnabled( bool enabled ){
1772 if ( CShader::m_lightingEnabled != enabled ) {
1773 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1775 ( *i ).second->unrealiseLighting();
1777 CShader::m_lightingEnabled = enabled;
1778 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1780 ( *i ).second->realiseLighting();
1785 const char* getTexturePrefix() const {
1786 return g_texturePrefix;
1790 Quake3ShaderSystem g_Quake3ShaderSystem;
1792 ShaderSystem& GetShaderSystem(){
1793 return g_Quake3ShaderSystem;
1796 void Shaders_Construct(){
1797 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1799 void Shaders_Destroy(){
1800 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1802 if ( Shaders_realised() ) {