9 ms·
A Tiny Wolfenstein-Like Software Engine
- petercooper 9y agoFWIW, this a nice miniature C project to noodle with Makefiles and some basic C if you're new to it. If you're on macOS, just slap a quick `brew install sdl2` before the instructions and it's golden - assuming you run https://brew.sh/ https://brew.sh/
- foota 9y agoNeat that they did it without a graphics API
- voltagex_ 9y agoSDL2 is the API
- foota 9y agoTrue, I guess I meant a rasterization library. It looked like they were mostly using SDL2 for interfacing with the OS and blitting to the screen. (and to be more specific I really meant without opengl|directx|vulcan etc)
- white-flame 9y agoGiven that the original game predated those sorts of systems hitting consumer desktops by years, it shouldn't be surprising at all. This is what early 3d games & software rendering did before accelerators came along, and as CPUs got faster in that interim there were plenty of fully polygonal software rendered engines as well.
- mhd 9y agoInteresting how meaning changes. Back when Wolfenstein was made, "without a graphics API" just meant mov ax, 13h int 10h
- Gnurou 9y ago... and even there you were using a graphics api, provided by your video card's bios and compatible across various manufacturers.
- Koshkin 9y agoNot really - you'd be mostly just writing into the video buffer.
- TickleSteve 9y agoint10h is the API (BIOS)... that just causes the mode switch. What he really meant (direct RAM access): void putpixel_13h(int x, int y, int color) { _farpokeb(_dos_ds, 0xA0000+y*320+x, color); }
- Koshkin 9y agoYeah, but nobody does that.
- pedrow 9y agoI wonder how many polygons make up a level of Wolfenstein in total - i.e. if you just asked a modern GPU to render every single wall in a level and relied on it to do the z-order sorting, culling, etc., what frame rate would you get?
- animal531 9y agoI'd guess that if you were to triangulate an average map it might have probably been only in the region of 1-10k triangles. Modern graphics cards on the other hand can easily push a few million triangles every frame with decent framerates. At 10k triangles * 10 or 20 maps you could load all maps from the game simultaneously and still easily get decent fps.
- maggit 9y agoThat's what "software engine" in the title is supposed to describe :)
- anigbrowl 9y agoGreat learning project. Having it <300 lines whiles till being clear and easy to navigate is fine work.
- userbinator 9y agoRelated --- doing something very similar in 128 bytes of binary: https://news.ycombinator.com/item?id=7960358 https://news.ycombinator.com/item?id=7960358
- conmarap 9y agoThis is pretty neat. I love how it fits (sanely) in a single file. I've been meaning to get into SDL for a while now, so this will be an interesting read.
- kedoremos 9y agoVery well written and easy to read C.
- deleted 9y ago[deleted]
- WalterGR 9y agoCan anyone recommend something similar but with more code comments?
- rileyphone 9y agohttps://github.com/antirez/kilo https://github.com/antirez/kilo A text editor by the guy that made redis, in less than 1000 lines and a lot of comments.
- pls2halp 9y agoThere’s also a tutorial to build kilo at https://viewsourcecode.org/snaptoken/ https://viewsourcecode.org/snaptoken/
- s-macke 9y agoVery similar technique (raycasting), but for different type of data. https://github.com/s-macke/VoxelSpace https://github.com/s-macke/VoxelSpace The text in the README describes the technique very well. The JavaScript code is a little bit more complicated to read because most of the source contains the usual organizational tasks around the algorithm (initialization, keyboard input, ...). But this is also true for the provided C-code of this article.
- PyComfy 9y agohttp://lodev.org/cgtutor/raycasting.html http://lodev.org/cgtutor/raycasting.html ~200 LOC
- simias 9y agoI wrote a similar code a few years ago, although it also supported textures (including ground and skybox, unlike the original Wolfenstein). I don't know if it'll be more to your taste but there are afew comments: https://bitbucket.org/simias/wolf/src/873cad46f3c8281e7d94293b21af3c72e831d860/wolf.c?at=default&fileviewer=file-view-default https://bitbucket.org/simias/wolf/src/873cad46f3c8281e7d9429... the magic happens in wolf_horizontal_intersect. Note that I use fixed point which obfuscates the arithmetic a bit (I was targeting an embedded platform without hard float at the time). It looks like this while running: https://svkt.org/~simias/misc/wolf-floor.png https://svkt.org/~simias/misc/wolf-floor.png That being said if you're interested in writing something like that I recommend that you use any basic tutorial on raycasting and try implementing it "from scratch" yourself, it's pretty simple and quite fun IMO. IIRC I used this one: http://lodev.org/cgtutor/raycasting.html http://lodev.org/cgtutor/raycasting.html
- LambdaComplex 9y ago> early wolfenstein and doom days. Wolfenstein yes, Doom no. Wolfenstein 3D used a very simple raycasting method that just shot rays out from the player's position to render a 2D level in psuedo-3D. Doom, while still technically using raycasting, uses a much more complicated method that involves BSP trees. A quick google search found this page, which explains it much better than I can: http://doom.wikia.com/wiki/Doom_rendering_engine http://doom.wikia.com/wiki/Doom_rendering_engine
- GrantSolar 9y agoFor anyone interested in the inner workings of Doom[1], I can't recommend Fabien Sanglard's blog enough. He also released a book on Wolfenstein 3D which goes into tremendous detail on the code, company, and hardware of the time. [1] http://fabiensanglard.net/doomIphone/doomClassicRenderer.php http://fabiensanglard.net/doomIphone/doomClassicRenderer.php
- coldacid 9y agoHe's actually working on a companion book covering the Doom / id Tech 1 engine now, too.
- llao 9y agoThe Doom community left Wikia to host their own, independent, non-profit wiki, please always try to use and link that one instead: https://doomwiki.org/wiki/Doom_rendering_engine https://doomwiki.org/wiki/Doom_rendering_engine
- glouwbug 9y agoCorrect. The doom adage was in reference to ceiling and floor casting support; wolfenstein painted the ceiling and floors a solid color.
- diimdeep 9y agoThere should be Awesome List with small simple implementations of any kind, like this.
- s-macke 9y agoIn principle, such a list exists. It is called Obfuscated C Code Contest https://www.ioccc.org/ https://www.ioccc.org/ Granted, the code is not really readable. But you can see the awesomeness. Boot Windows 3.0 and DOS with a 4kB emulator. C compiler in 4k, which can compile itself. Global illumination renderer in 4kB. Flight simulator in 4kB. ...
- s-macke 9y agoI forgot the fluid simulator in 4kB.
- diimdeep 9y agohttps://github.com/tuvtran/project-based-learning https://github.com/tuvtran/project-based-learning https://github.com/AlgoryL/Projects-from-Scratch https://github.com/AlgoryL/Projects-from-Scratch
- marcel-me 9y agoBeautiful
- deleted 9y ago[deleted]
- pedrow 9y agoInteresting that it passes/returns structs by value quite a lot which is not usual in C (in my experience.) I assume there's a reason for this? e.g. static Hero spin(Hero hero, const uint8_t* key) https://github.com/glouw/littlewolf/blob/339cb5624462d4f23019aaaac150de635a2a8a20/main.c#L263 https://github.com/glouw/littlewolf/blob/339cb5624462d4f2301...
- jeffreyrogers 9y agoI think it's just for simplicity, since all that copying can't be faster than passing in a reference to it.
- munificent 9y agoYou might be surprised. For small structs, copying is very fast and may give you a result with better locality. Passing around structs by pointer means more pointer dereferencing, which has its own overhead and can cause cache misses. Performance of modern CPUs is very nonobvious.
- glouwbug 9y agoYou are correct. On a much larger project with a similar software engine, if I change all structs I pass by value to pass by reference my instruction count shoots up by 40% according to cachegrind (flto + ofast). Amazing how powerful pass by value can be when it comes to pod structs.
- glouwbug 9y agoI do so to enforce a single return value from functions. Given: static void damage(const Hero* hero, const Sprite* sprite); The const can easily dropped later in production from both entities, potentionally creating two return values. This is analougous to good use of privacy in C++ classes. hero = spin(hero, key); Becomes: hero.spin(key); Where external objects dictate the private state of the hero object. Pointer aliasing is another thing. Inlining is easier with fewer pointers. A fun C++ experiment is to count your instruction and data cache points with valgrind's cachegrind with pod types both passed by value and by reference (&).