Welcome back. In the previous module, we focused on getting content onto the screen efficiently, culminating in the use of SkTextBlob to optimize the rendering of static text. Now, we shift our focus from static display to dynamic interaction. The heart of any interactive application is its ability to respond swiftly and accurately to user input.
This lesson marks our entry into interactive drawing. We will tackle the foundational step: capturing the raw input data from the user. Specifically, you will learn how to set up an SDL2 event loop to capture a high-frequency stream of mouse and stylus events. Crucially, we'll cover how to access pressure data from stylus input, which is essential for the pressure-sensitive drawing you aim to implement in your whiteboard application. For someone interested in high-performance systems, understanding how to handle a rapid influx of events without introducing latency is a critical skill.
The SDL Event Loop and the Event Queue
At its core, an interactive application is driven by events. The operating system continuously generates events in response to user actions like moving the mouse, pressing a key, or touching a screen. SDL provides a cross-platform abstraction layer for accessing these events. It collects them from the OS and places them in an internal event queue. Your application's main responsibility is to pull events from this queue in a timely manner and react to them.
The primary function for this task is SDL_PollEvent(). It checks if there is a pending event in the queue. If so, it copies that event into an SDL_Event struct you provide and removes it from the queue, returning true. If the queue is empty, it returns false immediately without waiting. This non-blocking nature is vital for a real-time application loop.
The structure of the event-polling logic is paramount for performance. A common but critical mistake is to process only one event per frame. Let's examine a real-world example of this error and its consequences.
Mouse handling SDL2 very slow and sluggish in linux when VSYNC is turned ON
This Stack Overflow thread discusses a classic performance problem: sluggish mouse input in an SDL2 application. The initial code, the user comments, and the final answer provide a perfect case study in how not to write an event loop.
First, quickly scan the initial question and the code. Notice the structure of the while ( SDL_PollEvent(&event) ) loop. Specifically, look at the break; statement inside the if blocks for SDL_MOUSEMOTION and SDL_MOUSEWHEEL. Next, read the comments below the question, particularly the ones from users 'Botje' and 'HolyBlackCat'. They immediately identify the problem. As 'Botje' notes, "you will handle one, then render a frame, wait 16 ms, handle another one". Finally, read the short, accepted answer. It confirms the diagnosis: the break statement prematurely exits the polling loop, causing a massive backlog of events.
As the case study demonstrates, using break inside your polling loop is a recipe for input lag. If the user moves the mouse quickly, the OS can generate dozens of motion events between your application's frames. By only processing one and then proceeding to render, the event queue grows larger and larger. The application is always reacting to outdated information, resulting in the cursor or stroke visibly trailing behind the user's physical action.
The correct approach is to use a while loop that "drains" the entire event queue each frame before you render anything. This ensures your application's state is as up-to-date as possible when you draw the new frame.
// Correct event loop structure
while (SDL_PollEvent(&event)) {
// Handle the event
switch (event.type) {
case SDL_QUIT:
running = false;
// No break needed, loop condition will handle it
break;
case SDL_MOUSEMOTION:
// Process mouse motion
break; // Breaks from the switch, not the while loop
case SDL_MOUSEBUTTONDOWN:
// Process mouse button press
break;
// ... other event types
}
} // The loop continues until the event queue is empty for this frame.
// Now, after all events are processed, proceed with updates and rendering.
update_game_state();
render_scene();
This structure guarantees that your application consumes all available input before making decisions about what to render, which is fundamental to creating a responsive experience.
Capturing Mouse and Stylus Input
Now that we have the correct loop structure, let's look at the specific event types for drawing.
Mouse Events
For standard mouse input, the key event is SDL_MOUSEMOTION. The SDL_Event union will contain an SDL_MouseMotionEvent structure. The most relevant fields for us are:
event.motion.x: The absolute X coordinate within the window.event.motion.y: The absolute Y coordinate within the window.
You should get this data directly from the event structure itself, rather than calling a separate function like SDL_GetMouseState(). The event provides the state of the mouse at the time the event was generated, which is exactly what we need for a precise input history.
Stylus and Touch Events (with Pressure)
To get pressure data, which is typical for styluses and some touch screens, we need to handle touch events. SDL reports these using a different set of event types and a different data structure. The main event type is SDL_FINGERMOTION.
When you receive a touch event, the SDL_Event union contains an SDL_TouchFingerEvent. This structure holds the detailed information we need, including pressure.
SDL2/SDL_TouchFingerEvent - SDL2 Wiki
This is the official SDL Wiki page for the SDL_TouchFingerEvent structure. It details all the fields you can access.
Take a look at the definition of the SDL_TouchFingerEvent struct. Pay close attention to these fields: x, y: These are the coordinates, but notice they are normalized in the range 0.0 to 1.0. You will need to multiply them by the window's width and height to get pixel coordinates. pressure: This is the value we're looking for, also normalized between 0.0 and 1.0. A value of 0.0 means no pressure, and 1.0 means maximum pressure. fingerId: An ID for the specific finger or stylus point, useful for multi-touch scenarios.
The three primary event types for touch are:
SDL_FINGERDOWN: Fired when a stylus first touches the screen.SDL_FINGERMOTION: Fired as the stylus moves across the screen. This is where you'll get the continuous stream of points for a stroke.SDL_FINGERUP: Fired when the stylus is lifted from the screen.
Tying It All Together: A Sample Loop
Here is a basic C++ snippet showing how to handle these events in a correct, high-frequency loop. For now, we will just print the captured data to the console.
#include <SDL.h>
#include <iostream>
// Assume 'window' is an initialized SDL_Window*
// int windowWidth, windowHeight;
// SDL_GetWindowSize(window, &windowWidth, &windowHeight);
bool running = true;
SDL_Event event;
while (running) {
// 1. Drain the entire event queue
while (SDL_PollEvent(&event)) {
switch (event.type) {
case SDL_QUIT:
running = false;
break;
case SDL_MOUSEMOTION:
// Check if a mouse button is held down (e.g., left button)
if (event.motion.state & SDL_BUTTON(SDL_BUTTON_LEFT)) {
std::cout << "Mouse Motion: (" << event.motion.x
<< ", " << event.motion.y << ")\n";
}
break;
case SDL_FINGERDOWN:
std::cout << "Stylus Down: (" << event.tfinger.x * windowWidth
<< ", " << event.tfinger.y * windowHeight
<< ") Pressure: " << event.tfinger.pressure << "\n";
break;
case SDL_FINGERMOTION:
std::cout << "Stylus Motion: (" << event.tfinger.x * windowWidth
<< ", " << event.tfinger.y * windowHeight
<< ") Pressure: " << event.tfinger.pressure << "\n";
break;
case SDL_FINGERUP:
std::cout << "Stylus Up: (" << event.tfinger.x * windowWidth
<< ", " << event.tfinger.y * windowHeight << ")\n";
break;
}
}
// 2. Perform updates and rendering after processing all events
// clearScreen();
// drawStrokes();
// presentFrame();
}
This loop correctly prioritizes consuming all available input before proceeding to the rendering stage, ensuring maximum responsiveness. Note the conversion of the normalized event.tfinger coordinates to screen space.
A Look Under the Hood
Your interest in low-level systems might make you curious about what SDL_PollEvent is actually doing. It's an abstraction over platform-specific APIs. On a Linux system using the X Window System, for example, SDL needs to communicate with the X server to get pending events.
The following image is a profiling capture from an application, showing the function call hierarchy when SDL checks for events.

In this trace, you can see SDL_PumpEventsInternal at the top, which is the main function SDL uses to fetch events from the OS. It calls X11_PumpEvents, which in turn calls deeper into the X11 library (libX11.so) with functions like XCheckIfEvent. This peek behind the curtain shows how SDL provides a clean interface, but underneath it's doing real systems-level work to interface with the windowing environment. The small percentages here indicate that event polling itself is typically not a major performance bottleneck, provided you drain the queue efficiently as we've discussed. The real performance cost comes from how you react to those events.
Conclusion
You now have the knowledge to build the first and most critical part of your interactive system: a responsive, high-frequency input handler. You've learned not only what events to capture but, more importantly, how to structure your event loop to avoid common performance pitfalls.
Key Takeaways:
- An application's event loop must drain the entire SDL event queue each frame by using a
while (SDL_PollEvent(...))loop. - Never use
breakto exit the event polling loop prematurely, as this will cause input events to back up, leading to severe lag. - Mouse movements are captured with the
SDL_MOUSEMOTIONevent, using theevent.motiondata. - Stylus/touch input, including pressure, is captured with
SDL_FINGERDOWN,SDL_FINGERMOTION, andSDL_FINGERUPevents, using theevent.tfingerdata. - Pressure and touch coordinates are normalized (0.0 to 1.0) and must be scaled by the window dimensions to get screen coordinates.
In our next lesson, we will build directly on this foundation. Now that we can capture a stream of position and pressure data, we need a way to store it. You will design and implement a data structure to represent a single, continuous user stroke, preparing us to eventually render it to the screen.