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.

Linux kernel series: HIGHMEM

This is the second post in the Linux kernel series. The first part of the series was about memblock. This week, the topic is HIGHMEM.

What is HIGHMEM?

The Linux kernel virtualizes its memory. Virtual memory addresses, which can be used by programs, are mapped to physical memory addresses. When the size of the physical memory exceeds the (maximum) size of the virtual memory, it is not possible for the kernel to map every physical address to a virtual address. So the kernel uses temporary mappings when it wants to access part of the physical memory that is not mapped to a permanent virtual memory address. The part of physical memory that does not have a permanent mapping to a virtual address is called high memory (HIGHMEM).[1]

HIGHMEM is often needed in 32 bit systems. A 32 bit address can represent at most 232 = 4,294,967,296 different addresses, which is approximately 4 GB (or exactly 4 GiB). But many systems have more than 4 GB of physical memory.

The available virtual memory space has to be divided between kernel space and user space. A common split is 3 : 1 user space : kernel space, which would mean 3 GB for user space and 1 GB for kernel space on a 32 bit system. The part of kernel space that has a direct mapping to a permanent physical memory is called LOWMEM. LOWMEM is contiguously mapped in physical memory.[2]

Creating Temporary Maps

There are several different functions for creating temporary virtual maps.[2]

vmap() : used to map multiple physical pages into a contiguous virtual address space for a long duration
kmap() : used to map a single physical page for a short duration. Prone to deadlocks when nested, and not recommended in new code
kmap_atomic() : used to map a single physical page for a very short duration. It performs well because it is restricted to the CPU that issued it, but the issuing task must stay on the same CPU until it finishes.
kmap_local_page() : recently developed as a replacement for kmap(). It creates a mapping that is restricted to local use by a single thread of execution. This function is preferred over the other functions and should be used when possible.[3]

References

  1. High Memory Handling — The Linux Kernel documentation
  2. Memory Mapping — The Linux Kernel documentation
  3. LKML Mailing List

Linux kernel series: memblock

This post is the first in a series in which I discuss the basics of a component or concept in the Linux kernel. The goal is to explain it in such a way that a beginner C or C++ programmer unfamiliar with the Linux kernel can comprehend it. There will be at least 2 posts in this series, including this one.

This week, the topic is memblock.

What is memblock?

When programming in kernel space, you cannot use the C standard library memory allocation functions available in user space, such as malloc, free, calloc, realloc, or reallocarray.[1] There are kernel space equivalents, such as kmalloc or kzalloc, kfree, kcalloc, krealloc_array, krealloc,[2] and more. However, these memory allocators are set up during the booting process. In the period of time before these allocators are available, the system still needs to allocate memory. During that period of time, a specialized allocator called memblock is used to allocate memory.[3]

memblock views the system memory as a collection of contiguous regions. memblock is made up of memblock types, which are themselves made up of multiple memory regions. There are several structures and functions for working with memblock.[4]

Structures

struct memblock

The struct memblock wraps the metadata for the memory and reserved memblock types, along with current_limit, the physical address of the current allocation limit, and bottom_up, the allocation direction. The allocation limit limits allocations to memory that is currently accessible and can be set with memblock_set_current_limit(phys_addr_t limit). The memblock structure is statically initialized at build time.

[5]
struct memblock {
	bool bottom_up;
	phys_addr_t current_limit;
	struct memblock_type memory;
	struct memblock_type reserved;
};

struct memblock_type

An array of several memory regions of the same type can be represented with struct memblock_type. This struct defines an array of memory regions regions of type name with cnt number of regions. The total size of the array is max, and the size of all the regions is total_size.

[5]
struct memblock_type {
	unsigned long cnt;
	unsigned long max;
	phys_addr_t total_size;
	struct memblock_region *regions;
	char *name;
};

The memory region types are memory, reserved, and physmem.

  • memory is the physical memory available to the kernel.
  • reserved refers to the regions that were actually allocated.
  • physmem is the total physical memory available during boot regardless of whether the kernel can access it. It is not available on some architectures.

struct memblock_region

The struct memblock_region represents a memory region with base address base, size size, memory region attributes flags, and NUMA node id nid. NUMA is a system with non-uniform memory access.

[5]
struct memblock_region {
	phys_addr_t base;
	phys_addr_t size;
	enum memblock_flags flags;
#ifdef CONFIG_NUMA
	int nid;
#endif
};

The memory region attributes are defined in enum memblock_flags.

[5]
enum memblock_flags {
	MEMBLOCK_NONE		= 0x0,	/* No special request */
	MEMBLOCK_HOTPLUG	= 0x1,	/* hotpluggable region */
	MEMBLOCK_MIRROR		= 0x2,	/* mirrored region */
	MEMBLOCK_NOMAP		= 0x4,	/* don't add to kernel direct mapping */
	MEMBLOCK_DRIVER_MANAGED = 0x8,	/* always detected via a driver */
};

Functions

memblock is initialized in setup_arch() and taken down in mem_init().

memblock has many useful APIs. These APIs include functions for adding and removing memory regions, and allocating and freeing memory, among many other functions.

Adding and removing

These functions add or remove a memblock region with the given base address and size. They return an int indicating whether the operation succeeded. The memblock_add_node() version adds the region within a NUMA node and includes parameters for the node id and memblock flags.

memblock_add(base, size)memblock_add_node(base, size, nid, flags)
memblock_remove(base, size)

Allocating

These functions allocate a memory block and return the physical address of the memory.

memblock_phys_alloc(size, align)
memblock_phys_alloc_range(size, align, start, end)
memblock_phys_alloc_try_nid(size, align, nid)

These functions allocate a memory block and return the virtual address of the memory.

memblock_alloc(size, align)
memblock_alloc_from(size, align, min_addr)
memblock_alloc_low(size, align)
memblock_alloc_node(size, align, nid)

Freeing

The functions memblock_free() and memblock_phys_free() free a boot memory block that was previously allocated; it is not released to the buddy allocator (which handles allocation after boot process completes). The *ptr parameter in memblock_free() is the starting address of the boot memory block. The functions memblock_free_all() and memblock_free_late() free pages to the buddy allocator.

memblock_free(*ptr, size)
memblock_phys_free(base, size)
memblock_free_all(void)
memblock_free_late(base, size)

References

  1. malloc(3) — Linux manual page
  2. Memory Management APIs — The Linux Kernel documentation
  3. Boot time memory management — The Linux Kernel documentation
  4. Boot time memory management — The Linux Kernel documentation: Functions and structures
  5. Code snippet from linux/memblock.h