A VM running SPIR-V code on the CPU
This is an implementation of the SPIR-V, the new intermediate language defined by Khronos.
See the official specification here: SPIR-V 1.0
It also produces C like code that represents the given SPIR-V file (for easier reading).
I'm mostly working on this out of academic interest. I do plan on using it in my universities "write a software rasterizer" project.
Once verification is implemented it could be used to verify the output of tools that produce SPIR-V (and right now, I guess if the program runs and gives the correct output, that is some kind of verification as well).
This is very much a work in progress. Not all instructions are implemented and performance has not been a priority so far.
This project downloads the latest spec and generates C++ files with useful declerations from it. Mostly information about op codes and structs to represent them to help with parsing and processing.
- SPIR-V Validation (validation rules described here)
- Implement all OpCodes
- Improve performance
- Better human readable parsing output (at least match the reference compiler)
Shader code:
#version 110
varying vec2 uv;
uniform sampler2D testTex;
void main() {
vec4 col = texture2D(testTex, uv);
col.rgb = col.rbr;
gl_FragColor = col;
gl_FragColor.a = 1.0;
}
C++ code:
int main(int argc, const char** argv) {
CmdArgs args;
if (!ParseArgs(argc, argv, &args)) {
std::cout << "Could not parse arguments. Usage: " << USAGE << std::endl;
return -1;
}
Parser parser(args.InputFile);
Program prog;
if (!parser.Parse(&prog)) {
std::cout << "Could not parse program." << std::endl;
return -1;
}
std::cout << "Validating program:...";
if(!validate(prog, std::cout)) {
std::cout << "Validation failed." << std::endl;
return -1;
}
std::cout << "done" << std::endl;
std::cout << "Generationg code:...";
if (!genCode(args.OutputFile, prog)) {
std::cout << "Could not generate code for program." << std::endl;
return -1;
}
std::cout << "done" << std::endl;
Environment env;
InterpretedVM vm(prog, env);
if(!vm.Setup()) {
std::cout << "Could not setup the VM." << std::endl;
return -1;
}
std::cout << "Running program:...";
Texture inTex = load_tex("data/testin.bmp");
Sampler* sampler = new Sampler{ 2, (uint32*)&inTex, inTex.data, FilterMode::FMPoint, WrapMode::WMRepeat };
Vec2* texSize = new Vec2{ (float)inTex.width, (float)inTex.height};
Light* light = new Light{ {1, 0, 0, 1}, {0.5f, 0.5f} };
Color* fragColor = new Color{ 0, 0, 0, 0 };
Vec2* uv = new Vec2{ 1.0f, 1.0f };
bool allVariablesSet = true;
allVariablesSet &= vm.SetVariable("uv", &uv);
allVariablesSet &= vm.SetVariable("texSize", &texSize);
allVariablesSet &= vm.SetVariable("testTex", &sampler);
allVariablesSet &= vm.SetVariable("light", &light);
allVariablesSet &= vm.SetVariable("gl_FragColor", &fragColor);
if(!allVariablesSet) {
std::cout << "Could not set all variables." << std::endl;
return -1;
}
Texture outTex = MakeFlatTexture(inTex.width, inTex.height, { 0, 0, 0, 1 });
bool didFail = false;
for (int x = 0; x < outTex.width && !didFail; x++) {
for (int y = 0; y < outTex.height; y++) {
uv->x = float(x) / outTex.width;
uv->y = float(y) / outTex.height;
if (!vm.Run()) {
std::cout << "Program failed to run.";
getchar();
return -1;
}
outTex.data[x + y * outTex.width] = **(Color**)vm.ReadVariable("gl_FragColor");
}
}
save_bmp("data/testout.bmp", outTex);
std::cout << " done";
return 0;
}