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();
127 const int kernelSize = 2;
128 KernelElement kernel_du[kernelSize] = {
132 KernelElement kernel_dv[kernelSize] = {
144 for ( KernelElement* i = kernel_du; i != kernel_du + kernelSize; ++i )
146 du += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
149 for ( KernelElement* i = kernel_dv; i != kernel_dv + kernelSize; ++i )
151 dv += ( getPixel( in, w, h, x + ( *i ).x, y + ( *i ).y )[0] / 255.0 ) * ( *i ).w;
154 float nx = -du * scale;
155 float ny = -dv * scale;
159 float norm = 1.0 / sqrt( nx * nx + ny * ny + nz * nz );
160 out[0] = float_to_integer( ( ( nx * norm ) + 1 ) * 127.5 );
161 out[1] = float_to_integer( ( ( ny * norm ) + 1 ) * 127.5 );
162 out[2] = float_to_integer( ( ( nz * norm ) + 1 ) * 127.5 );
175 Image* loadHeightmap( void* environment, const char* name ){
176 Image* heightmap = GlobalTexturesCache().loadImage( name );
177 if ( heightmap != 0 ) {
178 Image& normalmap = convertHeightmapToNormalmap( *heightmap, *reinterpret_cast<float*>( environment ) );
179 heightmap->release();
186 Image* loadSpecial( void* environment, const char* name ){
187 if ( *name == '_' ) { // special image
188 StringOutputStream bitmapName( 256 );
189 bitmapName << GlobalRadiant().getAppPath() << "bitmaps/" << name + 1 << ".png";
190 Image* image = loadBitmap( environment, bitmapName.c_str() );
195 return GlobalTexturesCache().loadImage( name );
198 class ShaderPoolContext
201 typedef Static<StringPool, ShaderPoolContext> ShaderPool;
202 typedef PooledString<ShaderPool> ShaderString;
203 typedef ShaderString ShaderVariable;
204 typedef ShaderString ShaderValue;
205 typedef CopiedString TextureExpression;
207 // clean a texture name to the qtexture_t name format we use internally
208 // NOTE: case sensitivity: the engine is case sensitive. we store the shader name with case information and save with case
209 // information as well. but we assume there won't be any case conflict and so when doing lookups based on shader name,
210 // we compare as case insensitive. That is Radiant is case insensitive, but knows that the engine is case sensitive.
211 //++timo FIXME: we need to put code somewhere to detect when two shaders that are case insensitive equal are present
212 template<typename StringType>
213 void parseTextureName( StringType& name, const char* token ){
214 StringOutputStream cleaned( 256 );
215 cleaned << PathCleaned( token );
216 name = CopiedString( StringRange( cleaned.c_str(), path_get_filename_base_end( cleaned.c_str() ) ) ).c_str(); // remove extension
219 bool Tokeniser_parseTextureName( Tokeniser& tokeniser, TextureExpression& name ){
220 const char* token = tokeniser.getToken();
222 Tokeniser_unexpectedError( tokeniser, token, "#texture-name" );
225 parseTextureName( name, token );
229 bool Tokeniser_parseShaderName( Tokeniser& tokeniser, CopiedString& name ){
230 const char* token = tokeniser.getToken();
232 Tokeniser_unexpectedError( tokeniser, token, "#shader-name" );
235 parseTextureName( name, token );
239 bool Tokeniser_parseString( Tokeniser& tokeniser, ShaderString& string ){
240 const char* token = tokeniser.getToken();
242 Tokeniser_unexpectedError( tokeniser, token, "#string" );
251 typedef std::list<ShaderVariable> ShaderParameters;
252 typedef std::list<ShaderVariable> ShaderArguments;
254 typedef std::pair<ShaderVariable, ShaderVariable> BlendFuncExpression;
258 std::size_t m_refcount;
262 ShaderParameters m_params;
264 TextureExpression m_textureName;
265 TextureExpression m_diffuse;
266 TextureExpression m_bump;
267 ShaderValue m_heightmapScale;
268 TextureExpression m_specular;
269 TextureExpression m_lightFalloffImage;
275 IShader::EAlphaFunc m_AlphaFunc;
278 IShader::ECull m_Cull;
290 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
291 if ( --m_refcount == 0 ) {
296 std::size_t refcount(){
300 const char* getName() const {
301 return m_Name.c_str();
303 void setName( const char* name ){
307 // -----------------------------------------
309 bool parseDoom3( Tokeniser& tokeniser );
310 bool parseQuake3( Tokeniser& tokeniser );
311 bool parseTemplate( Tokeniser& tokeniser );
314 void CreateDefault( const char *name ){
315 if ( g_enableDefaultShaders ) {
316 m_textureName = name;
326 class MapLayerTemplate
328 TextureExpression m_texture;
329 BlendFuncExpression m_blendFunc;
330 bool m_clampToBorder;
331 ShaderValue m_alphaTest;
333 MapLayerTemplate( const TextureExpression& texture, const BlendFuncExpression& blendFunc, bool clampToBorder, const ShaderValue& alphaTest ) :
334 m_texture( texture ),
335 m_blendFunc( blendFunc ),
336 m_clampToBorder( false ),
337 m_alphaTest( alphaTest ){
339 const TextureExpression& texture() const {
342 const BlendFuncExpression& blendFunc() const {
345 bool clampToBorder() const {
346 return m_clampToBorder;
348 const ShaderValue& alphaTest() const {
352 typedef std::vector<MapLayerTemplate> MapLayers;
357 bool Doom3Shader_parseHeightmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
358 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
359 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
360 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
361 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, heightmapScale ) );
362 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
366 bool Doom3Shader_parseAddnormals( Tokeniser& tokeniser, TextureExpression& bump ){
367 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
368 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, bump ) );
369 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "," ) );
370 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "heightmap" ) );
371 TextureExpression heightmapName;
372 ShaderValue heightmapScale;
373 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, heightmapName, heightmapScale ) );
374 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
378 bool Doom3Shader_parseBumpmap( Tokeniser& tokeniser, TextureExpression& bump, ShaderValue& heightmapScale ){
379 const char* token = tokeniser.getToken();
381 Tokeniser_unexpectedError( tokeniser, token, "#bumpmap" );
384 if ( string_equal( token, "heightmap" ) ) {
385 RETURN_FALSE_IF_FAIL( Doom3Shader_parseHeightmap( tokeniser, bump, heightmapScale ) );
387 else if ( string_equal( token, "addnormals" ) ) {
388 RETURN_FALSE_IF_FAIL( Doom3Shader_parseAddnormals( tokeniser, bump ) );
392 parseTextureName( bump, token );
410 TextureExpression m_texture;
411 BlendFuncExpression m_blendFunc;
412 bool m_clampToBorder;
413 ShaderValue m_alphaTest;
414 ShaderValue m_heightmapScale;
416 LayerTemplate() : m_type( LAYER_NONE ), m_blendFunc( "GL_ONE", "GL_ZERO" ), m_clampToBorder( false ), m_alphaTest( "-1" ), m_heightmapScale( "0" ){
420 bool parseShaderParameters( Tokeniser& tokeniser, ShaderParameters& params ){
421 Tokeniser_parseToken( tokeniser, "(" );
424 const char* param = tokeniser.getToken();
425 if ( string_equal( param, ")" ) ) {
428 params.push_back( param );
429 const char* comma = tokeniser.getToken();
430 if ( string_equal( comma, ")" ) ) {
433 if ( !string_equal( comma, "," ) ) {
434 Tokeniser_unexpectedError( tokeniser, comma, "," );
441 bool ShaderTemplate::parseTemplate( Tokeniser& tokeniser ){
442 m_Name = tokeniser.getToken();
443 if ( !parseShaderParameters( tokeniser, m_params ) ) {
444 globalErrorStream() << "shader template: " << makeQuoted( m_Name.c_str() ) << ": parameter parse failed\n";
448 return parseDoom3( tokeniser );
451 bool ShaderTemplate::parseDoom3( Tokeniser& tokeniser ){
452 LayerTemplate currentLayer;
455 // we need to read until we hit a balanced }
459 tokeniser.nextLine();
460 const char* token = tokeniser.getToken();
466 if ( string_equal( token, "{" ) ) {
470 else if ( string_equal( token, "}" ) ) {
472 if ( depth < 0 ) { // error
475 if ( depth == 0 ) { // end of shader
478 if ( depth == 1 ) { // end of layer
479 if ( currentLayer.m_type == LAYER_DIFFUSEMAP ) {
480 m_diffuse = currentLayer.m_texture;
482 else if ( currentLayer.m_type == LAYER_BUMPMAP ) {
483 m_bump = currentLayer.m_texture;
485 else if ( currentLayer.m_type == LAYER_SPECULARMAP ) {
486 m_specular = currentLayer.m_texture;
488 else if ( !string_empty( currentLayer.m_texture.c_str() ) ) {
489 m_layers.push_back( MapLayerTemplate(
490 currentLayer.m_texture.c_str(),
491 currentLayer.m_blendFunc,
492 currentLayer.m_clampToBorder,
493 currentLayer.m_alphaTest
496 currentLayer.m_type = LAYER_NONE;
497 currentLayer.m_texture = "";
502 if ( depth == 2 ) { // in layer
503 if ( string_equal_nocase( token, "blend" ) ) {
504 const char* blend = tokeniser.getToken();
507 Tokeniser_unexpectedError( tokeniser, blend, "#blend" );
511 if ( string_equal_nocase( blend, "diffusemap" ) ) {
512 currentLayer.m_type = LAYER_DIFFUSEMAP;
514 else if ( string_equal_nocase( blend, "bumpmap" ) ) {
515 currentLayer.m_type = LAYER_BUMPMAP;
517 else if ( string_equal_nocase( blend, "specularmap" ) ) {
518 currentLayer.m_type = LAYER_SPECULARMAP;
522 currentLayer.m_blendFunc.first = blend;
524 const char* comma = tokeniser.getToken();
527 Tokeniser_unexpectedError( tokeniser, comma, "#comma" );
531 if ( string_equal( comma, "," ) ) {
532 RETURN_FALSE_IF_FAIL( Tokeniser_parseString( tokeniser, currentLayer.m_blendFunc.second ) );
536 currentLayer.m_blendFunc.second = "";
537 tokeniser.ungetToken();
541 else if ( string_equal_nocase( token, "map" ) ) {
542 if ( currentLayer.m_type == LAYER_BUMPMAP ) {
543 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, currentLayer.m_texture, currentLayer.m_heightmapScale ) );
547 const char* map = tokeniser.getToken();
550 Tokeniser_unexpectedError( tokeniser, map, "#map" );
554 if ( string_equal( map, "makealpha" ) ) {
555 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
556 const char* texture = tokeniser.getToken();
557 if ( texture == 0 ) {
558 Tokeniser_unexpectedError( tokeniser, texture, "#texture" );
561 currentLayer.m_texture = texture;
562 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
566 parseTextureName( currentLayer.m_texture, map );
570 else if ( string_equal_nocase( token, "zeroclamp" ) ) {
571 currentLayer.m_clampToBorder = true;
574 else if ( depth == 1 ) {
575 if ( string_equal_nocase( token, "qer_editorimage" ) ) {
576 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
578 else if ( string_equal_nocase( token, "qer_trans" ) ) {
579 m_fTrans = string_read_float( tokeniser.getToken() );
580 m_nFlags |= QER_TRANS;
582 else if ( string_equal_nocase( token, "translucent" ) ) {
584 m_nFlags |= QER_TRANS;
586 else if ( string_equal( token, "DECAL_MACRO" ) ) {
588 m_nFlags |= QER_TRANS;
590 else if ( string_equal_nocase( token, "bumpmap" ) ) {
591 RETURN_FALSE_IF_FAIL( Doom3Shader_parseBumpmap( tokeniser, m_bump, m_heightmapScale ) );
593 else if ( string_equal_nocase( token, "diffusemap" ) ) {
594 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_diffuse ) );
596 else if ( string_equal_nocase( token, "specularmap" ) ) {
597 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_specular ) );
599 else if ( string_equal_nocase( token, "twosided" ) ) {
600 m_Cull = IShader::eCullNone;
601 m_nFlags |= QER_CULL;
603 else if ( string_equal_nocase( token, "nodraw" ) ) {
604 m_nFlags |= QER_NODRAW;
606 else if ( string_equal_nocase( token, "nonsolid" ) ) {
607 m_nFlags |= QER_NONSOLID;
609 else if ( string_equal_nocase( token, "liquid" ) ) {
610 m_nFlags |= QER_WATER;
612 else if ( string_equal_nocase( token, "areaportal" ) ) {
613 m_nFlags |= QER_AREAPORTAL;
615 else if ( string_equal_nocase( token, "playerclip" )
616 || string_equal_nocase( token, "monsterclip" )
617 || string_equal_nocase( token, "ikclip" )
618 || string_equal_nocase( token, "moveableclip" ) ) {
619 m_nFlags |= QER_CLIP;
621 if ( string_equal_nocase( token, "fogLight" ) ) {
624 else if ( !isFog && string_equal_nocase( token, "lightFalloffImage" ) ) {
625 const char* lightFalloffImage = tokeniser.getToken();
626 if ( lightFalloffImage == 0 ) {
627 Tokeniser_unexpectedError( tokeniser, lightFalloffImage, "#lightFalloffImage" );
630 if ( string_equal_nocase( lightFalloffImage, "makeintensity" ) ) {
631 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, "(" ) );
632 TextureExpression name;
633 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, name ) );
634 m_lightFalloffImage = name;
635 RETURN_FALSE_IF_FAIL( Tokeniser_parseToken( tokeniser, ")" ) );
639 m_lightFalloffImage = lightFalloffImage;
645 if ( string_empty( m_textureName.c_str() ) ) {
646 m_textureName = m_diffuse;
652 typedef SmartPointer<ShaderTemplate> ShaderTemplatePointer;
653 typedef std::map<CopiedString, ShaderTemplatePointer> ShaderTemplateMap;
655 ShaderTemplateMap g_shaders;
656 ShaderTemplateMap g_shaderTemplates;
658 ShaderTemplate* findTemplate( const char* name ){
659 ShaderTemplateMap::iterator i = g_shaderTemplates.find( name );
660 if ( i != g_shaderTemplates.end() ) {
661 return ( *i ).second.get();
666 class ShaderDefinition
669 ShaderDefinition( ShaderTemplate* shaderTemplate, const ShaderArguments& args, const char* filename )
670 : shaderTemplate( shaderTemplate ), args( args ), filename( filename ){
672 ShaderTemplate* shaderTemplate;
673 ShaderArguments args;
674 const char* filename;
677 typedef std::map<CopiedString, ShaderDefinition> ShaderDefinitionMap;
679 ShaderDefinitionMap g_shaderDefinitions;
681 bool parseTemplateInstance( Tokeniser& tokeniser, const char* filename ){
683 RETURN_FALSE_IF_FAIL( Tokeniser_parseShaderName( tokeniser, name ) );
684 const char* templateName = tokeniser.getToken();
685 ShaderTemplate* shaderTemplate = findTemplate( templateName );
686 if ( shaderTemplate == 0 ) {
687 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": shader template not found: " << makeQuoted( templateName ) << "\n";
690 ShaderArguments args;
691 if ( !parseShaderParameters( tokeniser, args ) ) {
692 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": argument parse failed\n";
696 if ( shaderTemplate != 0 ) {
697 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate, args, filename ) ) ).second ) {
698 globalErrorStream() << "shader instance: " << makeQuoted( name.c_str() ) << ": already exists, second definition ignored\n";
705 const char* evaluateShaderValue( const char* value, const ShaderParameters& params, const ShaderArguments& args ){
706 ShaderArguments::const_iterator j = args.begin();
707 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
709 const char* other = ( *i ).c_str();
710 if ( string_equal( value, other ) ) {
711 return ( *j ).c_str();
717 ///\todo BlendFunc parsing
718 BlendFunc evaluateBlendFunc( const BlendFuncExpression& blendFunc, const ShaderParameters& params, const ShaderArguments& args ){
719 return BlendFunc( BLEND_ONE, BLEND_ZERO );
722 qtexture_t* evaluateTexture( const TextureExpression& texture, const ShaderParameters& params, const ShaderArguments& args, const LoadImageCallback& loader = GlobalTexturesCache().defaultLoader() ){
723 StringOutputStream result( 64 );
724 const char* expression = texture.c_str();
725 const char* end = expression + string_length( expression );
726 if ( !string_empty( expression ) ) {
729 const char* best = end;
730 const char* bestParam = 0;
731 const char* bestArg = 0;
732 ShaderArguments::const_iterator j = args.begin();
733 for ( ShaderParameters::const_iterator i = params.begin(); i != params.end(); ++i, ++j )
735 const char* found = strstr( expression, ( *i ).c_str() );
736 if ( found != 0 && found < best ) {
738 bestParam = ( *i ).c_str();
739 bestArg = ( *j ).c_str();
743 result << StringRange( expression, best );
744 result << PathCleaned( bestArg );
745 expression = best + string_length( bestParam );
752 result << expression;
754 return GlobalTexturesCache().capture( loader, result.c_str() );
757 float evaluateFloat( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
758 const char* result = evaluateShaderValue( value.c_str(), params, args );
760 if ( !string_parse_float( result, f ) ) {
761 globalErrorStream() << "parsing float value failed: " << makeQuoted( result ) << "\n";
766 BlendFactor evaluateBlendFactor( const ShaderValue& value, const ShaderParameters& params, const ShaderArguments& args ){
767 const char* result = evaluateShaderValue( value.c_str(), params, args );
769 if ( string_equal_nocase( result, "gl_zero" ) ) {
772 if ( string_equal_nocase( result, "gl_one" ) ) {
775 if ( string_equal_nocase( result, "gl_src_color" ) ) {
776 return BLEND_SRC_COLOUR;
778 if ( string_equal_nocase( result, "gl_one_minus_src_color" ) ) {
779 return BLEND_ONE_MINUS_SRC_COLOUR;
781 if ( string_equal_nocase( result, "gl_src_alpha" ) ) {
782 return BLEND_SRC_ALPHA;
784 if ( string_equal_nocase( result, "gl_one_minus_src_alpha" ) ) {
785 return BLEND_ONE_MINUS_SRC_ALPHA;
787 if ( string_equal_nocase( result, "gl_dst_color" ) ) {
788 return BLEND_DST_COLOUR;
790 if ( string_equal_nocase( result, "gl_one_minus_dst_color" ) ) {
791 return BLEND_ONE_MINUS_DST_COLOUR;
793 if ( string_equal_nocase( result, "gl_dst_alpha" ) ) {
794 return BLEND_DST_ALPHA;
796 if ( string_equal_nocase( result, "gl_one_minus_dst_alpha" ) ) {
797 return BLEND_ONE_MINUS_DST_ALPHA;
799 if ( string_equal_nocase( result, "gl_src_alpha_saturate" ) ) {
800 return BLEND_SRC_ALPHA_SATURATE;
803 globalErrorStream() << "parsing blend-factor value failed: " << makeQuoted( result ) << "\n";
807 class CShader : public IShader
809 std::size_t m_refcount;
811 const ShaderTemplate& m_template;
812 const ShaderArguments& m_args;
813 const char* m_filename;
814 // name is shader-name, otherwise texture-name (if not a real shader)
817 qtexture_t* m_pTexture;
818 qtexture_t* m_notfound;
819 qtexture_t* m_pDiffuse;
820 float m_heightmapScale;
822 qtexture_t* m_pSpecular;
823 qtexture_t* m_pLightFalloffImage;
824 BlendFunc m_blendFunc;
830 static bool m_lightingEnabled;
832 CShader( const ShaderDefinition& definition ) :
834 m_template( *definition.shaderTemplate ),
835 m_args( definition.args ),
836 m_filename( definition.filename ),
837 m_blendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA ),
851 ASSERT_MESSAGE( m_refcount == 0, "deleting active shader" );
854 // IShaders implementation -----------------
859 ASSERT_MESSAGE( m_refcount != 0, "shader reference-count going below zero" );
860 if ( --m_refcount == 0 ) {
865 std::size_t refcount(){
869 // get/set the qtexture_t* Radiant uses to represent this shader object
870 qtexture_t* getTexture() const {
873 qtexture_t* getDiffuse() const {
876 qtexture_t* getBump() const {
879 qtexture_t* getSpecular() const {
883 const char* getName() const {
884 return m_Name.c_str();
886 bool IsInUse() const {
889 void SetInUse( bool bInUse ){
891 g_ActiveShadersChangedNotify();
893 // get the shader flags
894 int getFlags() const {
895 return m_template.m_nFlags;
897 // get the transparency value
898 float getTrans() const {
899 return m_template.m_fTrans;
901 // test if it's a true shader, or a default shader created to wrap around a texture
902 bool IsDefault() const {
903 return string_empty( m_filename );
906 void getAlphaFunc( EAlphaFunc *func, float *ref ) { *func = m_template.m_AlphaFunc; *ref = m_template.m_AlphaRef; };
907 BlendFunc getBlendFunc() const {
912 return m_template.m_Cull;
914 // get shader file name (ie the file where this one is defined)
915 const char* getShaderFileName() const {
918 // -----------------------------------------
921 m_pTexture = evaluateTexture( m_template.m_textureName, m_template.m_params, m_args );
923 if ( m_pTexture->texture_number == 0 ) {
924 m_notfound = m_pTexture;
927 StringOutputStream name( 256 );
928 name << GlobalRadiant().getAppPath() << "bitmaps/" << ( IsDefault() ? "notex.png" : "shadernotex.png" );
929 m_pTexture = GlobalTexturesCache().capture( LoadImageCallback( 0, loadBitmap ), name.c_str() );
937 GlobalTexturesCache().release( m_pTexture );
939 if ( m_notfound != 0 ) {
940 GlobalTexturesCache().release( m_notfound );
946 void realiseLighting(){
947 if ( m_lightingEnabled ) {
948 LoadImageCallback loader = GlobalTexturesCache().defaultLoader();
949 if ( !string_empty( m_template.m_heightmapScale.c_str() ) ) {
950 m_heightmapScale = evaluateFloat( m_template.m_heightmapScale, m_template.m_params, m_args );
951 loader = LoadImageCallback( &m_heightmapScale, loadHeightmap );
953 m_pDiffuse = evaluateTexture( m_template.m_diffuse, m_template.m_params, m_args );
954 m_pBump = evaluateTexture( m_template.m_bump, m_template.m_params, m_args, loader );
955 m_pSpecular = evaluateTexture( m_template.m_specular, m_template.m_params, m_args );
956 m_pLightFalloffImage = evaluateTexture( m_template.m_lightFalloffImage, m_template.m_params, m_args );
958 for ( ShaderTemplate::MapLayers::const_iterator i = m_template.m_layers.begin(); i != m_template.m_layers.end(); ++i )
960 m_layers.push_back( evaluateLayer( *i, m_template.m_params, m_args ) );
963 if ( m_layers.size() == 1 ) {
964 const BlendFuncExpression& blendFunc = m_template.m_layers.front().blendFunc();
965 if ( !string_empty( blendFunc.second.c_str() ) ) {
966 m_blendFunc = BlendFunc(
967 evaluateBlendFactor( blendFunc.first.c_str(), m_template.m_params, m_args ),
968 evaluateBlendFactor( blendFunc.second.c_str(), m_template.m_params, m_args )
973 const char* blend = evaluateShaderValue( blendFunc.first.c_str(), m_template.m_params, m_args );
975 if ( string_equal_nocase( blend, "add" ) ) {
976 m_blendFunc = BlendFunc( BLEND_ONE, BLEND_ONE );
978 else if ( string_equal_nocase( blend, "filter" ) ) {
979 m_blendFunc = BlendFunc( BLEND_DST_COLOUR, BLEND_ZERO );
981 else if ( string_equal_nocase( blend, "blend" ) ) {
982 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
986 globalErrorStream() << "parsing blend value failed: " << makeQuoted( blend ) << "\n";
993 void unrealiseLighting(){
994 if ( m_lightingEnabled ) {
995 GlobalTexturesCache().release( m_pDiffuse );
996 GlobalTexturesCache().release( m_pBump );
997 GlobalTexturesCache().release( m_pSpecular );
999 GlobalTexturesCache().release( m_pLightFalloffImage );
1001 for ( MapLayers::iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1003 GlobalTexturesCache().release( ( *i ).texture() );
1007 m_blendFunc = BlendFunc( BLEND_SRC_ALPHA, BLEND_ONE_MINUS_SRC_ALPHA );
1012 void setName( const char* name ){
1016 class MapLayer : public ShaderLayer
1018 qtexture_t* m_texture;
1019 BlendFunc m_blendFunc;
1020 bool m_clampToBorder;
1023 MapLayer( qtexture_t* texture, BlendFunc blendFunc, bool clampToBorder, float alphaTest ) :
1024 m_texture( texture ),
1025 m_blendFunc( blendFunc ),
1026 m_clampToBorder( false ),
1027 m_alphaTest( alphaTest ){
1029 qtexture_t* texture() const {
1032 BlendFunc blendFunc() const {
1035 bool clampToBorder() const {
1036 return m_clampToBorder;
1038 float alphaTest() const {
1043 static MapLayer evaluateLayer( const ShaderTemplate::MapLayerTemplate& layerTemplate, const ShaderParameters& params, const ShaderArguments& args ){
1045 evaluateTexture( layerTemplate.texture(), params, args ),
1046 evaluateBlendFunc( layerTemplate.blendFunc(), params, args ),
1047 layerTemplate.clampToBorder(),
1048 evaluateFloat( layerTemplate.alphaTest(), params, args )
1052 typedef std::vector<MapLayer> MapLayers;
1055 const ShaderLayer* firstLayer() const {
1056 if ( m_layers.empty() ) {
1059 return &m_layers.front();
1061 void forEachLayer( const ShaderLayerCallback& callback ) const {
1062 for ( MapLayers::const_iterator i = m_layers.begin(); i != m_layers.end(); ++i )
1068 qtexture_t* lightFalloffImage() const {
1069 if ( !string_empty( m_template.m_lightFalloffImage.c_str() ) ) {
1070 return m_pLightFalloffImage;
1076 bool CShader::m_lightingEnabled = false;
1078 typedef SmartPointer<CShader> ShaderPointer;
1079 typedef std::map<CopiedString, ShaderPointer, shader_less_t> shaders_t;
1081 shaders_t g_ActiveShaders;
1083 static shaders_t::iterator g_ActiveShadersIterator;
1085 void ActiveShaders_IteratorBegin(){
1086 g_ActiveShadersIterator = g_ActiveShaders.begin();
1089 bool ActiveShaders_IteratorAtEnd(){
1090 return g_ActiveShadersIterator == g_ActiveShaders.end();
1093 IShader *ActiveShaders_IteratorCurrent(){
1094 return static_cast<CShader*>( g_ActiveShadersIterator->second );
1097 void ActiveShaders_IteratorIncrement(){
1098 ++g_ActiveShadersIterator;
1101 void debug_check_shaders( shaders_t& shaders ){
1102 for ( shaders_t::iterator i = shaders.begin(); i != shaders.end(); ++i )
1104 ASSERT_MESSAGE( i->second->refcount() == 1, "orphan shader still referenced" );
1108 // will free all GL binded qtextures and shaders
1109 // NOTE: doesn't make much sense out of Radiant exit or called during a reload
1112 // empty the actives shaders list
1113 debug_check_shaders( g_ActiveShaders );
1114 g_ActiveShaders.clear();
1116 g_shaderTemplates.clear();
1117 g_shaderDefinitions.clear();
1118 g_ActiveShadersChangedNotify();
1121 bool ShaderTemplate::parseQuake3( Tokeniser& tokeniser ){
1122 // name of the qtexture_t we'll use to represent this shader (this one has the "textures\" before)
1123 m_textureName = m_Name.c_str();
1125 tokeniser.nextLine();
1127 // we need to read until we hit a balanced }
1131 tokeniser.nextLine();
1132 const char* token = tokeniser.getToken();
1138 if ( string_equal( token, "{" ) ) {
1142 else if ( string_equal( token, "}" ) ) {
1144 if ( depth < 0 ) { // underflow
1147 if ( depth == 0 ) { // end of shader
1155 if ( string_equal_nocase( token, "qer_nocarve" ) ) {
1156 m_nFlags |= QER_NOCARVE;
1158 else if ( string_equal_nocase( token, "qer_trans" ) ) {
1159 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_fTrans ) );
1160 m_nFlags |= QER_TRANS;
1162 else if ( string_equal_nocase( token, "qer_editorimage" ) ) {
1163 RETURN_FALSE_IF_FAIL( Tokeniser_parseTextureName( tokeniser, m_textureName ) );
1165 else if ( string_equal_nocase( token, "qer_alphafunc" ) ) {
1166 const char* alphafunc = tokeniser.getToken();
1168 if ( alphafunc == 0 ) {
1169 Tokeniser_unexpectedError( tokeniser, alphafunc, "#alphafunc" );
1173 if ( string_equal_nocase( alphafunc, "equal" ) ) {
1174 m_AlphaFunc = IShader::eEqual;
1176 else if ( string_equal_nocase( alphafunc, "greater" ) ) {
1177 m_AlphaFunc = IShader::eGreater;
1179 else if ( string_equal_nocase( alphafunc, "less" ) ) {
1180 m_AlphaFunc = IShader::eLess;
1182 else if ( string_equal_nocase( alphafunc, "gequal" ) ) {
1183 m_AlphaFunc = IShader::eGEqual;
1185 else if ( string_equal_nocase( alphafunc, "lequal" ) ) {
1186 m_AlphaFunc = IShader::eLEqual;
1190 m_AlphaFunc = IShader::eAlways;
1193 m_nFlags |= QER_ALPHATEST;
1195 RETURN_FALSE_IF_FAIL( Tokeniser_getFloat( tokeniser, m_AlphaRef ) );
1197 else if ( string_equal_nocase( token, "cull" ) ) {
1198 const char* cull = tokeniser.getToken();
1201 Tokeniser_unexpectedError( tokeniser, cull, "#cull" );
1205 if ( string_equal_nocase( cull, "none" )
1206 || string_equal_nocase( cull, "twosided" )
1207 || string_equal_nocase( cull, "disable" ) ) {
1208 m_Cull = IShader::eCullNone;
1210 else if ( string_equal_nocase( cull, "back" )
1211 || string_equal_nocase( cull, "backside" )
1212 || string_equal_nocase( cull, "backsided" ) ) {
1213 m_Cull = IShader::eCullBack;
1217 m_Cull = IShader::eCullBack;
1220 m_nFlags |= QER_CULL;
1222 else if ( string_equal_nocase( token, "surfaceparm" ) ) {
1223 const char* surfaceparm = tokeniser.getToken();
1225 if ( surfaceparm == 0 ) {
1226 Tokeniser_unexpectedError( tokeniser, surfaceparm, "#surfaceparm" );
1230 if ( string_equal_nocase( surfaceparm, "fog" ) ) {
1231 m_nFlags |= QER_FOG;
1232 if ( m_fTrans == 1.0f ) { // has not been explicitly set by qer_trans
1236 else if ( string_equal_nocase( surfaceparm, "nodraw" ) ) {
1237 m_nFlags |= QER_NODRAW;
1239 else if ( string_equal_nocase( surfaceparm, "nonsolid" ) ) {
1240 m_nFlags |= QER_NONSOLID;
1242 else if ( string_equal_nocase( surfaceparm, "water" ) ) {
1243 m_nFlags |= QER_WATER;
1245 else if ( string_equal_nocase( surfaceparm, "lava" ) ) {
1246 m_nFlags |= QER_LAVA;
1248 else if ( string_equal_nocase( surfaceparm, "areaportal" ) ) {
1249 m_nFlags |= QER_AREAPORTAL;
1251 else if ( string_equal_nocase( surfaceparm, "playerclip" ) ) {
1252 m_nFlags |= QER_CLIP;
1254 else if ( string_equal_nocase( surfaceparm, "botclip" ) ) {
1255 m_nFlags |= QER_BOTCLIP;
1268 TextureExpression m_texture;
1269 BlendFunc m_blendFunc;
1270 bool m_clampToBorder;
1272 float m_heightmapScale;
1274 Layer() : m_type( LAYER_NONE ), m_blendFunc( BLEND_ONE, BLEND_ZERO ), m_clampToBorder( false ), m_alphaTest( -1 ), m_heightmapScale( 0 ){
1278 std::list<CopiedString> g_shaderFilenames;
1280 void ParseShaderFile( Tokeniser& tokeniser, const char* filename ){
1281 g_shaderFilenames.push_back( filename );
1282 filename = g_shaderFilenames.back().c_str();
1283 tokeniser.nextLine();
1286 const char* token = tokeniser.getToken();
1292 if ( string_equal( token, "table" ) ) {
1293 if ( tokeniser.getToken() == 0 ) {
1294 Tokeniser_unexpectedError( tokeniser, 0, "#table-name" );
1297 if ( !Tokeniser_parseToken( tokeniser, "{" ) ) {
1302 const char* option = tokeniser.getToken();
1303 if ( string_equal( option, "{" ) ) {
1306 const char* value = tokeniser.getToken();
1307 if ( string_equal( value, "}" ) ) {
1312 if ( !Tokeniser_parseToken( tokeniser, "}" ) ) {
1321 if ( string_equal( token, "guide" ) ) {
1322 parseTemplateInstance( tokeniser, filename );
1326 if ( !string_equal( token, "material" )
1327 && !string_equal( token, "particle" )
1328 && !string_equal( token, "skin" ) ) {
1329 tokeniser.ungetToken();
1331 // first token should be the path + name.. (from base)
1333 if ( !Tokeniser_parseShaderName( tokeniser, name ) ) {
1335 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1336 shaderTemplate->setName( name.c_str() );
1338 g_shaders.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1340 bool result = ( g_shaderLanguage == SHADERLANGUAGE_QUAKE3 )
1341 ? shaderTemplate->parseQuake3( tokeniser )
1342 : shaderTemplate->parseDoom3( tokeniser );
1344 // do we already have this shader?
1345 if ( !g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( shaderTemplate->getName(), ShaderDefinition( shaderTemplate.get(), ShaderArguments(), filename ) ) ).second ) {
1347 globalOutputStream() << "WARNING: shader " << shaderTemplate->getName() << " is already in memory, definition in " << filename << " ignored.\n";
1353 globalErrorStream() << "Error parsing shader " << shaderTemplate->getName() << "\n";
1361 void parseGuideFile( Tokeniser& tokeniser, const char* filename ){
1362 tokeniser.nextLine();
1365 const char* token = tokeniser.getToken();
1371 if ( string_equal( token, "guide" ) ) {
1372 // first token should be the path + name.. (from base)
1373 ShaderTemplatePointer shaderTemplate( new ShaderTemplate );
1374 shaderTemplate->parseTemplate( tokeniser );
1375 if ( !g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) ).second ) {
1376 globalErrorStream() << "guide " << makeQuoted( shaderTemplate->getName() ) << ": already defined, second definition ignored\n";
1379 else if ( string_equal( token, "inlineGuide" ) ) {
1380 // skip entire inlineGuide definition
1381 std::size_t depth = 0;
1384 tokeniser.nextLine();
1385 token = tokeniser.getToken();
1386 if ( string_equal( token, "{" ) ) {
1389 else if ( string_equal( token, "}" ) ) {
1390 if ( --depth == 0 ) {
1399 void LoadShaderFile( const char* filename ){
1400 ArchiveTextFile* file = GlobalFileSystem().openTextFile( filename );
1403 globalOutputStream() << "Parsing shaderfile " << filename << "\n";
1405 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1407 ParseShaderFile( tokeniser, filename );
1409 tokeniser.release();
1414 globalOutputStream() << "Unable to read shaderfile " << filename << "\n";
1418 typedef FreeCaller1<const char*, LoadShaderFile> LoadShaderFileCaller;
1421 void loadGuideFile( const char* filename ){
1422 StringOutputStream fullname( 256 );
1423 fullname << "guides/" << filename;
1424 ArchiveTextFile* file = GlobalFileSystem().openTextFile( fullname.c_str() );
1427 globalOutputStream() << "Parsing guide file " << fullname.c_str() << "\n";
1429 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewScriptTokeniser( file->getInputStream() );
1431 parseGuideFile( tokeniser, fullname.c_str() );
1433 tokeniser.release();
1438 globalOutputStream() << "Unable to read guide file " << fullname.c_str() << "\n";
1442 typedef FreeCaller1<const char*, loadGuideFile> LoadGuideFileCaller;
1445 CShader* Try_Shader_ForName( const char* name ){
1447 shaders_t::iterator i = g_ActiveShaders.find( name );
1448 if ( i != g_ActiveShaders.end() ) {
1449 return ( *i ).second;
1452 // active shader was not found
1454 // find matching shader definition
1455 ShaderDefinitionMap::iterator i = g_shaderDefinitions.find( name );
1456 if ( i == g_shaderDefinitions.end() ) {
1457 // shader definition was not found
1459 // create new shader definition from default shader template
1460 ShaderTemplatePointer shaderTemplate( new ShaderTemplate() );
1461 shaderTemplate->CreateDefault( name );
1462 g_shaderTemplates.insert( ShaderTemplateMap::value_type( shaderTemplate->getName(), shaderTemplate ) );
1464 i = g_shaderDefinitions.insert( ShaderDefinitionMap::value_type( name, ShaderDefinition( shaderTemplate.get(), ShaderArguments(), "" ) ) ).first;
1467 // create shader from existing definition
1468 ShaderPointer pShader( new CShader( ( *i ).second ) );
1469 pShader->setName( name );
1470 g_ActiveShaders.insert( shaders_t::value_type( name, pShader ) );
1471 g_ActiveShadersChangedNotify();
1475 IShader *Shader_ForName( const char *name ){
1476 ASSERT_NOTNULL( name );
1478 IShader *pShader = Try_Shader_ForName( name );
1486 // the list of scripts/*.shader files we need to work with
1487 // those are listed in shaderlist file
1488 GSList *l_shaderfiles = 0;
1490 GSList* Shaders_getShaderFileList(){
1491 return l_shaderfiles;
1496 DumpUnreferencedShaders
1497 usefull function: dumps the list of .shader files that are not referenced to the console
1500 void IfFound_dumpUnreferencedShader( bool& bFound, const char* filename ){
1501 bool listed = false;
1503 for ( GSList* sh = l_shaderfiles; sh != 0; sh = g_slist_next( sh ) )
1505 if ( !strcmp( (char*)sh->data, filename ) ) {
1514 globalOutputStream() << "Following shader files are not referenced in any shaderlist.txt:\n";
1516 globalOutputStream() << "\t" << filename << "\n";
1519 typedef ReferenceCaller1<bool, const char*, IfFound_dumpUnreferencedShader> IfFoundDumpUnreferencedShaderCaller;
1521 void DumpUnreferencedShaders(){
1522 bool bFound = false;
1523 GlobalFileSystem().forEachFile( g_shadersDirectory, g_shadersExtension, IfFoundDumpUnreferencedShaderCaller( bFound ) );
1526 void ShaderList_addShaderFile( const char* dirstring ){
1529 for ( GSList* tmp = l_shaderfiles; tmp != 0; tmp = tmp->next )
1531 if ( string_equal_nocase( dirstring, (char*)tmp->data ) ) {
1533 globalOutputStream() << "duplicate entry \"" << (char*)tmp->data << "\" in shaderlist.txt\n";
1539 l_shaderfiles = g_slist_append( l_shaderfiles, strdup( dirstring ) );
1543 typedef FreeCaller1<const char*, ShaderList_addShaderFile> AddShaderFileCaller;
1549 build a CStringList of shader names
1552 void BuildShaderList( TextInputStream& shaderlist ){
1553 Tokeniser& tokeniser = GlobalScriptLibrary().m_pfnNewSimpleTokeniser( shaderlist );
1554 tokeniser.nextLine();
1555 const char* token = tokeniser.getToken();
1556 StringOutputStream shaderFile( 64 );
1557 while ( token != 0 )
1559 // each token should be a shader filename
1560 shaderFile << token << "." << g_shadersExtension;
1562 ShaderList_addShaderFile( shaderFile.c_str() );
1564 tokeniser.nextLine();
1565 token = tokeniser.getToken();
1569 tokeniser.release();
1572 void FreeShaderList(){
1573 while ( l_shaderfiles != 0 )
1575 free( l_shaderfiles->data );
1576 l_shaderfiles = g_slist_remove( l_shaderfiles, l_shaderfiles->data );
1580 void ShaderList_addFromArchive( const char *archivename ){
1581 const char *shaderpath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1582 if ( string_empty( shaderpath ) ) {
1586 StringOutputStream shaderlist( 256 );
1587 shaderlist << DirectoryCleaned( shaderpath ) << "shaderlist.txt";
1589 Archive *archive = GlobalFileSystem().getArchive( archivename, false );
1591 ArchiveTextFile *file = archive->openTextFile( shaderlist.c_str() );
1593 globalOutputStream() << "Found shaderlist.txt in " << archivename << "\n";
1594 BuildShaderList( file->getInputStream() );
1600 typedef FreeCaller1<const char *, ShaderList_addFromArchive> AddShaderListFromArchiveCaller;
1602 #include "stream/filestream.h"
1604 bool shaderlist_findOrInstall( const char* enginePath, const char* toolsPath, const char* shaderPath, const char* gamename ){
1605 StringOutputStream absShaderList( 256 );
1606 absShaderList << enginePath << gamename << '/' << shaderPath << "shaderlist.txt";
1607 if ( file_exists( absShaderList.c_str() ) ) {
1611 StringOutputStream directory( 256 );
1612 directory << enginePath << gamename << '/' << shaderPath;
1613 if ( !file_exists( directory.c_str() ) && !Q_mkdir( directory.c_str() ) ) {
1618 StringOutputStream defaultShaderList( 256 );
1619 defaultShaderList << toolsPath << gamename << '/' << "default_shaderlist.txt";
1620 if ( file_exists( defaultShaderList.c_str() ) ) {
1621 return file_copy( defaultShaderList.c_str(), absShaderList.c_str() );
1627 void Shaders_Load(){
1628 if ( g_shaderLanguage == SHADERLANGUAGE_QUAKE4 ) {
1629 GlobalFileSystem().forEachFile( "guides/", "guide", LoadGuideFileCaller(), 0 );
1632 const char* shaderPath = GlobalRadiant().getGameDescriptionKeyValue( "shaderpath" );
1633 if ( !string_empty( shaderPath ) ) {
1634 StringOutputStream path( 256 );
1635 path << DirectoryCleaned( shaderPath );
1637 if ( g_useShaderList ) {
1638 // preload shader files that have been listed in shaderlist.txt
1639 const char* basegame = GlobalRadiant().getRequiredGameDescriptionKeyValue( "basegame" );
1640 const char* gamename = GlobalRadiant().getGameName();
1641 const char* enginePath = GlobalRadiant().getEnginePath();
1642 const char* toolsPath = GlobalRadiant().getGameToolsPath();
1644 bool isMod = !string_equal( basegame, gamename );
1646 if ( !isMod || !shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename ) ) {
1647 gamename = basegame;
1648 shaderlist_findOrInstall( enginePath, toolsPath, path.c_str(), gamename );
1651 GlobalFileSystem().forEachArchive( AddShaderListFromArchiveCaller(), false, true );
1652 DumpUnreferencedShaders();
1656 GlobalFileSystem().forEachFile( path.c_str(), g_shadersExtension, AddShaderFileCaller(), 0 );
1659 GSList *lst = l_shaderfiles;
1660 StringOutputStream shadername( 256 );
1663 shadername << path.c_str() << reinterpret_cast<const char*>( lst->data );
1664 LoadShaderFile( shadername.c_str() );
1670 //StringPool_analyse(ShaderPool::instance());
1673 void Shaders_Free(){
1676 g_shaderFilenames.clear();
1679 ModuleObservers g_observers;
1681 std::size_t g_shaders_unrealised = 1; // wait until filesystem and is realised before loading anything
1682 bool Shaders_realised(){
1683 return g_shaders_unrealised == 0;
1685 void Shaders_Realise(){
1686 if ( --g_shaders_unrealised == 0 ) {
1688 g_observers.realise();
1691 void Shaders_Unrealise(){
1692 if ( ++g_shaders_unrealised == 1 ) {
1693 g_observers.unrealise();
1698 void Shaders_Refresh(){
1699 Shaders_Unrealise();
1703 class Quake3ShaderSystem : public ShaderSystem, public ModuleObserver
1710 Shaders_Unrealise();
1716 IShader* getShaderForName( const char* name ){
1717 return Shader_ForName( name );
1720 void foreachShaderName( const ShaderNameCallback& callback ){
1721 for ( ShaderDefinitionMap::const_iterator i = g_shaderDefinitions.begin(); i != g_shaderDefinitions.end(); ++i )
1723 callback( ( *i ).first.c_str() );
1727 void beginActiveShadersIterator(){
1728 ActiveShaders_IteratorBegin();
1730 bool endActiveShadersIterator(){
1731 return ActiveShaders_IteratorAtEnd();
1733 IShader* dereferenceActiveShadersIterator(){
1734 return ActiveShaders_IteratorCurrent();
1736 void incrementActiveShadersIterator(){
1737 ActiveShaders_IteratorIncrement();
1739 void setActiveShadersChangedNotify( const Callback& notify ){
1740 g_ActiveShadersChangedNotify = notify;
1743 void attach( ModuleObserver& observer ){
1744 g_observers.attach( observer );
1746 void detach( ModuleObserver& observer ){
1747 g_observers.detach( observer );
1750 void setLightingEnabled( bool enabled ){
1751 if ( CShader::m_lightingEnabled != enabled ) {
1752 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1754 ( *i ).second->unrealiseLighting();
1756 CShader::m_lightingEnabled = enabled;
1757 for ( shaders_t::const_iterator i = g_ActiveShaders.begin(); i != g_ActiveShaders.end(); ++i )
1759 ( *i ).second->realiseLighting();
1764 const char* getTexturePrefix() const {
1765 return g_texturePrefix;
1769 Quake3ShaderSystem g_Quake3ShaderSystem;
1771 ShaderSystem& GetShaderSystem(){
1772 return g_Quake3ShaderSystem;
1775 void Shaders_Construct(){
1776 GlobalFileSystem().attach( g_Quake3ShaderSystem );
1778 void Shaders_Destroy(){
1779 GlobalFileSystem().detach( g_Quake3ShaderSystem );
1781 if ( Shaders_realised() ) {