SDL2 documentation suggests that it’s essential to use
SDL_RenderClear() before drawing each frame. But can you safely clear the renderer another way? By using a texture (so long as it has no alpha pixels), or geometry?
SDL2 documentation suggests that it’s essential to use
I don’t see how it would be unsafe not to clear, if you know you’ll touch every pixel in the color buffer. OTH … does it buy you anything? Doesn’t seem like it would be much of an optimization on modern hardware at least.
I know that with OpenGL ES on iOS (admittedly, nobody uses OpenGL ES on iOS anymore) Apple specifically recommends clearing the screen at the start of the frame even if you intend to completely draw over it. The reason being that, according to Apple, their driver uses clearing the screen as a hint that you don’t care about the framebuffer’s previous contents. Otherwise it cannot guarantee you don’t intend to merely render a couple things on top of the previous frame, so it has to waste time copying the previous framebuffer back into the tilebuffer Just In Case. I imagine OpenGL ES drivers on Android do something similar.
Other backend APIs and/or other platforms may also not like it if you don’t clear the screen at the beginning of the frame.
Honestly, clearing the screen is extremely fast on any GPU made in the last decade (at least), and the old days of squeezing out a little more performance by not clearing the screen if you’re gonna render over the whole thing are over.
edit: SDL may also do some internal work when the screen is cleared, though I haven’t looked at the source code to confirm that.
Clearing the screen just means “filling the entire screen with a colour, so you don’t see the previous frame still there when the next one starts”.
Funnily enough, in some commercially released video games (Mario Kart 8 and Super Mario 3D World come to mind), when you go out of bounds you get a lot of visual artefacts that suggest that the screen isn’t cleared! Going out of bounds in those games allows you to “draw” on the screen with your player character, because the last frame will still be there when the next frame is drawn. It looks ridiculous, but it’s ultimately harmless behaviour.
You could indeed clear the screen manually, by making a big screen-filling texture draw, or making geometry big enough that it spans all the pixels on your screen. Ultimately, however, it doesn’t really matter as long as it works well and looks good.
All very interesting replies - thanks guys.
One of my games uses a texture to draw a static opaque backdrop every frame. Doing a RenderClear just seems unnecessary to me. Another of my games draws raytraced pixels to a texture, so again, no need to clear. In the later case, it’s very CPU intensive, especially on mobile, so any optimization is essential to squeeze a higher FPS rate. I’ll do tests later to see how much difference it makes to the FPS though.
I think I’ll leave SDL_RenderClear out of my code. I’ve tested it and it seems to work well on Android & Windows on all renderers. If a GPU is looking for SDL_RenderClear() to know a new frame is starting and will glitch if it doesn’t see it, that seems more of a hardware design failure. Very few games have a single colour as a background, so it seems silly to draw a colour that’ll never be seen.
Just to reiterate what was said earlier: on most phone GPUs, when you begin rendering to something (like the backbuffer, or a separate render target texture) the first thing the GPU will have to do is copy the existing contents of that buffer/texture from RAM into on-chip memory. The copy operation is expensive.
If the first operation is RenderClear instead of a draw, the driver will know to skip that copy operation. So clearing the backbuffer (or a render target) before drawing to it is much faster than avoiding a clear, on the platforms where that performance matters most (phones).
Why doesn’t
SDL_RenderPresent() do all that?
SDL_RenderPresent isn’t always the right time to clear the backbuffer for the next frame. For example if you start the next frame by rendering to a separate render target, and then the second half of the next frame is spent rendering to the backbuffer, if RenderPresent had cleared the backbuffer, the driver would have had to copy the cleared contents of the backbuffer from on-chip memory to RAM because of the clear.
Render Pass-style APIs make all this explicit (making you choose what to do when a render target is loaded into on-chip memory when beginning a render pass). SDL_Render is not a render pass-style API, and it can’t really automatically know what your code is going to do.
Hey, I do! I never figured out how to do Metal on iOS and I love Objective C, so I do all my games development with Objective C and the OpenGL code I’ve cobbled together over the years. I port my Objective C to C++ and use that with SDL for the Android and Windows versions of my games.
If Apple kill off OpenGL I’ll have to make the decision of either figuring out Metal, or write in C++ (yuk) and use SDL on iOS too.
I’ve knocked-up a test to see what the real-life results of this are. Only tested on Windows, but there doesn’t seem to be any difference in frame rate.
SDL_RenderClear() is lightning fast, on Windows at least, but removing it doesn’t seem to upset my GeForce card, so there doesn’t seem to be an added expense by removing it.
#include <SDL.h> int main(int argc, char** argv) { SDL_SetHint("SDL_RENDER_BATCHING", "1"); SDL_Init(SDL_INIT_VIDEO); SDL_Window * w = SDL_CreateWindow("", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_RESIZABLE); SDL_Renderer * r = SDL_CreateRenderer(w, -1, SDL_RENDERER_ACCELERATED); unsigned frames = 0; auto nextFrame = SDL_GetPerformanceCounter() + SDL_GetPerformanceFrequency(); bool running = true; bool clear = true; while (running) { SDL_PumpEvents(); SDL_Event event; while (SDL_PeepEvents(&event, 1, SDL_GETEVENT, SDL_FIRSTEVENT, SDL_LASTEVENT) == 1) { if (event.type == SDL_QUIT) running = false; else if (event.type == SDL_KEYDOWN) clear = !clear; } if (clear) { SDL_SetRenderDrawColor(r, 0, 0, 0, SDL_ALPHA_OPAQUE); SDL_RenderClear(r); } SDL_SetRenderDrawColor(r, 0, 64, 0, SDL_ALPHA_OPAQUE); SDL_FRect fill{ 0,0,800,600 }; SDL_RenderFillRectF(r, &fill); SDL_RenderPresent(r); if (SDL_GetPerformanceCounter() > nextFrame) { char str[50]; str[0] = clear? 'C' : ' '; SDL_itoa(frames, str+1, 10); SDL_SetWindowTitle(w, str); frames = 0; nextFrame += SDL_GetPerformanceFrequency(); } else frames++; } SDL_Quit(); return 0; }
Frame rate is displayed in the window title. Press any key to toggle the use of
SDL_RenderClear(). A “C” in the title will indicate it’s being used.
On a normal (not tile-based) desktop GPU you probably won’t see a difference, since there’s no tile buffer; they just render directly to the backbuffer. However, many (most? all?) mobile GPUs, including every GPU used in every Apple mobile device (and in all their desktop M-series GPUs), use a tile-based rendering method where the screen is rendered in tiles (chunks) to an on-chip tile buffer, which are then copied to system RAM as each tile is completed.
Since OpenGL has no way to specify that you aren’t interested in preserving the previous framebuffer’s contents when starting a new frame (unlike Metal, Vulkan, and DX12), on mobile GPUs you have to clear the screen at the start of the frame to tell the GPU driver that it doesn’t need to copy the contents of the previous frame into the tile buffer (which is slow, and wasted if you immediately draw over it).
In any case, regardless of GPU architecture, clearing the screen is done by the GPU, not the CPU, and is extremely fast. There really isn’t a good reason to not do it on modern hardware.
Also, just FYI, this advice comes straight from Apple:
Most often, a framebuffer’s previous contents are not necessary for drawing the next frame. Avoid the performance cost of loading previous buffer contents by calling
glClearwhenever you begin rendering a new frame.
Of course, this only applies to OpenGL and OpenGL ES, but I’m assuming that
SDL_RenderClear() calls
glClear() on the GL or GL ES backends. It’s still a good idea to call
SDL_RenderClear() in case you port your game to Android or Linux, since there’s no Vulkan backend for SDL.
I’ve now tested on all combinations of OpenGL on Windows and Android. Removing
SDL_RenderClear() increases the FPS. I even tested on an ancient Nexus 7 and get a big FPS increase without clearing.
I understand what you guys are saying in theory, but in practice it’s just not happening like that. So I think either SDL2 is doing something clever, or the Apple techies are other-thinking it and spreading myths.
Therefore, I recommend everyone to remove
SDL_RenderClear() if they don’t need it. It increases performance in every case I’ve tested. Until anyone can demonstrate a live test where it doesn’t.
That link I posted was literally Apple’s advice on increasing OpenGL ES performance on iOS devices, so not “over-thinking”; clearing the screen is what Apple themselves recommend. IIRC, if you use Apple’s OpenGL ES performance monitoring tools while running your iOS app, it will chastise you for not clearing the screen before drawing.
It’s possible the GL ES driver for whatever Android device you’re testing doesn’t load the tile buffer with the framebuffer contents anyway, or uses the CPU to clear the backbuffer instead of just clearing the tile buffer with the GPU.
I do! I have to use OpenGL rather than Metal, because I need the same code to run on all platforms (this may be user-written BASIC code out of my control, not just the code of my app). I also need to use ES 1 rather than ES 2, because I’m crucially reliant on the
glLogicOp() functionality, which is only in ES 1.
So, until Apple actually removes it anyway (at which point my app will stop working, permanently), I will continue to use and rely on OpenGL ES in iOS. Fortunately it works perfectly.
But you say you’ve only tested it in Windows and Android! How can you make that recommendation if you haven’t tested it in MacOS and iOS, especially when it’s specifically Apple that states you should call it? My recommendation is to do what SDL2 tells you to do, because it may matter on some platforms and one day it will bite you if you don’t.
It’s worth pointing out that Apple only says this for OpenGL ES (and SDL’s OpenGL ES backend does call
glClear()). It’s only a performance gain if you’re using the OpenGL ES backend.
With Metal (and Vulkan, and DX12) you have to explicitly specify what to do with the backbuffer/tilebuffer when starting a new frame (technically, when starting a new command buffer): load the previous frame’s contents, clear the backbuffer/tilebuffer, or that you don’t care.
Looking at the code, SDL’s Metal backend defaults to specifying “don’t care”, in which case the driver won’t copy the previous frame’s contents back into the tile buffer on tile-based GPUs, but doesn’t clear it either. Calling
SDL_RenderClear() with the Metal backend causes it to discard the current command buffer and create a new one with “clear” specified as the load action.
In summary, leaving out
SDL_RenderClear() can hurt performance on some platforms with certain backends, increase performance on others, or not really change performance either way on yet others. I still think it’s a good idea to call it by default unless you’re absolutely certain it’s killing your performance. And if you must leave it out, understand that the day may come when leaving it out makes things go wonky.
Throttling the performance of all platforms just in case one deprecated platform might have a performance hit (which has yet to be demonstrated) makes absolutely no sense. The FPS of my apps tell me all I need to know, which is
SDL_RenderClear() is a performance hit if the whole screen is drawn over anyway, and if that becomes an issue on any other platform I build on, it’s fixed with 2 lines of code wrapped in an #ifdef, so it’s not going to ever bite me.
I do not understand how removing only SDL_RenderClear can SIGNIFICANTLY increase performance? How often do you call for this function in your code per game cycle? In our project, with VSYNC turned on, we can’t see any changes in performance.
Surely you don’t want to have to re-build and re-release your app if (for example) a Windows upgrade or an Android upgrade breaks it? The only way to avoid that is to do what the documentation states you should do, not take risks which you happen to get away with now but may not in the future.