I suggest you first make sure the escape sequences are actually the bottleneck. I'm able to write 140+ frames per second (25 rows by 80 columns of ASCII text, plus 19 bytes of escape sequence per character, plus a newline per row, plus the escape sequence to home the cursor per frame).
// Quick hack to determine feasibility of high frame rates to console.
#define _CRT_SECURE_NO_WARNINGS // to allow strcpy without a warning
#include <chrono>
#include <iostream>
#include <string>
#include <string_view>
constexpr std::string_view character_sequence = "\x1B[38;2;rrr;ggg;bbbmX";
constexpr auto bytes_per_character = character_sequence.size();
void ClearScreen() {
static constexpr std::string_view sequence = "\x1B[2";
std::cout.write(sequence.data(), sequence.size());
}
void Home() {
static constexpr std::string_view sequence = "\x1B[H";
std::cout.write(sequence.data(), sequence.size());
}
void FillWindowRaw(char *text, int rows, int cols) {
Home();
std::cout.write(text, rows * (bytes_per_character*cols + 1));
}
int main() {
constexpr int kFrames = 1000;
constexpr int kRows = 25;
constexpr int kCols = 80;
char text1[kRows*(bytes_per_character*kCols + 1)];
char text2[kRows*(bytes_per_character*kCols + 1)];
char *p1 = text1;
char *p2 = text2;
for (int row = 0; row < kRows; ++row) {
for (int col = 0; col < kCols; ++col) {
std::strcpy(p1, "\x1B[38;2;255;255;255mX");
p1 += bytes_per_character;
std::strcpy(p2, "\x1B[38;2;000;255;000mO");
p2 += bytes_per_character;
}
*p1++ = '\n';
*p2++ = '\n';
}
const auto started = std::chrono::high_resolution_clock::now();
ClearScreen();
for (int i = 0; i < kFrames/2; ++i) {
FillWindowRaw(text1, kRows, kCols);
FillWindowRaw(text2, kRows, kCols);
}
const auto finished = std::chrono::high_resolution_clock::now();
const auto elapsed = finished - started;
const auto elapsed_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(elapsed).count();
std::cout << "\x1B[37m"; // restore foreground color
std::cout << "Frame: " << kFrames << '\n'
<< "Elapsed: " << elapsed_ms << " ms\n";
if (elapsed_ms > 0) {
const auto fps = 1000.0 * kFrames / elapsed_ms;
std::cout << "FPS: " << fps << std::endl;
}
return 0;
}
I suspect your bottleneck is elsewhere. Perhaps you're using lots of individual calls to send the data to the console or using formatted output to generate the escape sequences on the fly. If that's the case, you might consider building up a screen-sized buffer so that the output is just one call to std::cout.write
per frame. Rewriting the in-memory buffer is likely to be much faster than trying to generate the byte stream on the fly.
That said, the Win32 API does have a programmatic console interface that doesn't put the rendering options into the character sequence. But you'd have to do some profiling to see if that will be fast enough. Microsoft has been decoupling the terminal emulation from the console host so it's now easy to get escape sequence support in terminal windows.