How to "undraw/unrender"?

Let’s say I want to draw a circle that follows the user’s mouse around the viewport. I imagine I would have to draw and undraw the circle at every mouse motion. Drawing the circle using SDL functions is not the problem, but how exactly would I “undraw” the circle?

Specifically, if there is a complex drawing underneath the circle, I wouldn’t want that to be erased.

Games normally redraw everything each frame. As long as you use the Render API (or the new GPU API) it should normally be fast enough to do so. The docs for SDL_RenderPresent says “The backbuffer should be considered invalidated after each present; do not assume that previous contents will exist between frames.” so if you use that function you don’t really have a choice but to redraw everything each time.

Only redrawing the parts that have changed is only really worth it if you’re doing “software rendering” (e.g. if you use SDL_Surface instead of SDL_Texture, SDL_UpdateWindowSurfaceRects instead of SDL_RenderPresent, etc.)

5 Likes

this thing exists too:

I didn’t play with it yet

I am using SDL_Render to draw a green crosshair which follows the user’s mouse over the viewport. When the user’s mouse is still, the crosshair is still, when the user’s mouse moves, the crosshair moves – just like the normal cursor.

I am checking for the mouse motion case within the event loop and only drawing the cross hair when the mouse is moved. Every time I poll a new event, I clear the viewport and re-draw all my entities, including the crosshair. Below is my attempt:

    SDL_Event event;
    bool terminate = false;
    while (!terminate) {
        while (SDL_PollEvent(&event) != 0) {
            SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
            SDL_RenderClear(renderer); 
            switch (event.type) {
                case SDL_MOUSEBUTTONDOWN: {
                    // call mouse button down handler... 
                }
                case SDL_MOUSEMOTION: {
                    // Crosshair
                    SDL_SetRenderDrawColor(renderer, 0, 255, 0,
                                           SDL_ALPHA_OPAQUE);
                    SDL_RenderDrawLine(
                        renderer, event.button.x, event.button.y - 10,
                        event.button.x, event.button.y + 10);
                    SDL_RenderDrawLine(
                        renderer, event.button.x - 10, event.button.y,
                        event.button.x + 10, event.button.y);
                    SDL_RenderPresent(renderer);
                    break;
                }
                case SDL_QUIT:
                   terminate = true;
            }
        }

        SDL_RenderPresent(renderer); // !!! render present 
    }

The problem is that the drawings are now very choppy and there is a flash of black at every mouse motion. Is my attempt correct?

I think it’s generally good to

  1. handle events,
  2. update the game logic, and
  3. draw the graphics

as three separate steps.

I recommend that you move all your drawing code outside of the event loop and only call SDL_RenderPresent in one place.

Your code should work more like this: (untested)

SDL_Event event;
bool terminate = false;
int mouseX = 0;
int mouseY = 0;
while (!terminate) {
	// Handle events
	while (SDL_PollEvent(&event) != 0) {
		switch (event.type) {
			case SDL_MOUSEBUTTONDOWN: {
				// ... 
			}
			case SDL_MOUSEMOTION: {
				// When receiving a SDL_MOUSEMOTION event you should 
				// use event.motion rather than event.button!
				mouseX = event.motion.x;
				mouseY = event.motion.y;
				break;
			}
			case SDL_QUIT:
			   terminate = true;
		}
	}
	
	// Instead of updating mouseX and mouseY inside the event
	// loop you could just fetch them here instead like this:
	// SDL_GetMouseState(&mouseX, &mouseY);
	
	// Clear screen
	SDL_SetRenderDrawColor(renderer, 0, 0, 0, SDL_ALPHA_OPAQUE);
	SDL_RenderClear(renderer); 
	
	// Draw crosshair
	SDL_SetRenderDrawColor(renderer, 0, 255, 0, SDL_ALPHA_OPAQUE);
	SDL_RenderDrawLine(renderer, mouseX, mouseY - 10, mouseX, mouseY + 10);
	SDL_RenderDrawLine(renderer, event.button.x - 10, event.button.y, event.button.x + 10, event.button.y);

	// Update screen
	SDL_RenderPresent(renderer);
}
2 Likes

Do not draw to the default render target (which is invalidated every frame) but instead draw to a target texture. Then you can do your circle drawing in the old-school way: first draw the background, then draw the circle to follow the user’s mouse, etc.

No ‘undrawing’ is necessary (unless you want to offer the user an ‘undo’ feature, which would be more tricky). Every frame you blit the target texture to the screen, to satisfy the needs of the renderer, but otherwise you can forget about the requirement for periodic refreshing.

2 Likes

You might look into SDL_CreateColorCursor and SDL_SetCursor, which would let you create custom mouse pointers for your project.

Or create one that looks more official using SDL_CreateSystemCursor and one of the SDL_SystemCursor values.

1 Like

I should have been clearer in my question. I have a board game where pieces are captured. Those pieces have to disappear from the board once they are captured. I am using the Render API so I can draw textures to the viewport (the “pieces” that should be removed are the textures). If I have a bunch of other pieces on the board, I don’t want them to get removed by redrawing. Plus even if I do redraw, I don’t want there to be a sudden flash. Should I still stick to using the Render API or is there a way to do this with SDL_Surface?

You can use such optimized rendering for textures, but only if you create your own texture on which you will render the contents of the game frame. The contents of such a texture will not change without your knowledge, so you can repaint selected regions of it, only those that require repainting.

Such a texture is a simple back buffer, on which you first render what is needed, and then render this texture in the window. I use this technique myself, except that I have several back buffer textures, in different sizes, but I still render each frame from scratch. Multiple buffers allow me to render a game frame in low resolution (336x224, sharp pixel art), then scale it and apply various filters (ultimately shaders), and then use them to render a window decoration, a game frame in a given aspect ratio, and fill the remaining client space.

If you do not create your own back buffer texture, you can still repaint only selected regions of the window, but there is no guarantee that the contents of the window will always be available. Therefore, in such case, each game frame is rendered from scratch.

If you redraw everything that should be visible (including the background) and only call SDL_RenderPresent once at the end then there shouldn’t be any “flash”.

I restructured my game loop to load everything onto the renderer’s backbuffer (that I want to be displayed for that frame), and only after that I call SDL_RenderPresent. It’s working now. I also figured how to implement a little-crosshair that follows the user’s cursor around on the board to guide them in placing pieces. Thank you guys for the the help ! :slight_smile:

1 Like