Maybe SFML just looks higher-level, because it is designed nicely with an object-oriented approach?
Like I said, sf::Sprite is something of the representation of why SFML confuses me with what level to put it at. sf::Sprite has functions to move it, scale it, rotate it, get global and local boundaries that can be used in simple collision detection. These make it conceptually more than a dumb object passed around to give information to SFML for rendering, it makes it a higher-level of object and even if that's not the intention it
encourages people to use it as such.
I know the docs describe these as 'convenience functions', but I'd argue they expand the domain of the object considerably and make it something beyond what a low-level library would, or even maybe should, provide. So perhaps my critique is with the very existence of sf::Sprite as a render-level primitive.
I guess it just feels like using sf::Sprite to simply be a POD to pass a blit of an image, even a scaled and rotated blit, into SFML is a kind of conceptual overkill due to the presence of these functions, which makes me doubt that it's what I should be doing. All doubts flow from that point, I guess.
There are a few other things, like sf::Image having functions to load images from the filesystem that I'd like to see moved outside of sf::Image and into some kind of global function instead. Perhaps even moved out of sfml/Graphics entirely and into new packages that 'higher level functionality' like that can live (though C++'s library system as out-right
clunky as it is, that's maybe a step too far).
Separates the loading of an image from the file system straight out of the definition of what an image is, having image loading even as a static function conflates it with the very idea of an Image, and having it as a local function makes sf::Image needlessly stateful. To my mind, Image shouldn't even have a default constructor at all, and should require some kind of data be passed to it for it to even exist.
I'm pretty sure that's been discussed already though.
But this is what I mean by SFML being higher-level, it's got conceptually 'higher' ideas like loading an image from the file system living side-by-side with conceptually 'lower' ideas, like creating an image from a byte array, and that muddies the waters somewhat. Or
Sprites, with boundaries and moving, mixed up with
Blitting, which is just "here is a texture, here's the section to draw, here's a transformation, have at!".
I guess you could say my vision for SFML 3 is for it to finish the job started all the way back when Image and Texture were separated into two classes: Remove all these mixed concerns from the code, to make it simpler, purer and more streamlined.
On the whole multi-renderer thing, I've found the main thing that makes cross-renderer abstractions tricky is Shaders. DX12 and OpenGL have different shader languages, so how exactly you can abstract that away (not abstracting the existence of the multiple shader languages obviously, but the creation and passing around of those shaders) is a design question that I've not found any really nice answers, just 'somewhat acceptable work-arounds'.
I think it's wrong to ask an asynchronous version of XYZ feature when you can simply do
auto result = std::async([](std::string filename){sf::Image image; image.loadFromFile(filename); return image;}, "blop.png");
True, file I/O is just the thing other languages have conditioned me to think of as async-by-default, otherwise you're holding up a system thread even if you create a new thread just for loading it. But the more I think about it the idea does not carry over to C++'s async/threading model as well (yet), since most of those languages have the idea of some sort of task pools baked into the language or standard library (even the newer 'low-level' languages like Rust and Go).
Although, if the std filesystem API were ever to go with an async-first design, I'd then argue SFML should follow-suit for file I/O (or if SFML got it's much-suggested Filesystem API, that could declare 'f-u' and go async-based from the ground-up. But C++'s futures API isn't quite there yet, maybe when it inevitably becomes monadic).