#define NOMINMAX #define WIN32_LEAN_AND_MEAN #include #include "lua/gpu_lua.h" #include "lua/gpu_lua_profiles.h" #include "gpu_lua_sources.h" #include #include #include #include #include #include #include #include #include // This target deliberately uses the exact vendored LuaJIT compiler/bytecode. // No source is evaluated: luaL_loadbuffer only parses and creates a prototype. extern "C" { #include "lauxlib.h" #include "lua.h" } #include "lua/gpu_lua_proto.h" namespace ng::gpu_lua { uint64_t fingerprint(const unsigned char* data, std::size_t size) { uint64_t h=14695981039346656037ull; for(std::size_t i=0;i apis={ {"GetUpdatedEntityID",1},{"EntityGetTransform",2},{"EntityGetFirstComponent",3}, {"ComponentGetValue2",4},{"ComponentSetValue2",5},{"dofile_once",6},{"dofile",7}, {"RaytraceSurfaces",8},{"EntityApplyTransform",9}, {"ComponentGetValueVector2",4},{"ComponentSetValueVector2",5}}; const std::map math_apis={ {"abs",10},{"sqrt",11},{"atan2",12},{"cos",13},{"sin",14},{"max",15}}; using GCproto=NgLuaProto; using BCIns=NgLuaInstruction; static NgLuaOp bc_op(BCIns i){return i.op;} static unsigned bc_a(BCIns i){return i.a;} static unsigned bc_b(BCIns i){return i.b;} static unsigned bc_c(BCIns i){return i.c;} static unsigned bc_d(BCIns i){return i.d;} static int bc_j(BCIns i){return i.jump;} struct Instructions {GCproto* p;BCIns operator[](unsigned i){return ng_lua_instruction(p,i);}}; static Instructions proto_bc(GCproto* p){return {p};} std::string number(double n) { const float f=static_cast(n);uint32_t bits;std::memcpy(&bits,&f,4); std::ostringstream out;out<<"uintBitsToFloat(0x"< state{luaL_newstate(),lua_close}; GCproto view{};GCproto* pt=&view; explicit Parsed(const std::string& source,const std::string& path) { if(!state)throw std::runtime_error("Lua GPU: cannot allocate parser"); if(source.size()>1024*1024)throw std::runtime_error(path+": Lua GPU source limit exceeded"); if(!source.empty()&&static_cast(source[0])==0x1b)throw std::runtime_error(path+": Lua source required, bytecode input rejected"); if(luaL_loadbuffer(state.get(),source.data(),source.size(),path.c_str())!=0) throw std::runtime_error(lua_tostring(state.get(),-1)); // Check the linked VM's frame ABI before dereferencing private structures. std::vector dump; lua_dump(state.get(),[](lua_State*,const void* p,size_t n,void* dst)->int { auto& b=*static_cast*>(dst); b.insert(b.end(),static_cast(p),static_cast(p)+n);return 0; },&dump); if(dump.size()<5||dump[3]!=2||bool(dump[4]&8)!=bool(ng_lua_frame_shift()))throw std::runtime_error("Lua GPU: linked LuaJIT bytecode ABI mismatch"); if(!ng_lua_view(state.get(),pt))throw std::runtime_error("Lua GPU: expected Lua prototype"); } }; uint64_t compiled_fingerprint(Parsed& parsed) { uint64_t digest=14695981039346656037ull; const int status=ng_lua_dump_stripped(parsed.state.get(),parsed.pt, [](lua_State*,const void* bytes,size_t size,void* context)->int { auto& hash=*static_cast(context); const auto* data=static_cast(bytes); // Bytecode is binary: CR bytes must participate in this digest. for(size_t i=0;i=128;}))throw std::runtime_error("Lua GPU: ASCII strings required"); return result; } double numeric(GCproto* pt,unsigned d) { if(d>=pt->sizekn)throw std::runtime_error("Lua GPU: invalid numeric constant"); return ng_lua_number(pt,d); } struct Generator { std::string path; Parsed root; std::map symbols; std::map exports; std::map globals; uint64_t library_semantic = 0; explicit Generator(const std::string& source,const std::string& file):path(file),root(source,file){} unsigned intern(const std::string& s) { auto [it,added]=symbols.emplace(s,static_cast(symbols.size()+1));return it->second; } std::string literal(GCproto* p,BCIns ins) { const auto op=bc_op(ins);const unsigned d=bc_d(ins); if(op==NG_BC_KSHORT)return "ngp_num("+number(static_cast(d))+")"; if(op==NG_BC_KNUM)return "ngp_num("+number(numeric(p,d))+")"; if(op==NG_BC_KSTR)return "ngp_string("+std::to_string(intern(str(p,d)))+"u)"; if(op==NG_BC_KPRI&&d<=2)return d==0?"ngp_nil()":d==1?"ngp_bool(false)":"ngp_bool(true)"; throw std::runtime_error("Lua GPU: unsupported literal"); } void read_exports(const std::string& source) { // Reviewed utilities: top-level constants precede function/table values. // Those unread inert definitions are omitted; function bodies never run. Parsed lib(source,library);library_semantic=compiled_fingerprint(lib); // Both reviewed distributions have the same constants / edit_component // behavior. Steam additionally defines unused debug/vector/color/texture // helpers. Comments, source paths and line information are not behavior. if(library_semantic!=6066579123591393726ull && library_semantic!=13023294535491161211ull) throw std::runtime_error(std::string(library)+": unreviewed compiled library (fingerprint "+std::to_string(library_semantic)+")"); std::map values; for(unsigned i=1;isizebc;++i){ BCIns ins=proto_bc(lib.pt)[i];auto op=bc_op(ins);unsigned a=bc_a(ins),d=bc_d(ins); if(op==NG_BC_FNEW)break; if(op==NG_BC_KSHORT||op==NG_BC_KNUM||op==NG_BC_KPRI||op==NG_BC_KSTR)values[a]=literal(lib.pt,ins); else if(op==NG_BC_GSET&&values.count(a))exports[str(lib.pt,d)]=values.at(a); else throw std::runtime_error(std::string(library)+": unsupported library initializer"); } } std::string body() { GCproto* p=root.pt; if(p->sizebc>1024||p->framesize>64||p->sizeuv)throw std::runtime_error("Lua GPU: target limit exceeded (1024 instructions, 64 registers, no upvalues)"); auto reg=[&](unsigned r){if(r>=p->framesize)throw std::runtime_error("Lua GPU: register range exceeded");return "ngp_r["+std::to_string(r)+"]";}; std::ostringstream cases; for(unsigned ip=1;ipsizebc;++ip){ const BCIns ins=proto_bc(p)[ip];const auto op=bc_op(ins); const unsigned a=bc_a(ins),b=bc_b(ins),c=bc_c(ins),d=bc_d(ins); auto fail=[&](const std::string& why){throw std::runtime_error(path+":"+std::to_string(line(p,ip))+": "+why+" ("+ins.name+")");}; cases<<"case "<=p->sizebc||bc_op(proto_bc(p)[ip+1])!=NG_BC_JMP)fail("expected conditional jump"); cases<<"if(!("<second<<"u);"; else if(name=="math")cases<(globals.size())); cases<second<<"];"; }else fail("unsupported global/API '"+name+"'"); }else if(op==NG_BC_TGETS){ const auto field=str(p,c);const auto api=math_apis.find(field); if(api==math_apis.end())fail("unsupported math field '"+field+"'"); cases<<"if("<second<<"u);"; }else if(op==NG_BC_CALL){ if(b==0||c==0||b>9||c>9)fail("only fixed calls with up to eight arguments/results are supported"); cases<<"NgpTuple args=ngp_empty();args.count="<(ip)+1+static_cast(bc_j(ins)); if(target<1||target>=static_cast(p->sizebc))fail("jump target out of range"); cases<<"ngp_ip="<=NG_BC_ISLT&&op<=NG_BC_ISGT){ std::string cond="ngp_less("+reg(a)+","+reg(d)+","+((op==NG_BC_ISLE||op==NG_BC_ISGT)?"true":"false")+")"; // LuaJIT GE/GT are the complement tests, including unordered NaN. if(op==NG_BC_ISGE||op==NG_BC_ISGT)cond="!"+cond; test(cond); }else if(op>=NG_BC_ISEQV&&op<=NG_BC_ISNEP){ std::string rhs; if(op<=NG_BC_ISNEV)rhs=reg(d); else if(op<=NG_BC_ISNES)rhs="ngp_string("+std::to_string(intern(str(p,d)))+"u)"; else if(op<=NG_BC_ISNEN)rhs="ngp_num("+number(numeric(p,d))+")"; else {if(d>2)fail("invalid primitive");rhs=d==0?"ngp_nil()":d==1?"ngp_bool(false)":"ngp_bool(true)";} const bool unequal=(static_cast(op)-NG_BC_ISEQV)%2!=0; test(std::string(unequal?"!":"")+"ngp_equal("+reg(a)+","+rhs+")"); }else if(op==NG_BC_IST||op==NG_BC_ISF||op==NG_BC_ISTC||op==NG_BC_ISFC){ const bool truth=op==NG_BC_IST||op==NG_BC_ISTC; std::string cond=std::string(truth?"":"!")+"ngp_truth("+reg(d)+")"; if(op==NG_BC_ISTC||op==NG_BC_ISFC)cases<<"if("<=NG_BC_ADDVN&&op<=NG_BC_POW){ const int index=static_cast(op)-NG_BC_ADDVN; const int arithmetic=op==NG_BC_POW?5:index%5; std::string left,right; if(index<5){left=reg(b);right="ngp_num("+number(numeric(p,c))+")";} else if(index<10){left="ngp_num("+number(numeric(p,c))+")";right=reg(b);} else {left=reg(b);right=reg(c);} cases<(1,globals.size())<<"];\nvoid ngp_module_init(){"; for(const auto& [name,index]:globals)out<<"ngp_g["<framesize)<<"];\n" "for(int i=0;i{{"PROJECTILE","ProjectileComponent"},{"VELOCITY","VelocityComponent"},{"WHO","mWhoShot"},{"GRAVITY","gravity_y"},{"MOTION","mVelocity"},{"UTIL",library}}) out<<"const uint NGP_"< r(p->framesize);std::ostringstream out; out<<"uvec4 ngp_execute(inout FProj projectile,vec3 owner,float owner_id){\n"; auto fail=[&](unsigned ip,const std::string& why){throw std::runtime_error(path+":"+std::to_string(line(p,ip))+": scalar neighbor lowering: "+why);}; auto numeric_value=[&](const Value& v,unsigned ip){if(v.kind!=Number)fail(ip,"numeric value required");return v.expr;}; auto temp=[&](unsigned reg,unsigned ip,const std::string& expr){ const auto name="v"+std::to_string(ip)+"_"+std::to_string(reg); out<<"float "<sizebc;++ip) { const auto ins=proto_bc(p)[ip];const auto op=bc_op(ins);unsigned a=bc_a(ins),b=bc_b(ins),c=bc_c(ins),d=bc_d(ins); if(op==NG_BC_KNUM||op==NG_BC_KSHORT){double n=op==NG_BC_KNUM?numeric(p,d):static_cast(d);r.at(a)={Number,number(n),0,n};} else if(op==NG_BC_KSTR)r.at(a)={String,str(p,d)}; else if(op==NG_BC_MOV)r.at(a)=r.at(d); else if(op==NG_BC_GGET) { auto name=str(p,d);auto api=apis.find(name); if(name=="math")r.at(a)={Math}; else if(api!=apis.end())r.at(a)={Function,"",api->second}; else fail(ip,"unknown global "+name); }else if(op==NG_BC_TGETS) { auto api=math_apis.find(str(p,c)); if(r.at(b).kind!=Math||api==math_apis.end())fail(ip,"unsupported table access"); r.at(a)={Function,"",api->second}; }else if(op>=NG_BC_ADDVN&&op<=NG_BC_POW) { int index=int(op)-NG_BC_ADDVN, arithmetic=op==NG_BC_POW?5:index%5; Value left,right; if(index<5){left=r.at(b);double n=numeric(p,c);right={Number,number(n),0,n};} else if(index<10){double n=numeric(p,c);left={Number,number(n),0,n};right=r.at(b);} else{left=r.at(b);right=r.at(c);} auto x=numeric_value(left,ip),y=numeric_value(right,ip); if(arithmetic==5){if(right.constant!=2.0)fail(ip,"only square powers are reviewed");temp(a,ip,"("+x+"*"+x+")");} else if(arithmetic<4)temp(a,ip,"("+x+std::string(1,"+-*/"[arithmetic])+y+")"); else fail(ip,"unreviewed arithmetic"); }else if(op==NG_BC_UNM)temp(a,ip,"(-"+numeric_value(r.at(d),ip)+")"); else if(op>=NG_BC_ISLT&&op<=NG_BC_ISGT) { if(ip+1>=p->sizebc||bc_op(proto_bc(p)[ip+1])!=NG_BC_JMP)fail(ip,"conditional jump required"); auto jump=proto_bc(p)[ip+1];int target=int(ip)+2+bc_j(jump); if(target<=int(ip)||target>=int(p->sizebc)||bc_op(proto_bc(p)[target])!=NG_BC_RET0)fail(ip,"only terminal forward guards are reviewed"); auto x=numeric_value(r.at(a),ip),y=numeric_value(r.at(d),ip); std::string condition="("+x+((op==NG_BC_ISLE||op==NG_BC_ISGT)?"<=":"<")+y+")"; if(op==NG_BC_ISGE||op==NG_BC_ISGT)condition="!"+condition; out<<"if("< args;for(unsigned k=0;k=10&&api<=15){ count(api==12||api==15?2:1);if(b!=2)fail(ip,"one math result required"); const char* names[]={"abs","sqrt","ngp_scalar_atan","cos","sin","max"}; temp(a,ip,std::string(names[api-10])+"("+num(0)+(args.size()==2?","+num(1):"")+")"); }else fail(ip,"unreviewed native API"); }else if(op==NG_BC_RET0)out<<"return uvec4(0u);\n"; else fail(ip,std::string("unsupported instruction ")+ins.name); } out<<"}\n"; return "float ngp_scalar_atan(float y,float x){float a;if(y==0.0)a=(floatBitsToUint(x)&0x80000000u)!=0u?3.141592653589793:0.0;else if(x==0.0)a=1.5707963267948966;else a=atan(abs(y),x);return (floatBitsToUint(y)&0x80000000u)!=0u?-a:a;}\n"+out.str(); } }; } uint64_t library_fingerprint(const std::string& source) { Parsed parsed(source,library);return compiled_fingerprint(parsed); } bool dependency_matches(const Dependency& dep,const unsigned char* data,std::size_t size) { const auto raw=fingerprint(data,size); if(raw==dep.fingerprint||(dep.semantic!=0&&raw==dep.validated_fingerprint))return true; if(dep.semantic==0)return false; try { if(library_fingerprint(std::string(reinterpret_cast(data),size))!=dep.semantic)return false; dep.validated_fingerprint=raw;return true; }catch(const std::exception&){return false;} } Program translate(const std::string& source,const Reader& read,const std::string& path) { Generator gen(source,path);const std::string lib=read(library);gen.read_exports(lib); Program p; if(program_id(path)>=first_neighbor_program)p.function=gen.scalar_body(); else { p.function=gen.body(); // Discover symbols/globals before constructing helpers. p.function=gen.preamble()+sources::runtime+p.function; } p.shader=std::string(sources::layout)+sources::terrain+p.function+ "uvec4 ngp_dispatch(uint program,inout FProj p,vec3 owner,float owner_id){if(program!=1u)return uvec4(14u,0u,0u,0u);return ngp_execute(p,owner,owner_id);}\n" "uvec4 ngp_neighbor_dispatch(uint program,inout FProj p,vec3 owner){return uvec4(14u,0u,0u,0u);}\n"+sources::dispatch; p.instructions=gen.root.pt->sizebc-1;p.registers=gen.root.pt->framesize; for(const auto& [path,bytes]:std::map{{path,source},{library,lib}}) p.dependencies.push_back({path,fingerprint(reinterpret_cast(bytes.data()),bytes.size())}); for(auto& dep:p.dependencies)if(dep.path==library)dep.semantic=gen.library_semantic; return p; } Program translate_reviewed(const Reader& read) { Program bundle;std::map dependencies; std::ostringstream dispatch,neighbors; dispatch<<"uvec4 ngp_dispatch(uint program,inout FProj p,vec3 owner,float owner_id){switch(program){"; neighbors<<"uvec4 ngp_neighbor_dispatch(uint program,inout FProj p,vec3 owner){switch(program){"; unsigned id=0; for(const auto& profile:profiles) { ++id;const auto source=read(profile.path); if(fingerprint(reinterpret_cast(source.data()),source.size())!=profile.fingerprint) throw std::runtime_error(std::string(profile.path)+": modified projectile needs engine binding review"); std::string lowered=source; if(id>=first_neighbor_program) { // Fuse this reviewed additive neighbor loop into a per-target kernel. // Preserve the original arithmetic, both distance guards and constants. // Query/iteration/edit_component plumbing becomes GPU gather dispatch. std::vector lines;std::istringstream input(source);std::string line; while(std::getline(input,line)) { line.erase(std::remove(line.begin(),line.end(),'\r'),line.end()); auto start=line.find_first_not_of(" \t"); if(start!=std::string::npos)lines.push_back(line.substr(start)); } auto find=[&](const std::string& prefix) { std::string result;unsigned count=0; for(const auto& candidate:lines)if(candidate.rfind(prefix,0)==0){result=candidate;++count;} if(count!=1)throw std::runtime_error(std::string(profile.path)+": neighbor lowering shape changed: "+prefix); return result; }; std::ostringstream pair; if(std::string(profile.path)=="data/scripts/projectiles/effect_slow_bullets.lua") { pair<<"local comp=EntityGetFirstComponent(GetUpdatedEntityID(), 'VelocityComponent')\n" "local vel_x,vel_y=ComponentGetValueVector2(comp,'mVelocity')\n" <=first_neighbor_program)neighbors<<"case "< compile_spirv(const std::string& source,const std::filesystem::path& compiler_dll) { struct Dll {HMODULE module;~Dll(){if(module)FreeLibrary(module);}} dll{ LoadLibraryExW(std::filesystem::absolute(compiler_dll).c_str(),nullptr,LOAD_LIBRARY_SEARCH_DLL_LOAD_DIR|LOAD_LIBRARY_SEARCH_DEFAULT_DIRS)}; if(!dll.module)throw std::runtime_error("Lua GPU: cannot load native compiler "+compiler_dll.string()+" (Windows error "+std::to_string(GetLastError())+")"); auto get=[&](const char* name){auto f=GetProcAddress(dll.module,name);if(!f)throw std::runtime_error(std::string("Lua GPU: missing compiler symbol ")+name);return f;}; #define NG_SHADERC(name) const auto name##_fn=reinterpret_cast(get(#name)) NG_SHADERC(shaderc_compiler_initialize);NG_SHADERC(shaderc_compiler_release); NG_SHADERC(shaderc_compile_options_initialize);NG_SHADERC(shaderc_compile_options_release); NG_SHADERC(shaderc_compile_options_set_target_env);NG_SHADERC(shaderc_compile_options_set_optimization_level); NG_SHADERC(shaderc_compile_into_spv);NG_SHADERC(shaderc_result_release); NG_SHADERC(shaderc_result_get_compilation_status);NG_SHADERC(shaderc_result_get_error_message); NG_SHADERC(shaderc_result_get_length);NG_SHADERC(shaderc_result_get_bytes); #undef NG_SHADERC std::unique_ptr compiler(shaderc_compiler_initialize_fn(),shaderc_compiler_release_fn); std::unique_ptr options(shaderc_compile_options_initialize_fn(),shaderc_compile_options_release_fn); if(!compiler||!options)throw std::runtime_error("Lua GPU: native compiler allocation failed"); shaderc_compile_options_set_target_env_fn(options.get(),shaderc_target_env_vulkan,shaderc_env_version_vulkan_1_1); // Keep optimization in the Vulkan driver. The SDK optimizer expands this // bounded-register control flow heavily and crashes for larger VM bundles. // Straight-line neighbor kernels avoid the VM entirely. shaderc_compile_options_set_optimization_level_fn(options.get(),shaderc_optimization_level_zero); std::unique_ptr result( shaderc_compile_into_spv_fn(compiler.get(),source.data(),source.size(),shaderc_compute_shader,script,"main",options.get()),shaderc_result_release_fn); if(!result)throw std::runtime_error("Lua GPU: shader compiler returned no result"); if(shaderc_result_get_compilation_status_fn(result.get())!=shaderc_compilation_status_success) throw std::runtime_error(std::string("Lua GPU GLSL: ")+shaderc_result_get_error_message_fn(result.get())); const auto bytes=shaderc_result_get_length_fn(result.get()); if(bytes%4||bytes<20)throw std::runtime_error("Lua GPU: invalid SPIR-V output"); std::vector code(bytes/4);std::memcpy(code.data(),shaderc_result_get_bytes_fn(result.get()),bytes);return code; } }