Which SDL_Rect approach is better for blitting?


#1

Let’s say I have S sprites, each of which is one of M different models, each model has A different action animations, each animation consists of F different frames, each frame has a duration of D ticks. The values of A, F, and D are not constant for each model, action, and frame, respectively. As each of these values scales, is it more efficient to use a single SDL_Rect object for all blits and compute the dimensions before each blit operation, or create an SDL_Rect for each frame and organize them into some structure, e.g. linked list, and then pass a pointer to the SDL_Rect for the necessary frame? In other words, is there a point where the memory overhead of keeping x separate SDL_Rects outweighs the time needed to compute one for y blits, or is the better approach something else entirely?


#2

Kits, cats, sacks, and wives, how many were going to St. Ives?


#3

Not 100 % sure what your asking, but I will have a go anyways.

You could have one dest and one source rect for each game object like player and enemy.

If you have a level with 100s of tiles you can you 1 rect for dest and 1 for source where the tile id is used
to determine which part of the surface / texture to use.


#4

I should have just looked at that part of your post initially. If you’re blitting to/from surfaces or textures, having a single large surface/texture and blitting around will be faster than having a bunch of smaller surfaces/textures and blitting to/from them. I posted something similar to this a short while ago:
Texture performance for animation


#5

OK, that first post was not as clear as it sounded in my head. Let’s say I have a bunch of sprite sheets laid out like this:

A1e A2e A3e A4e B1e C1e C2e C3e D1e D2e D3e D4e D5e D6e
A1n A2n A3n A4n B1n C1n C2n C3n D1n D2n D3n D4n D5n D6n
...

A…D are different actions, which vary by duration and by sprite (the next one can be A1…A4, B1, C1…C6, F1…F5, etc.). How would memory use vs. speed compare given the following options:

Option A (store coordinates, make temporary SDL_Rect each blit):

struct Frame {
  int x; // y can be inferred from direction
  Frame* next;
}

struct Sprite {
  SDL_Texture * sheet; // if one for each sprite
  Frame* current_frame;
  int current_direction, w, h;
}

int DrawSprite(Sprite* s, int x, int y) {
  SDL_Rect src = {
    s->current_frame->x,
    s->current_frame->x * s->h * s->current_direction,
    s->w, s->h};
  SDL_Rect dest = {x, y, s->w, s->h};
  SDL_RenderCopy(renderer, s->sheet, &src, &dest);
}
  if (need_next_frame) GetNextFrame(s);

Option B (store SDL_Rect for each frame)

struct Frame {
  SDL_Rect* frame[4] // one for each direction
  Frame* next;
}

struct Sprite {
  SDL_Texture * sheet; // if one for each sprite
  Frame* current_frame;
  int current_direction;
}

int DrawSprite(Sprite* s, int x, int y) {
  SDL_Rect dest = {x, y, s->current_frame->frame->w, s->current_frame->frame->h};
  SDL_RenderCopy(renderer, s->sheet, s->current_frame, &dest);
}

// if each sprite has its own texture, no point in making 
// identical SDL_Rects for sprites that have the same dimensions.
SDL_Rect* MakeFrame(int x, int y, int w, int h) {
  SDL_Rect frame;
  if ((frame = FindFrame(x,y,w,h) == NULL)
    frame = {x,y,w,h};
  return &frame;
}

Option C (same as A, but calculate SDL_Rect each time frame changes, not each blit):

struct Frame {
  int x; // y can be inferred from direction
  Frame* next;
}

  struct Sprite {
  SDL_Texture * sheet; // if one for each sprite
  Frame* current_frame;
  int current_direction;
  SDL_Rect current_rect;
}

SDL_Rect* GetNextFrame(Sprite* s) {
    s->current_frame = s->current_frame->next;
    s->rect = {s->current_frame->x, 
    s->current_frame->x * s->h * s->current_direction,
    s->w, s->h};
}

int DrawSprite(Sprite* s, int x, int y) {
  SDL_Rect dest = {x, y, s->w, s->h};
  SDL_RenderCopy(renderer, s->sheet, &s->current_rect, &dest);
}

I’m sure there are errors, but hopefully you get the idea. All options could be done with one texture for all sprites, although then there wouldn’t be any identical SDL_Rects to reuse in B. As the number of blits increases relative to the number of frames, is there a point at which B or C make sense, i.e. the memory required to store a lot of SDL_Rects is a reasonable trade-off for the time spent recalculating the temporary SDL_Rect for each blit? Is there an option D that I’m oblivious of?


#6

Recalculating where the SDL_Rect is will not slow your program down as much as multiple draw calls will. In the link I posted, it basically states that blitting from one large texture will usually be faster than drawing from a bunch of smaller textures. Having a few large sprite sheets and drawing from them would probably work best for you. The game Factorio, uses the large sprite sheets method and it’s pretty fast, (WARNING: that game is basically crack in electronic form). Also, their Friday Fun Facts can be very interesting.

Calculating the new position for each SDL_Rect should only take a few CPU instructions, and the amount of time those take are measured in nanoseconds on gigahertz CPUs. If you’re just using simple rectangles to draw from, then you’ll have to decide whether you want slightly more performance, or more memory usage.

As long as your program knows where the start and stop of each sprite and group of sprites is, then you can reuse SDL_Rects if the sprites are the same size. Do keep in mind that you don’t need to keep all of your sprites the same size, you can mix them so long as you’re only blitting the portions that you need.


#7

You should implement whichever approach is easier for you to implement
and maintain. Once the whole game is finished, IF AND ONLY IF it is too
slow, use a profiler to find out which part is the bottleneck.
IF AND ONLY IF it is your SDL_Rect code, act according to the
profiler’s data.

MSB