Show HN: 3D Game Programming using bitblting in Windows
github.com
github.com
It's really an amazing look at how the sausage is made through every single step. Even if you've done a significant amount of games work, I'd still recommend working your way through it.
My only caveat is there's so much useful stuff, it's hard to get through it all. It's all time well spent, though.
I love and appreciate what he's trying to do, but it's overkill.
It helped me tremendously when I was into game development back in college. The math was always the biggest hangup for me. I'm in healthcare software now, so YMMV :)
There are several sites that teach OpenGL, which is one of the main APIs today:
* http://www.opengl-tutorial.org/beginners-tutorials/tutorial-...
imgWidth = pow(2, log(sqrt(stat_p.st_size/3))/log(2));
And I have no clue why because if you take 128 as input value; it converts it either to ~126 if you don't trunc to int before doing imgWidth * imgWidth * 3, if you truncate to int, you get 108; for what reason?
pow(2, log(sqrt(128 * 128 * 3 / 3)) / log(2))
pow(2, log(128) / log(2))
pow(2, 7)
128
It's funny that on the one hand the code is beautifully terse enough that this ugly wart is easily discovered, yet on the other that the code would have such a wart to begin with.
Broken down, you get this:
int toTest = 128;
var div = toTest / 3;
var sqrt = Math.Sqrt(div);
var logSqrt = Math.Log(sqrt);
var log2 = Math.Log(2);
var logDiv = logSqrt / log2;
var pow = Math.Pow(2, logDiv);
var result = pow * pow * 3;
int result2 = (int)Math.Pow(2, Math.Log(Math.Sqrt(128 / 3)) / Math.Log(2));
int total = result2 * result2 * 3;
this {Clusters.DAL.Tests.UtilitiesTests} Clusters.DAL.Tests.UtilitiesTests
toTest 128 int
div 42 int
sqrt 6.48074069840786 double
logSqrt 1.8688348091416842 double
log2 0.69314718055994529 double
logDiv 2.6961587113893803 double
pow 6.4807406984078613 double
result 126.00000000000004 double
result2 6 int
total 108 int [TestMethod]
public void NextPowerOfTwo()
{
const int bitPowers = 32;
const int StartValue = 2;
const int PowerOf = 2;
UInt32[] powers = new UInt32[bitPowers - 1];
UInt32 value = StartValue;
int i = 0;
powers[i++] = value;
for(; i < powers.Length; i++)
{
value *= PowerOf;
powers[i] = value;
}
int[] valuesToTest = new int[] { 126, 128, 130 };
int power;
foreach(int valueToTest in valuesToTest)
{
power = 0;
value = powers[power];
while(valueToTest > value)
{
power++;
value = powers[power];
}
System.Diagnostics.Debug.WriteLine($"{valueToTest} -> {value}");
}
}
Test Name: NextPowerOfTwo
Test Outcome: Passed
Result StandardOutput:
Debug Trace:
126 -> 128
128 -> 128
130 -> 256edit: just to add, I think "width = sqrt(filesize/3)" would be a better version for newbies. Even better would be to replace the code altogether.
Also, taking the 2log and then 2power is a no-op because it doesn't truncate / ceiling in-between the two.
You kind of see where the code was going to, but it never quite made it there.
It needlessly restricts your platform though. For example, I was very interested in the handmade hero tutorials linked elsewhere here, but was dismayed that the first few lessons are all windows api. I don't have a windows machine anymore, so I have no use for those lessons. I'm sure I can still learn from the later lessons, but I won't be able to simply follow along with his code. A significant number of people, especially people who would play Indie games, are now using macbooks nowadays. Maybe still not the windows gamers numbers, but enough that I wouldn't ignore it, and I've passed on quite a number of promising looking Indie games because they were windows only. Windows API is also notoriously clunky, at least, compared to the wrapper libraries.
Obviously if you only have a windows machine, by all means, focus on making it work for that, but why put roadblocks in your way for potential future ports when nowadays you really don't have to because great cross platform abstractions exist (SDL2, GLFW, SMFL, ...)
> There's nothing wrong with learning a platform.
Sure. If that's your goal. If its just a means to an end, why not use a library to do the hard work so you can focus on what you actually want to do (and not locking you out of other platforms later without a ton of work is an added bonus).
Windows: 98.04% OSX: 1.56% Linux: 0.32%
That's not a significant number in the least. Now the numbers may be skewed by developers vs players, mobile vs desktop etc, and yet even then it's still not worthwhile in general.
As to the 2nd part, I far more agree with that. There's no reason not to use SDL. But by that metric you should be using Unity/Unreal etc. anyway, unless if your focus is really on becoming an engine programmer.
http://blog.wolfire.com/2010/01/Why-you-should-use-OpenGL-an...
http://chrishecker.com/images/3/33/Gdmogl.pdf
Despite the technical differences, if you think this is morable acceptable, by all means do you all your development on Windows. Allow someone else to build a monopoly with no real gain for you.
But speaking for other devs, a long time ago DirectX was indeed really crap. It took for example 16+ pages of code just to initialise. But in later iterations MS did a lot of work to clean it up and improve it and its performance, which is a large contributing factor as to why people started using it (along with of course having to deal with countless OpenGL extensions, which became a really tiring exercise).
Again I'm with you, I dislike the modern Microsoft practice of tying specific DX versions to specific OS versions, in trying to force people to upgrade.
Sadly the solution to the problem isn't either of OSX or Linux. Apple is too closed off and expensive in general, and Linux perhaps too abstract to be bothered with by the average gamer.
Not necessarily. Casey discusses this issue well into his series, but it would be twenty or thirty hours in.
There are two general approaches to multiplatform.
Approach 1: the SDL way. Have an abstract system that represents a generic desktop environment (e.g. QT suite). Build your apps into that. The upside of this is that you can build once, run everywhere. Some downsides are that it will not necessarily feel like it fit into the ecosystem, and you need to learn an abstract platform. That platform may have license overhead. When they do version upgrades, you might not like what they do, but you are stuck with them.
Approach 2: native wrappers. Build your codebase in generic C/C++. Then write endpoints interface wrappers for each platform you deliver to. In this case, you could create a Windows codebase that #includes your game code, and calls into it from WinAPI. For linux, you can create a ncurses or GTK frontend, and then #include into the samre game codebase. Your product will feel like it was built for your target platform. Yes, you need to learn platform-specific ways of doing things. But once you have, your end-users get native feel. If you want to call a Windows voice API for the Windows version of your product, you can do it without depending on a vendor.
If you were building a forms application, Approach 2 would be painful. But if you are building a game, Approach 2 is viable. You probably need a single-window wrapper. It would need to have a menu system, to be able to display an image, and to be able to consume keystrokes and mouse events. I don't have much audio experience, but the little experience I have is that the moment you want to do something remotely sophisticated with audio (e.g. creating a music sequencer) you want to be working against native APIs. I would rather build something from first principles on a robust system API than debug bizarre threading behaviour in the SDL mixer library.
Proof: http://hg.libsdl.org/SDL/file/35da714ed287/src/core/windows
If you don't like SDL that is your opinion or choice, but don't come here presenting false facts to influence other people into having your same preferences (e.g: over half of what you said was technically incorrect and not true). You should really do 1 minute of research before making ill-intentioned accusations like these.
Strictly speaking, SDL does all the Windows API boilerplate for you. And also does it on macOS, and on Linux with no substantial cost since SDL itself is very lightweight.
It also does more stuff for you, like loading fonts, images, sounds, etc... and again, in a crossplatform manner.
All of this was provided to the community as open source, for free. Sam Lantinga is a hero.
If you don't like a specific part of SDL or whatever project, find an alternative project, submit a bug report, submit a fix, or fork it. That benefits everyone. That's how open source works.
When I was following Handmade Hero, some of the fanbase were maintaining a linux version of the project in SDL, and moving them forward at the same pace as Casey's project.
> It also does more stuff for you, like loading
> fonts, images, sounds, etc... and again, in
> a crossplatform manner.
I found it to be awkward for audio. When you pick a library, it can steer the direction of your software. You can end up having to do things the library-way instead of the you-way. As a general rule, the more the library does for you, the more likely this is to happen.This is going back a bit, but I remember SDL media having background threads. If you let SDL draw you into a multithreaded approach, you would lose tuning options you might need later, because you would not be able to reason cache behaviour.
> If you don't like a specific part of SDL or
> whatever project, find an alternative project
Through your forceful words, here and elsewhere, I get the impression that you think that this is the "one true approach". But I think there is a respectable alternative: suck up the native-integration cost in order to forgo the dependency cost. Bedrock against the system where it is non-horrific to do so, in order to own your codebase. I don't think a hacker should have to apologise for choosing that approach.Note also, in case you're not already aware, that SDL 2 is quite a different beast than SDL 1.x and is much improved in many ways, although I can't speak for addon libraries (I don't know what SDL media is like, for example).
[1] The free version of Wwise has a limit of 500 sounds and your budget must be under $150K; while FMOD is free as long as your budget under $500k and you can only publish one game a year under this license -- either way, both are financially accessible to Indies and both are free during evaluation/development.
And I understand if you ran into issues with an open source library. I personally ran into multi-monitor issues with SFML in the past. Maybe it has been fixed now.
But a different thing is to ask others to not use a project. Think that SDL is non-profit, ran by volunteer work mostly.
In the past, cross-platform game development has been the target of many FUD campaigns so the tone is mostly out of frustration at this point.
Then one point I didn't mention was licensing. Some open source projects may have licenses that are incompatible with a proprietary license. I think this was the case of SDL 1.x but SDL 2.0 uses the zlib license which is pretty compatible.
SDL_SysWMinfo info;
#ifdef _WIN32
if(SDL_GetWindowWMInfo(window, &info)) {
assert(info.subsystem == SDL_SYSWM_WINDOWS);
HWND hwnd = win.window;
HDC hdc = win.hdc;
HINSTANCE hinstance = win.hinstance;
...
}
#endifYou'll also find that people who work on high-performance games (i.e complex 3D) are pretty allergic to using existing libraries, and while it would make sense to use SDL in an example project like this, it's not unreasonable for someone to default to the "proper" way. Beyond things like the basic GL loader, linear algebra and font loading / text rendering libraries, anything that's available is almost guaranteed to be woefully insufficient, more effort than it's worth or loses you too much control. Not to mention you'll inevitably wind up with your own library that provides an equivalent of half the C++ std (even if most of it is implemented using the normal std), which often makes using anything that depends on the usual C++ std pretty painful.
For the above reasons, things like SDL or even libev/Boost.ASIO would only be considered suitable for toys. I can't say I'm too happy about having to re-implement things that have already been written a thousand times over already though.
Android is technically Linux, with a lot of APIs built on top of it.
Windows on phones is no longer a thing and phone games are a significant market.
Plus, people use Android more than they use Windows.
Steam measures 98% Windows, 1.56% OSX.
Google Play would be all Android software, which is Linux.
Better identifiers are possible.
e.g: https://wiki.libsdl.org/SDL_CreateWindow . CreateWindow, not "WndProc" or some ambiguous abbreviation. If you want to turn your job into playing hangman all day and guess what the hell the actual abbreviated words are, use the Windows API.
Compare doing fast async file I/O on Win32 vs POSIX. On Win32 it just boils down to creating an IOCP pipe, creating some worker threads, setting up the file handles and using the right flags in read/write calls. It's a bit obtuse, but the code that does it ends up reasonable, and it's the same drill for sockets. Doing that on POSIX? You get blocking, uncancelable I/O calls and threads to work with, that's it. Good luck, though the documentation involved is far simpler (or just use something like libuv if you value your sanity).
Using a library here would be preferable, but I really don't see why using Win32 would be the problem and not the fact that it's non-portable.
Then, guess what: Android, iOS, macOS, Linux, BSD... all of them are in some way or another, similar at the low level.
Windows API is unique, like TempleOS. But even the TempleOS API makes more sense, is more consistent and is better thought than the Windows API.
Not to mention basic operations like reading information about a process doesn't involve having to parse text that can contain semi-arbitrary strings (/proc/<pid>/stat contains the filename near the start, have fun)
I can't say I like Windows as an OS, but API-wise? Not fantastic either, but compared to every other major desktop OS/kernel it's a saint, and provides more functionality. In the context of complex 3D games; if you need to do something like transfer 1GB of data from disk to GPU memory as fast as physically possible, you have to care about this.
The UNIX philosophy ("Everything is a file") has pros and cons. It makes interoperability fairly trivial, and being a filesystem it has access control capabilities.
If you want to have something similar to the Windows' IO Completion Ports (IOCP) paradigm you can make use of https://www.kernel.org/doc/Documentation/scheduler/completio...
https://en.wikipedia.org/wiki/Vendor_lock-in
Windows API is carefully designed to maximize costs of switching to another platform. Every single thing is different to every other OS.
To make it clear, the main platforms for more AAA-like titles are Windows/PC, PS4, XboxOne, and now the Switch. The main platforms on mobile are iOS and Android. Each of these requires an adaptation layer to be written because they are all different to each other, some more than others though.
So you can "lock" yourself into one of these platforms, "lock" yourself into using an engine or existing library which covers some subset of those platforms, or write your own adaptation layer and engine.
For learning and experimenting it's quite easy and convenient to focus on a particular platform, Windows/PC in this case is a good simple default since it's available and doesn't require NDAs, licensing, etc.
Especially from the perspective of the beginner there is much to be wasted by learning proprietary API’s.