diff --git a/Makefile b/Makefile index 52312da..35bc677 100644 --- a/Makefile +++ b/Makefile @@ -17,6 +17,7 @@ DEMO = \ texture.cc \ rendertarget.cc \ programs.cc \ + shaders.cc \ camera.cc \ demo.cc \ \ diff --git a/externals.hh b/externals.hh index ff917fa..6eb23dd 100644 --- a/externals.hh +++ b/externals.hh @@ -19,6 +19,7 @@ #include #include #include +#include // ImGui #include diff --git a/shaders.cc b/shaders.cc new file mode 100644 index 0000000..7f00584 --- /dev/null +++ b/shaders.cc @@ -0,0 +1,163 @@ +#include "externals.hh" +#include "shaders.hh" + + +namespace { + const std::regex PreprocDirective_( "^\\s*//!\\s*([a-z]+(\\s+([^\\s]+))*)\\s*$" ); + + 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 ); + return t; + })()); + + + 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_( __rd__ FILE* const file , + __rw__ T_ShaderInput& input ) + : file( file ) , input( input ) + { } + ~T_InputReader_( ); + + void read( ); + void addAccumulated( ); + void handleDirective( + __rd__ T_Tokens_ const& tokens ); + void error( __rd__ std::string const& err ); + }; + + 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_::addAccumulated( ) + { + if ( accumLines ) { + auto& ck( input.chunks ); + ck.emplace( ck.end( ) , E_ShaderInputChunk::CODE , + accumulator , accumLines ); + accumulator = {}; + accumLines = 0; + } + } + + void T_InputReader_::handleDirective( + __rd__ T_Tokens_ const& tokens ) + { + auto const& directive( tokens[ 0 ] ); + + if ( directive == "include" ) { + if ( tokens.size( ) != 2 ) { + error( "invalid arguments" ); + return; + } + auto& ck( input.chunks ); + ck.emplace( ck.end( ) , E_ShaderInputChunk::INCLUDE , tokens[ 1 ] , 1 ); + + } else if ( directive == "type" ) { + 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 { + error( "unknown directive" ); + } + } + + void T_InputReader_::error( + __rd__ std::string const& err ) + { + input.errors.push_back( T_ShaderInputError{ + line , err } ); + } + +} // namespace + + +bool T_ShaderInput::load( + __rd__ std::string const& path ) +{ + type = E_ShaderInput::CHUNK; + chunks.clear( ); + errors.clear( ); + + FILE* const file{ fopen( path.c_str( ) , "r" ) }; + if ( !file ) { + errors.push_back( T_ShaderInputError{ 0 , "unable to open" } ); + return false; + } + + T_InputReader_ reader( file , *this ); + reader.read( ); + + return true; +} + + +#if 0 +void testLoadShaderFile( ) +{ + const std::string source( "test-loader.glsl" ); + const std::string sPath( "shaders/" + source ); + + T_ShaderInput input; + const bool ok( input.load( sPath ) ); + printf( "OK? %s\n" , ok ? "yes" : "no" ); + printf( "type: %d\n" , int( input.type ) ); + printf( "chunks: %zu\n" , input.chunks.size( ) ); + printf( "errors: %zu\n" , input.errors.size( ) ); + for ( auto const& error : input.errors ) { + printf( "\tl%d: %s\n" , error.line , error.error.c_str( ) ); + } +} +#endif diff --git a/shaders.hh b/shaders.hh new file mode 100644 index 0000000..d778e6a --- /dev/null +++ b/shaders.hh @@ -0,0 +1,56 @@ +#pragma once +#include "utilities.hh" + + +/*= INPUT FILES ==============================================================*/ + +// Type of input chunk +enum class E_ShaderInputChunk { + CODE , + INCLUDE +}; + +// Input chunk data +struct T_ShaderInputChunk +{ + E_ShaderInputChunk type; + std::string text; + uint32_t lines; + + T_ShaderInputChunk( ) = default; + T_ShaderInputChunk( + __rd__ const E_ShaderInputChunk type , + __rd__ std::string text , + __rd__ const uint32_t lines ) + : type( type ) , text( std::move( text ) ) , lines( lines ) + { } +}; + +// Input file type +enum class E_ShaderInput { + CHUNK , // Chunk that may be repeated + LIBRARY , // Library (will only be loaded once) + + // "Main" shader source files + VERTEX , FRAGMENT , +}; + +// Preprocessing errors +struct T_ShaderInputError +{ + uint32_t line; + std::string error; +}; + +// Source file +struct T_ShaderInput +{ + E_ShaderInput type = E_ShaderInput::CHUNK; + std::vector< T_ShaderInputChunk > chunks; + std::vector< T_ShaderInputError > errors; + + bool load( __rd__ std::string const& path ); +}; + + +//void testLoadShaderFile( );