> writing a good constant buffer manager is witchcraft
Tbh, I think the main lesson is simply to not try to work around the specific 3D API conventions too much, e.g. don't try to shoehorn mechanisms into the D3D11 layer that would be straightforward in Vulkan or D3D12 but would disable the special fast-paths in D3D11.
E.g. for a traditional "one uniform update per draw call" loop with one small constant buffer per shader updated via plain old UpdateSubresource(), D3D11 is incredibly fast (you can easily do hundreds-of-thousands update+drawcall pairs like this per 60Hz frame on a somewhat recent - e.g. post-2016 or so - mid-level gaming PC.
Of course trying to emulate D3D11 on Vulkan or D3D12 might have all sorts of issues that don't exist on a good "native" D3D11 driver.
Also somewhat related: in a GL shim it's typically still faster to update uniform data via the old glUniform*v calls instead of using uniform buffers, apparently most GL drivers lack the special fast-path optimizations of small D3D11 constant buffers, and the more abstract glUniform calls seems to give the driver more 'wiggle room' to implement an optimal upload path.
I mean sure you can do a generic layer and common denominator stuff, basically the kinda stuff webgpu or those rendering abstractions do and that's fair enough, it works well for simpler stuff!
The problem really is that if you do that you'll end up with random weird stalls or perf problems you can't really debug sadly :\
Re GL ye glUniform is usually much faster because you just do a lot less, you're writing a small amount of data directly into constant memory, it doesn't need resource tracking. UBOs / constant buffers come back to the theoretically slow but can-be-made-faster-with-driver-heuristics approach...i.e. if you see a small UBO which is only used as a UBO, you get the same direct constant bank upload
On NV it's quite fast, on AMD it's not really optimised, on intel just use buffersubdata because everything else sucks....