Emmanuel BENOîT
3de255aad0
The program is loaded and its init part is run. Many bugs were fixed in the process, including various new bugs in the shaders manager.
1255 lines
28 KiB
C++
1255 lines
28 KiB
C++
#include "externals.hh"
|
|
#include "shaders.hh"
|
|
#include "globals.hh"
|
|
|
|
#include <ebcl/Sets.hh>
|
|
|
|
|
|
namespace {
|
|
|
|
const std::regex PreprocDirective_( "^\\s*//!\\s*([a-z]+(\\s+([^\\s]+))*)\\s*$" );
|
|
const std::regex UniformName_( "^[A-Za-z][A-Za-z0-9_]*$" );
|
|
const std::regex GLSLErrorNv_( "^[0-9]*\\(([0-9]+).*$" );
|
|
|
|
const std::map< std::string , E_ShaderInput > InputTypes_( ([] {
|
|
std::map< std::string , E_ShaderInput > t;
|
|
t.emplace( "chunk" , E_ShaderInput::CHUNK );
|
|
t.emplace( "library" , E_ShaderInput::LIBRARY );
|
|
t.emplace( "lib" , E_ShaderInput::LIBRARY );
|
|
t.emplace( "vertex" , E_ShaderInput::VERTEX );
|
|
t.emplace( "fragment" , E_ShaderInput::FRAGMENT );
|
|
t.emplace( "compute" , E_ShaderInput::COMPUTE );
|
|
t.emplace( "geo" , E_ShaderInput::GEOMETRY );
|
|
t.emplace( "geometry" , E_ShaderInput::GEOMETRY );
|
|
return t;
|
|
})());
|
|
|
|
const std::unordered_map< std::string , E_UniformType > UniformTypes_( ([] {
|
|
std::unordered_map< std::string , E_UniformType > t;
|
|
t.emplace( "float" , E_UniformType::F1 );
|
|
t.emplace( "vec2" , E_UniformType::F2 );
|
|
t.emplace( "vec3" , E_UniformType::F3 );
|
|
t.emplace( "vec4" , E_UniformType::F4 );
|
|
t.emplace( "int" , E_UniformType::I1 );
|
|
t.emplace( "ivec2" , E_UniformType::I2 );
|
|
t.emplace( "ivec3" , E_UniformType::I3 );
|
|
t.emplace( "ivec4" , E_UniformType::I4 );
|
|
t.emplace( "sampler2D" , E_UniformType::SAMPLER2D );
|
|
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" );
|
|
|
|
|
|
/*============================================================================*/
|
|
|
|
// Input reader state and functions, used when loading a source file
|
|
struct T_InputReader_
|
|
{
|
|
using T_Tokens_ = std::vector< std::string >;
|
|
|
|
FILE* const file;
|
|
T_ShaderInput& input;
|
|
uint32_t line{ 0 };
|
|
|
|
char* buffer{ nullptr };
|
|
ssize_t readCount;
|
|
|
|
std::string accumulator{ };
|
|
uint32_t accumLines{ 0 };
|
|
|
|
T_InputReader_( FILE* const file ,
|
|
T_ShaderInput& input )
|
|
: file( file ) , input( input )
|
|
{ }
|
|
~T_InputReader_( );
|
|
|
|
void read( );
|
|
|
|
void handleDirective( T_Tokens_ const& tokens );
|
|
void parseInputDirective( T_Tokens_ const& tokens );
|
|
|
|
void error( T_String const& err );
|
|
void nl( );
|
|
void addAccumulated( );
|
|
};
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
T_InputReader_::~T_InputReader_( )
|
|
{
|
|
if ( buffer ) {
|
|
free( buffer );
|
|
}
|
|
fclose( file );
|
|
}
|
|
|
|
void T_InputReader_::read( )
|
|
{
|
|
size_t bsz( 0 );
|
|
while ( ( readCount = getline( &buffer , &bsz , file ) ) != -1 ) {
|
|
line ++;
|
|
std::cmatch match;
|
|
if ( std::regex_match( buffer , match , PreprocDirective_ ) ) {
|
|
const T_Tokens_ tokens( ([](std::string const& a) {
|
|
using stri = std::istream_iterator< std::string >;
|
|
std::istringstream iss( a );
|
|
return T_Tokens_{ stri( iss ) , stri( ) };
|
|
})( match[ 1 ].str( ) ) );
|
|
assert( tokens.size( ) >= 1 );
|
|
handleDirective( tokens );
|
|
} else {
|
|
accumulator += buffer;
|
|
accumLines ++;
|
|
}
|
|
}
|
|
addAccumulated( );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_InputReader_::handleDirective(
|
|
T_Tokens_ const& tokens )
|
|
{
|
|
auto const& directive( tokens[ 0 ] );
|
|
|
|
if ( directive == "include" ) {
|
|
if ( tokens.size( ) != 2 ) {
|
|
nl( );
|
|
error( "invalid arguments" );
|
|
return;
|
|
}
|
|
addAccumulated( );
|
|
auto& ck( input.chunks );
|
|
ck.addNew( E_ShaderInputChunk::INCLUDE , tokens[ 1 ].c_str( ) , 1 );
|
|
|
|
} else if ( directive == "type" ) {
|
|
nl( );
|
|
if ( tokens.size( ) != 2 ) {
|
|
error( "invalid arguments" );
|
|
return;
|
|
}
|
|
auto pos( InputTypes_.find( tokens[ 1 ] ) );
|
|
if ( pos == InputTypes_.end( ) ) {
|
|
error( "unknown type" );
|
|
} else {
|
|
input.type = pos->second;
|
|
}
|
|
|
|
} else if ( directive == "input" ) {
|
|
nl( );
|
|
parseInputDirective( tokens );
|
|
|
|
} else if ( directive == "uniforms" ) {
|
|
nl( );
|
|
for ( auto const& c : input.chunks ) {
|
|
if ( c.type == E_ShaderInputChunk::UNIFORMS ) {
|
|
error( "duplicate uniform generation" );
|
|
return;
|
|
}
|
|
}
|
|
input.chunks.addNew( E_ShaderInputChunk::UNIFORMS , "" , 1 );
|
|
|
|
} else {
|
|
nl( );
|
|
error( "unknown directive" );
|
|
}
|
|
}
|
|
|
|
void T_InputReader_::parseInputDirective(
|
|
T_Tokens_ const& tokens )
|
|
{
|
|
if ( tokens.size( ) != 4 ) {
|
|
error( "invalid arguments" );
|
|
return;
|
|
}
|
|
|
|
// Local/global
|
|
const bool global{ tokens[ 1 ] == "global" };
|
|
if ( !global && tokens[ 1 ] != "local" ) {
|
|
error( "second argument should be 'local' or 'global'" );
|
|
return;
|
|
}
|
|
|
|
// Name
|
|
std::string const& name{ tokens[ 2 ] };
|
|
if ( input.uniforms.contains( name.c_str( ) ) ) {
|
|
error( "duplicate uniform" );
|
|
return;
|
|
}
|
|
if ( !std::regex_match( name , UniformName_ ) ) {
|
|
error( "invalid uniform name" );
|
|
return;
|
|
}
|
|
|
|
// Type
|
|
auto tPos( UniformTypes_.find( tokens[ 3 ] ) );
|
|
if ( tPos == UniformTypes_.end( ) ) {
|
|
error( "unsupported uniform type" );
|
|
return;
|
|
}
|
|
|
|
input.uniforms.add( T_ShaderUniform{
|
|
tokens[ 2 ].c_str( ) , global , tPos->second } );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_InputReader_::error(
|
|
T_String const& err )
|
|
{
|
|
input.errors.addNew( line , err );
|
|
}
|
|
|
|
void T_InputReader_::nl( )
|
|
{
|
|
accumLines ++;
|
|
accumulator += '\n';
|
|
}
|
|
|
|
void T_InputReader_::addAccumulated( )
|
|
{
|
|
if ( accumLines ) {
|
|
auto& ck( input.chunks );
|
|
ck.addNew( E_ShaderInputChunk::CODE ,
|
|
accumulator.c_str( ) , accumLines );
|
|
accumulator = {};
|
|
accumLines = 0;
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
|
|
/*= T_ShaderInput ============================================================*/
|
|
|
|
bool T_ShaderInput::load(
|
|
T_String const& path )
|
|
{
|
|
type = E_ShaderInput::CHUNK;
|
|
chunks.clear( );
|
|
errors.clear( );
|
|
|
|
FILE* const file{ fopen( (char const*) path.toOSString( ).data( ) , "r" ) };
|
|
if ( !file ) {
|
|
return false;
|
|
}
|
|
|
|
T_InputReader_ reader( file , *this );
|
|
reader.read( );
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
/*= T_ShaderPogram ===========================================================*/
|
|
|
|
T_ShaderProgram::T_ShaderProgram( )
|
|
: T_ShaderProgram( 0 )
|
|
{ }
|
|
|
|
T_ShaderProgram::T_ShaderProgram(
|
|
T_ShaderProgram const& other )
|
|
: T_ShaderProgram( other.id_ )
|
|
{ }
|
|
|
|
T_ShaderProgram::T_ShaderProgram(
|
|
T_ShaderProgram&& other ) noexcept
|
|
: T_ShaderProgram( )
|
|
{
|
|
swap( other.id_ , id_ );
|
|
}
|
|
|
|
T_ShaderProgram::T_ShaderProgram(
|
|
const uint32_t id ) noexcept
|
|
: id_( id )
|
|
{
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).programs_[ id_ - 1 ].saReferences ++;
|
|
}
|
|
}
|
|
|
|
T_ShaderProgram::~T_ShaderProgram( )
|
|
{
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).dereferenceProgram( id_ - 1 );
|
|
}
|
|
}
|
|
|
|
T_ShaderProgram& T_ShaderProgram::operator=(
|
|
T_ShaderProgram const& other )
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).dereferenceProgram( id_ - 1 );
|
|
}
|
|
id_ = other.id_;
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).programs_[ id_ - 1 ].saReferences ++;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
T_ShaderProgram& T_ShaderProgram::operator=(
|
|
T_ShaderProgram&& other ) noexcept
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).dereferenceProgram( id_ - 1 );
|
|
}
|
|
id_ = other.id_;
|
|
other.id_ = T_String{};
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool T_ShaderProgram::valid( ) const noexcept
|
|
{
|
|
return id_ && Globals::Shaders( ).programs_[ id_ - 1 ].id != 0;
|
|
}
|
|
|
|
void T_ShaderProgram::enable( ) const
|
|
{
|
|
if ( !id_ ) {
|
|
return;
|
|
}
|
|
|
|
auto const& p( Globals::Shaders( ).programs_[ id_ - 1 ] );
|
|
if ( p.id ) {
|
|
glBindProgramPipeline( 0 );
|
|
glUseProgram( p.id );
|
|
}
|
|
}
|
|
|
|
GLuint T_ShaderProgram::id( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return Globals::Shaders( ).programs_[ id_ - 1 ].id;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
T_Optional< E_ShaderType > T_ShaderProgram::type( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return Globals::Shaders( ).programs_[ id_ - 1 ].code.type;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
T_String T_ShaderProgram::name( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return Globals::Shaders( ).programs_[ id_ - 1 ].name;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
|
|
/*= T_ShaderPipeline =========================================================*/
|
|
|
|
T_ShaderPipeline::T_ShaderPipeline( )
|
|
: T_ShaderPipeline( T_String{} )
|
|
{ }
|
|
|
|
T_ShaderPipeline::T_ShaderPipeline(
|
|
T_ShaderPipeline const& other )
|
|
: T_ShaderPipeline( other.id_ )
|
|
{ }
|
|
|
|
T_ShaderPipeline::T_ShaderPipeline(
|
|
T_ShaderPipeline&& other ) noexcept
|
|
: T_ShaderPipeline( )
|
|
{
|
|
swap( other.id_ , id_ );
|
|
}
|
|
|
|
T_ShaderPipeline::T_ShaderPipeline( T_String id ) noexcept
|
|
: id_( std::move( id ) )
|
|
{
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).pipelines_.get( id_ )->references ++;
|
|
}
|
|
}
|
|
|
|
T_ShaderPipeline::~T_ShaderPipeline( )
|
|
{
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).dereferencePipeline( id_ );
|
|
}
|
|
}
|
|
|
|
T_ShaderPipeline& T_ShaderPipeline::operator=(
|
|
T_ShaderPipeline const& other )
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).dereferencePipeline( id_ );
|
|
}
|
|
id_ = other.id_;
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).pipelines_.get( id_ )->references ++;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
T_ShaderPipeline& T_ShaderPipeline::operator=(
|
|
T_ShaderPipeline&& other ) noexcept
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
Globals::Shaders( ).dereferencePipeline( id_ );
|
|
}
|
|
id_ = other.id_;
|
|
other.id_ = T_String{};
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool T_ShaderPipeline::valid( ) const noexcept
|
|
{
|
|
return id_
|
|
&& Globals::Shaders( ).pipelines_.get( id_ )->id != 0;
|
|
}
|
|
|
|
void T_ShaderPipeline::enable( ) const
|
|
{
|
|
if ( !id_ ) {
|
|
return;
|
|
}
|
|
|
|
auto const* pl( Globals::Shaders( ).pipelines_.get( id_ ) );
|
|
if ( pl && pl->id ) {
|
|
glUseProgram( 0 );
|
|
glBindProgramPipeline( pl->id );
|
|
}
|
|
}
|
|
|
|
GLuint T_ShaderPipeline::id( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return Globals::Shaders( ).pipelines_.get( id_ )->id;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
GLuint T_ShaderPipeline::program(
|
|
const E_ShaderType program ) const
|
|
{
|
|
if ( !valid( ) ) {
|
|
return 0;
|
|
}
|
|
|
|
auto const& sm( Globals::Shaders( ) );
|
|
auto const& pl( *sm.pipelines_.get( id_ ) );
|
|
for ( auto const& pn : pl.programs ) {
|
|
auto const* pos( sm.programIndex_.get( pn ) );
|
|
if ( !pos ) {
|
|
continue;
|
|
}
|
|
auto& p( sm.programs_[ *pos ] );
|
|
if ( p.code.type == program ) {
|
|
return p.id;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*= T_CodeBuilder_ ===========================================================*/
|
|
|
|
namespace {
|
|
|
|
using F_GetInput_ = std::function< T_ShaderInput const*( T_String const& ) >;
|
|
using T_Code_ = T_ShaderManager::T_ShaderCode;
|
|
|
|
// Code builder, state and functions
|
|
struct T_CodeBuilder_
|
|
{
|
|
struct T_StackEntry_ {
|
|
T_String name;
|
|
T_ShaderInput const* input;
|
|
uint32_t pos;
|
|
};
|
|
|
|
F_GetInput_ loader;
|
|
const T_String name;
|
|
T_Code_& code;
|
|
|
|
T_ShaderInput const* main;
|
|
T_KeyValueTable< T_String , uint32_t > pos;
|
|
std::vector< T_StackEntry_ > stack;
|
|
T_Array< T_String > libraries;
|
|
T_ShaderInput const* current;
|
|
uint32_t cpos{ 0 };
|
|
T_String cname;
|
|
|
|
T_CodeBuilder_( F_GetInput_ loader ,
|
|
T_String const& name ,
|
|
T_Code_& code )
|
|
: loader( loader ) , name( name ) , code( code ) ,
|
|
main( loader( name ) )
|
|
{ }
|
|
|
|
bool buildCode( );
|
|
void appendChunk( T_ShaderInputChunk const& chunk );
|
|
void include( T_String const& name ,
|
|
const uint32_t lines );
|
|
void next( );
|
|
|
|
void addInputLoaderErrors(
|
|
T_ShaderInput const* input ,
|
|
T_String const& name );
|
|
};
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
bool T_CodeBuilder_::buildCode( )
|
|
{
|
|
code = T_Code_{ };
|
|
code.files.add( name , main != nullptr );
|
|
if ( !main ) {
|
|
return false;
|
|
}
|
|
if ( main->type == E_ShaderInput::CHUNK
|
|
|| main->type == E_ShaderInput::LIBRARY ) {
|
|
code.errors.addNew( name , 0 , "invalid type" );
|
|
} else {
|
|
code.type = E_ShaderType( int( main->type ) - 2 );
|
|
}
|
|
addInputLoaderErrors( main , name );
|
|
cname = name;
|
|
current = main;
|
|
|
|
while ( cpos < current->chunks.size( ) ) {
|
|
auto& chunk( current->chunks[ cpos ] );
|
|
if ( chunk.type == E_ShaderInputChunk::CODE ) {
|
|
appendChunk( chunk );
|
|
} else {
|
|
include( chunk.text , chunk.lines );
|
|
}
|
|
|
|
next( );
|
|
}
|
|
|
|
code.code << '\0';
|
|
return true;
|
|
}
|
|
|
|
void T_CodeBuilder_::appendChunk(
|
|
T_ShaderInputChunk const& chunk )
|
|
{
|
|
auto* p( pos.get( cname ) );
|
|
if ( !p ) {
|
|
pos.add( cname , 0u );
|
|
p = pos.get( cname );
|
|
}
|
|
code.sources.addNew( cname );
|
|
code.counts.add( chunk.lines );
|
|
code.starts.add( *p );
|
|
code.code << chunk.text;
|
|
*p += chunk.lines;
|
|
}
|
|
|
|
void T_CodeBuilder_::include(
|
|
T_String const& nname ,
|
|
const uint32_t lines )
|
|
{
|
|
auto* const p( pos.get( cname ) );
|
|
const auto prevPos( *p );
|
|
*p += lines;
|
|
|
|
// Avoid recursion
|
|
if ( nname == cname || 0 != count_if( stack.begin( ) , stack.end( ) ,
|
|
[nname] ( T_StackEntry_ const& e ) {
|
|
return nname == e.name;
|
|
} ) ) {
|
|
T_StringBuilder sb;
|
|
code.errors.addNew( cname , prevPos ,
|
|
sb << "recursive inclusion of '"
|
|
<< nname << "'" );
|
|
return;
|
|
}
|
|
|
|
// Avoid including libraries more than once
|
|
if ( libraries.contains( nname ) ) {
|
|
return;
|
|
}
|
|
|
|
T_ShaderInput const* const isi( loader( nname ) );
|
|
code.files.add( nname , isi != nullptr );
|
|
|
|
// Check for problems
|
|
if ( !isi ) {
|
|
// Not found
|
|
code.errors.addNew( cname , prevPos , "file not found" );
|
|
return;
|
|
}
|
|
if ( isi->type != E_ShaderInput::CHUNK && isi->type != E_ShaderInput::LIBRARY ) {
|
|
// Trying to load a top-level shader
|
|
code.errors.addNew( cname , prevPos , "trying to include a top-level file" );
|
|
return;
|
|
}
|
|
|
|
// Add input loader errors
|
|
if ( !libraries.contains( nname ) ) {
|
|
addInputLoaderErrors( isi , nname );
|
|
}
|
|
libraries.add( nname );
|
|
|
|
// Enter the new file
|
|
stack.push_back( T_StackEntry_{ cname , current , cpos } );
|
|
cname = nname;
|
|
current = isi;
|
|
cpos = UINT32_MAX;
|
|
pos.set( cname , 0u );
|
|
}
|
|
|
|
void T_CodeBuilder_::next( )
|
|
{
|
|
cpos ++;
|
|
while ( cpos == current->chunks.size( ) && !stack.empty( ) ) {
|
|
T_StackEntry_ const& se( stack[ stack.size( ) - 1 ] );
|
|
pos.remove( cname );
|
|
cpos = se.pos + 1;
|
|
current = se.input;
|
|
cname = se.name;
|
|
stack.pop_back( );
|
|
}
|
|
}
|
|
|
|
void T_CodeBuilder_::addInputLoaderErrors(
|
|
T_ShaderInput const* input ,
|
|
T_String const& name )
|
|
{
|
|
for ( auto const& errs : input->errors ) {
|
|
code.errors.addNew( name , errs.line , (char*) errs.error.toOSString( ).data( ) );
|
|
}
|
|
}
|
|
|
|
|
|
} // namespace
|
|
|
|
/*============================================================================*/
|
|
|
|
T_ShaderManager::T_ShaderManager( ) noexcept
|
|
: pipelines_( []( T_Pipeline_ const& p ) -> T_String {
|
|
return p.idString;
|
|
} )
|
|
{ }
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
T_ShaderProgram T_ShaderManager::program(
|
|
T_String const& name )
|
|
{
|
|
loadProgram( name );
|
|
assert( programIndex_.contains( name ) );
|
|
assert( programs_[ *programIndex_.get( name ) ].name == name );
|
|
return T_ShaderProgram( *programIndex_.get( name ) + 1 );
|
|
}
|
|
|
|
T_ShaderPipeline T_ShaderManager::pipeline(
|
|
std::initializer_list< T_String > shaders )
|
|
{
|
|
if ( shaders.size( ) < 1 || shaders.size( ) > 5 ) {
|
|
return {};
|
|
}
|
|
|
|
const T_String id( ([&shaders] () {
|
|
T_StringBuilder sb;
|
|
for ( auto const& s : shaders ) {
|
|
sb << s << '|';
|
|
}
|
|
sb << '\0';
|
|
return T_String{ std::move( sb.data( ) ) };
|
|
} )() );
|
|
|
|
auto const* existing( pipelines_.get( id ) );
|
|
if ( existing ) {
|
|
return T_ShaderPipeline{ existing->idString };
|
|
}
|
|
|
|
pipelines_.add( T_Pipeline_{ id , shaders } );
|
|
auto& p( *pipelines_.get( id ) );
|
|
for ( auto const& pName : shaders ) {
|
|
loadProgram( p.idString , pName );
|
|
}
|
|
initPipeline( p );
|
|
|
|
return T_ShaderPipeline{ p.idString };
|
|
}
|
|
|
|
T_ShaderPipeline T_ShaderManager::pipeline(
|
|
T_String const* shaderNames ,
|
|
uint32_t count )
|
|
{
|
|
if ( count < 1 || count > 5 ) {
|
|
return {};
|
|
}
|
|
|
|
const T_String id( ([shaderNames,count] () {
|
|
T_StringBuilder sb;
|
|
for ( auto i = 0u ; i < count ; i ++ ) {
|
|
sb << shaderNames[ i ] << '|';
|
|
}
|
|
sb << '\0';
|
|
return T_String{ std::move( sb.data( ) ) };
|
|
} )() );
|
|
|
|
auto const* existing( pipelines_.get( id ) );
|
|
if ( existing ) {
|
|
return T_ShaderPipeline{ existing->idString };
|
|
}
|
|
|
|
pipelines_.add( T_Pipeline_{ id , shaderNames , count } );
|
|
auto& p( *pipelines_.get( id ) );
|
|
for ( auto i = 0u ; i < count ; i ++ ) {
|
|
loadProgram( p.idString , shaderNames[ i ] );
|
|
}
|
|
initPipeline( p );
|
|
|
|
return T_ShaderPipeline{ p.idString };
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_ShaderManager::update( )
|
|
{
|
|
using namespace ebcl;
|
|
T_Set< T_String > temp{ UseTag< ArrayBacked< 64 > >( ) };
|
|
T_StringBuilder sb;
|
|
inputs_.clear( );
|
|
|
|
// Check for missing files
|
|
for ( auto it = missing_.keys( ).cbegin( ) ; it.valid( ) ; ++it ) {
|
|
const bool exists( ([&sb] ( T_String const& name ) -> bool {
|
|
struct stat buffer;
|
|
sb.clear( );
|
|
sb << "shaders/" << name << '\0';
|
|
return ( stat( sb.data( ) , &buffer ) == 0 );
|
|
})( *it ) );
|
|
if ( !exists ) {
|
|
continue;
|
|
}
|
|
auto& set( missing_.values( )[ it.pos( ) ] );
|
|
for ( auto& s : set ) {
|
|
if ( !updates_.contains( s ) ) {
|
|
updates_.add( std::move( s ) );
|
|
}
|
|
}
|
|
temp.add( *it );
|
|
}
|
|
|
|
// Remove all marked entries
|
|
for ( auto i = 0u ; i < temp.size( ) ; i ++ ) {
|
|
missing_.remove( temp[ i ] );
|
|
}
|
|
temp.clear( );
|
|
|
|
// Reset programs that need to be updated
|
|
for ( auto it = updates_.begin( ) ; it.valid( ) ; ) {
|
|
auto const* p( programIndex_.get( *it ) );
|
|
if ( p ) {
|
|
auto& pr( programs_[ *p ] );
|
|
for ( auto const& ref : pr.plReferences ) {
|
|
if ( !temp.contains( ref ) ) {
|
|
temp.add( ref );
|
|
}
|
|
}
|
|
resetProgram( pr );
|
|
++it;
|
|
} else {
|
|
updates_.removeSwap( it.pos( ) );
|
|
}
|
|
}
|
|
if ( updates_.empty( ) ) {
|
|
return;
|
|
}
|
|
|
|
// Reset pipelines affected by the programs above
|
|
for ( auto i = 0u ; i < temp.size( ) ; i ++ ) {
|
|
auto& pipeline( *pipelines_.get( temp[ i ] ) );
|
|
if ( pipeline.id != 0 ) {
|
|
glDeleteProgramPipelines( 1 , &pipeline.id );
|
|
pipeline.id = 0;
|
|
}
|
|
}
|
|
|
|
// Try to load all updated programs
|
|
sb.clear( );
|
|
for ( auto const& name : updates_ ) {
|
|
auto& pr( programs_[ *programIndex_.get( name ) ] );
|
|
initProgram( pr );
|
|
for ( auto const& e : pr.code.errors ) {
|
|
sb << e.source << ':' << e.line << ": "
|
|
<< e.error << '\n';
|
|
}
|
|
}
|
|
sb << '\0';
|
|
printf( "%s" , sb.data( ) );
|
|
|
|
// Try to initialise all affected pipelines
|
|
for ( auto i = 0u ; i < temp.size( ) ; i ++ ) {
|
|
initPipeline( *pipelines_.get( temp[ i ] ) );
|
|
}
|
|
|
|
updates_.clear( );
|
|
}
|
|
|
|
|
|
void T_ShaderManager::loadProgram(
|
|
T_String const& pipeline ,
|
|
T_String const& name )
|
|
{
|
|
if ( !useExistingProgram( pipeline , name ) ) {
|
|
initProgramRecord( name ).plReferences.add( pipeline );
|
|
}
|
|
}
|
|
|
|
bool T_ShaderManager::useExistingProgram(
|
|
T_String const& pipeline ,
|
|
T_String const& name )
|
|
{
|
|
auto const* pos( programIndex_.get( name ) );
|
|
if ( !pos ) {
|
|
return false;
|
|
}
|
|
auto& refs( programs_[ *pos ].plReferences );
|
|
assert( !refs.contains( pipeline ) );
|
|
refs.add( pipeline );
|
|
return true;
|
|
}
|
|
|
|
void T_ShaderManager::loadProgram(
|
|
T_String const& name )
|
|
{
|
|
if ( !useExistingProgram( name ) ) {
|
|
initProgramRecord( name );
|
|
}
|
|
}
|
|
|
|
bool T_ShaderManager::useExistingProgram(
|
|
T_String const& name )
|
|
{
|
|
auto const* pos( programIndex_.get( name ) );
|
|
if ( !pos ) {
|
|
return false;
|
|
}
|
|
programs_[ *pos ].saReferences ++;
|
|
return true;
|
|
}
|
|
|
|
|
|
uint32_t T_ShaderManager::newProgramRecord( )
|
|
{
|
|
uint32_t i = 0;
|
|
while ( i < programs_.size( ) ) {
|
|
if ( programs_[ i ].plReferences.empty( )
|
|
&& !programs_[ i ].saReferences ) {
|
|
return i;
|
|
}
|
|
i ++;
|
|
}
|
|
programs_.addNew( );
|
|
return i;
|
|
}
|
|
|
|
T_ShaderManager::T_Program_& T_ShaderManager::initProgramRecord(
|
|
T_String const& name )
|
|
{
|
|
const uint32_t index( newProgramRecord( ) );
|
|
auto& program( programs_[ index ] );
|
|
programIndex_.add( name , index );
|
|
program.name = name;
|
|
program.id = 0;
|
|
initProgram( program );
|
|
|
|
T_StringBuilder sb;
|
|
for ( auto const& e : program.code.errors ) {
|
|
sb << e.source << ':' << e.line << ": "
|
|
<< e.error << '\n';
|
|
}
|
|
sb << '\0';
|
|
printf( "%s" , sb.data( ) );
|
|
|
|
return program;
|
|
}
|
|
|
|
|
|
T_ShaderInput const* T_ShaderManager::getInput(
|
|
T_String const& name )
|
|
{
|
|
auto const* const existing( inputs_.get( name ) );
|
|
if ( existing ) {
|
|
return existing->get( );
|
|
}
|
|
|
|
T_ShaderInput ni;
|
|
T_StringBuilder sb;
|
|
sb << "shaders/" << name;
|
|
if ( !ni.load( sb ) ) {
|
|
return nullptr;
|
|
}
|
|
inputs_.add( name , NewOwned< T_ShaderInput >( std::move( ni ) ) );
|
|
return inputs_.get( name )->get( );
|
|
}
|
|
|
|
|
|
void T_ShaderManager::dereferencePipeline(
|
|
T_String const& id )
|
|
{
|
|
auto& pipeline( *pipelines_.get( id ) );
|
|
assert( pipeline.references > 0 );
|
|
pipeline.references --;
|
|
if ( pipeline.references > 0 ) {
|
|
return;
|
|
}
|
|
|
|
if ( pipeline.id != 0 ) {
|
|
glDeleteProgramPipelines( 1 , &pipeline.id );
|
|
pipeline.id = 0;
|
|
}
|
|
|
|
for ( auto const& pName : pipeline.programs ) {
|
|
auto const* pos( programIndex_.get( pName ) );
|
|
assert( pos );
|
|
dereferenceProgram( *pos , id );
|
|
}
|
|
|
|
pipelines_.remove( pipeline.idString );
|
|
}
|
|
|
|
|
|
void T_ShaderManager::dereferenceProgram(
|
|
const uint32_t index ,
|
|
T_String const& pipeline )
|
|
{
|
|
auto& program( programs_[ index ] );
|
|
auto& refs( program.plReferences );
|
|
const auto plidx( refs.indexOf( pipeline ) );
|
|
assert( plidx >= 0 );
|
|
refs.remove( plidx );
|
|
if ( !refs.empty( ) || program.saReferences ) {
|
|
return;
|
|
}
|
|
resetProgram( program );
|
|
programIndex_.remove( program.name );
|
|
}
|
|
|
|
|
|
void T_ShaderManager::dereferenceProgram(
|
|
const uint32_t index )
|
|
{
|
|
auto& program( programs_[ index ] );
|
|
assert( program.saReferences > 0 );
|
|
program.saReferences --;
|
|
if ( program.saReferences || !program.plReferences.empty( ) ) {
|
|
return;
|
|
}
|
|
resetProgram( program );
|
|
programIndex_.remove( program.name );
|
|
}
|
|
|
|
|
|
void T_ShaderManager::initPipeline(
|
|
T_Pipeline_& pipeline ) const
|
|
{
|
|
assert( pipeline.id == 0 );
|
|
|
|
printf( "init pipeline %s\n" , pipeline.idString.toOSString( ).data( ) );
|
|
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_.get( pName ) );
|
|
auto const& program( programs_[ pid ] );
|
|
if ( programs[ int( program.code.type ) ] != -1
|
|
|| program.id == 0 ) {
|
|
printf( "... failed\n" );
|
|
return;
|
|
}
|
|
programs[ int( program.code.type ) ] = pid;
|
|
}
|
|
|
|
GLuint id( 0 );
|
|
GL_CHECK( {} );
|
|
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 );
|
|
printf( "... failed\n" );
|
|
return;
|
|
});
|
|
|
|
pipeline.id = id;
|
|
printf( "... success\n" );
|
|
}
|
|
|
|
|
|
void T_ShaderManager::initProgram(
|
|
T_Program_& program )
|
|
{
|
|
assert( program.id == 0 );
|
|
|
|
// Build the code
|
|
auto name( program.name );
|
|
printf( "init program %s\n" , program.name.toOSString( ).data( ) );
|
|
auto& code( program.code );
|
|
T_CodeBuilder_ cb( [this]( T_String const& n ) { return getInput( n ); } ,
|
|
name , code );
|
|
const bool built(
|
|
T_CodeBuilder_{
|
|
[this]( T_String const& n ) { return getInput( n ); } ,
|
|
program.name , code
|
|
}.buildCode( ) );
|
|
|
|
// Initialise file watcher + missing files
|
|
program.watch = T_WatchedFiles{ Globals::Watcher( ) ,
|
|
[this,name]() {
|
|
programUpdated( name );
|
|
} };
|
|
const auto nf( code.files.size( ) );
|
|
auto& w( *( program.watch.target( ) ) );
|
|
for ( auto i = 0u ; i < nf ; i ++ ) {
|
|
T_String const& fn( code.files.keys( )[ i ] );
|
|
if ( code.files.values( )[ i ] ) {
|
|
T_StringBuilder sb;
|
|
sb << "shaders/" << fn;
|
|
w.watch( std::move( sb ) );
|
|
} else {
|
|
auto& mset( missing_.getOrCreate( fn ) );
|
|
if ( !mset.contains( name ) ) {
|
|
mset.add( name );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !( built && code.errors.empty( ) ) ) {
|
|
return;
|
|
}
|
|
|
|
// Try to compile the program
|
|
char const* const list[] = {
|
|
program.code.code.data( )
|
|
};
|
|
const GLenum sid = glCreateShaderProgramv(
|
|
ProgramTypes_[ int( code.type ) ] ,
|
|
1 , &list[ 0 ] );
|
|
if ( sid == 0 ) {
|
|
code.errors.addNew( 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;
|
|
printf( "... success\n" );
|
|
} else {
|
|
glDeleteProgram( sid );
|
|
}
|
|
}
|
|
|
|
void T_ShaderManager::parseGLSLError(
|
|
T_ShaderCode& code ,
|
|
char const* errorLine )
|
|
{
|
|
assert( !code.sources.empty( ) );
|
|
|
|
std::cmatch m;
|
|
uint32_t rawLine;
|
|
if ( std::regex_match( errorLine , m , GLSLErrorNv_ ) ) {
|
|
rawLine = atoi( m[ 1 ].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.addNew(
|
|
pos == 0 ? "*unknown*" : ((char*)code.sources[ pos - 1 ].toOSString( ).data( ) ) ,
|
|
pos == 0 ? 0 : ( rawLine + code.counts[ pos - 1 ] - check + code.starts[ pos - 1 ] ) ,
|
|
errorLine );
|
|
}
|
|
|
|
void T_ShaderManager::programUpdated(
|
|
T_String const& name )
|
|
{
|
|
if ( !updates_.contains( name ) ) {
|
|
updates_.add( name );
|
|
}
|
|
}
|
|
|
|
void T_ShaderManager::resetProgram(
|
|
T_Program_& program )
|
|
{
|
|
if ( program.watch ) {
|
|
program.watch->clear( );
|
|
}
|
|
if ( program.id != 0 ) {
|
|
glDeleteProgram( program.id );
|
|
program.id = 0;
|
|
return;
|
|
}
|
|
|
|
auto const& files( program.code.files );
|
|
const auto nf( files.size( ) );
|
|
for ( auto i = 0u ; i < nf ; i ++ ) {
|
|
T_String const& k{ files.keys( )[ i ] };
|
|
bool v{ files[ i ] };
|
|
auto* const ptr( missing_.get( k ) );
|
|
if ( v || !ptr ) {
|
|
continue;
|
|
}
|
|
auto& set( *ptr );
|
|
const auto index( set.indexOf( program.name ) );
|
|
if ( index != -1 ) {
|
|
set.removeSwap( index );
|
|
if ( set.empty( ) ) {
|
|
missing_.remove( k );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_ShaderManager::makeUI( )
|
|
{
|
|
if ( !uiEnabled_ ) {
|
|
return;
|
|
}
|
|
|
|
auto const& dspSize( ImGui::GetIO( ).DisplaySize );
|
|
ImGui::SetNextWindowSize( ImVec2( dspSize.x , 150 ) ,
|
|
ImGuiSetCond_Once );
|
|
ImGui::SetNextWindowPos( ImVec2( 0 , dspSize.y - 150 ) ,
|
|
ImGuiSetCond_Once );
|
|
ImGui::Begin( "Shaders" );
|
|
|
|
const auto n( std::count_if( programs_.begin( ) , programs_.end( ) ,
|
|
[]( auto const& p ) {
|
|
return !p.plReferences.empty( ) || p.saReferences != 0;
|
|
} ) );
|
|
|
|
std::vector< size_t > indices;
|
|
const auto rn = programs_.size( );
|
|
for ( auto i = 0u ; i < rn ; i ++ ) {
|
|
auto const& p( programs_[ i ] );
|
|
if ( !p.plReferences.empty( ) || p.saReferences ) {
|
|
indices.push_back( i );
|
|
}
|
|
}
|
|
std::sort( indices.begin( ) , indices.end( ) ,
|
|
[this]( size_t a , size_t b ) {
|
|
auto const& pa( programs_[ a ] );
|
|
auto const& pb( programs_[ b ] );
|
|
if ( pa.code.errors.size( ) != pb.code.errors.size( ) ) {
|
|
return pa.code.errors.size( ) > pb.code.errors.size( );
|
|
}
|
|
if ( pa.plReferences.size( ) != pb.plReferences.size( ) ) {
|
|
return pa.plReferences.size( ) > pb.plReferences.size( );
|
|
}
|
|
return programs_[ a ].name < programs_[ b ].name;
|
|
} );
|
|
|
|
for ( auto i = 0u ; i < n ; i ++ ) {
|
|
auto const& program( programs_[ indices[ i ] ] );
|
|
const auto nErrors( program.code.errors.size( ) );
|
|
|
|
const bool open( nErrors
|
|
? ImGui::TreeNodeEx( &program , ImGuiTreeNodeFlags_OpenOnArrow
|
|
| ImGuiTreeNodeFlags_OpenOnDoubleClick , "%s" ,
|
|
program.name.toOSString( ).data( ) )
|
|
: false );
|
|
|
|
if ( !nErrors ) {
|
|
ImGui::Text( "%s" , program.name.toOSString( ).data( ) );
|
|
}
|
|
|
|
ImGui::SameLine( 400 );
|
|
ImGui::Text( "Usage: %u" , program.plReferences.size( ) + program.saReferences );
|
|
ImGui::SameLine( 550 );
|
|
if ( program.code.errors.empty( ) ) {
|
|
ImGui::PushStyleColor( ImGuiCol_Text , ImVec4( .6 , 1 , .6 , 1 ) );
|
|
ImGui::Text( "No errors" );
|
|
} else {
|
|
ImGui::PushStyleColor( ImGuiCol_Text , ImVec4( 1 , .6 , .6 , 1 ) );
|
|
ImGui::Text( "%u error%s" , nErrors , nErrors > 1 ? "s" : "" );
|
|
}
|
|
ImGui::PopStyleColor( );
|
|
|
|
if ( open ) {
|
|
for ( auto const& err : program.code.errors ) {
|
|
ImGui::NewLine( );
|
|
ImGui::SameLine( 50 );
|
|
ImGui::Text( "%s" , err.source.toOSString( ).data( ) );
|
|
ImGui::SameLine( 250 );
|
|
ImGui::Text( "line %d" , err.line );
|
|
ImGui::SameLine( 370 );
|
|
ImGui::Text( "%s" , err.error.toOSString( ).data( ) );
|
|
}
|
|
ImGui::TreePop( );
|
|
}
|
|
}
|
|
|
|
ImGui::End( );
|
|
}
|