Modern C++ in demos

category: code [glöplog]
Navis: I used std::variant to replace hundreds of lines of crappy homebrew code to read parameters for effects and unified it all under a single interface. STL containers are excellent and auto means you don't have to write std::map<std::string, demorender::Shader*>::iterator when looping through all your shaders anymore unless you don't want to. Ranged for loops simplify and make safe the most common use case of going through all items in a container. For demo use (aka allocate everything in the beginning and forget about it) smart pointers aren't that useful but at work they've helped me avoid memory leaks and obscure bugs with unreleased resources. And lambdas are excellent for callbacks and specifying custom predicates for stuff like sorting.
added on the 2020-05-08 11:57:13 by Preacher Preacher
this variant is interesting.. thanks. I'll check for a small example somewhere
added on the 2020-05-08 13:00:10 by Navis Navis
std::variant is essentially a safe union (it will throw errors if you try to access the wrong type).
added on the 2020-05-08 15:14:18 by Sesse Sesse
std::lock_guard helps:

Why is this in the standard? Easy enough to do yourself, with C++98:
Code: struct ScopedLock { Mutex * const _m; ScopedLock(Mutex *m) _m(m) { m->lock(); } ~ScopedLock() { _m.unlock(); } };

(Add/delete non-copy semantics and all those freaking constructors, operator= and whatnot yourself if you care. Or make it a template to support general lock types)

I just get the feeling they are cramming *everything* plus five into the STL, including things that are actually language features and should be treated differently, like initializer_list (that are impossible to use without STL) and the type_traits stuff. I get that all of the type traits things used to be compiler-specific constructs that are now standardized under a single header, but having this in the STL seems dumb to me.

Some set of headers (and only headers!) that come with the compiler to abstract away compiler-specific stuff would be fine, but stuffing everything into the kitchen-sink library is imho the wrong way to go.

It's the same as with the (historical) separation of libm and libc under *NIX. Way more sane would be a libsystem (for syscalls only), libc (all of libc+libm) so that you could actually easily exclude libc if you wanted to.

And to end the post with a C++11 tip:
Code: const auto m = camMat * mry * mrz;

Multiply 3 matrices, each of different types. My code has specializations for many different matrix types, a rotate-by-X-axis matrix looks like
Code: typedef mat4def< E1, E0, E0, E0, E0, ED, ED, E0, E0, ED, ED, E0, E0, E0, E0, E1 > def_rotx;

where each entry specifies at compile time which value will be in that slot. Known to be 1 (E1), -1 (EM), 0 (E0), or dynamic (ED). Storage is only allocated for dynamic entries.
And the operator* is generalized to generate new types based on the two matrices being multiplied. The main reason to do this was to be able to (at compile time) eliminate redundant computation, such as multiplying by known constants could be optimized out, e.g. 1 would kill the multiply and mutiplying by 0 would kill both sides of the computation (saves a LOT of cycles on AVR but on x86+SSE this kind of optimization is unnecessary).

And the resulting type of the first code block is
Code: struct tmat4<struct fp1616,struct detail::mat4def<2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 2> > const

which is waaaay too much to type out everytime if you wanted to store the matrix in a variable. That's about the only reasonable use-case for auto i've found, generating new types at compile-time.
added on the 2020-05-08 15:56:20 by fgenesis fgenesis
Why is this in the standard? Easy enough to do yourself, with C++98:

Most things in the standard library are "easy enough to do yourself", but it's nice if you don't have to constantly re-invent the wheel and can just keep using the same stuff in all codebases.
In my software renderer I use variadic templates to put shaders into the GPU

Code:GPU<Wireframe, Envmap, Flat, FX1, FX2> gpu;

Each shader is a stateless struct with vertex/fragment and vertex array load functions.

Draw calls begin by recursing over the parameter pack to search for a matching programId. It stamps out an entire vertex/fragment pipeline for each shader, so it's an ICache disaster, but it's also consistent with GPU state changes generally being expensive.

std::function saves writing a lot of boilerplate code in e.g. thread work queues, as long as the size is small enough to avoid allocation, or the frequency is low enough that the allocation doesn't matter.

std::optional and std::variant are great, especially for parsing user input, json etc. You can also repeatedly construct/destruct an stack objects in the same scope with .reset() which is interesting for state-machines.

I'd like to start working with std::pmr but haven't yet.


std::future seems completely useless to me -- it blocks! I expected something more like Twisted's Deferred or JavaScript's Promise.
added on the 2020-05-08 16:15:10 by anix anix
anix: You're probably looking for C++20's coroutines.
added on the 2020-05-08 23:47:08 by Sesse Sesse
I've been using C++11 (avoiding too new features/standards to have a good OS and compiler portability) for a few years. It has been overall much nicer than older C++ standards by having unicode support, lambdas and so on. :)

However I'm slowly starting to move to other languages like Rust to have simpler language, more enforced memory safety and better dependency management.
added on the 2020-05-09 20:24:20 by waffle waffle
As discussed partially by some people already, in my opinion the modern C++ is a real double-edged sword. On the other hand it provides some very cool things like auto and smart pointers to make code writing easier, but on the other hand it provides many things which will get your code to be obfuscated and less understandable (as for example Smash said about understanding own code at 3am in darkness with beer circulating through the veins).

For me, the goal is to create an easy to maintain demo system which is controlled through easy scripting. Currently using QML and JavaScript for this, and barely touching C++ side. Also started to develop a new gen visual editor for timelining the demo effects, completely with QML so no burden of which kind of C++ should be used there either.
added on the 2020-05-15 13:27:44 by deepr deepr
Once you meet and understand newest C++ standards, there is not turning back and all that pre C++11 code looks just ugly and unmanageable and lame.

No compromise here on this and when I look at code regardless how cool visually with pointers instead of smart pointers, and lack of proper Standard C++ Library skills I just think you better code C#, or rust or whatever, but maintaining such an old c++ demo code is just nonsense.

Some of the interesting concepts I use as example:
- PIMPL pattern implementation for private members via private subclass and smart/unique pointer to it: https://en.cppreference.com/w/cpp/language/pimpl
- std::copy and std::move concept to carry object's resources properly and we don't use/play with raw/unmanaged pointers anywhere in our code
- various attributes (https://en.cppreference.com/w/cpp/language/attributes)
- nullptr, auto but these are rather natural shift, no brainer to adopt
- nested namespaces (no more namespace foo { namespace boo }} just namespace foo::boo { ... };
- std::thread especially in our cpu heavy code to use every single core available for computing if needed.
- defining classes always, not typedefs or structures.. aliasing sometimes is nice to have but usually non critical at the definition level, but hell no C code at all both in syntax and library wise.

But I also second to the comment above. Our newest version of the engine, we're completing right now has a very small c++ core with embedded scriptable demo platform to create actual product. Easier to create a tooling layer on top of it, which doesn't need to be C++ at all.
added on the 2020-05-15 14:32:36 by hollowone hollowone
And quite frankly to stick myself to what Smash respects as a rule I left C++ back in 2001 to favour C# and never turned back until C++11 came into a horizon. There is a reason why C# and Java was invented on top of hate for C++. There is a reason that it's much cooler to be Javascript programmer than C++ today (outside the company of some old nerdy dwarves how are right now sharpening their axes to avenge the statement).
added on the 2020-05-15 14:39:51 by hollowone hollowone
Though I like Qt and QML a lot as an almost silver bullet for cross-platform with its editors and availabilty, it is rather heavy and not suited for low power devices and it drags everything and the kitchen sink into your application. It got better with Qt 5 and I'm hoping for Qt 6 to improve in that regard. Qt on MCUs looks cool too.
added on the 2020-05-15 14:43:36 by raer raer
I still use raw pointers for example, and find them very natural in C/C++. I also avoid STL algorithms wherever possible. I find most of the newer stuff unnecessary (yes, they might improve comfort but they're not a requirement to do anything).


... you better code C#, or rust or whatever, but maintaining such an old c++ demo code is just nonsense.

I think the contrary. Most of the new high-level semantics looks like ugly hacks to me, and when I want to avoid all the low-level stuff then I use C# or Java, not the other way.

I do use some newer stuff for projects where size (and most of the time, performance) does not matter. In terms of readability, I find old C++ style much cleaner and more natural looking.

Opinions are too subjective, though, so I agree to disagree, that's ok.
added on the 2020-05-15 15:12:54 by imerso imerso
Sorry, asking for a friend here, but what is the worst thing that can happen if you don't bother cleaning up your pointers... In a DEMO !?
added on the 2020-05-16 13:32:29 by Navis Navis
Demos aren't immune to crashes...?
added on the 2020-05-16 13:41:55 by Gargaj Gargaj
Maybe only the people who write STL ports are able to clean up pointers, who knows. =)
added on the 2020-05-16 16:07:56 by imerso imerso
Imagine it crashing in the hidden part!!! OMG!
added on the 2020-05-16 16:24:19 by raer raer
Why would it crash if you don't release the pointers?

Most demos have an init stage (where they calculate things/consume memory) and they then play on rails, without more massive memory consumption. Lets say that you are lazy and don't release any memory from the couple of gigabytes (max) you consumed at init stage. So what?

I don't disagree that it is a lame practice, but in a demo ecosystem.. is it that important?
added on the 2020-05-16 16:31:07 by Navis Navis
Not too important, imo, and it won't crash because of that. =)

Also all major OSs these days will even clean up memory automatically on exit.

* Not saying that one should not release pointers properly, or that using smart pointers is not good, just saying that there is nothing wrong with raw pointers and old style C++.

Old style is still an advantage if you code for embedded devices as well, just saying.
added on the 2020-05-16 17:07:49 by imerso imerso
memory management became quite a big problem for our demos a long time ago actually.

if you have a bunch of parts, each doing quite different things, each using lots of memory and requiring different sets of buffers (im especially talking vram here) you have to manage it quite carefully.

demos with one effect are so much easier to handle. :)
added on the 2020-05-16 17:09:49 by smash smash
Smash: the problem already existed 30 years ago mind you, when you had only 512
KB of RAM (or 128 or 64) for a demo lasting 10 minutes and showing 15 different effects, with logos, lookup tables etc.

We would stream, reallocate memory dynamically, decompress the next thing while showing the current one etc.

Only the scale has changed, the concept remains more or less the same.
added on the 2020-05-16 17:34:37 by keops keops
But 99% of modern (windows) demos would all fit in memory at init, right?
added on the 2020-05-16 17:38:54 by Navis Navis
Well, raymarchers.. :D
added on the 2020-05-16 18:14:00 by smash smash
Raw string literals are pretty great for inline shader code.
Code:const char* shader = R"""( #version 460 uniform float time; void main() { /* code here /* })""";
added on the 2020-05-16 18:19:50 by cce cce
For quite a while I've been sticking to plain old C for demos, and using C# for things like tools. C# really is growing on me. It's really super fast to compile, and to write, and to understand.
My feeling is that C++ is that big radioactive mutant, or maybe just a hydra, that grows bigger and uglier each time you try to kill it.
Reading a conversation like this one confirms it.
added on the 2020-05-17 06:54:51 by BarZoule BarZoule