Emmanuel BENOîT
68d01ca42e
Shaders are no longer found under /shaders in the project; they can be anywhere. In addition, paths for both (program) instructions in the script and include directives in shader source code are relative to the file which contains them.
1109 lines
25 KiB
C++
1109 lines
25 KiB
C++
#include "externals.hh"
|
|
|
|
#include "common.hh"
|
|
#include "c-project.hh"
|
|
|
|
#include "ui.hh"
|
|
#include "ui-shaders.hh"
|
|
#include "ui-utilities.hh"
|
|
|
|
#include <ebcl/Sets.hh>
|
|
|
|
|
|
namespace {
|
|
|
|
const std::regex GLSLErrorNv_( "^[0-9]*\\(([0-9]+).*$" );
|
|
|
|
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" );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*= 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_ ) {
|
|
UI::Shaders( ).programs_[ id_ - 1 ].saReferences ++;
|
|
}
|
|
}
|
|
|
|
T_ShaderProgram::~T_ShaderProgram( )
|
|
{
|
|
if ( id_ ) {
|
|
UI::Shaders( ).dereferenceProgram( id_ - 1 );
|
|
}
|
|
}
|
|
|
|
T_ShaderProgram& T_ShaderProgram::operator=(
|
|
T_ShaderProgram const& other )
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
UI::Shaders( ).dereferenceProgram( id_ - 1 );
|
|
}
|
|
id_ = other.id_;
|
|
if ( id_ ) {
|
|
UI::Shaders( ).programs_[ id_ - 1 ].saReferences ++;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
T_ShaderProgram& T_ShaderProgram::operator=(
|
|
T_ShaderProgram&& other ) noexcept
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
UI::Shaders( ).dereferenceProgram( id_ - 1 );
|
|
}
|
|
id_ = other.id_;
|
|
other.id_ = T_String{};
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool T_ShaderProgram::valid( ) const noexcept
|
|
{
|
|
return id_ && UI::Shaders( ).programs_[ id_ - 1 ].id != 0;
|
|
}
|
|
|
|
void T_ShaderProgram::enable( ) const
|
|
{
|
|
if ( !id_ ) {
|
|
return;
|
|
}
|
|
|
|
auto const& p( UI::Shaders( ).programs_[ id_ - 1 ] );
|
|
if ( p.id ) {
|
|
glBindProgramPipeline( 0 );
|
|
glUseProgram( p.id );
|
|
}
|
|
}
|
|
|
|
GLuint T_ShaderProgram::id( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return UI::Shaders( ).programs_[ id_ - 1 ].id;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
T_Optional< E_ShaderType > T_ShaderProgram::type( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return UI::Shaders( ).programs_[ id_ - 1 ].code.type;
|
|
} else {
|
|
return {};
|
|
}
|
|
}
|
|
|
|
T_FSPath T_ShaderProgram::name( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return UI::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_ ) {
|
|
UI::Shaders( ).pipelines_.get( id_ )->references ++;
|
|
}
|
|
}
|
|
|
|
T_ShaderPipeline::~T_ShaderPipeline( )
|
|
{
|
|
if ( id_ ) {
|
|
UI::Shaders( ).dereferencePipeline( id_ );
|
|
}
|
|
}
|
|
|
|
T_ShaderPipeline& T_ShaderPipeline::operator=(
|
|
T_ShaderPipeline const& other )
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
UI::Shaders( ).dereferencePipeline( id_ );
|
|
}
|
|
id_ = other.id_;
|
|
if ( id_ ) {
|
|
UI::Shaders( ).pipelines_.get( id_ )->references ++;
|
|
}
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
T_ShaderPipeline& T_ShaderPipeline::operator=(
|
|
T_ShaderPipeline&& other ) noexcept
|
|
{
|
|
if ( this != &other ) {
|
|
if ( id_ ) {
|
|
UI::Shaders( ).dereferencePipeline( id_ );
|
|
}
|
|
id_ = other.id_;
|
|
other.id_ = T_String{};
|
|
}
|
|
return *this;
|
|
}
|
|
|
|
bool T_ShaderPipeline::valid( ) const noexcept
|
|
{
|
|
return id_
|
|
&& UI::Shaders( ).pipelines_.get( id_ )->id != 0;
|
|
}
|
|
|
|
void T_ShaderPipeline::enable( ) const
|
|
{
|
|
if ( !id_ ) {
|
|
return;
|
|
}
|
|
|
|
auto const* pl( UI::Shaders( ).pipelines_.get( id_ ) );
|
|
if ( pl && pl->id ) {
|
|
glUseProgram( 0 );
|
|
glBindProgramPipeline( pl->id );
|
|
UI::Shaders( ).cPipeline_ = id_;
|
|
}
|
|
}
|
|
|
|
GLuint T_ShaderPipeline::id( ) const
|
|
{
|
|
if ( id_ ) {
|
|
return UI::Shaders( ).pipelines_.get( id_ )->id;
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
GLuint T_ShaderPipeline::program(
|
|
const E_ShaderType program ) const
|
|
{
|
|
if ( !valid( ) ) {
|
|
return 0;
|
|
}
|
|
|
|
auto const& sm( UI::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_FSPath const& ) >;
|
|
using T_Code_ = T_ShaderManager::T_ShaderCode;
|
|
|
|
// Code builder, state and functions
|
|
struct T_CodeBuilder_
|
|
{
|
|
struct T_StackEntry_ {
|
|
T_FSPath name;
|
|
T_ShaderInput const* input;
|
|
uint32_t pos;
|
|
};
|
|
|
|
F_GetInput_ loader;
|
|
const T_FSPath name;
|
|
T_Code_& code;
|
|
|
|
T_ShaderInput const* main;
|
|
T_KeyValueTable< T_FSPath , uint32_t > pos;
|
|
std::vector< T_StackEntry_ > stack;
|
|
T_Array< T_FSPath > libraries;
|
|
T_ShaderInput const* current;
|
|
uint32_t cpos{ 0 };
|
|
T_FSPath cname;
|
|
|
|
T_CodeBuilder_( F_GetInput_ loader ,
|
|
T_FSPath 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_FSPath const& name ,
|
|
const uint32_t lines );
|
|
void next( );
|
|
|
|
void addInputLoaderErrors(
|
|
T_ShaderInput const* input ,
|
|
T_FSPath 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.data.value< T_FSPath >( ) , 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.data.value< T_String >( );
|
|
*p += chunk.lines;
|
|
}
|
|
|
|
void T_CodeBuilder_::include(
|
|
T_FSPath 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_FSPath 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_FSPath const& name )
|
|
{
|
|
loadProgram( name );
|
|
assert( programIndex_.contains( name ) );
|
|
assert( programs_[ *programIndex_.get( name ) ].name == name );
|
|
return T_ShaderProgram( *programIndex_.get( name ) + 1 );
|
|
}
|
|
|
|
T_ShaderProgram T_ShaderManager::program(
|
|
T_String const& name ,
|
|
const E_ShaderType type ,
|
|
char const* const source )
|
|
{
|
|
assert( name && name.startsWith( "*" ) );
|
|
assert( type != E_ShaderType::__COUNT__ );
|
|
loadBuiltinProgram( name , type , source );
|
|
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_FSPath > 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( ([] ( T_FSPath const& name ) -> bool {
|
|
struct stat buffer;
|
|
const auto p{ Common::Project( ).strPathOf( name ).toOSString( ) };
|
|
return ( stat( (char const*) p.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_FSPath const& name )
|
|
{
|
|
if ( !useExistingProgram( pipeline , name ) ) {
|
|
initProgramRecord( name ).plReferences.add( pipeline );
|
|
}
|
|
}
|
|
|
|
bool T_ShaderManager::useExistingProgram(
|
|
T_String const& pipeline ,
|
|
T_FSPath 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_FSPath const& name )
|
|
{
|
|
if ( !useExistingProgram( name ) ) {
|
|
initProgramRecord( name );
|
|
}
|
|
}
|
|
|
|
void T_ShaderManager::loadBuiltinProgram(
|
|
T_String const& name ,
|
|
const E_ShaderType type ,
|
|
char const* const source )
|
|
{
|
|
if ( !useExistingProgram( name ) ) {
|
|
initProgramRecord( name , type , source );
|
|
}
|
|
}
|
|
|
|
bool T_ShaderManager::useExistingProgram(
|
|
T_FSPath 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_FSPath const& name ,
|
|
const E_ShaderType type ,
|
|
char const* const source )
|
|
{
|
|
const uint32_t index( newProgramRecord( ) );
|
|
auto& program( programs_[ index ] );
|
|
programIndex_.add( name , index );
|
|
program.name = name;
|
|
program.id = 0;
|
|
if ( type == E_ShaderType::__COUNT__ || source == nullptr ) {
|
|
initProgram( program );
|
|
} else {
|
|
initBuiltinProgram( program , type , source );
|
|
}
|
|
|
|
T_StringBuilder sb;
|
|
for ( auto const& e : program.code.errors ) {
|
|
sb << e.source << ':' << e.line << ": "
|
|
<< e.error << '\n';
|
|
}
|
|
sb << '\0';
|
|
printf( "%s" , sb.data( ) );
|
|
|
|
if ( type != E_ShaderType::__COUNT__ && source != nullptr && program.id == 0 ) {
|
|
std::abort( );
|
|
}
|
|
|
|
return program;
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
T_ShaderInput const* T_ShaderManager::getInput(
|
|
T_FSPath const& name )
|
|
{
|
|
T_FSPath extra;
|
|
T_FSPath const* absPath;
|
|
T_FSPath const* relPath;
|
|
if ( name.isAbsolute( ) ) {
|
|
extra = name.makeRelative( Common::Project( ).basePath( ) );
|
|
absPath = &name;
|
|
relPath = &extra;
|
|
} else {
|
|
extra = Common::Project( ).pathOf( name );
|
|
absPath = &extra;
|
|
relPath = &name;
|
|
}
|
|
|
|
auto const* const existing( inputs_.get( *relPath ) );
|
|
if ( existing ) {
|
|
return existing->get( );
|
|
}
|
|
|
|
T_ShaderInput ni;
|
|
if ( !ni.load( *absPath ) ) {
|
|
return nullptr;
|
|
}
|
|
inputs_.add( *relPath , NewOwned< T_ShaderInput >( std::move( ni ) ) );
|
|
return inputs_.get( *relPath )->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.toString( ).toOSString( ).data( ) );
|
|
auto& code( program.code );
|
|
T_CodeBuilder_ cb{
|
|
[this]( T_FSPath const& n ) { return getInput( n ); } ,
|
|
name , code
|
|
};
|
|
const bool built{ cb.buildCode( ) };
|
|
|
|
// Initialise file watcher + missing files
|
|
program.watch = T_WatchedFiles{ Common::Watcher( ) ,
|
|
[this,name]() {
|
|
programUpdated( name );
|
|
} };
|
|
const auto nf( code.files.size( ) );
|
|
auto& w( *( program.watch.target( ) ) );
|
|
for ( auto i = 0u ; i < nf ; i ++ ) {
|
|
T_FSPath const& fn( code.files.keys( )[ i ] );
|
|
if ( code.files.values( )[ i ] ) {
|
|
w.watch( Common::Project( ).pathOf( fn ) );
|
|
} else {
|
|
auto& mset( missing_.getOrCreate( fn ) );
|
|
if ( !mset.contains( name ) ) {
|
|
mset.add( name );
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( !( built && code.errors.empty( ) ) ) {
|
|
printf( "... failed\n" );
|
|
return;
|
|
}
|
|
buildProgram( program );
|
|
}
|
|
|
|
void T_ShaderManager::initBuiltinProgram(
|
|
T_Program_& program ,
|
|
E_ShaderType type ,
|
|
char const* source )
|
|
{
|
|
auto name( program.name );
|
|
printf( "init built-in program %s\n" ,
|
|
program.name.toString( ).substr( 1 ).toOSString( ).data( ) );
|
|
|
|
program.code.code << source << '\0';
|
|
program.code.type = type;
|
|
program.code.counts.add( UINT32_MAX );
|
|
program.code.starts.add( 0 );
|
|
program.code.sources.add( program.name );
|
|
|
|
buildProgram( program );
|
|
}
|
|
|
|
void T_ShaderManager::buildProgram(
|
|
T_Program_& program )
|
|
{
|
|
// Try to compile the program
|
|
auto& code( program.code );
|
|
auto const& name( program.name );
|
|
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 ? T_FSPath{ "*unknown*" } : code.sources[ pos - 1 ] ,
|
|
pos == 0 ? 0 : ( rawLine + code.counts[ pos - 1 ] - check + code.starts[ pos - 1 ] ) ,
|
|
errorLine );
|
|
}
|
|
|
|
/*----------------------------------------------------------------------------*/
|
|
|
|
void T_ShaderManager::programUpdated(
|
|
T_FSPath 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_FSPath 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;
|
|
}
|
|
|
|
using namespace ImGui;
|
|
auto const& dspSize( GetIO( ).DisplaySize );
|
|
SetNextWindowSize( ImVec2( dspSize.x , dspSize.y * .34f ) , ImGuiSetCond_Appearing );
|
|
SetNextWindowPos( ImVec2( 0 , dspSize.y * .66f ) , ImGuiSetCond_Appearing );
|
|
Begin( "Shaders" , &uiEnabled_ , ImGuiWindowFlags_NoCollapse );
|
|
|
|
const auto n( std::count_if( programs_.begin( ) , programs_.end( ) ,
|
|
[]( auto const& p ) {
|
|
return ( !p.plReferences.empty( ) || p.saReferences != 0 )
|
|
&& p.name.isValid( );
|
|
} ) );
|
|
|
|
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 )
|
|
&& p.name.isValid( ) ) {
|
|
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
|
|
? TreeNodeEx( &program , ImGuiTreeNodeFlags_OpenOnArrow
|
|
| ImGuiTreeNodeFlags_OpenOnDoubleClick , "%s" ,
|
|
program.name.toString( ).toOSString( ).data( ) )
|
|
: false );
|
|
|
|
if ( !nErrors ) {
|
|
Text( "%s" , program.name.toString( ).toOSString( ).data( ) );
|
|
}
|
|
|
|
SameLine( 400 );
|
|
Text( "Usage: %u" , program.plReferences.size( ) + program.saReferences );
|
|
SameLine( 550 );
|
|
if ( program.code.errors.empty( ) ) {
|
|
PushStyleColor( ImGuiCol_Text , ImVec4( .6 , 1 , .6 , 1 ) );
|
|
Text( "No errors" );
|
|
} else {
|
|
PushStyleColor( ImGuiCol_Text , ImVec4( 1 , .6 , .6 , 1 ) );
|
|
Text( "%u error%s" , nErrors , nErrors > 1 ? "s" : "" );
|
|
}
|
|
PopStyleColor( );
|
|
|
|
if ( open ) {
|
|
for ( auto const& err : program.code.errors ) {
|
|
NewLine( );
|
|
SameLine( 50 );
|
|
Text( "%s" , err.source.makeRelative(
|
|
Common::Project( ).basePath( )
|
|
).toString( ).toOSString( ).data( ) );
|
|
SameLine( 350 );
|
|
Text( "line %d" , err.line );
|
|
SameLine( 470 );
|
|
Text( "%s" , err.error.toOSString( ).data( ) );
|
|
}
|
|
TreePop( );
|
|
}
|
|
}
|
|
|
|
End( );
|
|
}
|