The final piece of the puzzle was tying everything together and writing just enough of an OpenGL driver to get things working. This was generally less impressive than its components but there’s still a few neat things I had to get working here which are worth mentioning.
Firstly, for whatever reason you can’t actually generate and then mount shader_c files at runtime. You could save them out, but they’d be stuck in FileSystem.Data, because FileSystem.Mounted is read-only, and the mounted filesystem is the only place considered when you call Shader.Load. So I opted to make the DexGL compiler a separate C++ app instead of attempting to bundle all of glslang into the DEX module. Right now the game just caches out the GLSL sources to your local data folder, then I’ve got a batch script which will compile out the shader_c for your next run and throw them in the mounted assets folder where they belong. Not too big of a deal.
The second weird hack was with how we render geometry. Mupen64plus uses client-side arrays (sigh) when it believes it’s running under GLES (which it does in the case of DEX).
So your vertex layout is changing all the time with calls to glVertexAttribPointer etc. That data can be ordered pretty weirdly, and so unfortunately s&box’s VertexAttribute struct is a little restrictive for what we need to do. During runtime we have to sort out the VertexAttributes for each drawcall and order/pad them out to match how the data is laid out in memory.
Then we use the DEX C# preprocessor (remember how I said there was too much stuff to talk about and I couldn't fit it all into a single blog post?) to generate out some structs which have a fixed compile-time size so that we can actually upload the geometry.
Then we actually need to get the geometry on screen. Here’s where it gets really hairy. Turns out the only way to get geometry with a custom vertex layout on screen is to use an instance of Mesh and build a Model from it with ModelBuilder. All of the other Graphics.Draw APIs use Vertex (which obviously won’t work as it doesn’t have the right layout), or custom vertex structs which have explicit compile-time [VertexLayout] attributes (won’t work because our vertex layout is determined at runtime).
But obviously we can’t create a Model each frame for each draw call, that’s just going to murder the engine.
So I settled with a mesh pool, which buckets a shit ton of meshes by their vertex layout and vertex count, and adds new entries to the pool if it can’t acquire a mesh that we’re confident is out-of-flight. It’s a little hacky and a little slow (Mesh.SetVertexBufferData and DrawModel are by far the slowest functions when profiling) but it works well enough for what I need it to do.