/* Copyright (C) 2001-2006, William Joseph. All Rights Reserved. This file is part of GtkRadiant. GtkRadiant is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. GtkRadiant is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with GtkRadiant; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include "renderstate.h" #include "debugging/debugging.h" #include "warnings.h" #include "ishaders.h" #include "irender.h" #include "itextures.h" #include "igl.h" #include "iglrender.h" #include "renderable.h" #include "qerplugin.h" #include #include #include #include #include "math/matrix.h" #include "math/aabb.h" #include "generic/callback.h" #include "texturelib.h" #include "string/string.h" #include "container/hashfunc.h" #include "container/cache.h" #include "generic/reference.h" #include "moduleobservers.h" #include "stream/filestream.h" #include "stream/stringstream.h" #include "os/file.h" #include "preferences.h" #include "xywindow.h" #define DEBUG_RENDER 0 inline void debug_string( const char* string ){ #if (DEBUG_RENDER) globalOutputStream() << string << "\n"; #endif } inline void debug_int( const char* comment, int i ){ #if (DEBUG_RENDER) globalOutputStream() << comment << " " << i << "\n"; #endif } inline void debug_colour( const char* comment ){ #if ( DEBUG_RENDER ) Vector4 v; glGetFloatv( GL_CURRENT_COLOR, reinterpret_cast( &v ) ); globalOutputStream() << comment << " colour: " << v[0] << " " << v[1] << " " << v[2] << " " << v[3]; if ( glIsEnabled( GL_COLOR_ARRAY ) ) { globalOutputStream() << " ARRAY"; } if ( glIsEnabled( GL_COLOR_MATERIAL ) ) { globalOutputStream() << " MATERIAL"; } globalOutputStream() << "\n"; #endif } #include "timer.h" StringOutputStream g_renderer_stats; std::size_t g_count_prims; std::size_t g_count_states; std::size_t g_count_transforms; Timer g_timer; inline void count_prim(){ ++g_count_prims; } inline void count_state(){ ++g_count_states; } inline void count_transform(){ ++g_count_transforms; } void Renderer_ResetStats(){ g_count_prims = 0; g_count_states = 0; g_count_transforms = 0; g_timer.start(); } const char* Renderer_GetStats(){ g_renderer_stats.clear(); g_renderer_stats << "prims: " << Unsigned( g_count_prims ) << " | states: " << Unsigned( g_count_states ) << " | transforms: " << Unsigned( g_count_transforms ) << " | msec: " << g_timer.elapsed_msec(); return g_renderer_stats.c_str(); } void printShaderLog( GLhandleARB object ){ GLint log_length = 0; glGetObjectParameterivARB( object, GL_OBJECT_INFO_LOG_LENGTH_ARB, &log_length ); Array log( log_length ); glGetInfoLogARB( object, log_length, &log_length, log.data() ); globalErrorStream() << StringRange( log.begin(), log.begin() + log_length ) << "\n"; } void createShader( GLhandleARB program, const char* filename, GLenum type ){ GLhandleARB shader = glCreateShaderObjectARB( type ); GlobalOpenGL_debugAssertNoErrors(); // load shader { std::size_t size = file_size( filename ); FileInputStream file( filename ); ASSERT_MESSAGE( !file.failed(), "failed to open " << makeQuoted( filename ) ); Array buffer( size ); size = file.read( reinterpret_cast( buffer.data() ), size ); const GLcharARB* string = buffer.data(); GLint length = GLint( size ); glShaderSourceARB( shader, 1, &string, &length ); } // compile shader { glCompileShaderARB( shader ); GLint compiled = 0; glGetObjectParameterivARB( shader, GL_OBJECT_COMPILE_STATUS_ARB, &compiled ); if ( !compiled ) { printShaderLog( shader ); } ASSERT_MESSAGE( compiled, "shader compile failed: " << makeQuoted( filename ) ); } // attach shader glAttachObjectARB( program, shader ); glDeleteObjectARB( shader ); GlobalOpenGL_debugAssertNoErrors(); } void GLSLProgram_link( GLhandleARB program ){ glLinkProgramARB( program ); GLint linked = false; glGetObjectParameterivARB( program, GL_OBJECT_LINK_STATUS_ARB, &linked ); if ( !linked ) { printShaderLog( program ); } ASSERT_MESSAGE( linked, "program link failed" ); } void GLSLProgram_validate( GLhandleARB program ){ glValidateProgramARB( program ); GLint validated = false; glGetObjectParameterivARB( program, GL_OBJECT_VALIDATE_STATUS_ARB, &validated ); if ( !validated ) { printShaderLog( program ); } ASSERT_MESSAGE( validated, "program validation failed" ); } bool g_bumpGLSLPass_enabled = false; bool g_depthfillPass_enabled = false; class GLSLBumpProgram : public GLProgram { public: GLhandleARB m_program; qtexture_t* m_light_attenuation_xy; qtexture_t* m_light_attenuation_z; GLint u_view_origin; GLint u_light_origin; GLint u_light_color; GLint u_bump_scale; GLint u_specular_exponent; GLSLBumpProgram() : m_program( 0 ), m_light_attenuation_xy( 0 ), m_light_attenuation_z( 0 ){ } void create(){ // create program m_program = glCreateProgramObjectARB(); // create shader { StringOutputStream filename( 256 ); filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_vp.glsl"; createShader( m_program, filename.c_str(), GL_VERTEX_SHADER_ARB ); filename.clear(); filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_fp.glsl"; createShader( m_program, filename.c_str(), GL_FRAGMENT_SHADER_ARB ); } GLSLProgram_link( m_program ); GLSLProgram_validate( m_program ); glUseProgramObjectARB( m_program ); glBindAttribLocationARB( m_program, c_attr_TexCoord0, "attr_TexCoord0" ); glBindAttribLocationARB( m_program, c_attr_Tangent, "attr_Tangent" ); glBindAttribLocationARB( m_program, c_attr_Binormal, "attr_Binormal" ); glUniform1iARB( glGetUniformLocationARB( m_program, "u_diffusemap" ), 0 ); glUniform1iARB( glGetUniformLocationARB( m_program, "u_bumpmap" ), 1 ); glUniform1iARB( glGetUniformLocationARB( m_program, "u_specularmap" ), 2 ); glUniform1iARB( glGetUniformLocationARB( m_program, "u_attenuationmap_xy" ), 3 ); glUniform1iARB( glGetUniformLocationARB( m_program, "u_attenuationmap_z" ), 4 ); u_view_origin = glGetUniformLocationARB( m_program, "u_view_origin" ); u_light_origin = glGetUniformLocationARB( m_program, "u_light_origin" ); u_light_color = glGetUniformLocationARB( m_program, "u_light_color" ); u_bump_scale = glGetUniformLocationARB( m_program, "u_bump_scale" ); u_specular_exponent = glGetUniformLocationARB( m_program, "u_specular_exponent" ); glUseProgramObjectARB( 0 ); GlobalOpenGL_debugAssertNoErrors(); } void destroy(){ glDeleteObjectARB( m_program ); m_program = 0; } void enable(){ glUseProgramObjectARB( m_program ); glEnableVertexAttribArrayARB( c_attr_TexCoord0 ); glEnableVertexAttribArrayARB( c_attr_Tangent ); glEnableVertexAttribArrayARB( c_attr_Binormal ); GlobalOpenGL_debugAssertNoErrors(); debug_string( "enable bump" ); g_bumpGLSLPass_enabled = true; } void disable(){ glUseProgramObjectARB( 0 ); glDisableVertexAttribArrayARB( c_attr_TexCoord0 ); glDisableVertexAttribArrayARB( c_attr_Tangent ); glDisableVertexAttribArrayARB( c_attr_Binormal ); GlobalOpenGL_debugAssertNoErrors(); debug_string( "disable bump" ); g_bumpGLSLPass_enabled = false; } void setParameters( const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light ){ Matrix4 world2local( localToWorld ); matrix4_affine_invert( world2local ); Vector3 localLight( origin ); matrix4_transform_point( world2local, localLight ); Vector3 localViewer( viewer ); matrix4_transform_point( world2local, localViewer ); Matrix4 local2light( world2light ); matrix4_multiply_by_matrix4( local2light, localToWorld ); // local->world->light glUniform3fARB( u_view_origin, localViewer.x(), localViewer.y(), localViewer.z() ); glUniform3fARB( u_light_origin, localLight.x(), localLight.y(), localLight.z() ); glUniform3fARB( u_light_color, colour.x(), colour.y(), colour.z() ); glUniform1fARB( u_bump_scale, 1.0 ); glUniform1fARB( u_specular_exponent, 32.0 ); glActiveTexture( GL_TEXTURE3 ); glClientActiveTexture( GL_TEXTURE3 ); glMatrixMode( GL_TEXTURE ); glLoadMatrixf( reinterpret_cast( &local2light ) ); glMatrixMode( GL_MODELVIEW ); GlobalOpenGL_debugAssertNoErrors(); } }; GLSLBumpProgram g_bumpGLSL; class GLSLDepthFillProgram : public GLProgram { public: GLhandleARB m_program; void create(){ // create program m_program = glCreateProgramObjectARB(); // create shader { StringOutputStream filename( 256 ); filename << GlobalRadiant().getAppPath() << "gl/zfill_vp.glsl"; createShader( m_program, filename.c_str(), GL_VERTEX_SHADER_ARB ); filename.clear(); filename << GlobalRadiant().getAppPath() << "gl/zfill_fp.glsl"; createShader( m_program, filename.c_str(), GL_FRAGMENT_SHADER_ARB ); } GLSLProgram_link( m_program ); GLSLProgram_validate( m_program ); GlobalOpenGL_debugAssertNoErrors(); } void destroy(){ glDeleteObjectARB( m_program ); m_program = 0; } void enable(){ glUseProgramObjectARB( m_program ); GlobalOpenGL_debugAssertNoErrors(); debug_string( "enable depthfill" ); g_depthfillPass_enabled = true; } void disable(){ glUseProgramObjectARB( 0 ); GlobalOpenGL_debugAssertNoErrors(); debug_string( "disable depthfill" ); g_depthfillPass_enabled = false; } void setParameters( const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light ){ } }; GLSLDepthFillProgram g_depthFillGLSL; // ARB path void createProgram( const char* filename, GLenum type ){ std::size_t size = file_size( filename ); FileInputStream file( filename ); ASSERT_MESSAGE( !file.failed(), "failed to open " << makeQuoted( filename ) ); Array buffer( size ); size = file.read( reinterpret_cast( buffer.data() ), size ); glProgramStringARB( type, GL_PROGRAM_FORMAT_ASCII_ARB, GLsizei( size ), buffer.data() ); if ( GL_INVALID_OPERATION == glGetError() ) { GLint errPos; glGetIntegerv( GL_PROGRAM_ERROR_POSITION_ARB, &errPos ); const GLubyte* errString = glGetString( GL_PROGRAM_ERROR_STRING_ARB ); globalErrorStream() << reinterpret_cast( filename ) << ":" << errPos << "\n" << reinterpret_cast( errString ); ERROR_MESSAGE( "error in gl program" ); } } class ARBBumpProgram : public GLProgram { public: GLuint m_vertex_program; GLuint m_fragment_program; void create(){ glEnable( GL_VERTEX_PROGRAM_ARB ); glEnable( GL_FRAGMENT_PROGRAM_ARB ); { glGenProgramsARB( 1, &m_vertex_program ); glBindProgramARB( GL_VERTEX_PROGRAM_ARB, m_vertex_program ); StringOutputStream filename( 256 ); filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_vp.glp"; createProgram( filename.c_str(), GL_VERTEX_PROGRAM_ARB ); glGenProgramsARB( 1, &m_fragment_program ); glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, m_fragment_program ); filename.clear(); filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_fp.glp"; createProgram( filename.c_str(), GL_FRAGMENT_PROGRAM_ARB ); } glDisable( GL_VERTEX_PROGRAM_ARB ); glDisable( GL_FRAGMENT_PROGRAM_ARB ); GlobalOpenGL_debugAssertNoErrors(); } void destroy(){ glDeleteProgramsARB( 1, &m_vertex_program ); glDeleteProgramsARB( 1, &m_fragment_program ); GlobalOpenGL_debugAssertNoErrors(); } void enable(){ glEnable( GL_VERTEX_PROGRAM_ARB ); glEnable( GL_FRAGMENT_PROGRAM_ARB ); glBindProgramARB( GL_VERTEX_PROGRAM_ARB, m_vertex_program ); glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, m_fragment_program ); glEnableVertexAttribArrayARB( 8 ); glEnableVertexAttribArrayARB( 9 ); glEnableVertexAttribArrayARB( 10 ); glEnableVertexAttribArrayARB( 11 ); GlobalOpenGL_debugAssertNoErrors(); } void disable(){ glDisable( GL_VERTEX_PROGRAM_ARB ); glDisable( GL_FRAGMENT_PROGRAM_ARB ); glDisableVertexAttribArrayARB( 8 ); glDisableVertexAttribArrayARB( 9 ); glDisableVertexAttribArrayARB( 10 ); glDisableVertexAttribArrayARB( 11 ); GlobalOpenGL_debugAssertNoErrors(); } void setParameters( const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light ){ Matrix4 world2local( localToWorld ); matrix4_affine_invert( world2local ); Vector3 localLight( origin ); matrix4_transform_point( world2local, localLight ); Vector3 localViewer( viewer ); matrix4_transform_point( world2local, localViewer ); Matrix4 local2light( world2light ); matrix4_multiply_by_matrix4( local2light, localToWorld ); // local->world->light // view origin glProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 4, localViewer.x(), localViewer.y(), localViewer.z(), 0 ); // light origin glProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 2, localLight.x(), localLight.y(), localLight.z(), 1 ); // light colour glProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 3, colour.x(), colour.y(), colour.z(), 0 ); // bump scale glProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 1, 1, 0, 0, 0 ); // specular exponent glProgramLocalParameter4fARB( GL_FRAGMENT_PROGRAM_ARB, 5, 32, 0, 0, 0 ); glActiveTexture( GL_TEXTURE3 ); glClientActiveTexture( GL_TEXTURE3 ); glMatrixMode( GL_TEXTURE ); glLoadMatrixf( reinterpret_cast( &local2light ) ); glMatrixMode( GL_MODELVIEW ); GlobalOpenGL_debugAssertNoErrors(); } }; class ARBDepthFillProgram : public GLProgram { public: GLuint m_vertex_program; GLuint m_fragment_program; void create(){ glEnable( GL_VERTEX_PROGRAM_ARB ); glEnable( GL_FRAGMENT_PROGRAM_ARB ); { glGenProgramsARB( 1, &m_vertex_program ); glBindProgramARB( GL_VERTEX_PROGRAM_ARB, m_vertex_program ); StringOutputStream filename( 256 ); filename << GlobalRadiant().getAppPath() << "gl/zfill_vp.glp"; createProgram( filename.c_str(), GL_VERTEX_PROGRAM_ARB ); glGenProgramsARB( 1, &m_fragment_program ); glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, m_fragment_program ); filename.clear(); filename << GlobalRadiant().getAppPath() << "gl/zfill_fp.glp"; createProgram( filename.c_str(), GL_FRAGMENT_PROGRAM_ARB ); } glDisable( GL_VERTEX_PROGRAM_ARB ); glDisable( GL_FRAGMENT_PROGRAM_ARB ); GlobalOpenGL_debugAssertNoErrors(); } void destroy(){ glDeleteProgramsARB( 1, &m_vertex_program ); glDeleteProgramsARB( 1, &m_fragment_program ); GlobalOpenGL_debugAssertNoErrors(); } void enable(){ glEnable( GL_VERTEX_PROGRAM_ARB ); glEnable( GL_FRAGMENT_PROGRAM_ARB ); glBindProgramARB( GL_VERTEX_PROGRAM_ARB, m_vertex_program ); glBindProgramARB( GL_FRAGMENT_PROGRAM_ARB, m_fragment_program ); GlobalOpenGL_debugAssertNoErrors(); } void disable(){ glDisable( GL_VERTEX_PROGRAM_ARB ); glDisable( GL_FRAGMENT_PROGRAM_ARB ); GlobalOpenGL_debugAssertNoErrors(); } void setParameters( const Vector3& viewer, const Matrix4& localToWorld, const Vector3& origin, const Vector3& colour, const Matrix4& world2light ){ } }; ARBBumpProgram g_bumpARB; ARBDepthFillProgram g_depthFillARB; #if 0 // NV20 path (unfinished) void createProgram( GLint program, const char* filename, GLenum type ){ std::size_t size = file_size( filename ); FileInputStream file( filename ); ASSERT_MESSAGE( !file.failed(), "failed to open " << makeQuoted( filename ) ); Array buffer( size ); size = file.read( reinterpret_cast( buffer.data() ), size ); glLoadProgramNV( type, program, GLsizei( size ), buffer.data() ); if ( GL_INVALID_OPERATION == glGetError() ) { GLint errPos; glGetIntegerv( GL_PROGRAM_ERROR_POSITION_NV, &errPos ); const GLubyte* errString = glGetString( GL_PROGRAM_ERROR_STRING_NV ); globalErrorStream() << filename << ":" << errPos << "\n" << errString; ERROR_MESSAGE( "error in gl program" ); } } GLuint m_vertex_program; GLuint m_fragment_program; qtexture_t* g_cube = 0; qtexture_t* g_specular_lookup = 0; qtexture_t* g_attenuation_xy = 0; qtexture_t* g_attenuation_z = 0; void createVertexProgram(){ { glGenProgramsNV( 1, &m_vertex_program ); glBindProgramNV( GL_VERTEX_PROGRAM_NV, m_vertex_program ); StringOutputStream filename( 256 ); filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_vp.nv30"; createProgram( m_vertex_program, filename.c_str(), GL_VERTEX_PROGRAM_NV ); glGenProgramsNV( 1, &m_fragment_program ); glBindProgramNV( GL_FRAGMENT_PROGRAM_NV, m_fragment_program ); filename.clear(); filename << GlobalRadiant().getAppPath() << "gl/lighting_DBS_omni_fp.nv30"; createProgram( m_fragment_program, filename.c_str(), GL_FRAGMENT_PROGRAM_NV ); } g_cube = GlobalTexturesCache().capture( "generated/cube" ); g_specular_lookup = GlobalTexturesCache().capture( "generated/specular" ); g_attenuation_xy = GlobalTexturesCache().capture( "lights/squarelight1" ); glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, g_attenuation_xy->texture_number ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); g_attenuation_z = GlobalTexturesCache().capture( "lights/squarelight1a" ); glActiveTexture( GL_TEXTURE0 ); glBindTexture( GL_TEXTURE_2D, g_attenuation_z->texture_number ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); GlobalOpenGL_debugAssertNoErrors(); } void destroyVertexProgram(){ glDeleteProgramsNV( 1, &m_vertex_program ); glDeleteProgramsNV( 1, &m_fragment_program ); GlobalOpenGL_debugAssertNoErrors(); GlobalTexturesCache().release( g_cube ); GlobalTexturesCache().release( g_specular_lookup ); GlobalTexturesCache().release( g_attenuation_xy ); GlobalTexturesCache().release( g_attenuation_z ); } bool g_vertexProgram_enabled = false; void enableVertexProgram(){ //set up the register combiners //two general combiners glCombinerParameteriNV( GL_NUM_GENERAL_COMBINERS_NV, 2 ); //combiner 0 does tex0+tex1 -> spare0 glCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE0_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); glCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB ); glCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_C_NV, GL_TEXTURE1_ARB, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); glCombinerInputNV( GL_COMBINER0_NV, GL_RGB, GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_INVERT_NV, GL_RGB ); glCombinerOutputNV( GL_COMBINER0_NV, GL_RGB, GL_DISCARD_NV, GL_DISCARD_NV, GL_SPARE0_NV, GL_NONE, GL_NONE, GL_FALSE, GL_FALSE, GL_FALSE ); //combiner 1 does tex2 dot tex3 -> spare1 glCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_A_NV, GL_TEXTURE2_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); glCombinerInputNV( GL_COMBINER1_NV, GL_RGB, GL_VARIABLE_B_NV, GL_TEXTURE3_ARB, GL_EXPAND_NORMAL_NV, GL_RGB ); glCombinerOutputNV( GL_COMBINER1_NV, GL_RGB, GL_SPARE1_NV, GL_DISCARD_NV, GL_DISCARD_NV, GL_NONE, GL_NONE, GL_TRUE, GL_FALSE, GL_FALSE ); //final combiner outputs (1-spare0)*constant color 0*spare1 //do constant color 0*spare1 in the EF multiplier glFinalCombinerInputNV( GL_VARIABLE_E_NV, GL_SPARE1_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); glFinalCombinerInputNV( GL_VARIABLE_F_NV, GL_CONSTANT_COLOR0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); //now do (1-spare0)*EF glFinalCombinerInputNV( GL_VARIABLE_A_NV, GL_SPARE0_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); glFinalCombinerInputNV( GL_VARIABLE_B_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); glFinalCombinerInputNV( GL_VARIABLE_C_NV, GL_E_TIMES_F_NV, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); glFinalCombinerInputNV( GL_VARIABLE_D_NV, GL_ZERO, GL_UNSIGNED_IDENTITY_NV, GL_RGB ); glEnable( GL_VERTEX_PROGRAM_NV ); glEnable( GL_REGISTER_COMBINERS_NV ); glBindProgramNV( GL_VERTEX_PROGRAM_NV, m_vertex_program ); glBindProgramNV( GL_FRAGMENT_PROGRAM_NV, m_fragment_program ); glActiveTexture( GL_TEXTURE0 ); glEnable( GL_TEXTURE_2D ); glActiveTexture( GL_TEXTURE1 ); glEnable( GL_TEXTURE_1D ); glActiveTexture( GL_TEXTURE2 ); glEnable( GL_TEXTURE_2D ); glActiveTexture( GL_TEXTURE3 ); glEnable( GL_TEXTURE_2D ); glEnableClientState( GL_VERTEX_ATTRIB_ARRAY8_NV ); glEnableClientState( GL_VERTEX_ATTRIB_ARRAY9_NV ); glEnableClientState( GL_VERTEX_ATTRIB_ARRAY10_NV ); glEnableClientState( GL_VERTEX_ATTRIB_ARRAY11_NV ); GlobalOpenGL_debugAssertNoErrors(); g_vertexProgram_enabled = true; } void disableVertexProgram(){ glDisable( GL_VERTEX_PROGRAM_NV ); glDisable( GL_REGISTER_COMBINERS_NV ); glActiveTexture( GL_TEXTURE0 ); glDisable( GL_TEXTURE_2D ); glActiveTexture( GL_TEXTURE1 ); glDisable( GL_TEXTURE_1D ); glActiveTexture( GL_TEXTURE2 ); glDisable( GL_TEXTURE_2D ); glActiveTexture( GL_TEXTURE3 ); glDisable( GL_TEXTURE_2D ); glDisableClientState( GL_VERTEX_ATTRIB_ARRAY8_NV ); glDisableClientState( GL_VERTEX_ATTRIB_ARRAY9_NV ); glDisableClientState( GL_VERTEX_ATTRIB_ARRAY10_NV ); glDisableClientState( GL_VERTEX_ATTRIB_ARRAY11_NV ); GlobalOpenGL_debugAssertNoErrors(); g_vertexProgram_enabled = false; } class GLstringNV { public: const GLubyte* m_string; const GLint m_length; GLstringNV( const char* string ) : m_string( reinterpret_cast( string ) ), m_length( GLint( string_length( string ) ) ){ } }; GLstringNV g_light_origin( "light_origin" ); GLstringNV g_view_origin( "view_origin" ); GLstringNV g_light_color( "light_color" ); GLstringNV g_bumpGLSL_scale( "bump_scale" ); GLstringNV g_specular_exponent( "specular_exponent" ); void setVertexProgramEnvironment( const Vector3& localViewer ){ Matrix4 local2light( g_matrix4_identity ); matrix4_translate_by_vec3( local2light, Vector3( 0.5, 0.5, 0.5 ) ); matrix4_scale_by_vec3( local2light, Vector3( 0.5, 0.5, 0.5 ) ); matrix4_scale_by_vec3( local2light, Vector3( 1.0 / 512.0, 1.0 / 512.0, 1.0 / 512.0 ) ); matrix4_translate_by_vec3( local2light, vector3_negated( localViewer ) ); glActiveTexture( GL_TEXTURE3 ); glClientActiveTexture( GL_TEXTURE3 ); glMatrixMode( GL_TEXTURE ); glLoadMatrixf( reinterpret_cast( &local2light ) ); glMatrixMode( GL_MODELVIEW ); glTrackMatrixNV( GL_VERTEX_PROGRAM_NV, 0, GL_MODELVIEW_PROJECTION_NV, GL_IDENTITY_NV ); glTrackMatrixNV( GL_VERTEX_PROGRAM_NV, 4, GL_TEXTURE0_ARB, GL_IDENTITY_NV ); // view origin //qglProgramNamedParameter4fNV(m_fragment_program, g_view_origin.m_length, g_view_origin.m_string, localViewer.x(), localViewer.y(), localViewer.z(), 0); // light origin glProgramParameter4fNV( GL_VERTEX_PROGRAM_NV, 8, localViewer.x(), localViewer.y(), localViewer.z(), 1.0f ); // light colour glCombinerParameterfNV( GL_CONSTANT_COLOR0_NV, 1, 1, 1, 1 ) // bump scale //qglProgramNamedParameter4fNV(m_fragment_program, g_bumpGLSL_scale.m_length, g_bumpGLSL_scale.m_string, 1, 0, 0, 0); // specular exponent //qglProgramNamedParameter4fNV(m_fragment_program, g_specular_exponent.m_length, g_specular_exponent.m_string, 32, 0, 0, 0); GlobalOpenGL_debugAssertNoErrors(); } #endif bool g_vertexArray_enabled = false; bool g_normalArray_enabled = false; bool g_texcoordArray_enabled = false; bool g_colorArray_enabled = false; inline bool OpenGLState_less( const OpenGLState& self, const OpenGLState& other ){ //! Sort by sort-order override. if ( self.m_sort != other.m_sort ) { return self.m_sort < other.m_sort; } //! Sort by texture handle. if ( self.m_texture != other.m_texture ) { return self.m_texture < other.m_texture; } if ( self.m_texture1 != other.m_texture1 ) { return self.m_texture1 < other.m_texture1; } if ( self.m_texture2 != other.m_texture2 ) { return self.m_texture2 < other.m_texture2; } if ( self.m_texture3 != other.m_texture3 ) { return self.m_texture3 < other.m_texture3; } if ( self.m_texture4 != other.m_texture4 ) { return self.m_texture4 < other.m_texture4; } if ( self.m_texture5 != other.m_texture5 ) { return self.m_texture5 < other.m_texture5; } if ( self.m_texture6 != other.m_texture6 ) { return self.m_texture6 < other.m_texture6; } if ( self.m_texture7 != other.m_texture7 ) { return self.m_texture7 < other.m_texture7; } //! Sort by state bit-vector. if ( self.m_state != other.m_state ) { return self.m_state < other.m_state; } //! Comparing address makes sure states are never equal. return &self < &other; } void OpenGLState_constructDefault( OpenGLState& state ){ state.m_state = RENDER_DEFAULT; state.m_texture = 0; state.m_texture1 = 0; state.m_texture2 = 0; state.m_texture3 = 0; state.m_texture4 = 0; state.m_texture5 = 0; state.m_texture6 = 0; state.m_texture7 = 0; state.m_colour[0] = 1; state.m_colour[1] = 1; state.m_colour[2] = 1; state.m_colour[3] = 1; state.m_depthfunc = GL_LESS; state.m_blend_src = GL_SRC_ALPHA; state.m_blend_dst = GL_ONE_MINUS_SRC_ALPHA; state.m_alphafunc = GL_ALWAYS; state.m_alpharef = 0; state.m_linewidth = 1; state.m_pointsize = 1; state.m_linestipple_factor = 1; state.m_linestipple_pattern = 0xaaaa; state.m_fog = OpenGLFogState(); } /// \brief A container of Renderable references. /// May contain the same Renderable multiple times, with different transforms. class OpenGLStateBucket { public: struct RenderTransform { const Matrix4* m_transform; const OpenGLRenderable *m_renderable; const RendererLight* m_light; RenderTransform( const OpenGLRenderable& renderable, const Matrix4& transform, const RendererLight* light ) : m_transform( &transform ), m_renderable( &renderable ), m_light( light ){ } }; typedef std::vector Renderables; private: OpenGLState m_state; Renderables m_renderables; public: OpenGLStateBucket(){ } void addRenderable( const OpenGLRenderable& renderable, const Matrix4& modelview, const RendererLight* light = 0 ){ m_renderables.push_back( RenderTransform( renderable, modelview, light ) ); } OpenGLState& state(){ return m_state; } void render( OpenGLState& current, unsigned int globalstate, const Vector3& viewer ); }; #define LIGHT_SHADER_DEBUG 0 #if LIGHT_SHADER_DEBUG typedef std::vector LightDebugShaders; LightDebugShaders g_lightDebugShaders; #endif class OpenGLStateLess { public: bool operator()( const OpenGLState& self, const OpenGLState& other ) const { return OpenGLState_less( self, other ); } }; typedef ConstReference OpenGLStateReference; typedef std::map OpenGLStates; OpenGLStates g_state_sorted; class OpenGLStateBucketAdd { OpenGLStateBucket& m_bucket; const OpenGLRenderable& m_renderable; const Matrix4& m_modelview; public: typedef const RendererLight& first_argument_type; OpenGLStateBucketAdd( OpenGLStateBucket& bucket, const OpenGLRenderable& renderable, const Matrix4& modelview ) : m_bucket( bucket ), m_renderable( renderable ), m_modelview( modelview ){ } void operator()( const RendererLight& light ){ m_bucket.addRenderable( m_renderable, m_modelview, &light ); } }; class CountLights { std::size_t m_count; public: typedef RendererLight& first_argument_type; CountLights() : m_count( 0 ){ } void operator()( const RendererLight& light ){ ++m_count; } std::size_t count() const { return m_count; } }; class OpenGLShader : public Shader { typedef std::list Passes; Passes m_passes; IShader* m_shader; std::size_t m_used; ModuleObservers m_observers; public: OpenGLShader() : m_shader( 0 ), m_used( 0 ){ } ~OpenGLShader(){ } void construct( const char* name ); void destroy(){ if ( m_shader ) { m_shader->DecRef(); } m_shader = 0; for ( Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i ) { delete *i; } m_passes.clear(); } void addRenderable( const OpenGLRenderable& renderable, const Matrix4& modelview, const LightList* lights ){ for ( Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i ) { #if LIGHT_SHADER_DEBUG if ( ( ( *i )->state().m_state & RENDER_BUMP ) != 0 ) { if ( lights != 0 ) { CountLights counter; lights->forEachLight( makeCallback1( counter ) ); globalOutputStream() << "count = " << counter.count() << "\n"; for ( std::size_t i = 0; i < counter.count(); ++i ) { g_lightDebugShaders[counter.count()]->addRenderable( renderable, modelview ); } } } else #else if ( ( ( *i )->state().m_state & RENDER_BUMP ) != 0 ) { if ( lights != 0 ) { OpenGLStateBucketAdd add( *( *i ), renderable, modelview ); lights->forEachLight( makeCallback1( add ) ); } } else #endif { ( *i )->addRenderable( renderable, modelview ); } } } void incrementUsed(){ if ( ++m_used == 1 && m_shader != 0 ) { m_shader->SetInUse( true ); } } void decrementUsed(){ if ( --m_used == 0 && m_shader != 0 ) { m_shader->SetInUse( false ); } } bool realised() const { return m_shader != 0; } void attach( ModuleObserver& observer ){ if ( realised() ) { observer.realise(); } m_observers.attach( observer ); } void detach( ModuleObserver& observer ){ if ( realised() ) { observer.unrealise(); } m_observers.detach( observer ); } void realise( const CopiedString& name ){ construct( name.c_str() ); if ( m_used != 0 && m_shader != 0 ) { m_shader->SetInUse( true ); } for ( Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i ) { g_state_sorted.insert( OpenGLStates::value_type( OpenGLStateReference( ( *i )->state() ), *i ) ); } m_observers.realise(); } void unrealise(){ m_observers.unrealise(); for ( Passes::iterator i = m_passes.begin(); i != m_passes.end(); ++i ) { g_state_sorted.erase( OpenGLStateReference( ( *i )->state() ) ); } destroy(); } qtexture_t& getTexture() const { ASSERT_NOTNULL( m_shader ); return *m_shader->getTexture(); } unsigned int getFlags() const { ASSERT_NOTNULL( m_shader ); return m_shader->getFlags(); } IShader& getShader() const { ASSERT_NOTNULL( m_shader ); return *m_shader; } OpenGLState& appendDefaultPass(){ m_passes.push_back( new OpenGLStateBucket ); OpenGLState& state = m_passes.back()->state(); OpenGLState_constructDefault( state ); return state; } }; inline bool lightEnabled( const RendererLight& light, const LightCullable& cullable ){ return cullable.testLight( light ); } typedef std::set RendererLights; #define DEBUG_LIGHT_SYNC 0 class LinearLightList : public LightList { LightCullable& m_cullable; RendererLights& m_allLights; Callback m_evaluateChanged; typedef std::list Lights; mutable Lights m_lights; mutable bool m_lightsChanged; public: LinearLightList( LightCullable& cullable, RendererLights& lights, const Callback& evaluateChanged ) : m_cullable( cullable ), m_allLights( lights ), m_evaluateChanged( evaluateChanged ){ m_lightsChanged = true; } void evaluateLights() const { m_evaluateChanged(); if ( m_lightsChanged ) { m_lightsChanged = false; m_lights.clear(); m_cullable.clearLights(); for ( RendererLights::const_iterator i = m_allLights.begin(); i != m_allLights.end(); ++i ) { if ( lightEnabled( *( *i ), m_cullable ) ) { m_lights.push_back( *i ); m_cullable.insertLight( *( *i ) ); } } } #if ( DEBUG_LIGHT_SYNC ) else { Lights lights; for ( RendererLights::const_iterator i = m_allLights.begin(); i != m_allLights.end(); ++i ) { if ( lightEnabled( *( *i ), m_cullable ) ) { lights.push_back( *i ); } } ASSERT_MESSAGE( !std::lexicographical_compare( lights.begin(), lights.end(), m_lights.begin(), m_lights.end() ) && !std::lexicographical_compare( m_lights.begin(), m_lights.end(), lights.begin(), lights.end() ), "lights out of sync" ); } #endif } void forEachLight( const RendererLightCallback& callback ) const { evaluateLights(); for ( Lights::const_iterator i = m_lights.begin(); i != m_lights.end(); ++i ) { callback( *( *i ) ); } } void lightsChanged() const { m_lightsChanged = true; } }; inline void setFogState( const OpenGLFogState& state ){ glFogi( GL_FOG_MODE, state.mode ); glFogf( GL_FOG_DENSITY, state.density ); glFogf( GL_FOG_START, state.start ); glFogf( GL_FOG_END, state.end ); glFogi( GL_FOG_INDEX, state.index ); glFogfv( GL_FOG_COLOR, vector4_to_array( state.colour ) ); } #define DEBUG_SHADERS 0 class OpenGLShaderCache : public ShaderCache, public TexturesCacheObserver, public ModuleObserver { class CreateOpenGLShader { OpenGLShaderCache* m_cache; public: explicit CreateOpenGLShader( OpenGLShaderCache* cache = 0 ) : m_cache( cache ){ } OpenGLShader* construct( const CopiedString& name ){ OpenGLShader* shader = new OpenGLShader; if ( m_cache->realised() ) { shader->realise( name ); } return shader; } void destroy( OpenGLShader* shader ){ if ( m_cache->realised() ) { shader->unrealise(); } delete shader; } }; typedef HashedCache, CreateOpenGLShader> Shaders; Shaders m_shaders; std::size_t m_unrealised; bool m_lightingEnabled; bool m_lightingSupported; bool m_useShaderLanguage; public: OpenGLShaderCache() : m_shaders( CreateOpenGLShader( this ) ), m_unrealised( 3 ), // wait until shaders, gl-context and textures are realised before creating any render-states m_lightingEnabled( true ), m_lightingSupported( false ), m_useShaderLanguage( false ), m_lightsChanged( true ), m_traverseRenderablesMutex( false ){ } ~OpenGLShaderCache(){ for ( Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i ) { globalOutputStream() << "leaked shader: " << makeQuoted( ( *i ).key.c_str() ) << "\n"; } } Shader* capture( const char* name ){ ASSERT_MESSAGE( name[0] == '$' || *name == '[' || *name == '<' || *name == '(' || strchr( name, '\\' ) == 0, "shader name contains invalid characters: \"" << name << "\"" ); #if DEBUG_SHADERS globalOutputStream() << "shaders capture: " << makeQuoted( name ) << '\n'; #endif return m_shaders.capture( name ).get(); } void release( const char *name ){ #if DEBUG_SHADERS globalOutputStream() << "shaders release: " << makeQuoted( name ) << '\n'; #endif m_shaders.release( name ); } void render( RenderStateFlags globalstate, const Matrix4& modelview, const Matrix4& projection, const Vector3& viewer ){ glMatrixMode( GL_PROJECTION ); glLoadMatrixf( reinterpret_cast( &projection ) ); #if 0 //qglGetFloatv(GL_PROJECTION_MATRIX, reinterpret_cast(&projection)); #endif glMatrixMode( GL_MODELVIEW ); glLoadMatrixf( reinterpret_cast( &modelview ) ); #if 0 //qglGetFloatv(GL_MODELVIEW_MATRIX, reinterpret_cast(&modelview)); #endif ASSERT_MESSAGE( realised(), "render states are not realised" ); // global settings that are not set in renderstates glFrontFace( GL_CW ); glCullFace( GL_BACK ); glPolygonOffset( -1, 1 ); { const GLubyte pattern[132] = { 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55, 0xAA, 0xAA, 0xAA, 0xAA, 0x55, 0x55, 0x55, 0x55 }; glPolygonStipple( pattern ); } glEnableClientState( GL_VERTEX_ARRAY ); g_vertexArray_enabled = true; glColorMaterial( GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE ); if ( GlobalOpenGL().GL_1_3() ) { glActiveTexture( GL_TEXTURE0 ); glClientActiveTexture( GL_TEXTURE0 ); } if ( GlobalOpenGL().ARB_shader_objects() ) { glUseProgramObjectARB( 0 ); glDisableVertexAttribArrayARB( c_attr_TexCoord0 ); glDisableVertexAttribArrayARB( c_attr_Tangent ); glDisableVertexAttribArrayARB( c_attr_Binormal ); } if ( globalstate & RENDER_TEXTURE ) { glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT ); } OpenGLState current; OpenGLState_constructDefault( current ); current.m_sort = OpenGLState::eSortFirst; // default renderstate settings glLineStipple( current.m_linestipple_factor, current.m_linestipple_pattern ); glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); glDisable( GL_LIGHTING ); glDisable( GL_TEXTURE_2D ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); g_texcoordArray_enabled = false; glDisableClientState( GL_COLOR_ARRAY ); g_colorArray_enabled = false; glDisableClientState( GL_NORMAL_ARRAY ); g_normalArray_enabled = false; glDisable( GL_BLEND ); glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA ); glDisable( GL_CULL_FACE ); glShadeModel( GL_FLAT ); glDisable( GL_DEPTH_TEST ); glDepthMask( GL_FALSE ); glDisable( GL_ALPHA_TEST ); glDisable( GL_LINE_STIPPLE ); glDisable( GL_POLYGON_STIPPLE ); glDisable( GL_POLYGON_OFFSET_LINE ); glBindTexture( GL_TEXTURE_2D, 0 ); glColor4f( 1,1,1,1 ); glDepthFunc( GL_LESS ); glAlphaFunc( GL_ALWAYS, 0 ); glLineWidth( 1 ); glPointSize( 1 ); glHint( GL_FOG_HINT, GL_NICEST ); glDisable( GL_FOG ); setFogState( OpenGLFogState() ); GlobalOpenGL_debugAssertNoErrors(); debug_string( "begin rendering" ); for ( OpenGLStates::iterator i = g_state_sorted.begin(); i != g_state_sorted.end(); ++i ) { ( *i ).second->render( current, globalstate, viewer ); } debug_string( "end rendering" ); } void realise(){ if ( --m_unrealised == 0 ) { if ( lightingSupported() && lightingEnabled() ) { if ( useShaderLanguage() ) { g_bumpGLSL.create(); g_depthFillGLSL.create(); } else { g_bumpARB.create(); g_depthFillARB.create(); } } for ( Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i ) { if ( !( *i ).value.empty() ) { ( *i ).value->realise( i->key ); } } } } void unrealise(){ if ( ++m_unrealised == 1 ) { for ( Shaders::iterator i = m_shaders.begin(); i != m_shaders.end(); ++i ) { if ( !( *i ).value.empty() ) { ( *i ).value->unrealise(); } } if ( GlobalOpenGL().contextValid && lightingSupported() && lightingEnabled() ) { if ( useShaderLanguage() ) { g_bumpGLSL.destroy(); g_depthFillGLSL.destroy(); } else { g_bumpARB.destroy(); g_depthFillARB.destroy(); } } } } bool realised(){ return m_unrealised == 0; } bool lightingEnabled() const { return m_lightingEnabled; } bool lightingSupported() const { return m_lightingSupported; } bool useShaderLanguage() const { return m_useShaderLanguage; } void setLighting( bool supported, bool enabled ){ bool refresh = ( m_lightingSupported && m_lightingEnabled ) != ( supported && enabled ); if ( refresh ) { unrealise(); GlobalShaderSystem().setLightingEnabled( supported && enabled ); } m_lightingSupported = supported; m_lightingEnabled = enabled; if ( refresh ) { realise(); } } void extensionsInitialised(){ setLighting( GlobalOpenGL().GL_1_3() && GlobalOpenGL().ARB_vertex_program() && GlobalOpenGL().ARB_fragment_program() && GlobalOpenGL().ARB_shader_objects() && GlobalOpenGL().ARB_vertex_shader() && GlobalOpenGL().ARB_fragment_shader() && GlobalOpenGL().ARB_shading_language_100(), m_lightingEnabled ); if ( !lightingSupported() ) { globalOutputStream() << "Lighting mode requires OpenGL features not supported by your graphics drivers:\n"; if ( !GlobalOpenGL().GL_1_3() ) { globalOutputStream() << " GL version 1.3 or better\n"; } if ( !GlobalOpenGL().ARB_vertex_program() ) { globalOutputStream() << " GL_ARB_vertex_program\n"; } if ( !GlobalOpenGL().ARB_fragment_program() ) { globalOutputStream() << " GL_ARB_fragment_program\n"; } if ( !GlobalOpenGL().ARB_shader_objects() ) { globalOutputStream() << " GL_ARB_shader_objects\n"; } if ( !GlobalOpenGL().ARB_vertex_shader() ) { globalOutputStream() << " GL_ARB_vertex_shader\n"; } if ( !GlobalOpenGL().ARB_fragment_shader() ) { globalOutputStream() << " GL_ARB_fragment_shader\n"; } if ( !GlobalOpenGL().ARB_shading_language_100() ) { globalOutputStream() << " GL_ARB_shading_language_100\n"; } } } void setLightingEnabled( bool enabled ){ setLighting( m_lightingSupported, enabled ); } // light culling RendererLights m_lights; bool m_lightsChanged; typedef std::map LightLists; LightLists m_lightLists; const LightList& attach( LightCullable& cullable ){ return ( *m_lightLists.insert( LightLists::value_type( &cullable, LinearLightList( cullable, m_lights, EvaluateChangedCaller( *this ) ) ) ).first ).second; } void detach( LightCullable& cullable ){ m_lightLists.erase( &cullable ); } void changed( LightCullable& cullable ){ LightLists::iterator i = m_lightLists.find( &cullable ); ASSERT_MESSAGE( i != m_lightLists.end(), "cullable not attached" ); ( *i ).second.lightsChanged(); } void attach( RendererLight& light ){ ASSERT_MESSAGE( m_lights.find( &light ) == m_lights.end(), "light could not be attached" ); m_lights.insert( &light ); changed( light ); } void detach( RendererLight& light ){ ASSERT_MESSAGE( m_lights.find( &light ) != m_lights.end(), "light could not be detached" ); m_lights.erase( &light ); changed( light ); } void changed( RendererLight& light ){ m_lightsChanged = true; } void evaluateChanged(){ if ( m_lightsChanged ) { m_lightsChanged = false; for ( LightLists::iterator i = m_lightLists.begin(); i != m_lightLists.end(); ++i ) { ( *i ).second.lightsChanged(); } } } typedef MemberCaller EvaluateChangedCaller; typedef std::set Renderables; Renderables m_renderables; mutable bool m_traverseRenderablesMutex; // renderables void attachRenderable( const Renderable& renderable ){ ASSERT_MESSAGE( !m_traverseRenderablesMutex, "attaching renderable during traversal" ); ASSERT_MESSAGE( m_renderables.find( &renderable ) == m_renderables.end(), "renderable could not be attached" ); m_renderables.insert( &renderable ); } void detachRenderable( const Renderable& renderable ){ ASSERT_MESSAGE( !m_traverseRenderablesMutex, "detaching renderable during traversal" ); ASSERT_MESSAGE( m_renderables.find( &renderable ) != m_renderables.end(), "renderable could not be detached" ); m_renderables.erase( &renderable ); } void forEachRenderable( const RenderableCallback& callback ) const { ASSERT_MESSAGE( !m_traverseRenderablesMutex, "for-each during traversal" ); m_traverseRenderablesMutex = true; for ( Renderables::const_iterator i = m_renderables.begin(); i != m_renderables.end(); ++i ) { callback( *( *i ) ); } m_traverseRenderablesMutex = false; } }; static OpenGLShaderCache* g_ShaderCache; void ShaderCache_extensionsInitialised(){ g_ShaderCache->extensionsInitialised(); } void ShaderCache_setBumpEnabled( bool enabled ){ g_ShaderCache->setLightingEnabled( enabled ); } Vector3 g_DebugShaderColours[256]; Shader* g_defaultPointLight = 0; void ShaderCache_Construct(){ g_ShaderCache = new OpenGLShaderCache; GlobalTexturesCache().attach( *g_ShaderCache ); GlobalShaderSystem().attach( *g_ShaderCache ); if ( g_pGameDescription->mGameType == "doom3" ) { g_defaultPointLight = g_ShaderCache->capture( "lights/defaultPointLight" ); //Shader* overbright = g_ShaderCache->capture( "$OVERBRIGHT" ); #if LIGHT_SHADER_DEBUG for ( std::size_t i = 0; i < 256; ++i ) { g_DebugShaderColours[i] = Vector3( i / 256.0, i / 256.0, i / 256.0 ); } g_DebugShaderColours[0] = Vector3( 1, 0, 0 ); g_DebugShaderColours[1] = Vector3( 1, 0.5, 0 ); g_DebugShaderColours[2] = Vector3( 1, 1, 0 ); g_DebugShaderColours[3] = Vector3( 0.5, 1, 0 ); g_DebugShaderColours[4] = Vector3( 0, 1, 0 ); g_DebugShaderColours[5] = Vector3( 0, 1, 0.5 ); g_DebugShaderColours[6] = Vector3( 0, 1, 1 ); g_DebugShaderColours[7] = Vector3( 0, 0.5, 1 ); g_DebugShaderColours[8] = Vector3( 0, 0, 1 ); g_DebugShaderColours[9] = Vector3( 0.5, 0, 1 ); g_DebugShaderColours[10] = Vector3( 1, 0, 1 ); g_DebugShaderColours[11] = Vector3( 1, 0, 0.5 ); g_lightDebugShaders.reserve( 256 ); StringOutputStream buffer( 256 ); for ( std::size_t i = 0; i < 256; ++i ) { buffer << "(" << g_DebugShaderColours[i].x() << " " << g_DebugShaderColours[i].y() << " " << g_DebugShaderColours[i].z() << ")"; g_lightDebugShaders.push_back( g_ShaderCache->capture( buffer.c_str() ) ); buffer.clear(); } #endif } } void ShaderCache_Destroy(){ if ( g_pGameDescription->mGameType == "doom3" ) { g_ShaderCache->release( "lights/defaultPointLight" ); g_ShaderCache->release( "$OVERBRIGHT" ); g_defaultPointLight = 0; #if LIGHT_SHADER_DEBUG g_lightDebugShaders.clear(); StringOutputStream buffer( 256 ); for ( std::size_t i = 0; i < 256; ++i ) { buffer << "(" << g_DebugShaderColours[i].x() << " " << g_DebugShaderColours[i].y() << " " << g_DebugShaderColours[i].z() << ")"; g_ShaderCache->release( buffer.c_str() ); } #endif } GlobalShaderSystem().detach( *g_ShaderCache ); GlobalTexturesCache().detach( *g_ShaderCache ); delete g_ShaderCache; } ShaderCache* GetShaderCache(){ return g_ShaderCache; } inline void setTextureState( GLint& current, const GLint& texture, GLenum textureUnit ){ if ( texture != current ) { glActiveTexture( textureUnit ); glClientActiveTexture( textureUnit ); glBindTexture( GL_TEXTURE_2D, texture ); GlobalOpenGL_debugAssertNoErrors(); current = texture; } } inline void setTextureState( GLint& current, const GLint& texture ){ if ( texture != current ) { glBindTexture( GL_TEXTURE_2D, texture ); GlobalOpenGL_debugAssertNoErrors(); current = texture; } } inline void setState( unsigned int state, unsigned int delta, unsigned int flag, GLenum glflag ){ if ( delta & state & flag ) { glEnable( glflag ); GlobalOpenGL_debugAssertNoErrors(); } else if ( delta & ~state & flag ) { glDisable( glflag ); GlobalOpenGL_debugAssertNoErrors(); } } void OpenGLState_apply( const OpenGLState& self, OpenGLState& current, unsigned int globalstate ){ debug_int( "sort", int(self.m_sort) ); debug_int( "texture", self.m_texture ); debug_int( "state", self.m_state ); debug_int( "address", int(std::size_t( &self ) ) ); count_state(); if ( self.m_state & RENDER_OVERRIDE ) { globalstate |= RENDER_FILL | RENDER_DEPTHWRITE; } const unsigned int state = self.m_state & globalstate; const unsigned int delta = state ^ current.m_state; GlobalOpenGL_debugAssertNoErrors(); GLProgram* program = ( state & RENDER_PROGRAM ) != 0 ? self.m_program : 0; if ( program != current.m_program ) { if ( current.m_program != 0 ) { current.m_program->disable(); glColor4fv( vector4_to_array( current.m_colour ) ); debug_colour( "cleaning program" ); } current.m_program = program; if ( current.m_program != 0 ) { current.m_program->enable(); } } if ( delta & state & RENDER_FILL ) { //qglPolygonMode (GL_BACK, GL_LINE); //qglPolygonMode (GL_FRONT, GL_FILL); glPolygonMode( GL_FRONT_AND_BACK, GL_FILL ); GlobalOpenGL_debugAssertNoErrors(); } else if ( delta & ~state & RENDER_FILL ) { glPolygonMode( GL_FRONT_AND_BACK, GL_LINE ); GlobalOpenGL_debugAssertNoErrors(); } setState( state, delta, RENDER_OFFSETLINE, GL_POLYGON_OFFSET_LINE ); if ( delta & state & RENDER_LIGHTING ) { glEnable( GL_LIGHTING ); glEnable( GL_COLOR_MATERIAL ); glEnable( GL_RESCALE_NORMAL ); glEnableClientState( GL_NORMAL_ARRAY ); GlobalOpenGL_debugAssertNoErrors(); g_normalArray_enabled = true; } else if ( delta & ~state & RENDER_LIGHTING ) { glDisable( GL_LIGHTING ); glDisable( GL_COLOR_MATERIAL ); glDisable( GL_RESCALE_NORMAL ); glDisableClientState( GL_NORMAL_ARRAY ); GlobalOpenGL_debugAssertNoErrors(); g_normalArray_enabled = false; } if ( delta & state & RENDER_TEXTURE ) { GlobalOpenGL_debugAssertNoErrors(); if ( GlobalOpenGL().GL_1_3() ) { glActiveTexture( GL_TEXTURE0 ); glClientActiveTexture( GL_TEXTURE0 ); } glEnable( GL_TEXTURE_2D ); glColor4f( 1,1,1,self.m_colour[3] ); debug_colour( "setting texture" ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); GlobalOpenGL_debugAssertNoErrors(); g_texcoordArray_enabled = true; } else if ( delta & ~state & RENDER_TEXTURE ) { if ( GlobalOpenGL().GL_1_3() ) { glActiveTexture( GL_TEXTURE0 ); glClientActiveTexture( GL_TEXTURE0 ); } glDisable( GL_TEXTURE_2D ); glBindTexture( GL_TEXTURE_2D, 0 ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); GlobalOpenGL_debugAssertNoErrors(); g_texcoordArray_enabled = false; } if ( delta & state & RENDER_BLEND ) { // FIXME: some .TGA are buggy, have a completely empty alpha channel // if such brushes are rendered in this loop they would be totally transparent with GL_MODULATE // so I decided using GL_DECAL instead // if an empty-alpha-channel or nearly-empty texture is used. It will be blank-transparent. // this could get better if you can get glTexEnviv (GL_TEXTURE_ENV, to work .. patches are welcome glEnable( GL_BLEND ); if ( GlobalOpenGL().GL_1_3() ) { glActiveTexture( GL_TEXTURE0 ); } glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL ); GlobalOpenGL_debugAssertNoErrors(); } else if ( delta & ~state & RENDER_BLEND ) { glDisable( GL_BLEND ); if ( GlobalOpenGL().GL_1_3() ) { glActiveTexture( GL_TEXTURE0 ); } glTexEnvi( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE ); GlobalOpenGL_debugAssertNoErrors(); } setState( state, delta, RENDER_CULLFACE, GL_CULL_FACE ); if ( delta & state & RENDER_SMOOTH ) { glShadeModel( GL_SMOOTH ); GlobalOpenGL_debugAssertNoErrors(); } else if ( delta & ~state & RENDER_SMOOTH ) { glShadeModel( GL_FLAT ); GlobalOpenGL_debugAssertNoErrors(); } setState( state, delta, RENDER_SCALED, GL_NORMALIZE ); // not GL_RESCALE_NORMAL setState( state, delta, RENDER_DEPTHTEST, GL_DEPTH_TEST ); if ( delta & state & RENDER_DEPTHWRITE ) { glDepthMask( GL_TRUE ); #if DEBUG_RENDER GLboolean depthEnabled; glGetBooleanv( GL_DEPTH_WRITEMASK, &depthEnabled ); ASSERT_MESSAGE( depthEnabled, "failed to set depth buffer mask bit" ); #endif debug_string( "enabled depth-buffer writing" ); GlobalOpenGL_debugAssertNoErrors(); } else if ( delta & ~state & RENDER_DEPTHWRITE ) { glDepthMask( GL_FALSE ); #if DEBUG_RENDER GLboolean depthEnabled; glGetBooleanv( GL_DEPTH_WRITEMASK, &depthEnabled ); ASSERT_MESSAGE( !depthEnabled, "failed to set depth buffer mask bit" ); #endif debug_string( "disabled depth-buffer writing" ); GlobalOpenGL_debugAssertNoErrors(); } if ( delta & state & RENDER_COLOURWRITE ) { glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE ); GlobalOpenGL_debugAssertNoErrors(); } else if ( delta & ~state & RENDER_COLOURWRITE ) { glColorMask( GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE ); GlobalOpenGL_debugAssertNoErrors(); } setState( state, delta, RENDER_ALPHATEST, GL_ALPHA_TEST ); if ( delta & state & RENDER_COLOURARRAY ) { glEnableClientState( GL_COLOR_ARRAY ); GlobalOpenGL_debugAssertNoErrors(); debug_colour( "enabling color_array" ); g_colorArray_enabled = true; } else if ( delta & ~state & RENDER_COLOURARRAY ) { glDisableClientState( GL_COLOR_ARRAY ); glColor4fv( vector4_to_array( self.m_colour ) ); debug_colour( "cleaning color_array" ); GlobalOpenGL_debugAssertNoErrors(); g_colorArray_enabled = false; } if ( delta & ~state & RENDER_COLOURCHANGE ) { glColor4fv( vector4_to_array( self.m_colour ) ); GlobalOpenGL_debugAssertNoErrors(); } setState( state, delta, RENDER_LINESTIPPLE, GL_LINE_STIPPLE ); setState( state, delta, RENDER_LINESMOOTH, GL_LINE_SMOOTH ); setState( state, delta, RENDER_POLYGONSTIPPLE, GL_POLYGON_STIPPLE ); setState( state, delta, RENDER_POLYGONSMOOTH, GL_POLYGON_SMOOTH ); setState( state, delta, RENDER_FOG, GL_FOG ); if ( ( state & RENDER_FOG ) != 0 ) { setFogState( self.m_fog ); GlobalOpenGL_debugAssertNoErrors(); current.m_fog = self.m_fog; } if ( state & RENDER_DEPTHTEST && self.m_depthfunc != current.m_depthfunc ) { glDepthFunc( self.m_depthfunc ); GlobalOpenGL_debugAssertNoErrors(); current.m_depthfunc = self.m_depthfunc; } if ( state & RENDER_LINESTIPPLE && ( self.m_linestipple_factor != current.m_linestipple_factor || self.m_linestipple_pattern != current.m_linestipple_pattern ) ) { glLineStipple( self.m_linestipple_factor, self.m_linestipple_pattern ); GlobalOpenGL_debugAssertNoErrors(); current.m_linestipple_factor = self.m_linestipple_factor; current.m_linestipple_pattern = self.m_linestipple_pattern; } if ( state & RENDER_ALPHATEST && ( self.m_alphafunc != current.m_alphafunc || self.m_alpharef != current.m_alpharef ) ) { glAlphaFunc( self.m_alphafunc, self.m_alpharef ); GlobalOpenGL_debugAssertNoErrors(); current.m_alphafunc = self.m_alphafunc; current.m_alpharef = self.m_alpharef; } { GLint texture0 = 0; GLint texture1 = 0; GLint texture2 = 0; GLint texture3 = 0; GLint texture4 = 0; GLint texture5 = 0; GLint texture6 = 0; GLint texture7 = 0; //if(state & RENDER_TEXTURE) != 0) { texture0 = self.m_texture; texture1 = self.m_texture1; texture2 = self.m_texture2; texture3 = self.m_texture3; texture4 = self.m_texture4; texture5 = self.m_texture5; texture6 = self.m_texture6; texture7 = self.m_texture7; } if ( GlobalOpenGL().GL_1_3() ) { setTextureState( current.m_texture, texture0, GL_TEXTURE0 ); setTextureState( current.m_texture1, texture1, GL_TEXTURE1 ); setTextureState( current.m_texture2, texture2, GL_TEXTURE2 ); setTextureState( current.m_texture3, texture3, GL_TEXTURE3 ); setTextureState( current.m_texture4, texture4, GL_TEXTURE4 ); setTextureState( current.m_texture5, texture5, GL_TEXTURE5 ); setTextureState( current.m_texture6, texture6, GL_TEXTURE6 ); setTextureState( current.m_texture7, texture7, GL_TEXTURE7 ); } else { setTextureState( current.m_texture, texture0 ); } } if ( state & RENDER_TEXTURE && self.m_colour[3] != current.m_colour[3] ) { debug_colour( "setting alpha" ); glColor4f( 1,1,1,self.m_colour[3] ); GlobalOpenGL_debugAssertNoErrors(); } if ( !( state & RENDER_TEXTURE ) && ( self.m_colour[0] != current.m_colour[0] || self.m_colour[1] != current.m_colour[1] || self.m_colour[2] != current.m_colour[2] || self.m_colour[3] != current.m_colour[3] ) ) { glColor4fv( vector4_to_array( self.m_colour ) ); debug_colour( "setting non-texture" ); GlobalOpenGL_debugAssertNoErrors(); } current.m_colour = self.m_colour; if ( state & RENDER_BLEND && ( self.m_blend_src != current.m_blend_src || self.m_blend_dst != current.m_blend_dst ) ) { glBlendFunc( self.m_blend_src, self.m_blend_dst ); GlobalOpenGL_debugAssertNoErrors(); current.m_blend_src = self.m_blend_src; current.m_blend_dst = self.m_blend_dst; } if ( !( state & RENDER_FILL ) && self.m_linewidth != current.m_linewidth ) { glLineWidth( self.m_linewidth ); GlobalOpenGL_debugAssertNoErrors(); current.m_linewidth = self.m_linewidth; } if ( !( state & RENDER_FILL ) && self.m_pointsize != current.m_pointsize ) { glPointSize( self.m_pointsize ); GlobalOpenGL_debugAssertNoErrors(); current.m_pointsize = self.m_pointsize; } current.m_state = state; GlobalOpenGL_debugAssertNoErrors(); } void Renderables_flush( OpenGLStateBucket::Renderables& renderables, OpenGLState& current, unsigned int globalstate, const Vector3& viewer ){ const Matrix4* transform = 0; glPushMatrix(); for ( OpenGLStateBucket::Renderables::const_iterator i = renderables.begin(); i != renderables.end(); ++i ) { //qglLoadMatrixf(i->m_transform); if ( !transform || ( transform != ( *i ).m_transform && !matrix4_affine_equal( *transform, *( *i ).m_transform ) ) ) { count_transform(); transform = ( *i ).m_transform; glPopMatrix(); glPushMatrix(); glMultMatrixf( reinterpret_cast( transform ) ); glFrontFace( ( ( current.m_state & RENDER_CULLFACE ) != 0 && matrix4_handedness( *transform ) == MATRIX4_RIGHTHANDED ) ? GL_CW : GL_CCW ); } count_prim(); if ( current.m_program != 0 && ( *i ).m_light != 0 ) { const IShader& lightShader = static_cast( ( *i ).m_light->getShader() )->getShader(); if ( lightShader.firstLayer() != 0 ) { GLuint attenuation_xy = lightShader.firstLayer()->texture()->texture_number; GLuint attenuation_z = lightShader.lightFalloffImage() != 0 ? lightShader.lightFalloffImage()->texture_number : static_cast( g_defaultPointLight )->getShader().lightFalloffImage()->texture_number; setTextureState( current.m_texture3, attenuation_xy, GL_TEXTURE3 ); glActiveTexture( GL_TEXTURE3 ); glBindTexture( GL_TEXTURE_2D, attenuation_xy ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER ); setTextureState( current.m_texture4, attenuation_z, GL_TEXTURE4 ); glActiveTexture( GL_TEXTURE4 ); glBindTexture( GL_TEXTURE_2D, attenuation_z ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER ); glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); AABB lightBounds( ( *i ).m_light->aabb() ); Matrix4 world2light( g_matrix4_identity ); if ( ( *i ).m_light->isProjected() ) { world2light = ( *i ).m_light->projection(); matrix4_multiply_by_matrix4( world2light, matrix4_transposed( ( *i ).m_light->rotation() ) ); matrix4_translate_by_vec3( world2light, vector3_negated( lightBounds.origin ) ); // world->lightBounds } if ( !( *i ).m_light->isProjected() ) { matrix4_translate_by_vec3( world2light, Vector3( 0.5f, 0.5f, 0.5f ) ); matrix4_scale_by_vec3( world2light, Vector3( 0.5f, 0.5f, 0.5f ) ); matrix4_scale_by_vec3( world2light, Vector3( 1.0f / lightBounds.extents.x(), 1.0f / lightBounds.extents.y(), 1.0f / lightBounds.extents.z() ) ); matrix4_multiply_by_matrix4( world2light, matrix4_transposed( ( *i ).m_light->rotation() ) ); matrix4_translate_by_vec3( world2light, vector3_negated( lightBounds.origin ) ); // world->lightBounds } current.m_program->setParameters( viewer, *( *i ).m_transform, lightBounds.origin + ( *i ).m_light->offset(), ( *i ).m_light->colour(), world2light ); debug_string( "set lightBounds parameters" ); } } ( *i ).m_renderable->render( current.m_state ); } glPopMatrix(); renderables.clear(); } void OpenGLStateBucket::render( OpenGLState& current, unsigned int globalstate, const Vector3& viewer ){ if ( ( globalstate & m_state.m_state & RENDER_SCREEN ) != 0 ) { OpenGLState_apply( m_state, current, globalstate ); debug_colour( "screen fill" ); glMatrixMode( GL_PROJECTION ); glPushMatrix(); glLoadMatrixf( reinterpret_cast( &g_matrix4_identity ) ); glMatrixMode( GL_MODELVIEW ); glPushMatrix(); glLoadMatrixf( reinterpret_cast( &g_matrix4_identity ) ); glBegin( GL_QUADS ); glVertex3f( -1, -1, 0 ); glVertex3f( 1, -1, 0 ); glVertex3f( 1, 1, 0 ); glVertex3f( -1, 1, 0 ); glEnd(); glMatrixMode( GL_PROJECTION ); glPopMatrix(); glMatrixMode( GL_MODELVIEW ); glPopMatrix(); } else if ( !m_renderables.empty() ) { OpenGLState_apply( m_state, current, globalstate ); Renderables_flush( m_renderables, current, globalstate, viewer ); } } class OpenGLStateMap : public OpenGLStateLibrary { typedef std::map States; States m_states; public: ~OpenGLStateMap(){ ASSERT_MESSAGE( m_states.empty(), "OpenGLStateMap::~OpenGLStateMap: not empty" ); } typedef States::iterator iterator; iterator begin(){ return m_states.begin(); } iterator end(){ return m_states.end(); } void getDefaultState( OpenGLState& state ) const { OpenGLState_constructDefault( state ); } void insert( const char* name, const OpenGLState& state ){ bool inserted = m_states.insert( States::value_type( name, state ) ).second; ASSERT_MESSAGE( inserted, "OpenGLStateMap::insert: " << name << " already exists" ); } void erase( const char* name ){ std::size_t count = m_states.erase( name ); ASSERT_MESSAGE( count == 1, "OpenGLStateMap::erase: " << name << " does not exist" ); } iterator find( const char* name ){ return m_states.find( name ); } }; OpenGLStateMap* g_openglStates = 0; inline GLenum convertBlendFactor( BlendFactor factor ){ switch ( factor ) { case BLEND_ZERO: return GL_ZERO; case BLEND_ONE: return GL_ONE; case BLEND_SRC_COLOUR: return GL_SRC_COLOR; case BLEND_ONE_MINUS_SRC_COLOUR: return GL_ONE_MINUS_SRC_COLOR; case BLEND_SRC_ALPHA: return GL_SRC_ALPHA; case BLEND_ONE_MINUS_SRC_ALPHA: return GL_ONE_MINUS_SRC_ALPHA; case BLEND_DST_COLOUR: return GL_DST_COLOR; case BLEND_ONE_MINUS_DST_COLOUR: return GL_ONE_MINUS_DST_COLOR; case BLEND_DST_ALPHA: return GL_DST_ALPHA; case BLEND_ONE_MINUS_DST_ALPHA: return GL_ONE_MINUS_DST_ALPHA; case BLEND_SRC_ALPHA_SATURATE: return GL_SRC_ALPHA_SATURATE; } return GL_ZERO; } /// \todo Define special-case shaders in a data file. void OpenGLShader::construct( const char* name ){ OpenGLState& state = appendDefaultPass(); switch ( name[0] ) { case '(': sscanf( name, "(%g %g %g)", &state.m_colour[0], &state.m_colour[1], &state.m_colour[2] ); state.m_colour[3] = 1.0f; state.m_state = RENDER_FILL | RENDER_LIGHTING | RENDER_DEPTHTEST | RENDER_CULLFACE | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortFullbright; break; case '[': sscanf( name, "[%g %g %g]", &state.m_colour[0], &state.m_colour[1], &state.m_colour[2] ); state.m_colour[3] = 0.5f; state.m_state = RENDER_FILL | RENDER_LIGHTING | RENDER_DEPTHTEST | RENDER_CULLFACE | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_BLEND; state.m_sort = OpenGLState::eSortTranslucent; break; case '<': sscanf( name, "<%g %g %g>", &state.m_colour[0], &state.m_colour[1], &state.m_colour[2] ); state.m_colour[3] = 1; state.m_state = RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortFullbright; state.m_depthfunc = GL_LESS; state.m_linewidth = 1; state.m_pointsize = 1; break; case '$': { OpenGLStateMap::iterator i = g_openglStates->find( name ); if ( i != g_openglStates->end() ) { state = ( *i ).second; break; } } if ( string_equal( name + 1, "POINT" ) ) { state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortControlFirst; state.m_pointsize = 4; } else if ( string_equal( name + 1, "SELPOINT" ) ) { state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortControlFirst + 1; state.m_pointsize = 4; } else if ( string_equal( name + 1, "BIGPOINT" ) ) { state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortControlFirst; state.m_pointsize = 6; } else if ( string_equal( name + 1, "PIVOT" ) ) { state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHTEST | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortGUI1; state.m_linewidth = 2; state.m_depthfunc = GL_LEQUAL; OpenGLState& hiddenLine = appendDefaultPass(); hiddenLine.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHTEST | RENDER_LINESTIPPLE; hiddenLine.m_sort = OpenGLState::eSortGUI0; hiddenLine.m_linewidth = 2; hiddenLine.m_depthfunc = GL_GREATER; } else if ( string_equal( name + 1, "LATTICE" ) ) { state.m_colour[0] = 1; state.m_colour[1] = 0.5; state.m_colour[2] = 0; state.m_colour[3] = 1; state.m_state = RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortControlFirst; } else if ( string_equal( name + 1, "WIREFRAME" ) ) { state.m_state = RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortFullbright; } else if ( string_equal( name + 1, "CAM_HIGHLIGHT" ) ) { state.m_colour[0] = 1; state.m_colour[1] = 0; state.m_colour[2] = 0; state.m_colour[3] = 0.3f; state.m_state = RENDER_FILL | RENDER_DEPTHTEST | RENDER_CULLFACE | RENDER_BLEND | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortHighlight; state.m_depthfunc = GL_LEQUAL; } else if ( string_equal( name + 1, "CAM_OVERLAY" ) ) { #if 0 state.m_state = RENDER_CULLFACE | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortOverlayFirst; #else state.m_state = RENDER_CULLFACE | RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_OFFSETLINE; state.m_sort = OpenGLState::eSortOverlayFirst + 1; state.m_depthfunc = GL_LEQUAL; OpenGLState& hiddenLine = appendDefaultPass(); hiddenLine.m_colour[0] = 0.75; hiddenLine.m_colour[1] = 0.75; hiddenLine.m_colour[2] = 0.75; hiddenLine.m_colour[3] = 1; hiddenLine.m_state = RENDER_CULLFACE | RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_OFFSETLINE | RENDER_LINESTIPPLE; hiddenLine.m_sort = OpenGLState::eSortOverlayFirst; hiddenLine.m_depthfunc = GL_GREATER; hiddenLine.m_linestipple_factor = 2; #endif } else if ( string_equal( name + 1, "XY_OVERLAY" ) ) { state.m_colour[0] = g_xywindow_globals.color_selbrushes[0]; state.m_colour[1] = g_xywindow_globals.color_selbrushes[1]; state.m_colour[2] = g_xywindow_globals.color_selbrushes[2]; state.m_colour[3] = 1; state.m_state = RENDER_COLOURWRITE | RENDER_LINESTIPPLE; state.m_sort = OpenGLState::eSortOverlayFirst; state.m_linewidth = 2; state.m_linestipple_factor = 3; } else if ( string_equal( name + 1, "DEBUG_CLIPPED" ) ) { state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortLast; } else if ( string_equal( name + 1, "POINTFILE" ) ) { state.m_colour[0] = 1; state.m_colour[1] = 0; state.m_colour[2] = 0; state.m_colour[3] = 1; state.m_state = RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortFullbright; state.m_linewidth = 4; } else if ( string_equal( name + 1, "LIGHT_SPHERE" ) ) { state.m_colour[0] = .15f * .95f; state.m_colour[1] = .15f * .95f; state.m_colour[2] = .15f * .95f; state.m_colour[3] = 1; state.m_state = RENDER_CULLFACE | RENDER_DEPTHTEST | RENDER_BLEND | RENDER_FILL | RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_blend_src = GL_ONE; state.m_blend_dst = GL_ONE; state.m_sort = OpenGLState::eSortTranslucent; } else if ( string_equal( name + 1, "Q3MAP2_LIGHT_SPHERE" ) ) { state.m_colour[0] = .05f; state.m_colour[1] = .05f; state.m_colour[2] = .05f; state.m_colour[3] = 1; state.m_state = RENDER_CULLFACE | RENDER_DEPTHTEST | RENDER_BLEND | RENDER_FILL; state.m_blend_src = GL_ONE; state.m_blend_dst = GL_ONE; state.m_sort = OpenGLState::eSortTranslucent; } else if ( string_equal( name + 1, "WIRE_OVERLAY" ) ) { #if 0 state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE; state.m_sort = OpenGLState::eSortOverlayFirst; #else state.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE; state.m_sort = OpenGLState::eSortGUI1; state.m_depthfunc = GL_LEQUAL; OpenGLState& hiddenLine = appendDefaultPass(); hiddenLine.m_state = RENDER_COLOURARRAY | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE | RENDER_LINESTIPPLE; hiddenLine.m_sort = OpenGLState::eSortGUI0; hiddenLine.m_depthfunc = GL_GREATER; #endif } else if ( string_equal( name + 1, "FLATSHADE_OVERLAY" ) ) { state.m_state = RENDER_CULLFACE | RENDER_LIGHTING | RENDER_SMOOTH | RENDER_SCALED | RENDER_COLOURARRAY | RENDER_FILL | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE; state.m_sort = OpenGLState::eSortGUI1; state.m_depthfunc = GL_LEQUAL; OpenGLState& hiddenLine = appendDefaultPass(); hiddenLine.m_state = RENDER_CULLFACE | RENDER_LIGHTING | RENDER_SMOOTH | RENDER_SCALED | RENDER_COLOURARRAY | RENDER_FILL | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_DEPTHTEST | RENDER_OVERRIDE | RENDER_POLYGONSTIPPLE; hiddenLine.m_sort = OpenGLState::eSortGUI0; hiddenLine.m_depthfunc = GL_GREATER; } else if ( string_equal( name + 1, "CLIPPER_OVERLAY" ) ) { state.m_colour[0] = g_xywindow_globals.color_clipper[0]; state.m_colour[1] = g_xywindow_globals.color_clipper[1]; state.m_colour[2] = g_xywindow_globals.color_clipper[2]; state.m_colour[3] = 1; state.m_state = RENDER_CULLFACE | RENDER_COLOURWRITE | RENDER_DEPTHWRITE | RENDER_FILL | RENDER_POLYGONSTIPPLE; state.m_sort = OpenGLState::eSortOverlayFirst; } else if ( string_equal( name + 1, "OVERBRIGHT" ) ) { const float lightScale = 2; state.m_colour[0] = lightScale * 0.5f; state.m_colour[1] = lightScale * 0.5f; state.m_colour[2] = lightScale * 0.5f; state.m_colour[3] = 0.5; state.m_state = RENDER_FILL | RENDER_BLEND | RENDER_COLOURWRITE | RENDER_SCREEN; state.m_sort = OpenGLState::eSortOverbrighten; state.m_blend_src = GL_DST_COLOR; state.m_blend_dst = GL_SRC_COLOR; } else { // default to something recognisable.. =) ERROR_MESSAGE( "hardcoded renderstate not found" ); state.m_colour[0] = 1; state.m_colour[1] = 0; state.m_colour[2] = 1; state.m_colour[3] = 1; state.m_state = RENDER_COLOURWRITE | RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortFirst; } break; default: // construction from IShader m_shader = QERApp_Shader_ForName( name ); if ( g_ShaderCache->lightingSupported() && g_ShaderCache->lightingEnabled() && m_shader->getBump() != 0 && m_shader->getBump()->texture_number != 0 ) { // is a bump shader state.m_state = RENDER_FILL | RENDER_CULLFACE | RENDER_TEXTURE | RENDER_DEPTHTEST | RENDER_DEPTHWRITE | RENDER_COLOURWRITE | RENDER_PROGRAM; state.m_colour[0] = 0; state.m_colour[1] = 0; state.m_colour[2] = 0; state.m_colour[3] = 1; state.m_sort = OpenGLState::eSortOpaque; if ( g_ShaderCache->useShaderLanguage() ) { state.m_program = &g_depthFillGLSL; } else { state.m_program = &g_depthFillARB; } OpenGLState& bumpPass = appendDefaultPass(); bumpPass.m_texture = m_shader->getDiffuse()->texture_number; bumpPass.m_texture1 = m_shader->getBump()->texture_number; bumpPass.m_texture2 = m_shader->getSpecular()->texture_number; bumpPass.m_state = RENDER_BLEND | RENDER_FILL | RENDER_CULLFACE | RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_SMOOTH | RENDER_BUMP | RENDER_PROGRAM; if ( g_ShaderCache->useShaderLanguage() ) { bumpPass.m_state |= RENDER_LIGHTING; bumpPass.m_program = &g_bumpGLSL; } else { bumpPass.m_program = &g_bumpARB; } bumpPass.m_depthfunc = GL_LEQUAL; bumpPass.m_sort = OpenGLState::eSortMultiFirst; bumpPass.m_blend_src = GL_ONE; bumpPass.m_blend_dst = GL_ONE; } else { state.m_texture = m_shader->getTexture()->texture_number; state.m_state = RENDER_FILL | RENDER_TEXTURE | RENDER_DEPTHTEST | RENDER_COLOURWRITE | RENDER_LIGHTING | RENDER_SMOOTH; if ( ( m_shader->getFlags() & QER_CULL ) != 0 ) { if ( m_shader->getCull() == IShader::eCullBack ) { state.m_state |= RENDER_CULLFACE; } } else { state.m_state |= RENDER_CULLFACE; } if ( ( m_shader->getFlags() & QER_ALPHATEST ) != 0 ) { state.m_state |= RENDER_ALPHATEST; IShader::EAlphaFunc alphafunc; m_shader->getAlphaFunc( &alphafunc, &state.m_alpharef ); switch ( alphafunc ) { case IShader::eAlways: state.m_alphafunc = GL_ALWAYS; case IShader::eEqual: state.m_alphafunc = GL_EQUAL; case IShader::eLess: state.m_alphafunc = GL_LESS; case IShader::eGreater: state.m_alphafunc = GL_GREATER; case IShader::eLEqual: state.m_alphafunc = GL_LEQUAL; case IShader::eGEqual: state.m_alphafunc = GL_GEQUAL; } } reinterpret_cast( state.m_colour ) = m_shader->getTexture()->color; state.m_colour[3] = 1.0f; if ( ( m_shader->getFlags() & QER_TRANS ) != 0 ) { state.m_state |= RENDER_BLEND; state.m_colour[3] = m_shader->getTrans(); state.m_sort = OpenGLState::eSortTranslucent; BlendFunc blendFunc = m_shader->getBlendFunc(); state.m_blend_src = convertBlendFactor( blendFunc.m_src ); state.m_blend_dst = convertBlendFactor( blendFunc.m_dst ); if ( state.m_blend_src == GL_SRC_ALPHA || state.m_blend_dst == GL_SRC_ALPHA ) { state.m_state |= RENDER_DEPTHWRITE; } } else { state.m_state |= RENDER_DEPTHWRITE; state.m_sort = OpenGLState::eSortFullbright; } } } } #include "modulesystem/singletonmodule.h" #include "modulesystem/moduleregistry.h" class OpenGLStateLibraryAPI { OpenGLStateMap m_stateMap; public: typedef OpenGLStateLibrary Type; STRING_CONSTANT( Name, "*" ); OpenGLStateLibraryAPI(){ g_openglStates = &m_stateMap; } ~OpenGLStateLibraryAPI(){ g_openglStates = 0; } OpenGLStateLibrary* getTable(){ return &m_stateMap; } }; typedef SingletonModule OpenGLStateLibraryModule; typedef Static StaticOpenGLStateLibraryModule; StaticRegisterModule staticRegisterOpenGLStateLibrary( StaticOpenGLStateLibraryModule::instance() ); class ShaderCacheDependencies : public GlobalShadersModuleRef, public GlobalTexturesModuleRef, public GlobalOpenGLStateLibraryModuleRef { public: ShaderCacheDependencies() : GlobalShadersModuleRef( GlobalRadiant().getRequiredGameDescriptionKeyValue( "shaders" ) ){ } }; class ShaderCacheAPI { ShaderCache* m_shaderCache; public: typedef ShaderCache Type; STRING_CONSTANT( Name, "*" ); ShaderCacheAPI(){ ShaderCache_Construct(); m_shaderCache = GetShaderCache(); } ~ShaderCacheAPI(){ ShaderCache_Destroy(); } ShaderCache* getTable(){ return m_shaderCache; } }; typedef SingletonModule ShaderCacheModule; typedef Static StaticShaderCacheModule; StaticRegisterModule staticRegisterShaderCache( StaticShaderCacheModule::instance() );