summaryrefslogtreecommitdiff
path: root/haunt/posts/sdl-tutorial-part-0x00.org
diff options
context:
space:
mode:
Diffstat (limited to 'haunt/posts/sdl-tutorial-part-0x00.org')
-rw-r--r--haunt/posts/sdl-tutorial-part-0x00.org323
1 files changed, 323 insertions, 0 deletions
diff --git a/haunt/posts/sdl-tutorial-part-0x00.org b/haunt/posts/sdl-tutorial-part-0x00.org
new file mode 100644
index 0000000..dab5990
--- /dev/null
+++ b/haunt/posts/sdl-tutorial-part-0x00.org
@@ -0,0 +1,323 @@
+#+TITLE: SDL Tutorial Part 0x00 - Boilerplate, Windowing and Rendering
+#+DATE: <2016-09-14 Sun 21:02>
+#+TAGS: tutorial programming game-development c
+
+
+#+BEGIN_EXPORT html
+<header class="article-front-matter article-warning">
+ <p>This is one of my older tutorials and follows a style unlike
+ my current one. I also no longer hold the same claims I made about the SDL
+ documentation that I originally made in this article. I think it's perfectly
+ fine. You just need to spend some time looking around because it's not
+ organized like other documentation is. For that reason, I have no plans to
+ continue this tutorial series unless someone specifically asks for me to
+ continue it.</p>
+</header>
+#+END_EXPORT
+
+
+SDL2 is my favorite graphics library right now. It might not be as powerful as
+something like raw OpenGL, but it's simple. Simple enough that you can just
+pick it up and start using it. There's a glaring issue with it, though. The
+documentation is horrible. Absolutely horrible. A lot of it is unfinished, and
+it doesn't look like it's getting attention any time soon. The SDL1.2
+documentation wasn't as bad, but that version of the library is vastly outdated
+by today's standards. So here's my take on a tutorial for SDL - part 0x00 of a
+I-don't-know-how-long-I'm-going-to-drag-this-on series. My examples are going
+to be written in C, because the constructs I show here can still be used
+verbatim in C++ (and probably SDL's other language bindings as well). This
+tutorial will be covering the little boilerplate that SDL requires, as well as
+the basics of windowing and rendering. Let's get into it.
+
+The first thing you have to worry about is installing and setting up SDL2.0. I
+won't cover it in detail because it's something you should be able to figure out
+yourself. If your operating system doesn't provide a means of package
+management, you should be able to find a download on the [[https://libsdl.org/][official website]].
+
+You should also figure out how to link SDL2 when you're compiling, nothing I
+teach here will work if it isn't properly linked. If you're having trouble with
+anything, fire up your favorite search engine or feel free to [[http://tsar-fox.com/][contact me]].
+
+Alright, assuming you've successfully installed SDL, let's get to actually
+programming. As with any C library, the first thing you should worry about is
+including the header files. While SDL provides header files for specific
+subsystems, we're not going to worry about that right now. There's a header
+file that contains everything and we're going to use that for right now.
+
+#+BEGIN_SRC c :hl_lines 0
+#include <SDL2/SDL.h>
+#+END_SRC
+
+Depending on how header files are organized on your system, you may have to use
+this instead:
+
+#+BEGIN_SRC c :hl_lines 0
+#include <SDL.h>
+#+END_SRC
+
+That will give us function prototypes and type definitions for everything in
+the SDL library, but we have to initialize SDL before we can really do anything
+with it. This is actually really simple, done with a single function call.
+
+#+BEGIN_SRC c :hl_lines 0
+SDL_Init(SDL_INIT_VIDEO);
+#+END_SRC
+
+=SDL_Init= takes a flag as a parameter so it knows which subsystems to
+initialize. /A/ parameter. One, not several. This might be a bit confusing to
+some, especially if you're not familiar with bitwise arithmetic, but
+=SDL_INIT_VIDEO= is nothing more than a preprocessor macro representing some
+number. =SDL_Init= interprets that number, and initializes the subsystems
+associated with it. We don't write the number out in our code, though (or at
+least you shouldn't). We use the macros, but there aren't macros for every
+combination of subsystems you can come up with. Does this mean that SDL can only
+initialize one subsystem at a time? Not at all, to combine macros and represent
+multiple subsystems, you would hook them together with the [[https://en.wikipedia.org/wiki/Bitwise_operation#OR][bitwise OR operator]].
+(=|=, not =||=). For example, if we wanted to initialize SDL's video AND audio
+subsystems, we would do this
+
+#+BEGIN_SRC c :hl_lines 0
+SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);
+#+END_SRC
+
+But we're not going to be working with audio just yet.
+
+=SDL_Init= also returns an integer value, and it's pretty important. If it's
+zero, SDL was initialized properly. Great! But if SDL can't be initialized for
+some reason, it will return a negative number. This is where another SDL
+function comes into the picture. =SDL_GetError= takes no arguments, but will
+return a string literal explaining what went wrong. So if we wanted to do some
+error checking (which you always should), we could do this:
+
+#+BEGIN_SRC c :hl_lines 0
+if (SDL_Init(SDL_INIT_VIDEO)) {
+ fprintf(stderr, "Here's the error: %s\n",
+ SDL_GetError());
+ return 1;
+}
+#+END_SRC
+
+You can pretty much do anything here to handle the error. I'm using =fprintf(3)=
+for simplicity, but SDL provides more advanced logging features which I'll cover
+in a later tutorial.
+
+In SDL, you need to be responsible and clean up after yourself. For every
+function that initializes or creates something, there is a complementary
+function that deinitializes or destroys it. The function to counter =SDL_Init=
+is =SDL_Quit=. It takes no parameters and returns nothing, you can just call it
+and be done with it. With that covered, we've learned SDL's few lines of
+boilerplate code. Much more appealing than something like Direct3D, eh? If you
+compile and run the program right now, nothing interesting will happen. It
+initialized and deinitialized SDL (unless something went wrong), but didn't
+bother creating windows or doing anything because we didn't tell it to. Let's
+change that. We first have to know about two important typedefs in SDL.
+*SDL_Window, and SDL_Renderer.* =SDL_Window= is self-explanatory, it's a struct
+representing a window, and =SDL_Renderer= is how you would put something into a
+window. Renderers in SDL are capable of hardware acceleration and vertical-sync,
+which is why SDL2 is awesome and SDL1.2 is left in the dust. These are just
+structs, though, they don't do anything by themselves. Let's create a window,
+and capture it in a =SDL_Window= struct:
+
+#+BEGIN_SRC c :hl_lines 0
+SDL_Window *my_cool_window = SDL_CreateWindow("A Cool Window",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ 400, 400, SDL_WINDOW_SHOWN);
+#+END_SRC
+
+Whoa, that's a mouthful, but it isn't as complicated as it looks. The first
+argument is just a title for the window; you can name it whatever you want. The
+next two arguments are X and Y values for where the window should be placed on
+the screen. People usually don't care about this; you can use
+=SDL_WINDOWPOS_UNDEFINED= if you don't. After that are the window's width and
+height. I'm choosing to make my window 400 by 400 pixels, but you can choose
+whatever size works best for your program. There are ways to change it later on,
+too. Finally, we get to a flag. Its usage is similar to the flag we used with
+=SDL_Init=, you use a bitwise OR to combine flags. We're not doing anything
+fancy just yet, though, so =SDL_WINDOW_SHOWN= on its own will suffice. It
+ensures that the window will be visible, rather than minimized.
+
+As you can hopefully tell from the example code above, =SDL_CreateWindow=
+returns a pointer of type =SDL_Window=. If a window cannot be created, though,
+it will return =NULL=. You should always do error checking, so throw something
+in to see if =my_cool_window= (or whatever you named your window variable) is
+=NULL=.
+
+#+BEGIN_SRC c :hl_lines 0
+if (!my_cool_window) {
+ fprintf(stderr, "Window couldn't be created. %s\n",
+ SDL_GetError());
+ return 1;
+}
+#+END_SRC
+
+There's =SDL_GetError= again! He's our friend and you should be using him every
+time you do error checking.
+
+Remember how I said that SDL has a complementary function to destroy anything
+that is created? This is no exception. =SDL_DestroyWindow= is very similar to
+=SDL_Quit=, but it takes a =SDL_Window= pointer as an argument.
+
+#+BEGIN_SRC c :hl_lines 0
+SDL_DestroyWindow(my_cool_window);
+#+END_SRC
+
+It's sad to see him go, but we're done so we need to free the resources. At this
+point, you can compile and run the C source file. It still kinda sucks, though.
+When you run it, the window pops up and immediately goes away. One useful
+function is =SDL_Delay=. It might seem mundane now, but it will become quite
+important when we need to cap our program's framerate. It takes a number of
+milliseconds as a parameter and temporarily stops your program so that SDL and
+your computer can take a short break. Alright. So when we put that in our code
+(after the window creation but before the window destruction), compile, and run
+it, we get this:
+
+#+CAPTION: A screenshot of the window on my system.
+[[./window.png]]
+
+I'm running i3wm, so it will probably look slightly different for you, but
+we've finally got a window! It still sucks, though. It doesn't do anything,
+it doesn't even clear itself! Let's make it white and learn a little bit
+about renderers in SDL.
+
+If we want to do stuff within a window, we have two options. One is to create an
+=SDL_Surface= from the window and draw to the surface, which is the sucky legacy
+way of doing it so we're going to pretend that I didn't mention it, or you can
+create a =SDL_Renderer=, which we're going to do because it's so much more
+capable. We're going to use another SDL function call to create a renderer, and
+we'll capture it in a =SDL_Renderer= pointer:
+
+#+BEGIN_SRC c :hl_lines 0
+SDL_Renderer *my_cool_renderer;
+SDL_CreateRenderer(my_cool_window, -1,
+ SDL_RENDERER_ACCELERATED);
+if (!my_cool_renderer) {
+ fprintf(stderr, "There was an error %s\n",
+ SDL_GetError());
+}
+#+END_SRC
+
+Alright, so clearly the first argument is the window we want to create a
+renderer for. The second is more complicated. It's the index of the driver to
+initialize, which you probably don't care too much about so you can just put -1
+in there to have it use the first one that's available. The last is a flag,
+which you probably know so much about by now. Finally, as you can imagine,
+=SDL_CreateRenderer= returns =NULL= if a renderer cannot be created. This should
+all seem pretty familiar, it's the same format as creating a window - create a
+struct pointer to capture it, use a function call to create it, and do some
+basic error checking. Dead simple, and it just gets better from here.
+
+Once again, don't forget to clean up after yourself. The function to remove a
+renderer when you're done with it is =SDL_DestroyRenderer=. It just takes a
+=SDL_Renderer= pointer as an argument.
+
+Now we've got a renderer, but if we compile and run our code - the effect is
+the same because we haven't used it for anything. So let's change that and
+learn a little bit about drawing in SDL.
+
+Renderers in SDL have a color associated with them, which they use when drawing
+primitive geometry like lines and quadrilaterals. It doesn't affect textures,
+but you'll probably end up using primitive geometry at some point so it's good
+to know. =SDL_SetRenderDrawColor= changes the aforementioned color. We're
+actually not going to be drawing any primitive geometry in this tutorial, but
+I'm bringing this up because the renderer will use its associated color when
+clearing the screen. So more about the function - it takes a renderer as an
+argument, followed by red, green, blue, and alpha (transparency) values. I'm
+going to make mine white (0xFF, 0xFF, 0xFF, 0xFF), but feel free to experiment.
+After that, we'll be calling =SDL_RenderClear=, which takes a renderer as an
+argument and, as I briefly mentioned a few lines ago, fills it with whatever
+color the renderer is currently associated with. And finally, we'll call
+=SDL_RenderPresent= to update the screen. This is where some people get a little
+confused. Basically, in SDL you draw everything and /then/ update the screen,
+meaning that you have as much time as you want to make the scene perfect before
+you have to show it to the user. And we're pretty much done! Let's look at the
+basic program:
+
+#+BEGIN_SRC c :hl_lines 0
+#include <stdio.h>
+#include <SDL2/SDL.h>
+
+int main(int argc, char *argv[]) {
+ SDL_Window *my_cool_window;
+ SDL_Renderer *my_cool_renderer;
+
+ if (SDL_Init(SDL_INIT_VIDEO)) {
+ fprintf(stderr, "ERROR: %s\n", SDL_GetError());
+ return 1;
+ }
+
+ my_cool_window = SDL_CreateWindow("Bush Did Harambe",
+ SDL_WINDOWPOS_UNDEFINED,
+ SDL_WINDOWPOS_UNDEFINED,
+ 400, 400, SDL_WINDOW_SHOWN);
+ if (!my_cool_window) {
+ fprintf(stderr, "ERROR: %s\n", SDL_GetError());
+ return 1;
+ }
+
+ my_cool_renderer = SDL_CreateRenderer(my_cool_window,
+ -1,
+ SDL_RENDERER_ACCELERATED);
+ if (!my_cool_renderer) {
+ fprintf(stderr, "ERROR: %s\n", SDL_GetError());
+ return 1;
+ }
+
+ SDL_SetRenderDrawColor(my_cool_renderer, 0xFF, 0xFF, 0xFF, 0xFF);
+ SDL_RenderClear(my_cool_renderer);
+ SDL_RenderPresent(my_cool_renderer);
+
+ SDL_Delay(4000);
+ SDL_DestroyRenderer(my_cool_renderer);
+ SDL_DestroyWindow(my_cool_window);
+ SDL_Quit();
+ return 0;
+}
+#+END_SRC
+
+Let's run it:
+
+#+CAPTION: A screenshot of the completed window on my system.
+[[./finished-window.png]]
+
+To recap, we learned about:
+
+* SDL Functions
+
+- *SDL_Init*: Used to initialize SDL. Takes a flag as a parameter. Returns 0 if
+ it succeeds, or a negative value if it fails.
+- *SDL_Quit*: Complements =SDL_Init=. Takes no parameters and returns nothing.
+- *SDL_CreateWindow*: Creates a window and returns a pointer to it, or =NULL= if
+ it fails. Takes a title, X and Y positions, width, height, and a flag as
+ parameters.
+- *SDL_DestroyWindow*: Complements =SDL_CreateWindow=, takes a =SDL_Window=
+ pointer as an argument and returns nothing.
+- *SDL_CreateRenderer*: Called to create a renderer, and returns a pointer to
+ it, or =NULL= if it fails. Takes the =SDL_Window= pointer for the window you
+ want to create a renderer for, an index (usually -1), and a flag as
+ parameters.
+- *SDL_DestroyRenderer*: Complements =SDL_CreateRenderer=. Takes a
+ =SDL_Renderer= pointer as an argument and returns nothing
+- *SDL_Delay*: Takes a number of milliseconds as an argument, and proceeds to
+ wait for that period of time.
+- *SDL_SetRenderDrawColor*: Takes a renderer, red, green, blue, and alpha values
+ as arguments, and changes the color associated with the given renderer.
+- *SDL_RenderClear*: Takes a renderer as an argument and fills it with whatever
+ color is currently associated with that renderer.
+- *SDL_RenderPresent*: "Refreshes" the renderer, presenting the image to the
+ user.
+
+* Type Definitions
+
+- *SDL_Window*: Captures the result of =SDL_CreateWindow=.
+- *SDL_Renderer*: Captures the result of =SDL_CreateRenderer=.
+
+And if you would like to read more, here are some additional resources:
+
+- [[https://wiki.libsdl.org/SDL_Init#Remarks][SDL Documentation - SDL_Init (Remarks)]]
+- [[https://wiki.libsdl.org/SDL_WindowFlags][SDL Documentation - Window Flags]]
+- [[https://wiki.libsdl.org/SDL_RendererFlags][SDL Documentation - Renderer Flags]]
+
+You're still reading? Well, this is my first tutorial ever. If you have any
+feedback, be it positive or negative, I'd love to hear it! I hope this tutorial
+was helpful, there are many more to come.