#include "externals.hh" #include "control.hh" #include "globals.hh" #include "sync.hh" #include "profiling.hh" using namespace ops; using namespace ebcl; /*= OPCODE INFORMATIONS ========================================================*/ namespace { struct T_OpInfo { char const* name; uint32_t nArgs; int32_t sdMain{ 0 }; int32_t sdFPU{ 0 }; uint32_t fpuReq{ 0 }; template< typename ... Mods > constexpr T_OpInfo( char const* name , uint32_t nArgs , Mods&&... mods ); constexpr T_OpInfo( char const* name ); }; template< typename ... Mods > constexpr void ApplyMods_( T_OpInfo& op , Mods&&... mods ); template< > constexpr void ApplyMods_( T_OpInfo& ) { } template< typename M0 , typename ... Mods > constexpr void ApplyMods_( T_OpInfo& op , M0&& mod , Mods&&... mods ) { mod.applyTo( op ); ApplyMods_( op , std::forward< Mods >( mods ) ... ); } template< typename ... Mods > constexpr T_OpInfo::T_OpInfo( char const* name , uint32_t nArgs , Mods&&... mods ) : name( name ) , nArgs( nArgs ) { ApplyMods_( *this , std::forward< Mods >( mods ) ... ); } constexpr T_OpInfo::T_OpInfo( char const* name ) : T_OpInfo( name , 0 ) {} struct OpStackMain { int32_t value; explicit constexpr OpStackMain( int32_t v ) : value( v ) { } constexpr void applyTo( T_OpInfo& op ) { op.sdMain = value; } }; struct OpStackFPU { int32_t value; explicit constexpr OpStackFPU( int32_t v ) : value( v ) { } constexpr void applyTo( T_OpInfo& op ) { op.sdFPU = value; } }; static T_KeyValueTable< E_OpType , T_OpInfo > OpInfoTable_{ ([]() { T_KeyValueTable< E_OpType , T_OpInfo > infos; infos.add( E_OpType::OP_END , T_OpInfo{ "end" } ); // infos.add( E_OpType::OP_CALL , T_OpInfo{ "call" , 1 } ); infos.add( E_OpType::OP_RET , T_OpInfo{ "ret" , 1 , OpStackMain{ -1 } } ); // infos.add( E_OpType::OP_SKIP , T_OpInfo{ "skip" , 1 } ); infos.add( E_OpType::OP_COND_SKIP , T_OpInfo{ "cond-skip" , 2 } ); // infos.add( E_OpType::OP_RES_STACK , T_OpInfo{ "res-stack" , 1 } ); infos.add( E_OpType::OP_PUSH , T_OpInfo{ "push" , 0 , OpStackMain{ 1 } } ); infos.add( E_OpType::OP_POP , T_OpInfo{ "pop" , 1 } ); infos.add( E_OpType::OP_DUP , T_OpInfo{ "dup" , 1 , OpStackMain{ 1 } } ); // infos.add( E_OpType::OP_LOAD , T_OpInfo{ "load" , 1 } ); infos.add( E_OpType::OP_SLOAD , T_OpInfo{ "load-stack" , 1 } ); infos.add( E_OpType::OP_CONST , T_OpInfo{ "const" , 1 } ); // infos.add( E_OpType::OP_FP_LOAD , T_OpInfo{ "fp-load" , 1 , OpStackFPU{ 1 } } ); infos.add( E_OpType::OP_FP_SLOAD , T_OpInfo{ "fp-load-stack" , 1 , OpStackFPU{ 1 } } ); infos.add( E_OpType::OP_FP_STORE , T_OpInfo{ "fp-store" , 1 , OpStackFPU{ -1 } } ); infos.add( E_OpType::OP_FP_SSTORE , T_OpInfo{ "fp-store-stack" , 1 , OpStackFPU{ -1 } } ); infos.add( E_OpType::OP_FP_SSTORE_INT , T_OpInfo{ "fp-store-stack-int" , 1 , OpStackFPU{ -1 } } ); // infos.add( E_OpType::OP_FP_CMP , T_OpInfo{ "fp-cmp" , 1 , OpStackFPU{ -1 } } ); infos.add( E_OpType::OP_FP_ADD , T_OpInfo{ "fp-add" , 0 , OpStackFPU{ -1 } } ); infos.add( E_OpType::OP_FP_SUB , T_OpInfo{ "fp-sub" , 0 , OpStackFPU{ -1 } } ); infos.add( E_OpType::OP_FP_MUL , T_OpInfo{ "fp-mul" , 0 , OpStackFPU{ -1 } } ); infos.add( E_OpType::OP_FP_DIV , T_OpInfo{ "fp-div" , 0 , OpStackFPU{ -1 } } ); infos.add( E_OpType::OP_FP_POW , T_OpInfo{ "fp-pow" , 0 , OpStackFPU{ -1 } } ); // infos.add( E_OpType::OP_FP_NEG , T_OpInfo{ "fp-neg" , 0 } ); infos.add( E_OpType::OP_FP_INV , T_OpInfo{ "fp-inv" , 0 } ); infos.add( E_OpType::OP_FP_NOT , T_OpInfo{ "fp-not" , 0 } ); infos.add( E_OpType::OP_FP_SIN , T_OpInfo{ "fp-sin" , 0 } ); infos.add( E_OpType::OP_FP_COS , T_OpInfo{ "fp-cos" , 0 } ); infos.add( E_OpType::OP_FP_TAN , T_OpInfo{ "fp-tan" , 0 } ); infos.add( E_OpType::OP_FP_SQRT , T_OpInfo{ "fp-sqrt" , 0 } ); infos.add( E_OpType::OP_FP_EXP , T_OpInfo{ "fp-exp" , 0 } ); infos.add( E_OpType::OP_FP_LN , T_OpInfo{ "fp-ln" , 0 } ); // infos.add( E_OpType::OP_INIT_PIPELINE , T_OpInfo{ "pipeline" , 1 , OpStackMain{ -1 } } ); infos.add( E_OpType::OP_INIT_PROGRAM , T_OpInfo{ "program" , 1 , OpStackMain{ -1 } } ); infos.add( E_OpType::OP_INIT_TEXTURE , T_OpInfo{ "texture" , 2 , OpStackMain{ -3 } } ); infos.add( E_OpType::OP_INIT_SAMPLER , T_OpInfo{ "sampler" , 2 , OpStackMain{ -3 } } ); infos.add( E_OpType::OP_FB_ATTACH , T_OpInfo{ "fb-attach" , 2 , OpStackMain{ -1 } } ); // infos.add( E_OpType::OP_USE_FRAMEBUFFER , T_OpInfo{ "use-framebuffer" , 0 , OpStackMain{ -1 } } ); infos.add( E_OpType::OP_FB_TOGGLE , T_OpInfo{ "fb-toggle" , 2 } ); infos.add( E_OpType::OP_USE_PIPELINE , T_OpInfo{ "use-pipeline" , 0 , OpStackMain{ -1 } } ); infos.add( E_OpType::OP_USE_PROGRAM , T_OpInfo{ "use-program" , 0 , OpStackMain{ -1 } } ); infos.add( E_OpType::OP_USE_TEXTURE , T_OpInfo{ "use-texture" , 1 , OpStackMain{ -2 } } ); infos.add( E_OpType::OP_UNIFORMS , T_OpInfo{ "uniforms" , 2 , OpStackMain{ -2 } } ); infos.add( E_OpType::OP_VIEWPORT , T_OpInfo{ "viewport" , 0 , OpStackMain{ -4 } } ); // infos.add( E_OpType::OP_FULLSCREEN , T_OpInfo{ "fullscreen" } ); infos.add( E_OpType::OP_CLEAR , T_OpInfo{ "clear" , 0 , OpStackMain{ -4 } } ); // infos.add( E_OpType::OP_UI_PENTER , T_OpInfo{ "ui-prof-enter" , 1 } ); infos.add( E_OpType::OP_UI_PEXIT , T_OpInfo{ "ui-prof-exit" } ); infos.add( E_OpType::OP_UI_INPUT_DFT , T_OpInfo{ "ui-input-default" , 2 } ); infos.add( E_OpType::OP_UI_ODBG , T_OpInfo{ "ui-odbg" , 2 , OpStackMain{ -1 } } ); return infos; })( ) }; } // namespace uint32_t ops::ArgumentsFor( const E_OpType op ) noexcept { assert( OpInfoTable_.contains( op ) ); return OpInfoTable_.get( op )->nArgs; } int32_t ops::DeltaMainStack( const E_OpType op ) noexcept { assert( OpInfoTable_.contains( op ) ); return OpInfoTable_.get( op )->sdMain; } int32_t ops::DeltaFPUStack( const E_OpType op ) noexcept { assert( OpInfoTable_.contains( op ) ); return OpInfoTable_.get( op )->sdFPU; } /*= STRING FORMATTING ==========================================================*/ T_StringBuilder& ops::operator<<( T_StringBuilder& sb , const E_OpType et ) { assert( OpInfoTable_.contains( et ) ); sb << OpInfoTable_.get( et )->name; return sb; } T_StringBuilder& ops::operator<<( T_StringBuilder& sb , T_Op const& op ) { sb << op.op; const auto args{ OpInfoTable_.get( op.op )->nArgs }; for ( auto i = 0u ; i < args ; i ++ ) { sb << ' ' << op.args[ i ]; } return sb; } /*= EMULATOR ===================================================================*/ X_OpFailure::X_OpFailure( T_Op const& op , T_String error ) noexcept : op_( &op ) , error_( std::move( error ) ) { T_StringBuilder sb; sb << "operation (" << op << ") failed; source: " << op.location << '\0'; fullMessage_ = std::move( sb ); } char const* X_OpFailure::what( ) const noexcept { return fullMessage_.data( ); } /*------------------------------------------------------------------------------*/ T_OpContext::T_OpContext( T_OpProgram& program ) noexcept : program( program ) { stack.ensureCapacity( stack.growth( ) ); const auto nc{ program.constants.size( ) }; const auto ts{ 3 + nc + program.nVariables + program.nPrograms + program.nPipelines + program.nSamplers + program.nTextures }; values.resize( ts ); initialInputs.resize( program.inputs.size( ) ); for ( auto i = 0u ; i < nc ; i ++ ) { values[ i + 3 ] = program.constants[ nc ]; } } void T_OpContext::run( const E_RunTarget target , const float time , const float width , const float height ) { assert( !aborted ); x87sp = 0; instrPtr = program.ops.firstOf( target == R_INIT ? program.init : program.frame ); values[ 0 ] = time; values[ 1 ] = width; values[ 2 ] = height; stack.clear( ); stack.add( 0xffffffff ); while ( 0 /* FIXME */ ) { // if ( instrPtr >= program.ops. auto const& instr{ program.ops[ instrPtr ] }; switch ( instr.op ) { case OP_END: throw X_OpFailure{ instr , "invalid instruction" }; // -------------------------------------------------------------------------------- case OP_CALL: if ( program.ops.size( ) <= instr.args[ 0 ] ) { throw X_OpFailure{ instr , "invalid function index" }; } stack.add( instrPtr + 1 ); instrPtr = program.ops.firstOf( instr.args[ 0 ] ); continue; case OP_RET: ensureStack( instr , 1 + instr.args[ 0 ] ); instrPtr = stack.last( ).u; stack.resize( stack.size( ) - instr.args[ 0 ] - 1 ); continue; case OP_SKIP: // FIXME: make sure we don't go past the end instrPtr += instr.args[ 0 ]; break; case OP_COND_SKIP: ensureStack( instr , 1 ); if ( stack.last( ).u != instr.args[ 1 ] ) { // FIXME: make sure we don't go past the end instrPtr += instr.args[ 0 ]; } break; // -------------------------------------------------------------------------------- case OP_RES_STACK: stack.resize( stack.size( ) + instr.args[ 0 ] ); break; case OP_PUSH: stack.add( wreg ); break; case OP_POP: ensureStack( instr , instr.args[ 0 ] + 1 ); stack.resize( stack.size( ) - instr.args[ 0 ] - 1 ); break; case OP_DUP: ensureStack( instr , instr.args[ 0 ] + 1 ); stack.add( stack[ stack.size( ) - instr.args[ 0 ] - 1 ] ); break; // -------------------------------------------------------------------------------- case OP_LOAD: checkAddress( instr , instr.args[ 0 ] ); wreg = values[ instr.args[ 0 ] ]; break; case OP_SLOAD: ensureStack( instr , instr.args[ 0 ] + 1 ); wreg = stack[ stack.size( ) - instr.args[ 0 ] - 1 ]; break; case OP_CONST: wreg = instr.args[ 0 ]; break; // -------------------------------------------------------------------------------- case OP_FP_LOAD: ensureFpuStack( instr , 0 , 1 ); checkAddress( instr , instr.args[ 0 ] ); x87stack[ x87sp ++ ] = values[ instr.args[ 0 ] ].f; break; case OP_FP_STORE: ensureFpuStack( instr , 1 , 0 ); checkAddress( instr , instr.args[ 0 ] ); values[ instr.args[ 0 ] ].f = x87stack[ -- x87sp ]; break; case OP_FP_SLOAD: ensureFpuStack( instr , 0 , 1 ); ensureStack( instr , instr.args[ 0 ] + 1 ); x87stack[ x87sp ++ ] = stack[ stack.size( ) - instr.args[ 0 ] - 1 ].f; break; case OP_FP_SSTORE: ensureFpuStack( instr , 1 , 0 ); ensureStack( instr , instr.args[ 0 ] + 1 ); stack[ stack.size( ) - instr.args[ 0 ] - 1 ].f = x87stack[ -- x87sp ]; break; case OP_FP_SSTORE_INT: ensureFpuStack( instr , 1 , 0 ); ensureStack( instr , instr.args[ 0 ] + 1 ); stack[ stack.size( ) - instr.args[ 0 ] - 1 ].i = int32_t( x87stack[ -- x87sp ] ); break; // -------------------------------------------------------------------------------- case OP_FP_CMP: { ensureFpuStack( instr , 2 , 0 ); const auto v2( x87stack[ x87sp - 1 ] ) , v1( x87stack[ x87sp - 2 ] ); x87sp --; const bool rv{ ([&]( ) -> bool { switch ( instr.args[ 0 ] ) { case 0: return v1 == v2; case 1: return v1 != v2; case 2: return v1 > v2; case 3: return v1 >= v2; case 4: return v1 < v2; case 5: return v1 <= v2; } throw X_OpFailure( instr , "invalid operation" ); })() }; x87stack[ x87sp - 1 ] = rv ? 1 : 0; break; } case OP_FP_ADD: { ensureFpuStack( instr , 2 , 0 ); x87stack[ x87sp - 2 ] += x87stack[ x87sp - 1 ]; x87sp --; break; } case OP_FP_SUB: { ensureFpuStack( instr , 2 , 0 ); x87stack[ x87sp - 2 ] -= x87stack[ x87sp - 1 ]; x87sp --; break; } case OP_FP_MUL: { ensureFpuStack( instr , 2 , 0 ); x87stack[ x87sp - 2 ] *= x87stack[ x87sp - 1 ]; x87sp --; break; } case OP_FP_DIV: { ensureFpuStack( instr , 2 , 0 ); if ( x87stack[ x87sp - 1 ] == 0 ) { throw X_OpFailure{ instr , "arithmetic error" }; } x87stack[ x87sp - 2 ] *= x87stack[ x87sp - 1 ]; x87sp --; break; } case OP_FP_POW: { ensureFpuStack( instr , 2 , 0 ); x87stack[ x87sp - 2 ] = pow( x87stack[ x87sp - 2 ] , x87stack[ x87sp - 1 ] ); x87sp --; break; } // -------------------------------------------------------------------------------- case OP_FP_NEG: { ensureFpuStack( instr , 1 , 1 ); x87stack[ x87sp - 1 ] = -x87stack[ x87sp - 1 ]; break; } case OP_FP_INV: { ensureFpuStack( instr , 1 , 1 ); if ( x87stack[ x87sp - 1 ] == 0 ) { throw X_OpFailure{ instr , "arithmetic error" }; } x87stack[ x87sp - 1 ] = 1.0 / x87stack[ x87sp - 1 ]; break; } case OP_FP_NOT: { ensureFpuStack( instr , 1 , 0 ); x87stack[ x87sp - 1 ] = x87stack[ x87sp - 1 ] == 0 ? 1 : 0; break; } case OP_FP_SIN: { ensureFpuStack( instr , 1 , 0 ); x87stack[ x87sp - 1 ] = sin( x87stack[ x87sp - 1 ] ); break; } case OP_FP_COS: { ensureFpuStack( instr , 1 , 0 ); x87stack[ x87sp - 1 ] = cos( x87stack[ x87sp - 1 ] ); break; } case OP_FP_TAN: { ensureFpuStack( instr , 1 , 1 ); const auto c( cos( x87stack[ x87sp - 1 ] ) ); if ( c == 0 ) { throw X_OpFailure{ instr , "arithmetic error" }; } x87stack[ x87sp - 1 ] = sin( x87stack[ x87sp - 1 ] ) / c; break; } case OP_FP_SQRT: { ensureFpuStack( instr , 1 , 0 ); const auto v( x87stack[ x87sp - 1 ] ); if ( v < 0 ) { throw X_OpFailure{ instr , "arithmetic error" }; } x87stack[ x87sp - 1 ] = sqrt( v ); break; } case OP_FP_EXP: { ensureFpuStack( instr , 1 , 0 ); // FIXME: need FPU stack space x87stack[ x87sp - 1 ] = exp( x87stack[ x87sp - 1 ] ); break; } case OP_FP_LN: { ensureFpuStack( instr , 1 , 0 ); // FIXME: need FPU stack space x87stack[ x87sp - 1 ] = log( x87stack[ x87sp - 1 ] ); break; } // -------------------------------------------------------------------------------- // TODO resource init // -------------------------------------------------------------------------------- // TODO resource usage case OP_UNIFORMS: { ensureStack( instr , 3 + instr.args[ 0 ] ); const auto ss( stack.size( ) ); T_OpValue values[ 4 ]; for ( auto i = 0u ; i <= instr.args[ 0 ] ; i ++ ) { values[ i ] = stack[ ss - 2 - i ]; } // FIXME can't actually finish this, I'm stupid. break; } case OP_VIEWPORT: { ensureStack( instr , 4 ); const auto ss( stack.size( ) ); glViewport( stack[ ss - 1 ].f , stack[ ss - 2 ].f , stack[ ss - 3 ].f , stack[ ss - 4 ].f ); stack.resize( ss - 4 ); break; } // -------------------------------------------------------------------------------- case OP_FULLSCREEN: glDrawArrays( GL_TRIANGLE_STRIP , 0 , 4 ); break; case OP_CLEAR: { ensureStack( instr , 4 ); const auto ss( stack.size( ) ); glClearColor( stack[ ss - 1 ].f , stack[ ss - 2 ].f , stack[ ss - 3 ].f , stack[ ss - 4 ].f ); glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT ); stack.resize( ss - 4 ); break; } // -------------------------------------------------------------------------------- case OP_UI_PENTER: { T_String const& section( program.uiStrings[ instr.args[ 0 ] ] ); Globals::Profiler( ).start( section ); profiling.add( section ); break; } case OP_UI_PEXIT: Globals::Profiler( ).end( profiling.last( ) ); profiling.removeLast( ); break; case OP_UI_INPUT_DFT: initialInputs[ instr.args[ 0 ] ] = values[ instr.args[ 1 ] ].f; break; } instrPtr ++; } } void T_OpContext::ensureStack( T_Op const& op , const uint32_t min ) { if ( stack.size( ) < min ) { throw X_OpFailure{ op , "stack underrun" }; } } void T_OpContext::ensureFpuStack( T_Op const& op , const uint32_t minStacked , const uint32_t minFree ) { if ( x87sp < minStacked ) { throw X_OpFailure{ op , "FPU stack underrun" }; } if ( 8 - x87sp < minFree ) { throw X_OpFailure{ op , "FPU stack overflow" }; } } void T_OpContext::checkAddress( T_Op const& op , const uint32_t address ) { if ( address >= values.size( ) ) { throw X_OpFailure( op , "invalid access" ); } }