3

I am looking for a function that draws a filled circle using SDL2 without using a renderer at all. I currently have this:

void Circle(int center_x, int center_y, int radius, SDL_Color color) {
  eraseOldCircle();

  uint32_t *pixels = (uint32_t *) windowSurface->pixels;
  SDL_PixelFormat *windowFormat = windowSurface->format;
  SDL_LockSurface(windowSurface); // Lock surface for direct pixel access capability

  int radiussqrd = radius * radius;

  for(int x=center_x-radius; x<=center_x+radius; x++) {
    int dx = center_x - x;
    for(int y=center_y-radius; y<=center_y+radius; y++) {
      int dy = center_y - y;
      if((dy * dy + dx * dx) <= radiussqrd) {
        pixels[(y * WIDTH + x)] = SDL_MapRGB(windowFormat, color.r, color.g, color.b);
      }
    }
  }

  SDL_UnlockSurface(windowSurface);
  SDL_UpdateWindowSurface(window);
}

which has been adapted from another function I found here, it draws the pixels directly to the windowSurface after calling eraseOldCircle (which puts the game's background image back to the previous position of the circle, effectively erasing it from there.) but it is still too slow for what I need (probably the maths?). What would be the fastest way to draw a circle using direct pixel access? I need it to be high speed so I can use it in a 2D game. I haven't been able to find anything until now, everything I see uses SDL_Renderer, but I should strictly never use it.

Here is eraseOldCircle() in case it helps:

void eraseOldCircle() {
  //Calculate previous position of ball
  SDL_Rect pos = {circlePosition.x-(radius+steps), circlePosition.y-(radius+steps), radius*radius, radius*2+steps};
  SDL_BlitSurface(backgroundImage, &pos, windowSurface, &pos);
}
ggorlen
  • 44,755
  • 7
  • 76
  • 106
  • If you think of the circle as a stack of rows of pixels, then all you need is the endpoints of those rows, and then you can use e.g. a loop, `memcpy()`, or `std::copy()` to fill in the rows quickly. For the endpoints, all you need is one quadrant's worth of deltas for a given radius. If the radii are known in advance, you can even precompute the deltas. I'm not posting this as an answer because an answer would probably call for more detail, but maybe this will help point you in the right direction. – scg Jan 14 '21 at 17:38
  • @scg Thanks a lot, I'll try to see if I can do something with that – Κοσμάς Ράπτης Jan 14 '21 at 17:53
  • Are you sure the fastest way isn't SDL_Renderer? – user253751 Jan 14 '21 at 18:03
  • @user253751 The game needs to be able to run on the original Xbox (ported using the nxdk toolchain), and SDL_Renderer is very slow in it. – Κοσμάς Ράπτης Jan 14 '21 at 18:19
  • I thought of something that might helps, instead of drawing individual pixels, maybe just draw a line for each Y value of the circle, beginning from the left part of the circumference to the other, and the length changes each time. you can have preloaded sin and cos values in an array to access so you don't compute them at runtime too – Ggsgn Hdjwngnf Jan 17 '21 at 00:20
  • @Ggsgn Hdjwngnf: That's essentially the same as what I posted above. – scg Jan 17 '21 at 04:05

2 Answers2

3

I'm not too sure how to do it with surfaces and memory management and all that, but if this helps, here is a version using an SDL_Renderer that runs pretty quickly:

void draw_circle(SDL_Renderer *renderer, int x, int y, int radius, SDL_Color color)
{
    SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
    for (int w = 0; w < radius * 2; w++)
    {
        for (int h = 0; h < radius * 2; h++)
        {
            int dx = radius - w; // horizontal offset
            int dy = radius - h; // vertical offset
            if ((dx*dx + dy*dy) <= (radius * radius))
            {
                SDL_RenderDrawPoint(renderer, x + dx, y + dy);
            }
        }
    }
}
Ggsgn Hdjwngnf
  • 133
  • 2
  • 10
1

If you draw many circles, I would guess SDL_UpdateWindowSurface is where you spend the most time. Try this instead

  SDL_LockSurface
  // erase and draw all circles (possibly >1000)
  SDL_UnlockSurface
  SDL_UpdateWindowSurface

You can optimize your circle drawing code a bit, but it is probably fast enough. I also think that SDL_Renderer is probably fast enough.

The documentation for SDL_UpdateWindowSurface says it will copy the surface to the screen. You only need to do this once per frame.

Peer Sommerlund
  • 502
  • 6
  • 14