Skip to content

A Windows screensaver depicting a dynamic section of a Menger sponge

License

Notifications You must be signed in to change notification settings

Project2100/some-screens-must-save

Repository files navigation

Some screens must save

What is this?

It's my first project ever in graphics programming, and my first screensaver too. Whichever is hardest to learn, I'll let you be the judge.

Where can I find it? I want to try it

You can grab a binary form the "Releases" section, both 32-bit and 64-bit versions are available. To be able to set it as your screensaver, right-click on the .scr file, and select "Install".

Technical details and requirements

The program is implemented in C (standard 17) and HLSL, and uses the Win32 and Direct3D 11 APIs.

Although different tools have been used to build this project in previous revisions (Clang, basically), it is currently buildable from the Developer Consoles provided with Visual Studio by using nmake. The Windows SDK version used as of today (2021-05-08) is 10.0.19041.0, some previous versions have proved to be fine too.

Finally got over cl, yes.

History

I learned to greatly enjoy microtonal music, and one day one of my favorite artists uploaded this video, which coincidentally became one my most loved tracks.

One day, a thought crossed my mind:

Oh, how cool it would be if my computer showed that crazy geometry stuff when idle...

The day after that, I was already deep into MSDN's docs trying to whip up a window. The first commit is the result of roughly two weeks' occasional work.

Hopefully, this will get as close as possible to the video, without blowing up any GPU.

Lessons learned

I want to dedicate a special section here to those takeaways that may not be clear at all when developing such programs, and nothing seems to concretely help you even with your best efforts.

"Screensavers don't like files"

To some, this may sound obvious at first. Observe that this is a program that is required by design to execute without a console. Barring debuggers, a very easy choice for getting a trace of what is happening at runtime is to set up a logfile.

A screensaver that uses logfiles will work perfectly on preview, and even up to first install; however, it will fail when it is actually invoked after the idle interval, and no logs will be created whatsoever. What may surprise, is that this behaviour can be reproduced by simply importing stdio.h in the source, without using a single file-related function at all.

The only hint of what happens on crash is in the Windows Event Viewer, which reports a STATUS_STACK_BUFFER_OVERRUN error; I've learned from the arguably most famous Windows guru that this error can be infuriatingly misleading.

However, the sneaky catch lies in the consequences for DirectX 11: a popular strategy in building the graphics pipeline involves compiling the shaders' files at runtime by using D3DCompileFromFile. As you might have guessed by now, this is a file operation. Same problem here.

Another quirk of screensaver's aversion to files is, when showing the configuration dialog, it is possible to open/create new files with Win32 functions, but writes will effectively become no-ops.

If this isn't enough, the Microsoft doc page talks about saving the screensaver configuration in a .ini file or the registry by using old Win32 functions that had their behaviour changed over the years; turns out they don't seem to work reliably, and it's easier and more readable to save the configuration using directly the registry functions anyway.

The takeaway here is: when you want to build a .scr file that actually works, check carefully for anything that may involve a file, and possibly change it to avoid using the file itself.

Do however keep a logging infrastructure for a separate build scenario, if you manage for it to work. I did, and it still serves me well.

"Screensavers don't like WM_PAINT much either"

That doc page for screensavers also does not cite the paint message WM_PAINT in the main routine either, delegating the draw triggers to a timer. Originally, this program did use the paint message to draw the graphics, as a DirectX app is normally expected to do that.

The point is: if you let the paint message fall through your routine, and pass it to the default procedure, then the routine will stop receiving more paint messages. The obvious effect of this, if no other trigger loop is in place, is that your graphic drawings will freeze.

Having said that, there are two approaches to build a draw loop:

  • either let the paint message in the default procedure (thus stopping them), and build a timer to trigger the drawings, as seen in the sample doc,
  • or keep using the paint message to draw, and take care to not pass it to the default procedure; no timer involved here.

The advantage of the first approach is that refresh rate throttling is more immediate to regulate, but I am yet to see if the second approach has a more reliable, accurate timing mechanism.

Acknowledgements and pointers*

Yeah... couldn't resist.

So far, I'm the only one actually writing the stuff, but I stole so much from 🔓DirectXTutorials that I believe it would've taken me months to get to this point without its lessons. I highly recommend it if you wish to get your feet wet with DirectX in general; be advised that the examples found there are actually written in C++. Also, to be completely honest and transparent, I've only followed the non-paid lessons; I don't know what's in the paid lessons, but the beginner lessons do show promise.

Other than that, BraynzarSoft is a nice complement if you're looking for a second opinion. They do have lessons of their own, and seeing two different tutorials back to back can be enlightening. Also, their whole course is actually free, so you can look for guidance on more advanced topics.

And of course, MSDN and StackExchange are regular places to go look for specific stuff. Although, in my opinion, sometimes MSDN can be your best friend (obviously), and your worst enemy (not so obviously): bring lots of patience.

Many thanks go to my friends McLytar, Gaamda Lurt and MarMik for testing the program on their machines, providing better tessellations for the L2 surfaces, and assisting me in indexing them.

Tech stuff aside, I'm deeply grateful to Sevish, the artist behind the inspirational video, and all the guys that are in his Discord corner: now I know that I'll never run out of interesting stuff to learn and new, good music to listen. Thank you, I really mean it.

Special shout-out to YAreyaREdAzE. You know why 😉.