Using SDL Part 2

This is part 2 in a series on using SDL. See part 1 if you missed it. This post introduces more useful functions, structures, and constants in the SDL 2.0 API. It covers basic details such as return values and parameters of functions and names and data types of members of structs. For more detailed info, visit the links in the references.

Rendering

Call the SDL_CreateRenderer() function to create a renderer in which you can render objects such as rectangles in a window.[1] The window should have already been initialized. See part 1 to review how to create an SDL_Window instance.

SDL_Renderer *SDL_CreateRenderer(SDL_Window *window,
int index, Uint32 flags);

Returns: if successful, a valid rendering context; on error, NULL

window is an SDL_Window instance where the renderer will be displayed.
index is the index of the rendering driver to initialize, or -1 to initialize the first one supporting the requested flags.
flags is 0, or one or more SDL_RendererFlags OR’d together. Possible values for flags:

  • SDL_RENDERER_SOFTWARE: the renderer is a software fallback
  • SDL_RENDERER_ACCELERATED: the renderer uses hardware acceleration
  • SDL_RENDERER_PRESENTVSYNC: present is synchronized with the refresh rate
  • SDL_RENDERER_TARGETTEXTURE: the renderer supports rendering to texture

You will likely want to render to a texture, which is created with SDL_CreateTexture().[2]

SDL_Texture *SDL_CreateTexture(SDL_Renderer *renderer,
                                Uint32 format,
                                int access, int w,
                                int h);

Returns: a pointer to the created texture or NULL if no rendering context was active, the format was unsupported, or the width or height were out of range
  • renderer is the rendering context.
  • format is one of the enumerated values in SDL_PixelFormatEnum.
  • access is one of the enumerated values in SDL_TextureAccess, which are
    • SDL_TEXTUREACCESS_STATIC: changes rarely, not lockable
    • SDL_TEXTUREACCESS_STREAMING: changes frequently, lockable
    • SDL_TEXTUREACCESS_TARGET: can be used as a render target
  • w is the width of the texture in pixels.
  • h is the height of the texture in pixels.

Use SDL_SetRenderTarget() is set a texture as the target of a renderer.[3]

int SDL_SetRenderTarget(SDL_Renderer *renderer,
                        SDL_Texture *texture);

Returns: 0 on success or a negative error code on failure

renderer is the rendering context.
texture is the targeted texture, which must be created with the SDL_TEXTUREACCESS_TARGET flag, or NULL to render to the window instead of a texture.

To set the color used when rendering, use SDL_SetRenderDrawColor().[4]

int SDL_SetRenderDrawColor(SDL_Renderer *renderer,
                   Uint8 r, Uint8 g, Uint8 b,
                   Uint8 a);

Returns: 0 on success or a negative error code on failure

renderer is the rendering context.
r, g, and b are the red, green, and blues value used to draw on the rendering target, respectively. These should be a value between 0 and 255, inclusive.
a is the alpha value (opacity) used to draw on the rendering target. This should be a value between 0 and 255, inclusive. Use SDL_ALPHA_OPAQUE or 255 to specify opaque. If you want to have a transparent object, use SDL_SetRenderDrawBlendMode().

Use SDL_RenderClear()[5] to color the entire rendering target with the color previously set with SDL_SetRenderDrawColor().

int SDL_RenderClear(SDL_Renderer *renderer);

Returns: 0 on success or a negative error code on failure

renderer is the rendering context.

Use SDL_RenderFillRect() to render a filled rectangle with no outline.[6]

int SDL_RenderFillRect(SDL_Renderer *renderer,
                       const SDL_Rect *rect);

Returns: 0 on success or a negative error code on failure

renderer is the rendering context.
rect is the SDL_Rect structure representing the rectangle to fill, or NULL for the entire rendering target.

[7]
typedef struct SDL_Rect
{
    int x, y;
    int w, h;
} SDL_Rect;

x and y are the x and y values of the rectangle’s upper left corner.
w and h are the width and height of the rectangle, respectively.

Use SDL_RenderDrawRect() to draw the outline of a rectangle.[8] Its arguments are identical to the arguments for SDL_RenderFillRect().

int SDL_RenderDrawRect(SDL_Renderer *renderer,
                       const SDL_Rect *rect);

Returns: 0 on success or a negative error code on failure

To destroy a renderer for a window and free associated textures, use SDL_DestroyRenderer().[9]

void SDL_DestroyRenderer(SDL_Renderer *renderer);

renderer is the rendering context to destroy.

References

  1. https://wiki.libsdl.org/SDL_CreateRenderer
  2. https://wiki.libsdl.org/SDL_CreateTexture
  3. https://wiki.libsdl.org/SDL_SetRenderTarget
  4. https://wiki.libsdl.org/SDL_SetRenderDrawColor
  5. https://wiki.libsdl.org/SDL_RenderClear
  6. https://wiki.libsdl.org/SDL_RenderFillRect
  7. https://wiki.libsdl.org/SDL_Rect
  8. https://wiki.libsdl.org/SDL_RenderDrawRect
  9. https://wiki.libsdl.org/SDL_DestroyRenderer

Using SDL Part 1

This post introduces useful functions, structures, and constants in the SDL 2.0 API. It covers basic details such as return values and parameters of functions and names and data types of members of structs. For more detailed info, visit the links in the references.

Initialization

To use SDL 2.0 in your program, call the SDL_Init() function.[1]

int SDL_Init(Uint32 flags);

Returns: 0 if successful or a negative error code if unsuccessful 

The flags parameter specifies which subsystems should be initialized. It can be one or more of the following:

  • SDL_INIT_TIMER: timer subsystem
  • SDL_INIT_AUDIO: audio subsystem
  • SDL_INIT_VIDEO: video subsystem; automatically initializes the events subsystem
  • SDL_INIT_JOYSTICK: joystick subsystem; automatically initializes the events subsystem
  • SDL_INIT_HAPTIC: haptic (force feedback) subsystem
  • SDL_INIT_GAMECONTROLLER: controller subsystem; automatically initializes the joystick subsystem
  • SDL_INIT_EVENTS: events subsystem
  • SDL_INIT_EVERYTHING: all of the above subsystems
  • SDL_INIT_NOPARACHUTE: compatibility; this flag is ignored

Use a logical OR to use more than one flag, e.g.,

SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO);

There is another function called SDL_InitSubSystem() that is identical to SDL_Init() in all but the name of the function.[2]

Shutdown Cleanup

Call the SDL_Quit() function to perform final cleanup of all initialized subsystems.

void SDL_Quit(void);

This function can be used with atexit() to ensure that it is called when your program shuts down.[3]

atexit(SDL_Quit);

Error Handling

SDL_GetError()

SDL_GetError() gets a message about the last error on the current thread and returns it as a string.[4]

const char* SDL_GetError(void);

Returns: a string with information about the last error, or an empty string if there hasn't been an error since the last call to SDL_ClearError()

SDL_GetError() should be called if an SDL function signals that an error occurred. It should not be used to determine whether an error occurred since some SDL functions will set an error string even when successful.

SDL_ClearError()

SDL_ClearError() clears the error message for the current thread.[5]

void SDL_ClearError(void);

SDL_SetError()

SDL_SetError() sets the SDL error message for the current thread. Its arguments work similarly to printf().[6]

int SDL_SetError(SDL_PRINTF_FORMAT_STRING const char *fmt, …);

Returns: always returns -1

fmt is the desired error message. It is a printf-style format string.

... means that there can be any number of additional parameters matching % tokens in the fmt string.

Example code:

[6]
if (error_code) {
    return SDL_SetError("This operation has failed: %d", error_code);
}

Creating and Destroying Windows

SDL_CreateWindow()

SDL_CreateWindow() createDL_CreateWindow()s a new window with the given title, size, and position.[7]

SDL_Window *SDL_CreateWindow(const char *title,
int x, int y, int w,
int h, Uint32 flags);

Returns: on success, a pointer to the new window instance created; on failure, NULL

title is a string that will be the title of the window.
x is the x position of the window, SDL_WINDOWPOS_CENTERED, or SDL_WINDOWPOS_UNDEFINED.
y is the y position of the window, SDL_WINDOWPOS_CENTERED, or SDL_WINDOWPOS_UNDEFINED.
w is the width of the window in screen coordinates.
h is the height of the window in screen coordinates.
flags is either 0 or one or more of the following SDL_WindowFlags OR’d together:

SDL_WINDOW_FULLSCREEN: fullscreen window
SDL_WINDOW_FULLSCREEN_DESKTOP: fullscreen window at desktop resolution
SDL_WINDOW_OPENGL: window usable with an OpenGL context
SDL_WINDOW_VULKAN: window usable with a Vulkan instance
SDL_WINDOW_METAL: window usable with a Metal instance
SDL_WINDOW_HIDDEN: window is not visible
SDL_WINDOW_BORDERLESS: no window decoration
SDL_WINDOW_RESIZABLE: window can be resized
SDL_WINDOW_MINIMIZED: window is minimized
SDL_WINDOW_MAXIMIZED: window is maximized
SDL_WINDOW_INPUT_GRABBED: window has grabbed input focus
SDL_WINDOW_ALLOW_HIGHDPI: window should be created in high-DPI mode if supported (>= SDL 2.0.1)

SDL_DestroyWindow

SDL_DestroyWindow() destroys the given window instance.[8]

void SDL_DestroyWindow(SDL_Window *window);

window is the window to destroy. If window is NULL, the SDL error message will be set.

Getters and Setters

You can associate a name and other data with a window instance using SDL_SetWindowData().[9]

void* SDL_SetWindowData(SDL_Window *window,
const char *name,
void *userdata);

Returns: the previous data associated with name

window is the window to associate with the pointer to the data.
name is the name of the pointer to the data.
userdata is the associated pointer to the data.

You can then retrieve that data using SDL_GetWindowData().[10]

void* SDL_GetWindowData(SDL_Window *window,
const char *name);

Returns: the data associated with name

window is the window to query.
name is the name of the pointer to the data.

Each window instance has an ID associated with it, which is used by SDL_WindowEvent structure. To work with that structure, it is often necessary to be able to convert from a window instance to its ID and vice versa. Fortunately, there are functions to facilitate both conversions.

SDL_GetWindowID() retrieves the ID of the given window.[11]

Uint32 SDL_GetWindowID(SDL_Window *window);

Returns: the ID of the window on success or 0 on failure

window is the window to query.

SDL_GetWindowFromID() retrieves the window instance with the given ID.[12]

SDL_Window *SDL_GetWindowFromID(Uint32 id);

Returns: the window associated with id or NULL if it doesn't exist

id is the ID of the window.

References

  1. https://wiki.libsdl.org/SDL_Init
  2. https://wiki.libsdl.org/SDL_InitSubSystem
  3. https://wiki.libsdl.org/SDL_Quit
  4. https://wiki.libsdl.org/SDL_GetError
  5. https://wiki.libsdl.org/SDL_ClearError
  6. https://wiki.libsdl.org/SDL_SetError
  7. https://wiki.libsdl.org/SDL_CreateWindow
  8. https://wiki.libsdl.org/SDL_DestroyWindow
  9. https://wiki.libsdl.org/SDL_SetWindowData
  10. https://wiki.libsdl.org/SDL_GetWindowData
  11. https://wiki.libsdl.org/SDL_GetWindowID
  12. https://wiki.libsdl.org/SDL_GetWindowFromID

Stay tuned for part 2.

Building an 8080 Emulator: Planning

My Capstone project this term is Build an Emulator and Run Space Invaders ROM. I will be working with a team of three other students. I am excited about working on this project because I am interested in low-level programming and old-school video games. Most of the code will be written in C (my favorite programming language!), but 8080 assembly code will also be used.

The program will take a ROM file as input. Currently, we are planning to target the Space Invaders ROM, but we may test with other ROM files if time allows. Since my group includes both Linux and Windows users, the program will be compatible with Linux and Windows. As an extension, we may develop for compatibility with other platforms, such as mobile.

Building the Disassembler

The first step will be to build a disassembler that will read hexadecimal data from the ROM file and print out the corresponding 8080 assembly instruction. There are 256 instructions, corresponding to opcodes 0x00 through 0xff. Thus, the implementation will likely involve a really long switch statement. For example, this snippet from emulator 101:

	switch (*code) {    
	case 0x00: 
		printf("NOP"); break;    
	case 0x01: 
		printf("LXI    B,#$%02x%02x", code[2], code[1]);
		opbytes=3; break;    

Memory Map

The Intel 8080 CPU has 16 address pins, so it uses 16 bit addresses (0000 through ffff). The memory map for Space Invaders tells us what each address is used for.

0000-1FFF 8K ROM
2000-23FF 1K RAM
2400-3FFF 7K Video RAM
4000- RAM mirror

Implementing Opcodes

The next step will be to implement the instructions. We will create data structures to store info about the current state of the registers and the values of the FLAGS. The Intel 8080 CPU has seven 8-bit registers (A, B, C, D, E, H, and L) as well as the 16-bit registers for the stack pointer (SP), program counter (PC), and status register (FLAGS).

The program will modify the state (stored in the data structures) according to what effect that instruction would actually have on the registers in the 8080.

Emulating the Rest of the Hardware

We will need to emulate the rest of the hardware in the Space Invaders machine to actually see and play the game. This includes interrupts, graphics, buttons, and sounds. We will also need to emulate the speed of the 8080 processor, which has a CPU clock rate of 2 Mhz.

From Windows-only user to Linux kernel programmer

Until 2014, I was a Windows user who liked the idea of Linux but believed that it was too complicated for me to install and use. I didn’t consider myself a programmer or particularly tech-savvy. Today, I contributed my fourth clean-up patch to the Linux kernel. Additionally, I am less than three months away from graduating with a BS in Computer Science, and I am very confident in my technical abilities.

Finding my passion

My first degree was a BS in Chemical Engineering from the University of Texas at Austin. After I graduated, I began a doctoral program in Chemical Engineering at the University of California at Davis. I dropped out after the first quarter when I realized what I had suspected during my undergraduate degree: that I was not very passionate about the subject.

I then embarked on a search for my “passion”. I tried many different things that didn’t quite work out. Then, I decided to try web development and Android app development. I took some courses on Udemy and checked out programming books from the library. When I started learning to program, it immediately clicked. Somehow I just knew that Computer Science was that long sought-after “passion”.

Embracing Linux

In 2014, I met my current partner, who convinced me that Linux was awesome and installed Ubuntu on my six-year-old Dell laptop. I eventually learned how to install Linux on my own (and bought a new computer!). Ironically, at UT, I had dated a Linux-obsessed Computer Science major, who may have offered to do this but should have been more pushy about it!

After I learned more about programming and Linux, I wanted to contribute to open source projects, but I was having trouble getting started. I would read through the documentation for open source projects but then feel too intimidated to get involved.

One day, I was looking at the New contributors task list for a Wikimedia project, and I noticed that someone had posted a comment asking if a particular task could be used for Outreachy. I hadn’t heard of that before. When I looked into it, I realized that it was the perfect opportunity for me.

Outreachy

Outreachy is an internship program in which interns work on open source and open science projects. Outreachy seeks to increase diversity in open source communities:

Outreachy provides internships to people subject to systemic bias and impacted by underrepresentation in the technical industry where they are living.

https://www.outreachy.org/

The internship is remote and lasts three months. Interns work 30 hours per week and are paid a $7,000 stipend. Interns also work with a mentor. Unlike most internships, applicants aren’t required to be enrolled in school. So, I could do it after I graduate.

Contributing

I applied, and my initial application was approved! Now I am in the contribution period, in which applicants make contributions to one or more projects. I was immediately drawn to the Linux kernel projects. The set up was a bit extensive and included building the kernel from source, but it was much more approachable than trying to do it on my own. Even if I am not selected for an internship, I will be grateful that I was able to participate in the contribution period.