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.
921 lines
25 KiB
C++
921 lines
25 KiB
C++
#include "externals.hh"
|
|
#include "control.hh"
|
|
#include "globals.hh"
|
|
#include "profiling.hh"
|
|
#include "rendertarget.hh"
|
|
#include "sync.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_STORE , T_OpInfo{ "store" , 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_OFFSET , T_OpInfo{ "offset" , 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_GEN_ASSETS , T_OpInfo{ "gen-assets" , 2 } );
|
|
infos.add( E_OpType::OP_INIT_PIPELINE , T_OpInfo{ "pipeline" , 1 , OpStackMain{ -1 } } );
|
|
infos.add( E_OpType::OP_INIT_PROGRAM , T_OpInfo{ "program" , 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: "
|
|
<< 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( ) ) {
|
|
Globals::Profiler( ).end( context.profiling.last( ) );
|
|
context.profiling.removeLast( );
|
|
}
|
|
glUseProgram( 0 );
|
|
glBindProgramPipeline( 0 );
|
|
glBindFramebuffer( GL_FRAMEBUFFER , 0 );
|
|
Globals::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 );
|
|
|
|
while ( !stack.empty( ) ) {
|
|
auto const& instr{ program.ops[ instrPtr ] };
|
|
|
|
//#define YOUR_MUM_IS_A_TRACE
|
|
#ifdef YOUR_MUM_IS_A_TRACE
|
|
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" );
|
|
}
|
|
|
|
T_StringBuilder sb;
|
|
sb << "EXECUTE " << 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 << '\n' << '\0';
|
|
printf( "%s" , sb.data( ) );
|
|
#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 ] );
|
|
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_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 >(
|
|
Globals::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 >(
|
|
Globals::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" };
|
|
}
|
|
|
|
Globals::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 ];
|
|
for ( auto i = 0u ; i <= instr.args[ 0 ] ; i ++ ) {
|
|
if ( instr.args[ 1 ] ) {
|
|
values[ i ] = uint32_t( stack[ ss - 3 - i ].f );
|
|
} else {
|
|
values[ i ] = stack[ ss - 3 - i ];
|
|
}
|
|
}
|
|
|
|
const auto sv( stack.last( ).u );
|
|
if ( sv == 0 || sv > programs.size( ) || !programs[ sv - 1 ] ) {
|
|
throw X_OpFailure{ instr , "invalid program" };
|
|
}
|
|
|
|
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( stack[ ss - 1 ].f , stack[ ss - 2 ].f ,
|
|
stack[ ss - 3 ].f , 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;
|
|
return;
|
|
}
|
|
|
|
const auto index( sv - 1 );
|
|
if ( !framebuffers[ index ] ) {
|
|
framebuffers[ index ] = NewOwned< T_Rendertarget >( );
|
|
}
|
|
glBindFramebuffer( GL_FRAMEBUFFER , framebuffers[ index ]->id( ) );
|
|
curFb = index;
|
|
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;
|
|
|
|
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" };
|
|
}
|
|
Globals::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" );
|
|
}
|
|
}
|