#include "externals.hh" #include "common.hh" #include "c-sync.hh" #include "ui.hh" #include "ui-odbg.hh" #include "ui-opemu.hh" #include "ui-profiling.hh" #include "ui-rendertarget.hh" #include "ui-utilities.hh" using namespace ops; using namespace ebcl; /*= 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: " << error_ << "; 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.nFramebuffers + program.nPrograms + program.nPipelines + program.nSamplers + program.nTextures }; values.resize( ts ); initialInputs.resize( program.inputs.size( ) ); framebuffers.resize( program.nFramebuffers ); pipelines.resize( program.nPipelines ); samplers.resize( program.nSamplers ); textures.resize( program.nTextures ); memset( &values[ 0 ] , 0 , values.size( ) * 4 ); for ( auto i = 0u ; i < nc ; i ++ ) { values[ i + 3 + program.nVariables ] = program.constants[ i ]; } } namespace { struct T_RunGuard { T_OpContext& context; T_RunGuard( T_OpContext& ctx ) noexcept : context( ctx ) {} ~T_RunGuard( ) { while ( !context.profiling.empty( ) ) { UI::Profiler( ).end( context.profiling.last( ) ); context.profiling.removeLast( ); } glUseProgram( 0 ); glBindProgramPipeline( 0 ); glBindFramebuffer( GL_FRAMEBUFFER , 0 ); UI::Textures( ).reset( ); } }; } void T_OpContext::run( const E_RunTarget target , const float time , const float width , const float height ) { assert( !aborted ); T_RunGuard rg( *this ); 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 ); installOverrides.clear( ); while ( !stack.empty( ) ) { auto const& instr{ program.ops[ instrPtr ] }; #ifdef INVASIVE_TRACES GL_ASSERT( ); T_StringBuilder sb; sb << "\nEXECUTE " << instrPtr << ":\t(" << instr << ") {" << instr.location << "}\nSTACK:"; for ( auto i = 0u ; i < stack.size( ) ; i ++ ) { sb << ' ' << stack[ i ].u; } sb << "\nFPU STACK:"; for ( auto i = 0u ; i < x87sp ; i ++ ) { sb << ' ' << x87stack[ i ]; } sb << "\nWREG: " << wreg.f << '/' << wreg.u << '\n' << '\0'; printf( "%s" , sb.data( ) ); printf( "VALUES\n00" ); for ( auto i = 0u ; i < values.size( ) ; i ++ ) { printf( " %08x" , values[ i ].u ); if ( i % 4 == 3 ) { printf( "\n%02x" , i + 1 ); } } if ( values.size( ) % 4 != 0 ) { printf( "\n" ); } #endif 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 ] + 1 ); 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_STORE: checkAddress( instr , instr.args[ 0 ] ); values[ instr.args[ 0 ] ] = wreg; break; case OP_SLOAD: ensureStack( instr , instr.args[ 0 ] + 1 ); wreg = stack[ stack.size( ) - instr.args[ 0 ] - 1 ]; break; case OP_CONST: case OP_OFFSET: wreg = instr.args[ 0 ]; break; // -------------------------------------------------------------------------------- case OP_GET_INPUT: ensureFpuStack( instr , 0 , 1 ); x87stack[ x87sp ++ ] = Common::Sync( ).inputs( )[ 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; } // -------------------------------------------------------------------------------- case OP_GEN_ASSETS: { auto idx = 0u; switch ( instr.args[ 0 ] ) { case 3: idx += program.nSamplers; // fallthrough case 2: idx += program.nPrograms + program.nPipelines; // fallthrough case 1: idx += program.nFramebuffers; // fallthrough case 0: idx += 3 + program.nVariables + program.constants.size( ); break; default: throw X_OpFailure( instr , "invalid argument" ); } auto count = 0u; switch ( instr.args[ 0 ] ) { case 3: count = program.nTextures; break; case 2: count = program.nSamplers; break; case 1: count = program.nPipelines; break; case 0: count = program.nFramebuffers; break; } for ( auto i = 0u ; i < count ; i ++ ) { values[ idx + i ].u = i + 1; } break; } case OP_INIT_PIPELINE: { ensureStack( instr , 2 + instr.args[ 0 ] ); const auto sv( stack.last( ).u ); stack.removeLast( ); if ( sv == 0 || sv > pipelines.size( ) ) { throw X_OpFailure{ instr , "invalid pipeline" }; } const auto plIndex( sv - 1 ); T_String progNames[ 6 ]; for ( auto i = 0u ; i <= instr.args[ 0 ] ; i ++ ) { const auto prIndex( stack.last( ).u ); stack.removeLast( ); if ( !prIndex ) { throw X_OpFailure{ instr , "pipeline uses uninitialised program" }; } progNames[ i ] = programs[ prIndex - 1 ]->name( ); } pipelines[ plIndex ] = NewOwned< T_ShaderPipeline >( UI::Shaders( ).pipeline( progNames , instr.args[ 0 ] + 1 ) ); break; } case OP_INIT_PROGRAM: { if ( instr.args[ 0 ] >= program.progNames.size( ) ) { throw X_OpFailure{ instr , "invalid argument" }; } programs.add( NewOwned< T_ShaderProgram >( UI::Shaders( ).program( program.progNames[ instr.args[ 0 ] ] ) ) ); wreg = programs.size( ); break; } case OP_INIT_SAMPLER: { ensureStack( instr , 3 ); const auto sv( stack.last( ).u ); stack.removeLast( ); if ( sv == 0 || sv > samplers.size( ) ) { throw X_OpFailure{ instr , "invalid sampler" }; } const auto samplerIndex( sv - 1 ); if ( !samplers[ samplerIndex ] ) { samplers[ samplerIndex ] = NewOwned< T_TextureSampler >( ); } const float max( stack.last( ).f ); stack.removeLast( ); const float min( stack.last( ).f ); stack.removeLast( ); samplers[ samplerIndex ]->sampling( E_TexSampling( ( instr.args[ 0 ] & 4 ) >> 2 ) ) .wrap( E_TexWrap( instr.args[ 1 ] ) ) .lod( min , max ); if ( ( instr.args[ 0 ] & 2 ) == 0 ) { samplers[ samplerIndex ]->noMipmap( ); } else { samplers[ samplerIndex ]->mipmap( E_TexSampling( instr.args[ 0 ] & 1 ) ); } break; } case OP_INIT_TEXTURE: { ensureStack( instr , 3 + ( instr.args[ 1 ] ? 1 : 0 ) ); const auto sv( stack.last( ).u ); stack.removeLast( ); if ( sv == 0 || sv > textures.size( ) ) { throw X_OpFailure{ instr , "invalid texture" }; } const auto index( sv - 1 ); const uint32_t w( stack.last( ).f ); stack.removeLast( ); const uint32_t h( stack.last( ).f ); stack.removeLast( ); const uint32_t lods( instr.args[ 1 ] ? stack.last( ).f : 1 ); if ( instr.args[ 1 ] ) { stack.removeLast( ); } textures[ index ] = NewOwned< T_Texture >( w , h , E_TexType( instr.args[ 0 ] ) , lods ); break; } case OP_FB_ATTACH: { // instr[ 0 ] = id (0 for depth attachment, 1+ for color attachments) // instr[ 1 ] = has LOD? // stack: TOP < TEX ( < LOD if has LOD ) ensureStack( instr , 1 + ( instr.args[ 1 ] ? 1 : 0 ) ); if ( curFb < 0 ) { throw X_OpFailure{ instr , "no framebuffer selected" }; } const auto svt( stack.last( ).u ); stack.removeLast( ); if ( svt == 0 || svt > textures.size( ) || !textures[ svt - 1 ] ) { throw X_OpFailure{ instr , "invalid texture" }; } const auto& texture( *textures[ svt - 1 ] ); const uint32_t lod( instr.args[ 1 ] ? uint32_t( stack.last( ).f ) : 0 ); if ( instr.args[ 1 ] ) { stack.removeLast( ); } framebuffers[ curFb ]->attach( texture , lod , instr.args[ 0 ] ); break; } // -------------------------------------------------------------------------------- case OP_USE_TEXTURE: { ensureStack( instr , 2 ); const auto svt( stack.last( ).u ); stack.removeLast( ); if ( svt == 0 || svt > textures.size( ) || !textures[ svt - 1 ] ) { throw X_OpFailure{ instr , "invalid texture" }; } const auto svs( stack.last( ).u ); stack.removeLast( ); if ( svs == 0 || svs > samplers.size( ) || !samplers[ svs - 1 ] ) { throw X_OpFailure{ instr , "invalid sampler" }; } UI::Textures( ).bind( instr.args[ 0 ] , *textures[ svt - 1 ] , *samplers[ svs - 1 ] ); break; } case OP_USE_PIPELINE: { ensureStack( instr , 1 ); const auto sv( stack.last( ).u ); stack.removeLast( ); if ( sv == 0 || sv > pipelines.size( ) || !pipelines[ sv - 1 ] ) { throw X_OpFailure{ instr , "invalid pipeline" }; } pipelines[ sv - 1 ]->enable( ); break; } case OP_USE_PROGRAM: { ensureStack( instr , 1 ); const auto sv( stack.last( ).u ); stack.removeLast( ); if ( sv == 0 || sv > programs.size( ) || !programs[ sv - 1 ] ) { throw X_OpFailure{ instr , "invalid program" }; } programs[ sv - 1 ]->enable( ); break; } case OP_FB_TOGGLE: { if ( curFb < 0 ) { throw X_OpFailure{ instr , "no framebuffer selected" }; } framebuffers[ curFb ]->toggle( instr.args[ 0 ] , instr.args[ 1 ] ); break; } case OP_UNIFORMS: { ensureStack( instr , 3 + instr.args[ 0 ] ); const auto ss( stack.size( ) ); T_OpValue values[ 4 ]; // printf( "OP_UNIFORMS %d %d" , instr.args[ 0 ] , instr.args[ 1 ] ); for ( auto i = 0u ; i <= instr.args[ 0 ] ; i ++ ) { if ( instr.args[ 1 ] ) { values[ i ] = uint32_t( stack[ ss - 3 - i ].f ); // printf( " %d" , values[ i ].i ); } else { values[ i ] = stack[ ss - 3 - i ]; // printf( " %f" , values[ i ].f ); } } const auto sv( stack.last( ).u ); if ( sv == 0 || sv > programs.size( ) || !programs[ sv - 1 ] ) { throw X_OpFailure{ instr , "invalid program" }; } // printf( " -> %s %d\n" , programs[ sv - 1 ]->name( ).toOSString( ).data( ) , stack[ ss - 2 ].u ); typedef void (*F_SetUniform_)( int , int , int , void* ); void const* const funcs[] = { &glProgramUniform1fv , &glProgramUniform2fv , &glProgramUniform3fv , &glProgramUniform4fv , &glProgramUniform1iv , &glProgramUniform2iv , &glProgramUniform3iv , &glProgramUniform4iv , }; const F_SetUniform_ func{ *(F_SetUniform_*) funcs[ instr.args[ 0 ] + ( instr.args[ 1 ] ? 4 : 0 ) ] }; func( programs[ sv - 1 ]->id( ) , stack[ ss - 2 ].u , 1 , values ); stack.resize( ss - 3 - instr.args[ 0 ] ); break; } case OP_VIEWPORT: { ensureStack( instr , 4 ); const auto ss( stack.size( ) ); glViewport( int32_t( stack[ ss - 1 ].f ) , int32_t( stack[ ss - 2 ].f ) , int32_t( stack[ ss - 3 ].f ) , int32_t( stack[ ss - 4 ].f ) ); stack.resize( ss - 4 ); break; } case OP_USE_FRAMEBUFFER: { ensureStack( instr , 1 ); const auto sv( stack.last( ).u ); stack.removeLast( ); if ( sv == 0 ) { glBindFramebuffer( GL_FRAMEBUFFER , 0 ); curFb = -1; break; } const auto index( sv - 1 ); if ( !framebuffers[ index ] ) { framebuffers[ index ] = NewOwned< T_Rendertarget >( ); } glBindFramebuffer( GL_FRAMEBUFFER , framebuffers[ index ]->id( ) ); curFb = index; break; } case OP_IMAGE: { ensureStack( instr , 3 + ( instr.args[ 1 ] ? 1 : 0 ) ); const auto ss( stack.size( ) ); uint32_t texId = stack[ ss - 1 ].u , unit = stack[ ss - 2 ].f , level = stack[ ss - 3 ].f , layer = ( instr.args[ 1 ] ? stack[ ss - 4 ].f : 0 ); if ( texId == 0 || texId > textures.size( ) || !textures[ texId - 1 ] ) { throw X_OpFailure{ instr , "invalid texture" }; } const auto& texture( *textures[ texId - 1 ] ); if ( instr.args[ 0 ] == 0 ) { throw X_OpFailure{ instr , "invalid access mode" }; } static const GLenum AccessModes_[] = { GL_READ_ONLY , GL_WRITE_ONLY , GL_READ_WRITE }; glBindImageTexture( unit , texture.id( ) , level , instr.args[ 1 ] != 0 , layer , AccessModes_[ instr.args[ 0 ] - 1 ] , texture.internalFormat( ) ); stack.resize( ss - 3 - ( instr.args[ 1 ] ? 1 : 0 ) ); 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_COMPUTE: { ensureStack( instr , 3 ); const auto ss( stack.size( ) ); glDispatchCompute( stack[ ss - 1 ].f , stack[ ss - 2 ].f , stack[ ss - 3 ].f ); stack.resize( ss - 3 ); break; } // -------------------------------------------------------------------------------- case OP_UI_PENTER: { T_String const& section( program.uiStrings[ instr.args[ 0 ] ] ); UI::Profiler( ).start( section ); profiling.add( section ); break; } case OP_UI_PEXIT: glFinish( ); UI::Profiler( ).end( profiling.last( ) ); profiling.removeLast( ); break; case OP_UI_INPUT_DFT: initialInputs[ instr.args[ 0 ] ] = values[ instr.args[ 1 ] ].f; break; case OP_UI_INPUT_OVR: if ( !installOverrides ) { installOverrides = NewOwned< T_SyncOverrideSection >( "*" ); } installOverrides->merge( *( program.overrides[ instr.args[ 0 ] ] ) ); break; case OP_UI_ODBG: { ensureStack( instr , 1 ); const auto svt( stack.last( ).u ); stack.removeLast( ); if ( svt == 0 || svt > textures.size( ) || !textures[ svt - 1 ] ) { throw X_OpFailure{ instr , "invalid texture" }; } UI::ODbg( ).registerTexture( *textures[ svt - 1 ] , E_ODbgMode( instr.args[ 0 ] ) , program.uiStrings[ instr.args[ 1 ] ] ); 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" ); } }