So I went through several revisions before I landed on this solution. The whole thing was done in about 2 weeks while I was still working on the demo.
Originally it was only super sampling anti-aliasing, which is still in there, but ended up being too blurry by itself and also tanked performance when increasing resolution scaling too high.
I experimented with Lanczos, and had something sort of working, but it didn't have the quality I wanted and would have needed a sharpening pass like FSR does, but the code just got too complex and I didn't like it.
I also considered just porting FSR, since that is open source, but I read through the code and it is extremely complicated and long. It also uses features that require Vulkan/DirectX12 and I'm working in OpenGL so it would have made porting difficult. So I abandoned that idea.
Finally I discovered a method called directional averaging, which is fairly straight-forward and not complex. The idea is that instead of sampling in a fixed manner (for example, just grabbing the 4 nearest pixels) you scan the image and look for patterns in the diagonals.
So here I am sampling in a sort of star/cross kind of pattern (9 taps) which computes the color difference of each pair of pixels, and then finds the most relevant 2 pixels to blend (based on the pixels that are most visible in color and also closest in color so they likely come from the same object).
This actually works well, but results in a somewhat pixelated image (though still much better than nearest neighbor or bilinear interpolation). So to fix this I add a super sampling pass, which runs at any resolution (it helps above or below native).
The super sampling takes 4 taps, but in a rotated box formation, so the pixels do not coincide, which gives better results than standard sampling (which may just pick the 4 closest pixels).
I have sliders so you can blend between the upscaling and the super sampling, which allows you to tweak how sharp or soft the image is, though in my experiments, just leaving it at 50% almost always gives the best result.