24

I'm attempting to make a console side scrolling shooter, I know this isn't the ideal medium for it but I set myself a bit of a challenge.

The problem is that whenever it updates the frame, the entire console is flickering. Is there any way to get around this?

I have used an array to hold all of the necessary characters to be output, here is my updateFrame function. Yes, I know system("cls") is lazy, but unless that's the cause of problem I'm not fussed for this purpose.

void updateFrame()
{
system("cls");
updateBattleField();
std::this_thread::sleep_for(std::chrono::milliseconds(33));
for (int y = 0; y < MAX_Y; y++)
{
    for (int x = 0; x < MAX_X; x++)
    {
        std::cout << battleField[x][y];
    }
    std::cout << std::endl;
}
}
ScottishTapWater
  • 3,656
  • 4
  • 38
  • 81
  • There are duplicates for this. If nothing, you should be halting the thread **after** you print something, not when the screen is cleared, but I guess the flickering will be still visible on Windows. – LogicStuff Jan 17 '16 at 19:21
  • 2
    you should try to use ncurses, it allows to do whatever you want in the shell. – Pierre Emmanuel Lallemant Jan 17 '16 at 19:36
  • @LogicStuff: Duplicates or not, this is a question I've been wanting to answer for a very long time, since I spent a long time fiddling with similar things myself. There's ways to avoid the flickering. – Cameron Jan 17 '16 at 19:36

4 Answers4

53

Ah, this brings back the good old days. I did similar things in high school :-)

You're going to run into performance problems. Console I/O, especially on Windows, is slow. Very, very slow (sometimes slower than writing to disk, even). In fact, you'll quickly become amazed how much other work you can do without it affecting the latency of your game loop, since the I/O will tend to dominate everything else. So the golden rule is simply to minimize the amount of I/O you do, above all else.

First, I suggest getting rid of the system("cls") and replace it with calls to the actual Win32 console subsystem functions that cls wraps (docs):

#define NOMINMAX
#define WIN32_LEAN_AND_MEAN
#include <Windows.h>

void cls()
{
    // Get the Win32 handle representing standard output.
    // This generally only has to be done once, so we make it static.
    static const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

    CONSOLE_SCREEN_BUFFER_INFO csbi;
    COORD topLeft = { 0, 0 };

    // std::cout uses a buffer to batch writes to the underlying console.
    // We need to flush that to the console because we're circumventing
    // std::cout entirely; after we clear the console, we don't want
    // stale buffered text to randomly be written out.
    std::cout.flush();

    // Figure out the current width and height of the console window
    if (!GetConsoleScreenBufferInfo(hOut, &csbi)) {
        // TODO: Handle failure!
        abort();
    }
    DWORD length = csbi.dwSize.X * csbi.dwSize.Y;
    
    DWORD written;

    // Flood-fill the console with spaces to clear it
    FillConsoleOutputCharacter(hOut, TEXT(' '), length, topLeft, &written);

    // Reset the attributes of every character to the default.
    // This clears all background colour formatting, if any.
    FillConsoleOutputAttribute(hOut, csbi.wAttributes, length, topLeft, &written);

    // Move the cursor back to the top left for the next sequence of writes
    SetConsoleCursorPosition(hOut, topLeft);
}

Indeed, instead of redrawing the entire "frame" every time, you're much better off drawing (or erasing, by overwriting them with a space) individual characters at a time:

// x is the column, y is the row. The origin (0,0) is top-left.
void setCursorPosition(int x, int y)
{
    static const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    std::cout.flush();
    COORD coord = { (SHORT)x, (SHORT)y };
    SetConsoleCursorPosition(hOut, coord);
}

// Step through with a debugger, or insert sleeps, to see the effect.
setCursorPosition(10, 5);
std::cout << "CHEESE";
setCursorPosition(10, 5);
std::cout 'W';
setCursorPosition(10, 9);
std::cout << 'Z';
setCursorPosition(10, 5);
std::cout << "     ";  // Overwrite characters with spaces to "erase" them
std::cout.flush();
// Voilà, 'CHEESE' converted to 'WHEEZE', then all but the last 'E' erased

Note that this eliminates the flicker, too, since there's no longer any need to clear the screen completely before redrawing -- you can simply change what needs changing without doing an intermediate clear, so the previous frame is incrementally updated, persisting until it's completely up to date.

I suggest using a double-buffering technique: Have one buffer in memory that represents the "current" state of the console screen, initially populated with spaces. Then have another buffer that represents the "next" state of the screen. Your game update logic will modify the "next" state (exactly like it does with your battleField array right now). When it comes time to draw the frame, don't erase everything first. Instead, go through both buffers in parallel, and write out only the changes from the previous state (the "current" buffer at that point contains the previous state). Then, copy the "next" buffer into the "current" buffer to set up for your next frame.

char prevBattleField[MAX_X][MAX_Y];
std::memset((char*)prevBattleField, 0, MAX_X * MAX_Y);

// ...

for (int y = 0; y != MAX_Y; ++y)
{
    for (int x = 0; x != MAX_X; ++x)
    {
        if (battleField[x][y] == prevBattleField[x][y]) {
            continue;
        }
        setCursorPosition(x, y);
        std::cout << battleField[x][y];
    }
}
std::cout.flush();
std::memcpy((char*)prevBattleField, (char const*)battleField, MAX_X * MAX_Y);

You can even go one step further and batch runs of changes together into a single I/O call (which is significantly cheaper than many calls for individual character writes, but still proportionally more expensive the more characters are written).

// Note: This requires you to invert the dimensions of `battleField` (and
// `prevBattleField`) in order for rows of characters to be contiguous in memory.
for (int y = 0; y != MAX_Y; ++y)
{
    int runStart = -1;
    for (int x = 0; x != MAX_X; ++x)
    {
        if (battleField[y][x] == prevBattleField[y][x]) {
            if (runStart != -1) {
                setCursorPosition(runStart, y);
                std::cout.write(&battleField[y][runStart], x - runStart);
                runStart = -1;
            }
        }
        else if (runStart == -1) {
            runStart = x;
        }
    }
    if (runStart != -1) {
        setCursorPosition(runStart, y);
        std::cout.write(&battleField[y][runStart], MAX_X - runStart);
    }
}
std::cout.flush();
std::memcpy((char*)prevBattleField, (char const*)battleField, MAX_X * MAX_Y);

In theory, that will run a lot faster than the first loop; however in practice it probably won't make a difference since std::cout is already buffering writes anyway. But it's a good example (and a common pattern that shows up a lot when there is no buffer in the underlying system), so I included it anyway.

Finally, note that you can reduce your sleep to 1 millisecond. Windows will actually often sleep longer, typically up 15ms, but it will prevent your CPU core from reaching 100% usage with a minimum of additional latency.

Note that this not at all the way "real" games do things; they almost always clear the buffer and redraw everything every frame. They don't get flickering because they use the equivalent of a double-buffer on the GPU, where the previous frame stays visible until the new frame is completely finished being drawn.

Bonus: You can change the colour to any of 8 different system colours, and the background too:

void setConsoleColour(unsigned short colour)
{
    static const HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
    std::cout.flush();
    SetConsoleTextAttribute(hOut, colour);
}

// Example:
const unsigned short DARK_BLUE = FOREGROUND_BLUE;
const unsigned short BRIGHT_BLUE = FOREGROUND_BLUE | FOREGROUND_INTENSITY;

std::cout << "Hello ";
setConsoleColour(BRIGHT_BLUE);
std::cout << "world";
setConsoleColour(DARK_BLUE);
std::cout << "!" << std::endl;
Cameron
  • 96,106
  • 25
  • 196
  • 225
  • 2
    I've not managed to implement everything that you suggested so far, however, the double buffering method appears to have solved the flickering (I'm tempted to even update that buffer on a separate thread so that it is further optimized on multicore machines - I think that works). However, I'm not entirely sure what the `std::memcpy((char*)prevBattleField, (char const*)battleField, MAX_X * MAX_Y);` section does, or what you mean by being contiguous in memory, so further clarification might help. Thanks for everything though! – ScottishTapWater Jan 18 '16 at 18:48
  • 2
    @James: Don't add threads to the mix if you can help it -- it likely won't improve performance, but it will vastly complicate your code and may introduce subtle non-deterministic bugs (race conditions). The `memcpy` just copies all the bytes from `battleField` to `prevBattleField` so that the two contain identical values after (setting up `prevBattleField` for the next frame). A 'contiguous' region just means the bytes are all next to each other in memory. With `a[x][y]`, all the bytes for a given column are contiguous, but you want all the bytes for a given row to be contiguous (`a[y][x]`). – Cameron Jan 18 '16 at 20:05
  • 3
    @Ben: Great :D It's an answer I had wanted to write for years, so when the opportunity came up I took it. – Cameron May 20 '16 at 16:49
  • in your second example, what is the purpose of `std::cout.flush();`? is it still needed if the program terminates there? – Alex Jul 22 '20 at 22:51
  • The flush is needed to send all text written to `cout` to the console; the default is for `cout` to internally buffer all written characters until an `endl` is written (normally this is desired, because it improves performance by not writing every character individually, but in this case we need to manually synchronize the stream because we're manipulating properties that `cout` doesn't know about, and thus does not take into account in its buffering). – Cameron Jul 23 '20 at 13:09
  • Great answer! You must of had a lot of fun in high school! Quick question though, in your first example, what are the `NOMINMAX` and `WIN32_LEAN_AND_MEAN` used for? Haven't seen those before – Alex Oct 18 '20 at 17:33
  • 1
    `NOMINMAX` prevents `windows.h` from defining `min` and `max` macros which interfere with `std::min` and `std::max`. `WIN32_LEAN_AND_MEAN` prevents `windows.h` from including its more obscure sub-headers, reducing global namespace pollution. They can be omitted here, I just define them by default when including `windows.h`. – Cameron Oct 19 '20 at 13:29
10

system("cls") is the cause of your problem. For updating frame your program has to spawn another process and then load and execute another program. This is quite expensive. cls clears your screen, which means for a small amount of the time (until control returns to your main process) it displays completely nothing. That's where flickering comes from. You should use some library like ncurses which allows you to display the "scene", then move your cursor position to <0,0> without modifying anything on the screen and redisplay your scene "over" the old one. This way you'll avoid flickering, because your scene will always display something, without 'completely blank screen' step.

nsilent22
  • 2,763
  • 10
  • 14
2

One method is to write the formatted data to a string (or buffer) then block write the buffer to the console.

Every call to a function has an overhead. Try go get more done in a function. In your Output, this could mean a lot of text per output request.

For example:

static char buffer[2048];
char * p_next_write = &buffer[0];
for (int y = 0; y < MAX_Y; y++)
{
    for (int x = 0; x < MAX_X; x++)
    {
        *p_next_write++ = battleField[x][y];
    }
    *p_next_write++ = '\n';
}
*p_next_write = '\0'; // "Insurance" for C-Style strings.
cout.write(&buffer[0], std::distance(p_buffer - &buffer[0]));

I/O operations are expensive (execution-wise), so the best use is to maximize the data per output request.

Thomas Matthews
  • 56,849
  • 17
  • 98
  • 154
1

With the accepted answer the rendering would still be flickering if your updated area is big enough. Even if you animate a single horizontal line to move from top to bottom you'll most of the time see it like this:

                     ###########################
#####################

This happens because you see the previous frame in the process of being overwritten by a newer one. For complex scenes like video or 3D rendering, this is barely acceptable. The proper way to do it is by using the double buffering technique. The idea is to draw all the "pixels" into an off-screen buffer and when done display it all at once. Gladly Windows console supports this approach pretty well. Please see the full example on how to do the double buffering below:

#include <chrono>
#include <thread>
#include <Windows.h>
#include <vector>


const unsigned FPS = 25;
std::vector<char> frameData;
short cursor = 0;

// Get the intial console buffer.
auto firstBuffer = GetStdHandle(STD_OUTPUT_HANDLE);

// Create an additional buffer for switching.
auto secondBuffer = CreateConsoleScreenBuffer(
    GENERIC_READ | GENERIC_WRITE,
    FILE_SHARE_WRITE | FILE_SHARE_READ,
    nullptr,
    CONSOLE_TEXTMODE_BUFFER,
    nullptr);

// Assign switchable back buffer.
HANDLE backBuffer = secondBuffer;
bool bufferSwitch = true;

// Returns current window size in rows and columns.
COORD getScreenSize()
{
    CONSOLE_SCREEN_BUFFER_INFO bufferInfo;
    GetConsoleScreenBufferInfo(firstBuffer, &bufferInfo);
    const auto newScreenWidth = bufferInfo.srWindow.Right - bufferInfo.srWindow.Left + 1;
    const auto newscreenHeight = bufferInfo.srWindow.Bottom - bufferInfo.srWindow.Top + 1;

    return COORD{ static_cast<short>(newScreenWidth), static_cast<short>(newscreenHeight) };
}

// Switches back buffer as active.
void swapBuffers()
{
    WriteConsole(backBuffer, &frameData.front(), static_cast<short>(frameData.size()), nullptr, nullptr);
    SetConsoleActiveScreenBuffer(backBuffer);
    backBuffer = bufferSwitch ? firstBuffer : secondBuffer;
    bufferSwitch = !bufferSwitch;
    std::this_thread::sleep_for(std::chrono::milliseconds(1000 / FPS));
}

// Draw horizontal line moving from top to bottom.
void drawFrame(COORD screenSize)
{
    for (auto i = 0; i < screenSize.Y; i++)
    {
        for (auto j = 0; j < screenSize.X; j++)
            if (cursor == i)
                frameData[i * screenSize.X + j] = '@';
            else
                frameData[i * screenSize.X + j] = ' ';
    }

    cursor++;
    if (cursor >= screenSize.Y)
        cursor = 0;
}

int main()
{
    const auto screenSize = getScreenSize();
    SetConsoleScreenBufferSize(firstBuffer, screenSize);
    SetConsoleScreenBufferSize(secondBuffer, screenSize);
    frameData.resize(screenSize.X * screenSize.Y);

    // Main rendering loop: 
    // 1. Draw frame to the back buffer.
    // 2. Set back buffer as active.
    while (true)
    {
        drawFrame(screenSize);
        swapBuffers();
    }
}

In this example, I went with a static FPS value for the sake of simplicity. You may also want to introduce some functionality to stabilize frame frequency output by counting the actual FPS. That would make your animation run smoothly independent of the console throughput.

Tali Oat
  • 121
  • 1
  • 5