dx11 isn't bad but it has a looot of cruft around it, writing a good constant buffer manager is witchcraft (remember, there's no "loose uniforms" injected into the command stream, it's all constant buffer updates, drivers do all sorts of writecombining magic to mitigate it, but the naive map / unmap cycle without any driver optimisations would absolutely tank....
Look at a GDC talk (https://d29g4g2dyqv443.cloudfront.net/sites/default/files/ak...), efficient dx11 is also "reinvent a diy memory allocator" level except that you don't have much control.
btw you can run dx11 on other platforms because dxvk exists, but while d3d9vk is a solid idea (high-level API, lowering to vulkan often makes it much faster than the crummy MS d3d9 runtime + DDI combo), d3d11vk is just often a wash or even a regression. I've had to debug dxvk-only perf problems a bit too many times...
anyway, point is, it's not really an easy-to-use or intuitive API
> 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.
Nothing is an easy to use intuitive api with all the features and all the performance, it's (Dx11) the sweet spot of where I felt like it was easy to learn and easy to use without worrying about performance too much.
Sure if you are building the newer rendering pipelines DirectX 11 leaves a lot on the table but it brought a lot of nice things with it, Debug Runtime alone was worth moving to it, 99/100 times your app written in DirectX 11 even if not optimised properly will perform better than DirectX 9 in my experience.
This holds with OpenGL though the modern OpenGL 4.6 and stuff wasn't bad, but it's a lot of cruft still.
Either way there are no perfect APIs, a truly general purpose API that just gives you basics like Texture handling and io and data mapping would be enough today imho. Fully general purpose compute APIs are the solution with some additional tweaks to make graphics stuff convenient. IMHO
Though it's definitely much harder than plain and simple paint colors and textures on vertices & geometry uv approach of old.