init
This commit is contained in:
commit
87bff164f1
416 files changed
+279512
No files matched your search
@@ -0,0 +1,456 @@
|
||||
#define NOMINMAX
|
||||
#define WIN32_LEAN_AND_MEAN
|
||||
#include <windows.h>
|
||||
#include "lua/gpu_lua.h"
|
||||
#include "lua/gpu_lua_profiles.h"
|
||||
#include "gpu_lua_sources.h"
|
||||
#include <shaderc/shaderc.h>
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstring>
|
||||
#include <iomanip>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
|
||||
// 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<size;++i)if(data[i]!=13)h=(h^data[i])*1099511628211ull;
|
||||
return h;
|
||||
}
|
||||
namespace {
|
||||
const char* const library="data/scripts/lib/utilities.lua";
|
||||
const std::map<std::string,unsigned> 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<std::string,unsigned> 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<float>(n);uint32_t bits;std::memcpy(&bits,&f,4);
|
||||
std::ostringstream out;out<<"uintBitsToFloat(0x"<<std::hex<<bits<<"u)";return out.str();
|
||||
}
|
||||
struct Parsed {
|
||||
std::unique_ptr<lua_State,decltype(&lua_close)> 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<unsigned char>(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<unsigned char> dump;
|
||||
lua_dump(state.get(),[](lua_State*,const void* p,size_t n,void* dst)->int {
|
||||
auto& b=*static_cast<std::vector<unsigned char>*>(dst);
|
||||
b.insert(b.end(),static_cast<const unsigned char*>(p),static_cast<const unsigned char*>(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<uint64_t*>(context);
|
||||
const auto* data=static_cast<const unsigned char*>(bytes);
|
||||
// Bytecode is binary: CR bytes must participate in this digest.
|
||||
for(size_t i=0;i<size;++i)hash=(hash^data[i])*1099511628211ull;
|
||||
return 0;
|
||||
},&digest);
|
||||
if(status)throw std::runtime_error("Lua GPU: cannot fingerprint compiled library");
|
||||
return digest;
|
||||
}
|
||||
unsigned line(GCproto* pt,unsigned ip){return ng_lua_instruction(pt,ip).line;}
|
||||
std::string str(GCproto* pt,unsigned d) {
|
||||
size_t length=0;const char* bytes=ng_lua_string(pt,d,&length);
|
||||
if(!bytes)throw std::runtime_error("Lua GPU: invalid string constant");
|
||||
std::string result(bytes,length);
|
||||
if(std::any_of(result.begin(),result.end(),[](unsigned char c){return c>=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<std::string,unsigned> symbols;
|
||||
std::map<std::string,std::string> exports;
|
||||
std::map<std::string,unsigned> 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<unsigned>(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<int16_t>(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<unsigned,std::string> values;
|
||||
for(unsigned i=1;i<lib.pt->sizebc;++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;ip<p->sizebc;++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 "<<ip<<": {ngp_line="<<line(p,ip)<<"u;ngp_ip="<<ip+1<<";";
|
||||
auto test=[&](const std::string& cond){
|
||||
if(ip+1>=p->sizebc||bc_op(proto_bc(p)[ip+1])!=NG_BC_JMP)fail("expected conditional jump");
|
||||
cases<<"if(!("<<cond<<"))ngp_ip++;";
|
||||
};
|
||||
if(op==NG_BC_KSHORT||op==NG_BC_KNUM||op==NG_BC_KPRI||op==NG_BC_KSTR)cases<<reg(a)<<"="<<literal(p,ins)<<";";
|
||||
else if(op==NG_BC_MOV)cases<<reg(a)<<"="<<reg(d)<<";";
|
||||
else if(op==NG_BC_KNIL){reg(d);for(unsigned k=a;k<=d;k++)cases<<reg(k)<<"=ngp_nil();";}
|
||||
else if(op==NG_BC_NOT)cases<<reg(a)<<"=ngp_bool(!ngp_truth("<<reg(d)<<"));";
|
||||
else if(op==NG_BC_UNM)cases<<reg(a)<<"=ngp_num(-ngp_number("<<reg(d)<<"));";
|
||||
else if(op==NG_BC_GGET){
|
||||
const std::string name=str(p,d);const auto api=apis.find(name);
|
||||
if(api!=apis.end())cases<<reg(a)<<"=NgpValue(4u,0.0,"<<api->second<<"u);";
|
||||
else if(name=="math")cases<<reg(a)<<"=NgpValue(5u,0.0,1u);";
|
||||
else if(exports.count(name)){
|
||||
auto [it,added]=globals.emplace(name,static_cast<unsigned>(globals.size()));
|
||||
cases<<reg(a)<<"=ngp_g["<<it->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("<<reg(b)<<".tag!=5u)ngp_fault(3u);"<<reg(a)<<"=NgpValue(4u,0.0,"<<api->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="<<c-1<<";";
|
||||
for(unsigned k=0;k<c-1;k++)cases<<"args.v["<<k<<"]="<<reg(a+1+ng_lua_frame_shift()+k)<<";";
|
||||
cases<<"NgpTuple result=ngp_call("<<reg(a)<<",args);";
|
||||
for(unsigned k=0;k<b-1;k++)cases<<reg(a+k)<<"=result.v["<<k<<"];";
|
||||
}else if(op==NG_BC_JMP||op==NG_BC_UCLO){
|
||||
const int target=static_cast<int>(ip)+1+static_cast<int>(bc_j(ins));
|
||||
if(target<1||target>=static_cast<int>(p->sizebc))fail("jump target out of range");
|
||||
cases<<"ngp_ip="<<target<<";";
|
||||
}else if(op==NG_BC_RET0||op==NG_BC_RET1||op==NG_BC_RET)cases<<"projectile=ngp_proj;return ngp_error;";
|
||||
else if(op==NG_BC_LOOP){} // Marker only; backedges are ordinary JMP.
|
||||
else if(op>=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<int>(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("<<cond<<")"<<reg(a)<<"="<<reg(d)<<";";
|
||||
test(cond);
|
||||
}else if(op>=NG_BC_ADDVN&&op<=NG_BC_POW){
|
||||
const int index=static_cast<int>(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<<reg(a)<<"=ngp_arithmetic("<<arithmetic<<","<<left<<","<<right<<");";
|
||||
}else fail("unsupported Lua GPU instruction");
|
||||
cases<<"break;}\n";
|
||||
}
|
||||
std::ostringstream out;
|
||||
out<<"NgpValue ngp_g["<<std::max<std::size_t>(1,globals.size())<<"];\nvoid ngp_module_init(){";
|
||||
for(const auto& [name,index]:globals)out<<"ngp_g["<<index<<"]="<<exports.at(name)<<";";
|
||||
out<<"}\nuvec4 ngp_execute(inout FProj projectile,vec3 owner,float owner_id){\n"
|
||||
"ngp_proj=projectile;ngp_owner=owner;ngp_owner_id=owner_id;ngp_failed=false;ngp_loaded=false;ngp_error=uvec4(0u);ngp_line=0u;\n"
|
||||
"for(int i=0;i<ngp_g.length();i++)ngp_g[i]=ngp_nil();\nNgpValue ngp_r["<<unsigned(p->framesize)<<"];\n"
|
||||
"for(int i=0;i<ngp_r.length();i++)ngp_r[i]=ngp_nil();int ngp_ip=1;\n"
|
||||
"for(int budget=0;budget<4096&&!ngp_failed;budget++){switch(ngp_ip){\n"<<cases.str()<<
|
||||
"default:ngp_fault(3u);break;}}\nif(!ngp_failed)ngp_fault(4u);projectile=ngp_proj;return ngp_error;}\n";
|
||||
return out.str();
|
||||
}
|
||||
std::string preamble() {
|
||||
std::ostringstream out;
|
||||
for(const auto& [label,value]:std::map<std::string,std::string>{{"PROJECTILE","ProjectileComponent"},{"VELOCITY","VelocityComponent"},{"WHO","mWhoShot"},{"GRAVITY","gravity_y"},{"MOTION","mVelocity"},{"UTIL",library}})
|
||||
out<<"const uint NGP_"<<label<<"="<<intern(value)<<"u;\n";
|
||||
out<<"uint ngp_rank(uint s){switch(s){";
|
||||
unsigned rank=0;for(const auto& [s,id]:symbols)out<<"case "<<id<<"u:return "<<rank++<<"u;";
|
||||
out<<"default:return 0u;}}\nbool ngp_parse_number(uint s,out float n){n=0.0;switch(s){";
|
||||
for(const auto& [s,id]:symbols){
|
||||
lua_pushlstring(root.state.get(),s.data(),s.size());
|
||||
if(lua_isnumber(root.state.get(),-1))out<<"case "<<id<<"u:n="<<number(lua_tonumber(root.state.get(),-1))<<";return true;";
|
||||
lua_pop(root.state.get(),1);
|
||||
}
|
||||
out<<"default:return false;}}\nvoid ngp_module_init();\n";return out.str();
|
||||
}
|
||||
std::string scalar_body() {
|
||||
// Typed, acyclic SSA lowering for fused additive neighbor bodies. There
|
||||
// is no register interpreter or dynamic API dispatch in the inner loop.
|
||||
struct Value {int kind=0;std::string expr;unsigned api=0;double constant=NAN;};
|
||||
enum {Number=1,Self,Velocity,Function,String,Math};
|
||||
auto* p=root.pt;std::vector<Value> 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 "<<name<<"="<<expr<<";\n";r.at(reg)={Number,name};
|
||||
};
|
||||
for(unsigned ip=1;ip<p->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<int16_t>(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("<<condition<<")return uvec4(0u);\n";++ip;
|
||||
}else if(op==NG_BC_CALL) {
|
||||
if(r.at(a).kind!=Function||b==0||c==0)fail(ip,"fixed native call required");
|
||||
std::vector<Value> args;for(unsigned k=0;k<c-1;++k)args.push_back(r.at(a+1+ng_lua_frame_shift()+k));
|
||||
auto count=[&](unsigned n){if(args.size()!=n)fail(ip,"argument count mismatch");};
|
||||
auto num=[&](unsigned n){return numeric_value(args.at(n),ip);};
|
||||
auto api=r.at(a).api;
|
||||
if(api==1){count(0);r.at(a)={Self};}
|
||||
else if(api==2){
|
||||
count(1);std::string source;
|
||||
if(args[0].kind==Self)source="projectile.a";
|
||||
else if(args[0].kind==Number&&args[0].constant==1.0)source="owner";
|
||||
else fail(ip,"unreviewed entity handle");
|
||||
if(b!=3)fail(ip,"two transform results required");
|
||||
temp(a,ip,source+".x");temp(a+1,ip,source+".y");
|
||||
}else if(api==3){count(2);if(args[0].kind!=Self||args[1].kind!=String||args[1].expr!="VelocityComponent")fail(ip,"velocity component required");r.at(a)={Velocity};}
|
||||
else if(api==4||api==5){
|
||||
count(api==4?2:4);
|
||||
if(args[0].kind!=Velocity||args[1].kind!=String||args[1].expr!="mVelocity")fail(ip,"velocity vector required");
|
||||
if(api==4){if(b!=3)fail(ip,"two velocity results required");temp(a,ip,"projectile.a.z*60.0");temp(a+1,ip,"projectile.a.w*60.0");}
|
||||
else {if(b!=1)fail(ip,"write must have no results");out<<"projectile.a.zw=vec2("<<num(2)<<","<<num(3)<<")/60.0;\n";}
|
||||
}else if(api>=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<const char*>(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<std::string,std::string>{{path,source},{library,lib}})
|
||||
p.dependencies.push_back({path,fingerprint(reinterpret_cast<const unsigned char*>(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<std::string,Dependency> 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<const unsigned char*>(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<std::string> 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"
|
||||
<<find("vel_x = vel_x *")<<'\n'<<find("vel_y = vel_y *")<<'\n'
|
||||
<<"ComponentSetValueVector2(comp,'mVelocity',vel_x,vel_y)\n";
|
||||
} else {
|
||||
pair<<"local px,py=EntityGetTransform((GetUpdatedEntityID()))\nlocal x,y=EntityGetTransform(1)\n"
|
||||
<<find("local distance =")<<'\n'<<find("local distance_full =")<<'\n';
|
||||
auto diamond=find("if ( distance < distance_full *");auto end=diamond.find(" and ");
|
||||
if(end==std::string::npos)throw std::runtime_error("neighbor diamond guard missing");
|
||||
pair<<diamond.substr(0,end)<<" then\n"<<find("distance = math.sqrt")<<"\nlocal "<<find("direction =")<<'\n'
|
||||
<<find("if ( distance < distance_full )")<<'\n'
|
||||
<<find("local gravity_percent =")<<'\n'<<find("local gravity_coeff =")<<'\n'
|
||||
<<"local comp=EntityGetFirstComponent(GetUpdatedEntityID(), 'VelocityComponent')\n"
|
||||
"local vel_x,vel_y=ComponentGetValueVector2(comp,'mVelocity')\n"
|
||||
<<find("local offset_x =")<<'\n'<<find("local offset_y =")<<'\n'
|
||||
<<find("vel_x = vel_x +")<<'\n'<<find("vel_y = vel_y +")<<'\n'
|
||||
<<"ComponentSetValueVector2(comp,'mVelocity',vel_x,vel_y)\nend\nend\n";
|
||||
}
|
||||
lowered=pair.str();
|
||||
}
|
||||
auto part=translate(lowered,read,profile.path);
|
||||
// The dependency is always the original asset, never the fused fragment.
|
||||
for(auto& dep:part.dependencies)if(dep.path==profile.path)dep.fingerprint=profile.fingerprint;
|
||||
// Each module owns its constants, registers and dofile_once globals.
|
||||
// Only the terrain query is shared; it carries no invocation state.
|
||||
for(const std::string prefix:{"ngp_","NGP_"}) {
|
||||
const std::string replacement=prefix+std::to_string(id)+"_";
|
||||
size_t at=0;
|
||||
while((at=part.function.find(prefix,at))!=std::string::npos) {
|
||||
if(part.function.compare(at,11,"ngp_surface")==0){at+=11;continue;}
|
||||
part.function.replace(at,prefix.size(),replacement);at+=replacement.size();
|
||||
}
|
||||
}
|
||||
for(const std::string type:{"NgpValue","NgpTuple"}) {
|
||||
size_t at=0;const auto replacement=type+std::to_string(id);
|
||||
while((at=part.function.find(type,at))!=std::string::npos) {
|
||||
part.function.replace(at,type.size(),replacement);at+=replacement.size();
|
||||
}
|
||||
}
|
||||
bundle.function+=part.function;
|
||||
bundle.instructions+=part.instructions;bundle.registers=std::max(bundle.registers,part.registers);
|
||||
for(const auto& dep:part.dependencies)dependencies[dep.path]=dep;
|
||||
dispatch<<"case "<<id<<"u:return ngp_"<<id<<"_execute(p,owner,owner_id);";
|
||||
if(id>=first_neighbor_program)neighbors<<"case "<<id<<"u:return ngp_"<<id<<"_execute(p,owner,1.0);";
|
||||
}
|
||||
dispatch<<"default:return uvec4(14u,0u,0u,0u);}}\n";
|
||||
neighbors<<"default:return uvec4(14u,0u,0u,0u);}}\n";
|
||||
bundle.function+=dispatch.str()+neighbors.str();
|
||||
bundle.shader=std::string(sources::layout)+sources::terrain+bundle.function+sources::dispatch;
|
||||
for(const auto& [path,dep]:dependencies)bundle.dependencies.push_back(dep);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
std::vector<uint32_t> 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<decltype(&name)>(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<shaderc_compiler,decltype(shaderc_compiler_release_fn)> compiler(shaderc_compiler_initialize_fn(),shaderc_compiler_release_fn);
|
||||
std::unique_ptr<shaderc_compile_options,decltype(shaderc_compile_options_release_fn)> 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<shaderc_compilation_result,decltype(shaderc_result_release_fn)> 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<uint32_t> code(bytes/4);std::memcpy(code.data(),shaderc_result_get_bytes_fn(result.get()),bytes);return code;
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user