From 40e6911209c606cc0c61f377efb5c3b333021052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Emmanuel=20Beno=C3=AEt?= Date: Wed, 4 Oct 2017 16:29:39 +0200 Subject: [PATCH] Hopefully almost finish shader management --- externals.hh | 5 +- main.cc | 4 - shaders.cc | 498 +++++++++++++++++++++++++++++++++++++++++++++------ shaders.hh | 146 ++++++++------- 4 files changed, 516 insertions(+), 137 deletions(-) diff --git a/externals.hh b/externals.hh index dcfda8b..aab13f9 100644 --- a/externals.hh +++ b/externals.hh @@ -2,6 +2,7 @@ #include // System (C) +#include #include #include #include @@ -45,5 +46,5 @@ CLS( CLS const& ); \ CLS& operator =( CLS const& ) #define MOVE( CLS ) \ - CLS( CLS&& ); \ - CLS& operator =( CLS&& ) + CLS( CLS&& ) noexcept; \ + CLS& operator =( CLS&& ) noexcept diff --git a/main.cc b/main.cc index 51ff500..4fcd696 100644 --- a/main.cc +++ b/main.cc @@ -199,10 +199,6 @@ void T_Main::initProgram( ) int main( int , char** ) { T_Main m; -#if 0 - testLoadShaderFile( ); -#else m.mainLoop( ); -#endif return 0; } diff --git a/shaders.cc b/shaders.cc index 05d4a81..a2ca3c7 100644 --- a/shaders.cc +++ b/shaders.cc @@ -7,6 +7,8 @@ namespace { const std::regex PreprocDirective_( "^\\s*//!\\s*([a-z]+(\\s+([^\\s]+))*)\\s*$" ); +const std::regex GLSLErrorNv_( "^\\d+\\((\\d+)\\) : " ); + const std::map< std::string , E_ShaderInput > InputTypes_( ([] { std::map< std::string , E_ShaderInput > t; t.emplace( "chunk" , E_ShaderInput::CHUNK ); @@ -20,6 +22,23 @@ const std::map< std::string , E_ShaderInput > InputTypes_( ([] { return t; })()); +const GLenum ProgramTypes_[] = { + GL_VERTEX_SHADER , + GL_FRAGMENT_SHADER , + GL_GEOMETRY_SHADER , + GL_COMPUTE_SHADER +}; + +const GLbitfield PipelineStages_[] = { + GL_VERTEX_SHADER_BIT , + GL_FRAGMENT_SHADER_BIT , + GL_GEOMETRY_SHADER_BIT , + GL_COMPUTE_SHADER_BIT +}; + +static_assert( sizeof( PipelineStages_ ) / sizeof( GLbitfield ) == size_t( E_ShaderType::__COUNT__ ) , + "missing pipeline stage constants" ); + /*============================================================================*/ @@ -166,10 +185,89 @@ bool T_ShaderInput::load( } -/*============================================================================*/ +/*= T_ShaderPipeline =========================================================*/ + +T_ShaderPipeline::T_ShaderPipeline( ) + : T_ShaderPipeline( -1 ) +{ } + +T_ShaderPipeline::T_ShaderPipeline( + __rd__ T_ShaderPipeline const& other ) + : T_ShaderPipeline( other.smIndex_ ) +{ } + +T_ShaderPipeline::T_ShaderPipeline( + __rw__ T_ShaderPipeline&& other ) noexcept + : T_ShaderPipeline( ) +{ + std::swap( other.smIndex_ , smIndex_ ); +} + +T_ShaderPipeline::T_ShaderPipeline( + __rd__ const int32_t index ) + : smIndex_( index ) +{ + if ( smIndex_ >= 0 ) { + Globals::Shaders( ).pipelines_[ smIndex_ ].references ++; + } +} + +T_ShaderPipeline::~T_ShaderPipeline( ) +{ + if ( smIndex_ >= 0 ) { + Globals::Shaders( ).dereferencePipeline( smIndex_ ); + } +} + +T_ShaderPipeline& T_ShaderPipeline::operator=( + __rd__ T_ShaderPipeline const& other ) +{ + if ( this != &other ) { + if ( smIndex_ >= 0 ) { + Globals::Shaders( ).dereferencePipeline( smIndex_ ); + } + smIndex_ = other.smIndex_; + if ( smIndex_ >= 0 ) { + Globals::Shaders( ).pipelines_[ smIndex_ ].references ++; + } + } + return *this; +} + +T_ShaderPipeline& T_ShaderPipeline::operator=( + __rw__ T_ShaderPipeline&& other ) noexcept +{ + if ( this != &other ) { + if ( smIndex_ >= 0 ) { + Globals::Shaders( ).dereferencePipeline( smIndex_ ); + } + smIndex_ = other.smIndex_; + other.smIndex_ = -1; + } + return *this; +} + +bool T_ShaderPipeline::valid( ) const noexcept +{ + return smIndex_ >= 0 + && Globals::Shaders( ).pipelines_[ smIndex_ ].id != 0; +} + +void T_ShaderPipeline::enable( ) const +{ + if ( valid( ) ) { + glBindProgramPipeline( Globals::Shaders( ).pipelines_[ smIndex_ ].id ); + } +} + + +/*= T_CodeBuilder_ ===========================================================*/ namespace { +using F_GetInput_ = std::function< T_ShaderInput const*( std::string const& ) >; +using T_Code_ = T_ShaderManager::T_ShaderCode; + // Code builder, state and functions struct T_CodeBuilder_ { @@ -179,9 +277,9 @@ struct T_CodeBuilder_ uint32_t pos; }; - T_ShaderCodeLoader& loader; + F_GetInput_ loader; const std::string name; - T_Frankenshader& code; + T_Code_& code; T_ShaderInput const* main; std::map< std::string , uint32_t > pos; @@ -191,11 +289,11 @@ struct T_CodeBuilder_ uint32_t cpos{ 0 }; std::string cname; - T_CodeBuilder_( __rw__ T_ShaderCodeLoader& loader , + T_CodeBuilder_( __rd__ F_GetInput_ loader , __rd__ std::string const& name , - __rw__ T_Frankenshader& code ) + __rw__ T_Code_& code ) : loader( loader ) , name( name ) , code( code ) , - main( loader.getInput( name ) ) + main( loader( name ) ) { } bool buildCode( ); @@ -209,7 +307,7 @@ struct T_CodeBuilder_ bool T_CodeBuilder_::buildCode( ) { - code = T_Frankenshader{ }; + code = T_Code_{ }; code.files.emplace( name , main != nullptr ); if ( !main ) { return false; @@ -263,7 +361,7 @@ void T_CodeBuilder_::include( return; } - T_ShaderInput const* const isi( loader.getInput( nname ) ); + T_ShaderInput const* const isi( loader( nname ) ); code.files.emplace( nname , isi != nullptr ); // Check for problems @@ -313,31 +411,153 @@ void T_CodeBuilder_::next( ) } // namespace - /*============================================================================*/ -T_ShaderCodeLoader::T_ShaderCodeLoader( - __rw__ T_FilesWatcher& watcher ) - : watcher_( watcher ) -{ -} -bool T_ShaderCodeLoader::load( - __rd__ std::string const& name , - __wr__ T_Frankenshader& code ) +T_ShaderPipeline T_ShaderManager::pipeline( + __rd__ std::initializer_list< std::string > shaders ) { - T_CodeBuilder_ cb( *this , name , code ); - const bool rv( cb.buildCode( ) ); - - // Update dependencies - for ( auto const& dep : code.files ) { - deps_[ name ].insert( dep.first ); + if ( shaders.size( ) < 1 || shaders.size( ) > 5 ) { + return {}; } - return rv; + const std::string id( ([&shaders] () { + std::ostringstream oss; + std::copy( shaders.begin( ) , shaders.end( ) , + std::ostream_iterator< std::string >( oss , "|" ) ); + return oss.str( ); + } )() ); + auto pos( pipelineIndex_.find( id ) ); + if ( pos != pipelineIndex_.end( ) ) { + return T_ShaderPipeline{ int32_t( pos->second ) }; + } + + const auto index( newPipelineRecord( ) ); + auto& p( pipelines_[ index ] ); + pipelineIndex_.emplace( id , index ); + p.idString = id; + p.id = 0; + p.references = 0; + p.programs = shaders; + for ( auto const& pName : shaders ) { + loadProgram( index , pName ); + } + initPipeline( p ); + + return T_ShaderPipeline{ int32_t( index ) }; } -T_ShaderInput const* T_ShaderCodeLoader::getInput( + +void T_ShaderManager::update( ) +{ + // Check for missing files + for ( auto it = missing_.begin( ) ; it != missing_.end( ) ; ++ it ) { + const bool exists( ([] ( std::string const& name ) -> bool { + struct stat buffer; + return ( stat( name.c_str( ) , &buffer ) == 0 ); + })( "shaders/" + (*it).first ) ); + if ( !exists ) { + continue; + } + updates_.insert( (*it).second.begin( ) , (*it).second.end( ) ); + missing_.erase( (*it).first ); + } + + // Reset programs that need to be updated + std::set< uint32_t > pipelines; + for ( auto const& name : updates_ ) { + auto p( programIndex_.find( name ) ); + if ( p == programIndex_.end( ) ) { + updates_.erase( name ); + } else { + auto& pr( programs_[ p->second ] ); + pipelines.insert( pr.references.begin( ) , pr.references.end( ) ); + resetProgram( pr ); + } + } + + // Reset pipelines affected by the programs above + for ( auto plid : pipelines ) { + auto& pipeline( pipelines_[ plid ] ); + if ( pipeline.id != 0 ) { + glDeleteProgramPipelines( 1 , &pipeline.id ); + pipeline.id = 0; + } + } + + // Try to load all updated programs + for ( auto const& name : updates_ ) { + auto& pr( programs_[ programIndex_[ name ] ] ); + initProgram( pr ); + } + + // Try to initialise all affected pipelines + for ( auto plid : pipelines ) { + initPipeline( pipelines_[ plid ] ); + } +} + + +uint32_t T_ShaderManager::newPipelineRecord( ) +{ + uint32_t i = 0; + while ( i < pipelines_.size( ) ) { + if ( pipelines_[ i ].references == 0 ) { + return i; + } + i ++; + } + pipelines_.emplace_back( ); + return i; +} + +uint32_t T_ShaderManager::newProgramRecord( ) +{ + uint32_t i = 0; + while ( i < programs_.size( ) ) { + if ( programs_[ i ].references.empty( ) ) { + return i; + } + i ++; + } + programs_.emplace_back( ); + return i; +} + + +void T_ShaderManager::loadProgram( + __rd__ const uint32_t pipeline , + __rd__ std::string const& name ) +{ + if ( useExistingProgram( pipeline , name ) ) { + return; + } + + const uint32_t index( newProgramRecord( ) ); + auto& program( programs_[ index ] ); + programIndex_.emplace( name , index ); + program.name = name; + program.references.insert( pipeline ); + program.id = 0; + initProgram( program ); +} + +bool T_ShaderManager::useExistingProgram( + __rd__ const uint32_t pipeline , + __rd__ std::string const& name ) +{ + auto pos( programIndex_.find( name ) ); + if ( pos == programIndex_.end( ) ) { + return false; + } + auto& refs( programs_[ pos->second ].references ); + assert( refs.find( pipeline ) == refs.end( ) ); + refs.insert( pipeline ); + return true; +} + + +T_ShaderInput const* T_ShaderManager::getInput( __rd__ std::string const& name ) { auto pos( inputs_.find( name ) ); @@ -352,50 +572,216 @@ T_ShaderInput const* T_ShaderCodeLoader::getInput( return inputs_.find( name )->second.get( ); } -void T_ShaderCodeLoader::removeInput( - __rd__ std::string const& name ) -{ - inputs_.erase( name ); -} -void T_ShaderCodeLoader::onFileUpdated( - __rd__ std::string const& name ) +void T_ShaderManager::dereferencePipeline( + __rd__ const uint32_t index ) { - inputs_.erase( name ); - auto& deps( deps_[ name ] ); - for ( auto const& dep : deps ) { - pending_.insert( dep ); + auto& pipeline( pipelines_[ index ] ); + assert( pipeline.references > 0 ); + pipeline.references --; + if ( pipeline.references > 0 ) { + return; + } + + pipelineIndex_.erase( pipeline.idString ); + if ( pipeline.id != 0 ) { + glDeleteProgramPipelines( 1 , &pipeline.id ); + pipeline.id = 0; + } + + for ( auto const& pName : pipeline.programs ) { + auto pos( programIndex_.find( pName ) ); + assert( pos != programIndex_.end( ) ); + dereferenceProgram( pos->second , index ); } } -bool T_ShaderPipeline::valid( ) const noexcept +void T_ShaderManager::dereferenceProgram( + __rd__ const uint32_t index , + __rd__ const uint32_t pipeline ) { - return Globals::Shaders( ).pipelines_[ smIndex_ ].id != 0; + auto& program( programs_[ index ] ); + auto& refs( program.references ); + assert( refs.find( pipeline ) != refs.end( ) ); + refs.erase( pipeline ); + if ( refs.size( ) != 0 ) { + return; + } + resetProgram( program ); + programIndex_.erase( program.name ); } -#if 1 -void testLoadShaderFile( ) +void T_ShaderManager::initPipeline( + __rw__ T_Pipeline_& pipeline ) const { - const std::string source( "test-loader.glsl" ); - T_ShaderCodeLoader loader( Globals::Watcher( ) ); - T_Frankenshader code; - if ( loader.load( source , code ) ) { - printf( "SUCCESS! TYPE = %d\n" , int( code.type ) ); - printf( "FILES:\n" ); - for ( auto const& e : code.files ) { - printf( " -> %s (%s)\n" , e.first.c_str( ) , - e.second ? "found" : "missing" ); + assert( pipeline.id == 0 ); + + constexpr auto nst{ size_t( E_ShaderType::__COUNT__ ) }; + int32_t programs[ nst ]; + for ( auto i = 0u ; i < nst ; i ++ ) { + programs[ i ] = -1; + } + + for ( auto const& pName : pipeline.programs ) { + const uint32_t pid( programIndex_.find( pName )->second ); + auto const& program( programs_[ pid ] ); + if ( programs[ int( program.code.type ) ] != -1 + || program.id == 0 ) { + return; } - if ( code.errors.size( ) ) { - printf( "ERRORS:\n" ); - for ( auto const& err : code.errors ) { - printf( "%s:%d: %s\n" , err.source.c_str( ) , err.line , err.error.c_str( ) ); - } + programs[ int( program.code.type ) ] = pid; + } + + GLuint id( 0 ); + glGenProgramPipelines( 1 , &id ); + GL_CHECK( return ); + for ( auto i = 0u ; i < nst ; i ++ ) { + const auto pid( programs[ i ] ); + if ( pid == -1 ) { + continue; } + auto& program( programs_[ pid ] ); + const GLbitfield type( PipelineStages_[ int( program.code.type ) ] ); + glUseProgramStages( id , type , program.id ); + } + GL_CHECK({ + glDeleteProgramPipelines( 1 , &id ); + return; + }); + + pipeline.id = id; +} + + +void T_ShaderManager::initProgram( + __rw__ T_Program_& program ) +{ + assert( program.id == 0 ); + + // Build the code + auto name( program.name ); + auto& code( program.code ); + T_CodeBuilder_ cb( [this]( std::string const& n ) { return getInput( n ); } , + name , code ); + const bool built( + T_CodeBuilder_{ + [this]( std::string const& n ) { return getInput( n ); } , + program.name , code + }.buildCode( ) ); + + // Initialise file watcher + missing files + program.watch = std::make_unique< T_WatchedFiles >( Globals::Watcher( ) , + [this,name]() { + programUpdated( name ); + } ); + for ( auto entry : code.files ) { + if ( entry.second ) { + program.watch->watch( entry.first ); + } else { + missing_[ entry.first ].insert( name ); + } + } + + if ( !( built && code.errors.empty( ) ) ) { + return; + } + + // Try to compile the program + char const* const list[] = { + program.code.code.c_str( ) + }; + const GLenum sid = glCreateShaderProgramv( + ProgramTypes_[ int( code.type ) ] , + 1 , &list[ 0 ] ); + if ( sid == 0 ) { + code.errors.push_back( T_ShaderError{ + name , 0 , "failed to create GL program" } ); + return; + } + + // Read and convert the log + int infoLogLength( 0 ); + glGetProgramiv( sid , GL_INFO_LOG_LENGTH , &infoLogLength ); + if ( infoLogLength ) { + char buffer[ infoLogLength + 1 ]; + glGetProgramInfoLog( sid , infoLogLength , nullptr , buffer ); + char* start( buffer ); + char* found( strchr( buffer , '\n' ) ); + while ( found ) { + *found = 0; + parseGLSLError( code , start ); + start = found + 1; + found = strchr( start , '\n' ); + } + if ( start < &buffer[ infoLogLength - 1 ] ) { + parseGLSLError( code , start ); + } + } + + // Did it link? + GLint lnk( 0 ); + glGetProgramiv( sid , GL_LINK_STATUS , &lnk ); + if ( lnk ) { + program.id = sid; } else { - printf( "fail :'(\n" ); + glDeleteProgram( sid ); + } +} + +void T_ShaderManager::parseGLSLError( + __rw__ T_ShaderCode& code , + __rd__ char const* errorLine ) +{ + assert( !code.sources.empty( ) ); + + std::cmatch m; + uint32_t rawLine; + if ( std::regex_match( errorLine , m , GLSLErrorNv_ ) ) { + rawLine = atoi( m[ 0 ].str( ).c_str( ) ); + } else { + rawLine = 0; + } + + uint32_t check = 0 , pos = 0; + while ( pos < code.starts.size( ) && check < rawLine ) { + check += code.counts[ pos ]; + pos ++; + } + + code.errors.push_back( T_ShaderError{ + pos == 0 ? "*unknown*" : code.sources[ pos - 1 ] , + pos == 0 ? 0 : ( rawLine - ( check + code.counts[ pos - 1 ] ) + code.starts[ pos - 1 ] ) , + errorLine } ); +} + +void T_ShaderManager::programUpdated( + __rd__ std::string const& name ) +{ + updates_.insert( name ); +} + +void T_ShaderManager::resetProgram( + __rw__ T_Program_& program ) +{ + if ( program.watch ) { + program.watch.reset( ); + } + if ( program.id != 0 ) { + glDeleteProgram( program.id ); + program.id = 0; + return; + } + + for ( auto entry : program.code.files ) { + if ( entry.second || missing_.find( entry.first ) == missing_.end( ) ) { + continue; + } + auto& set( missing_[ entry.first ] ); + set.erase( program.name ); + if ( set.empty( ) ) { + missing_.erase( entry.first ); + } } } -#endif diff --git a/shaders.hh b/shaders.hh index 7d1c582..f2f64bb 100644 --- a/shaders.hh +++ b/shaders.hh @@ -58,7 +58,8 @@ using P_ShaderInput = std::unique_ptr< T_ShaderInput >; // Type of shader enum class E_ShaderType { - VERTEX , FRAGMENT , COMPUTE , + VERTEX , FRAGMENT , GEOMETRY , + COMPUTE , __COUNT__ }; @@ -72,81 +73,25 @@ struct T_ShaderError std::string error; }; -// Shader code after reconstitution -// XXX rename this -struct T_Frankenshader -{ - E_ShaderType type; - std::vector< uint32_t > starts; // Position of chunk in source file - std::vector< uint32_t > counts; // Chunk lengths - std::vector< std::string > sources; // Chunk source files - std::string code; - std::vector< T_ShaderError > errors; - std::map< std::string , bool > files; -}; -using P_Frankenshader = std::unique_ptr< T_Frankenshader >; - -// A shader program -struct T_ShaderProgram2 // FIXME: name -{ - ~T_ShaderProgram2( ) // XXX pipelines, notify loader - { if ( valid( ) ) glDeleteProgram( id_ ); } - - - bool valid( ) const noexcept - { return id_ != 0; } - std::string const& name( ) const noexcept - { return name_; } - GLuint id( ) const noexcept - { return id_; } - - bool activate( ) const - { - if ( valid( ) ) { - glUseProgram( id_ ); - GL_CHECK( return false ); - } - return valid( ); - } - - private: - std::string name_; - E_ShaderType type_; - GLuint id_; -}; - -// XXX test for the loader -struct T_ShaderCodeLoader -{ - T_ShaderCodeLoader( ) = delete; - explicit T_ShaderCodeLoader( - __rw__ T_FilesWatcher& watcher ); - - bool load( __rd__ std::string const& name , - __wr__ T_Frankenshader& code ); - - T_ShaderInput const* getInput( __rd__ std::string const& name ); - void removeInput( __rd__ std::string const& name ); - - private: - T_FilesWatcher& watcher_; - - std::set< std::string > pending_; - std::map< std::string , P_ShaderInput > inputs_; - std::map< std::string , P_Frankenshader > code_; - std::map< std::string , std::set< std::string > > deps_; - - void onFileUpdated( __rd__ std::string const& name ); -}; +struct T_ShaderManager; struct T_ShaderPipeline { + friend struct T_ShaderManager; + + T_ShaderPipeline( ); + COPY( T_ShaderPipeline ); + MOVE( T_ShaderPipeline ); + ~T_ShaderPipeline( ); + bool valid( ) const noexcept; void enable( ) const; private: - uint32_t smIndex_; + explicit T_ShaderPipeline( + __rd__ const int32_t index ); + int32_t smIndex_; }; @@ -154,31 +99,82 @@ struct T_ShaderManager { friend struct T_ShaderPipeline; + struct T_ShaderCode + { + E_ShaderType type; + std::vector< uint32_t > starts; // Position of chunk in source file + std::vector< uint32_t > counts; // Chunk lengths + std::vector< std::string > sources; // Chunk source files + std::string code; + std::vector< T_ShaderError > errors; + std::map< std::string , bool > files; + }; + T_ShaderPipeline pipeline( __rd__ std::initializer_list< std::string > shaders ); + void update( ); + private: struct T_Pipeline_ { uint32_t references; std::string idString; GLuint id; - int32_t programs[ size_t( E_ShaderType::__COUNT__ ) ]; + std::vector< std::string > programs; }; struct T_Program_ { - uint32_t references; std::string name; - E_ShaderType type; + std::set< uint32_t > references; + T_ShaderCode code; GLuint id; + std::unique_ptr< T_WatchedFiles > watch; }; std::vector< T_Pipeline_ > pipelines_; std::map< std::string , uint32_t > pipelineIndex_; - std::vector< T_Program_ > program_; + std::vector< T_Program_ > programs_; + std::map< std::string , uint32_t > programIndex_; + + std::map< std::string , P_ShaderInput > inputs_; + + std::map< std::string , std::set< std::string > > missing_; + std::set< std::string > updates_; + + uint32_t newPipelineRecord( ); + uint32_t newProgramRecord( ); + + void loadProgram( + __rd__ const uint32_t pipeline , + __rd__ std::string const& name ); + bool useExistingProgram( + __rd__ const uint32_t pipeline , + __rd__ std::string const& name ); + + T_ShaderInput const* getInput( + __rd__ std::string const& name ); + + void dereferencePipeline( + __rd__ const uint32_t index ); + void dereferenceProgram( + __rd__ const uint32_t index , + __rd__ const uint32_t pipeline ); + + void initPipeline( + __rw__ T_Pipeline_& pipeline ) const; + void initProgram( + __rw__ T_Program_& program ); + + void parseGLSLError( + __rw__ T_ShaderCode& code , + __rd__ char const* errorLine ); + + void programUpdated( + __rd__ std::string const& name ); + + void resetProgram( + __rw__ T_Program_& program ); }; - - -void testLoadShaderFile( );