<-- Chapter 3 | Back to Index | Chapter 5 -->
Casey is using some different variable names than previous chapters of Handmade Penguin. To keep up, you'll need to rename:
int BytesPerPixel = 4;We can then use this in our malloc() and SDL_UpdateTexture calls, so that if this number changes, we have fewer places to update it.
Casey talked about the difference between Windows' StretchDIBits and BitBlt functions. As we looked at yesterday, we use SDL_UpdateTexture and SDL_RenderCopy with SDL to update our texture. This is because SDL doesn't have an equivalent of StretchDIBits directly: we can't stretch directly from out own memory. SDL_UpdateTexture is sort-of like StretchDIBits, but without the ability to stretch (so, really like the SetDIBits windows function), and without the ability to output directly to the screen. We then use SDL_RenderCopy to do the actual stretch: this is like the windows StretchBlt function, which is the equivalent of BitBlt but with stretching support.
The other thing we need to note is that we used malloc() and free() in the last chapter to allocate memory. Those functions exist on windows as well, but are part of the C runtime library, not the core windows API, so Casey is using VirtualAlloc. The equivalent here is the Linux mmap() function, which is sort-of a combination of VirtualAlloc and the Windows function MapViewOfFile.
mmap() is quite a complicated function:
void *mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);Technically, what mmap() was intended to do was to let you map parts of a file into memory, so that when you accessed memory, the operating system would magically load the right part of the file. Because we only want to get some memory, not map a file, we will create what's called an anonymous map. We'll look at how to do this as we're looking at the parameters to mmap():
Be warned! Anonymous mapping is not a part of the POSIX standard, which governs how UNIX operating systems should function. This means that OSes other than Linux might support it differently, or not at all. Check the documentation for mmap() on your system with the man mmap command. Alternatively, you could keep using malloc() and free(), which will work across all systems. |
Pixels = mmap(0, Width * Height * 4, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);To free this, we can use munmap(), which accepts the pointer, and the length.
if (Pixels) { munmap(Pixels, Width * Height * 4); }Finally, we need to include the sys/mman.h header, which has mmap(), munmap() as well as the equivalent of the VirtualProtect() Casey mentioned, mprotect().
Fortunately, the process of writing pixels once we have our backbuffer is pretty similar between platforms. We can re-use Casey's for loops pretty much identically. The only thing we need to change is to make sure all of our variables have the right names, and add BitmapHeight and BytesPerPixel variables and the code will work as-is.
Compile and run, and you'll see a black screen. However, when you resize the window you should see whatever it was you were trying to render:
Because SDL_WaitEvent() blocks (doesn't return until there's actually an event waiting), we can't do smooth animation. Much like in windows we swap GetMessageA for PeekMessageA, in SDL we replace SDL_WaitEvent with SDL_PollEvent().
SDL_PollEvent() is very similar to SDL_WaitEvent(), it just returns 1 if there was a message waiting, and 0 otherwise. We therefore just want to run a loop processing events until we have none left. We can then render our screen.
We'll start by replacing our for(;;) loop with a bool Running = true variable and a while(Running) loop. This is because we'll want to signal our desire to quit from loops-within-loops, and break isn't quite that powerful by itself. We then insert a new loop inside to handle events until there are no more left. If HandleEvent() ever returns true, we can comfortably set Running to false.
while(Running) { SDL_Event Event; while(SDL_PollEvent(&Event)) { if (HandleEvent(&Event)) { Running = false; } } RenderWeirdGradient(XOffset, YOffset); SDLUpdateWindow(Window, Renderer); }
If we give that a try, we'll notice that nothing has changed: our window still doesn't display anything for the first frame. This is because we don't get a SDL_WINDOWEVENT_SIZE_CHANGED event when our window is first opened, so we start off with no bitmap. Let's get the window size and resize our texture as soon as we've created our renderer.
int Width, Height; SDL_GetWindowSize(Window, &Width, &Height); SDLResizeTexture(Renderer, Width, Height);
Be warned! The exact mechanics of when your window gets events can differ depending on what window manager you're using. The way X11 and window managers work is something of a huge mess, and while SDL does its best to force some consistency on them, sometime's you're on your own. Fortunately, resizing the window to the same size wouldn't cause any problem (at worst, it'll be a slightly slower, but since it would only happen once, it's not a big deal. |
Cool! Now it works. If we add the animation code with XOffset and YOffset from the stream, you'll see your code animate!
Notice how it gets faster the smaller the window is? That's because we have fewer pixels to fill when the window is smaller.
Note that, unlike the windows version, it's quite likely that SDL is using something like OpenGL under the hood. Sometimes this is faster (we're bypassing a lot of old cruft designed for boring applications this way), though it may seem slower if your computer has VSync enabled. VSync makes SDL_RenderPresent() wait until the monitor is just about to redraw the screen before doing any rendering. This stops tearing from happening, as you'll only see complete frames renderered, but it also limits the performance of your application to the refresh rate of the monitor: usually 60 frames per second. If you're testing performance out, try experimenting with the SDL_RENDERER_SOFTWARE and SDL_RENDERER_PRESENTVSYNC flags for the SDL_CreateRenderer() function.
Wow: animation already! It's been quite a week so far. Tomorrow's stream will largely revolve around refactoring, and is likely pretty cross-platform, so the chapter will probably be quite a bit smaller. We're pretty much at the point now where our graphics is cross-platform!
If you've bought Handmade Hero, the source for the Linux version can be downloaded here.
Update (2014-11-23): Thanks to Adam Rosenfield for pointing out that some mmap() implementations require fd to be -1 when performing an anonymous mapping.