35 comments

[ 3.0 ms ] story [ 84.2 ms ] thread
I don't know why but I always find C/C++ libraries' documentation to be god awful to read compared to say Python or even JS libraries. An auto generated dump of javadoc comments is a terrible introduction.

For example, if I want to open an image [1], it shows what I need to build the plugin but it doesn't have any code samples on how to use it. The constructor requires an manager -- what's that? After importing, how do I get the texture buffer? I'm sure all of this info is somewhere on the docs but I shouldn't have to search for something that should all be on the same page.

[1] https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1A...

I’ve had the complete opposite experience. I’ve yet to use a non-trivial Python library/framework where I didn’t end up having to dig through the source code to figure out how to do something. The dynamic nature of the language makes it hard to auto generate useful docs. With something like Doxygen you can easily know exactly what types a function expects, how to construct those types, etc.

...OTOH, digging through Python source code is more pleasant than C++. So it balances out I guess.

Which python libs do you find insufficiently documented? Admittedly I've only used Python for quick scripts or apps (bs4, fitz, flask) rather than my primary language so maybe my view is somewhat biased
same, C++ doc can be awful...if it's hand-written, but doxygen is quite good. On the other hand, docs for dynamic languages like javascript and python often hang me there with ambiguous descriptions like "string-like" or "buffer-like" -- yes, I know what those descriptions _mean_ but that doesn't really give me enough confidence to use those APIs.
While I'm here, a shameless self-promo -- Magnum uses a Python doc generator that includes type annotations by default, moving it closer to what you're used to from C++: https://blog.magnum.graphics/announcements/introducing-pytho...

(Sure, it still means the library has to make those annotations in the first place, and have them correct, but the newer and more complex libraries fortunately do, if even just to make IDE autocompletion work.)

Hah, good point, seems like this class suffers from documentation erosion. It got expanded and expanded and expanded and the essential piece of information got somehow lost down there in the gory details. The actual usage info and code snippet is in the base class documentation [1], it's just that it's no longer linked as prominently as it should be. I'll fix that.

It's not this bad everywhere tho, docs of fresher APIs didn't have a chance to erode yet: https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1S...

[1] https://doc.magnum.graphics/magnum/classMagnum_1_1Trade_1_1A...

Where did they come up with this name?
Some other interesting libraries I've seen along these lines:

- Sokol (sokol_gfx in particular): https://github.com/floooh/sokol

- wgpu (see wgpu-native for the C API): https://github.com/gfx-rs/wgpu

It kind of seems like graphics abstractions for modern hardware are getting pretty "figured out". There are wrappers that work for most DirectX/Metal/OpenGL applications so they can run just about anywhere, and new 2D/3D applications have a lot of accessible/open-source options to build on top of. Projects like Mesa's Zink will centralize the burden of maintaining legacy APIs away from hardware manufacturers. Valve's Proton is making waves. WebGPU is on the way.

The future looks bright.

That isn't nothing new, 10 years ago, before free beer Unity and Unreal, one would be using something like Ogre 3D.
Or Irrlicht, if you were me as a younger lad and scared off by Ogre at the time. There's always been a vibrant landscape of these sorts of tools, so it's neat to see another one
Oh, I wish. There are plenty of game engines and graphics libraries for C++. We have enough of those. This is not yet true for Rust. There seem to be, but if you start to push the performance limits, problems appear.

I've been writing a client for Second Life, which, despite all the hype from NFT land, is still the best metaverse around. It's just sluggish, so I'm trying to fix that. The stack I'm using is Rust->Rend3->WGPU->Vulkan->NVidia.

Rust is doing fine. No problems at the language level. For this, I need Rust's safe concurrency; there are a lot of threads manipulating shared data. 18 months on, I only needed a debugger once, and the problem wasn't in my code.

Rend3/WGPU provides a cross platform layer. Put in meshes, materials, textures, and transforms, and have a loop endlessly calling the render function. Other threads change the scene in parallel with rendering. Rend3 handles memory allocation and scheduling. Vulkan gives you access to the GPU's memory. Managing it is someone else's problem. WGPU provides a common interface to Vulkan, DirectX, and Apple's Metal, which all do pretty much the same thing, but incompatibly. WGPU sits below most of the major game engines on Rust.

It all works, but not fast enough. There are locking bottlenecks at the Rend3 and WGPU levels which cause the render thread to be stalled when another thread is loading content. The whole point of Vulkan is that you can do both at once, but the Rend3 and WGPU levels don't quite have that working yet. The right functionality is there, but not the optimization needed for serious work.

Nobody seems to be building AAA titles in Rust. I'm pushing the scene complexity to AAA title levels[1], and I seem to be close to the first person to do that. Not where I wanted to be.

If you want to work on the lower levels, the WGPU and Rend3 projects, which are open source, need more good people.

[1] https://video.hardlimit.com/w/peBesyAgtzfRWS5FnDQQtn

Embark Studios started an all Rust operation a few years ago. https://github.com/EmbarkStudios
Yes, and they're still working on the basics. Their system for writing shaders in Rust may be useful, but it's not done yet.
It's a tough problem when it comes to AAA adoption. I'm not even certain if Rust has been in the game developer's conscious enough for a robust, performant engine to arise yet. i.e. if we assume that a studio deadset on making a Rust engine for a AAA game was given 5-6 years of full time engine development, they'd still be in the thick of things.

And on top of that, there's just so much tooling built around existing engines/software/IDE's to be built for truly productive AAA work. There's so much to work on and developers as of now aren't convinced enough of the potential benefits to jump into that foray (at least, not on the company's dime).

It is. I'm doing this because in the "metaverse" space, defined as virtual worlds with user-created content, the existing game engines are not that helpful. They all rely on heavy content preprocessing in some desktop environment. Virtual worlds full of items created by huge numbers of users don't have that step. Which means Unity and Unreal Engine are out, at least in their current versions. (This, by the way, is why you can't sell furniture NFTs in Decentraland; the unit of updating is the land parcel with everything on it, edited offline.) Everybody who has a good, editable virtual world has their own engine.

Roblox is doing some very nice work, but the system is entirely proprietary. Watch this build in Roblox. It's not just blocks any more.[1] That's a very nice 3D editor, using constructive solid geometry.

[1] https://www.youtube.com/watch?v=fpiRDk510Wg

Sokol author here (thanks for the mention). I think it's important to note that Magnum has a much wider scope than both sokol-gfx and WebGPU (see here: https://magnum.graphics/features/extras/). sokol-gfx and WebGPU are "just" 3D API wrappers, while Magnum is a much more complete C++ framework for writing 3D applications. I've been following Magnum (and the author Vladimír Vondruš) for a long time, it's an excellent project with a lot of great ideas, and a nicely balanced C++ API (which is a feat of its own). Also I'm kinda jealous of the documentation: https://doc.magnum.graphics/magnum/ :)

PS: also don't forget BGFX (https://github.com/bkaradzic/bgfx) which sits somewhere in the middle between sokol-gfx/WebGPU and Magnum when it comes to API abstraction and feature set.

It's been 30 years and GUI toolkits are worse than they were in 1995
Check out gunslinger, a pure C99 game framework, with a very clean design, inspired at least in part by Sokol.

The development has been making huge strides and they have a ton of great example code, as well as a fairly active discord channel:

https://github.com/MrFrenik/gunslinger

What they don't have are example games, which is the first thing I look for in a game framework. I need to know at the very least that it's complete enough to build a simple game.
This looks like good, modern code.

+1, would use.

I’ll definitely give this a try, it arrived exactly when I was exploring other visualization middleware.

Also it’s a small thing but it was refreshing to see, in the use-cases section, the developer refer to Godot as a better fit for someone who needs a WYSIWYG editor for games.

Yikes, a GL-inspired graphics library... let's see, how many sins do I count:

* Still using global enable/disable flags for all the state bits (e.g. depth test, face culling) rather than packing them into a PSO struct. Why?

* Uniforms are bound manually and kept statefully rather than being provided as a UBO interface.

* No support for draw call buffering or sorting, and what appears like limited support for multiple framebuffers.

* Ooh, "SceneGraph", is that where the pass manager is? Oh, no, it's an object layer that seems to resemble circa OGRE around 2011. Doesn't seem to do any transparency sorting or pass management either.

I'd really recommend something like bgfx, sokol, or wgpu instead, which have reduced their dependence on global state, and provide a much better foundation for modern graphics.

Are we looking at the same code? I'm not an OpenGL or Vulkan expert, but:

> ...rather than packing them into a PSO struct. Why?

* You can clearly read on the front page of the repository that this supports OpenGL 2 through 4.6. I think you're just not looking hard enough. He has Vulkan support in here with a clearly marked file named Pipeline.cpp. The guy knows what a pipeline is...

> ...rather than being provided as a UBO interface.

* Is this not a UBO interface?

> layout(std140) uniform material { vec4 diffuse; vec4 specular; };

It's in his documentation for AbstractShaderProgram. He has explicit binding for stuff pre OpenGL 3.3 or whenever OpenGL added UBOs.

> ...appears like limited support for multiple framebuffers.

* I don't see why you think there's limited support for multiple framebuffers...? From his docs:

> @section GL-Framebuffer-usage-multiple-outputs Multiple fragment shader outputs

And then it goes on to explain how to use multiple framebuffer outputs for something like deferred rendering.

Moreover, his docs have snippets like this:

> @deprecated_gl Prefer to use @ref AbstractFramebuffer::clearDepth() @ref AbstractFramebuffer::clearDepthStencil() instead of @ref setClearDepth() and @ref AbstractFramebuffer::clear() as it leads to less state changes

Which clearly shows this guy knows what global state is and why it's bad.

I think you're making very broad assumptions based on a very cursory glance at this project. I've only looked at it for about 30 minutes and it looks like a well developed library by somebody who knows what they're doing.

I'm sure I could find code that counters the rest of your argument, but I don't feel like looking anymore. None of your criticism seem well intentioned. It might behoove you to give people the benefit of the doubt and realize that you may be able to learn something from them, even if they're so clearly inferior to you.

P.S. Don't start off a criticism by saying "how many sins do I count". I don't know your experience, but that's just such an incredibly pretentious statement that's begging to be countered by somebody way more knowledgeable than you about this stuff.

> He has Vulkan support in here with a clearly marked file named Pipeline.cpp. The guy knows what a pipeline is...

There is a Vulkan API wrapper. However, there is no "Vk Renderer" -- no code seems to use the Vulkan parts of the code system, and the two projects seem unrelated.

> * Is this not a UBO interface?

There are ways of making a uniform buffer, however the examples don't cover them and the API doesn't adapt automatically. See how all of the setters assert if UBOs are enabled.

https://github.com/mosra/magnum/blob/cfc02599e54e02337dd56bb...

> * I don't see why you think there's limited support for multiple framebuffers...?

The code I do see is about binding/unbinding framebuffers in a stateful manner, e.g. AbstractFramebuffer::bind(), rather than supporting passes.

> None of your criticism seem well intentioned. It might behoove you to give people the benefit of the doubt and realize that you may be able to learn something from them, even if they're so clearly inferior to you.

To put it simply, I've taught enough graphics to know first-hand the kinds of misconceptions that OpenGL-styled APIs can cause, and I'm just a bit tired to see it continue. Admittedly I was a bit harsh, I don't mean any harm towards the author. There are just graphics APIs with interfaces I consider to be much better designed.

The dismissive, know-it-all, didnt-read-but-holds-opinion attitude is the norm on HN, it is best to simply ignore such comments.
Does OpenGL even support PSOs? I thought that was a Vulkan thing.
(comment deleted)
(comment deleted)
Project author here. Hello :)

Let me just clarify a few bits. The GL wrapper is deliberately this low-level, because the library provides a baseline for a much broader range of uses than just games themselves. For an application that performs real-time image / video processing on a GPU, draw call or transparency sorting would be extra overhead. An _actual game engine_ that implements its own custom draw batching would have to work around the implicit batching, if it was there. The wrapper is just lower-level than you expected, that's all, and same is for the currently unadvertised in-progress Vulkan wrapper.

Regarding uniforms, other people already pointed out there is a UBO interface [1]. But there are also users who _rely_ on WebGL 1.0 or GL 2.1 compatibility (imagine that!), so I can't just drop the classic uniform path (and other related suffering). On the other hand, not all hardware and drivers have the UBO path faster in all cases, so providing an UBO iterface and failing back to classic uniforms only if UBOs are not available doesn't make sense either. In my measurements, the only case where UBOs actually improved performance across all platforms was in a multi-draw scenario, with UBOs being split by frequency of update, containing data for 100+ draws and being uploaded at most once per frame. The engine supports such use case [2], but doing that efficiently means having your data and logic ready upfront. If the library would try to attempt that automagically implicitly, it wouldn't end well and the gains would be minimal. That being said, I don't rule out a possibility for an opt-in high-level "render graph" or "pass management" API that does fancy things on its own, but it needs a lower-level layer built upon. Not all or nothing, layers. Layers so projects with specific needs can ignore that and use the layer below directly.

There's no global state anymore except for the Renderer, and I'm painfully aware of that last piece, yes. It's getting fixed, but because on certain targets (WebGL) every call matters, it's not as simple as packing all that state into a single struct and then resetting it in a bulk before a draw call.

"SceneGraph"? Yes, that part is in a maintenance mode, and I recommend new users to use entt, flecs or other ECS frameworks instead, until I build a replacement for this ancient technology that's there since 2010 (yes). But a lot of projects still want to use it because it's _damn convenient_, so I can't really remove it yet either to save the project from immediately raising a "red flag" to certain people ;)

Finally, the project has been under frantic development since its last version annoucement in 2020. Not everything is done and documented yet, the website shows slightly outdated info and the examples don't really use the fresh APIs yet. The Vulkan parts you saw as "unrelated" or "unused", those are still getting finished. Plus there's of course a roadmap to satisfy the needs of long-running Magnum-based projects, so the focus is often in different areas than the "yikes"-triggering ones. Such as asset management and conversion [3].

Have a nice day.

[1] https://doc.magnum.graphics/magnum/shaders.html#shaders-usag... [2] https://doc.magnum.graphics/magnum/shaders.html#shaders-usag... [3] https://blog.magnum.graphics/announcements/new-geometry-pipe...

> In my measurements, the only case where UBOs actually improved performance across all platforms was in a multi-draw scenario, with UBOs being split by frequency of update, containing data for 100+ draws and being uploaded at most once per frame.

That's interesting -- back when I was working on mobile platforms, I found that UBOs were a win from call overhead alone -- replacing N different glUniform* calls with one UBO upload per object was a win on both the Adreno and Mali drivers, though we did have to work around some driver bugs (as is Android tradition).

I haven't done the measurements as well, but I suspect it's also a win on WebGL 2 over the old strategy, because calling the WebGL functions is just more expensive than it should be (I've been told this is getting fixed in the browsers eventually, but I was also told that nearly 3 years ago, and it hasn't happened yet...)

But these days I definitely advocate for a single giant UBO for your scene, uploaded at the start of your frame. I prepare all draw calls at the start of the frame in structures and allocate a single UBO for the frame at the same time, and add them to the relevant passes, sort the passes, and draw them using something that makes as few state changes as possible between the draw calls.

> it's not as simple as packing all that state into a single struct and then resetting it in a bulk before a draw call.

No, it's definitely not, you need to track your current state and the draw call's state, but I think it's a lot easier to program against, and more readily adapts to Vulkan, Direct3D, Metal, etc., and is the pattern adopted by wgpu/sokol/bgfx.

https://github.com/magcius/noclip.website/blob/master/src/gf...

> on mobile platforms, I found that UBOs were a win from call overhead alone

That was what I got from it also. Be it a phone or WebGL translated using ANGLE to something else, every call mattered. But on desktop drivers that were relatively low-overhead to begin with, going to UBOs was not always a win, especially on Intel GPUs with dynamic indexing into a UBO array or if I didn't use the GL 4.4 immutable buffer storage. And it didn't matter if I was on Linux with Mesa or on an Intel Mac. Of course no such problem on NV or AMD, but it made me approach UBOs with caution. I still need to dive deeper, because it'd be silly if such implicit overhead carried over to Vulkan.

So what I found ideal is submitting a multi-draw call with as many draws as possible, trading the weird extra UBO overhead with less time spent in the driver. That's limited by max bound UBO size, so depending on how well I pack the data I might only get as low as 256 draws in a single submit, and then I'd have to rebind another UBO range and submit another multi-draw call.

> you need to track your current state and the draw call's state

Yup, and that's the thing -- once I ship it, it has to be completely bug-free to not break existing apps in boundary conditions. And since a large portion of users is doing crazy advanced stuff like sharing the GL context with Qt or Gtk, or using it together with 3rd party GL libraries for drawing vector graphics and whatnot, the state tracker needs to be able to reset itself (or, worse, reset the GL state to not make a buggy 3rd party lib misbehave). Altogether it's a lot of tiny things to get right, and somehow there was always something more important that users needed so this fell on the bottom of the priority list. If I would be creating a new GL wrapper from scratch, I'd definitely go this way from the start, it's harder to retrofit a codebase with over a decade of history and a ton of depending projects that learned to expect smooth upgrades :)