-
Notifications
You must be signed in to change notification settings - Fork 9.1k
Description
WriteConsole() performs reasonably well when writing plain uncolored text to the console (~2ms at best, ~20ms at worst with a 240x64 buffer), but is incredibly slow when writing text with color escape sequences. Below is some code that demonstrates this.
I write an entire screen worth of colored text as fast as possible shifting every character to the one next to it in ASCII as to avoid any possible caching that might happen. I measure the time it takes for each WriteConsole() call and print the results at the end. No third-party libraries are needed to compile.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#define NUM_OF_MEASUREMENTS 100
static LONGLONG perf_counter_freq;
LONGLONG
get_timestamp(void)
{
LARGE_INTEGER timestamp;
QueryPerformanceCounter(×tamp);
LONGLONG result = timestamp.QuadPart;
return result;
}
float
get_seconds_elapsed(LONGLONG start, LONGLONG end)
{
float result = (float)(end - start) / (float)perf_counter_freq;
return result;
}
void
generate_text(char start_char, char *buffer, size_t buffer_size)
{
for (size_t i = 0; i < buffer_size; i++)
{
buffer[i] = 'A' + (((start_char - 'A') + i) % 26);
}
}
char *
copy_string(char *dest, char *source)
{
while (*source)
{
*dest++ = *source++;
}
return dest;
}
int main(int argc, char **argv)
{
HANDLE con_out = CreateFile("CONOUT$", GENERIC_READ | GENERIC_WRITE, FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
if (con_out == INVALID_HANDLE_VALUE) goto cleanup;
DWORD console_mode;
GetConsoleMode(con_out, &console_mode);
console_mode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING | ENABLE_WRAP_AT_EOL_OUTPUT;
SetConsoleMode(con_out, console_mode);
CONSOLE_SCREEN_BUFFER_INFO buffer_info;
GetConsoleScreenBufferInfo(con_out, &buffer_info);
LARGE_INTEGER perf_counter_freq_result;
QueryPerformanceFrequency(&perf_counter_freq_result);
perf_counter_freq = perf_counter_freq_result.QuadPart;
int console_width = (buffer_info.srWindow.Right - buffer_info.srWindow.Left) + 1;
int console_height = (buffer_info.srWindow.Bottom - buffer_info.srWindow.Top);
size_t buffer_size = console_width * console_height;
char *buffer1 = (char*)malloc(buffer_size);
if (!buffer1) goto cleanup;
char *buffer2 = (char*)malloc(buffer_size);
if (!buffer2) goto cleanup;
generate_text('A', buffer1, buffer_size);
generate_text('B', buffer2, buffer_size);
size_t screen_buffer_size = 1048576;
char *screen_buffer = (char*)malloc(screen_buffer_size);
if (!screen_buffer) goto cleanup;
int measurement_index = 0;
int num_of_measurements = NUM_OF_MEASUREMENTS;
float *measurements = (float*)malloc(num_of_measurements*sizeof(float));
if (!measurements) goto cleanup;
int c = 0;
int b = 1;
for (int count = 0; count < num_of_measurements; count++)
{
char *src = b ? buffer1 : buffer2;
char *at = screen_buffer;
at = copy_string(at, "\x1b[H");
for (int j = 0; j < console_height; j++)
{
size_t row_offset = j * console_width;
char *row = &src[row_offset];
for (int i = 0; i < console_width; i++)
{
at = copy_string(at, "\x1b[38;2;");
switch(c)
{
case 1: { at = copy_string(at, "28;215;119m"); } break;
case 2: { at = copy_string(at, "0;159;255m"); } break;
default: { at = copy_string(at, "226;51;73m"); } break;
}
c = (c + 1) % 3;
*at++ = row[i];
at = copy_string(at, "\x1b[0m");
}
at = copy_string(at, "\n");
}
LONGLONG start = get_timestamp();
DWORD written = 0;
size_t chars_to_write = at - screen_buffer;
WriteConsole(con_out, screen_buffer, (DWORD)chars_to_write, &written, 0);
LONGLONG end = get_timestamp();
float seconds_elapsed = get_seconds_elapsed(start, end);
measurements[measurement_index++] = seconds_elapsed;
b = !b;
}
DWORD written = 0;
WriteConsole(con_out, "\x1b[2J\x1b[H", 7, &written, 0);
float fastest = measurements[0];
float slowest = measurements[0];
float total = 0;
for (int i = 0; i < num_of_measurements; i++)
{
float measurement = measurements[i];
fastest = measurement < fastest ? measurement : fastest;
slowest = measurement > slowest ? measurement : slowest;
total += measurement;
}
float average = total / num_of_measurements;
printf("Fastest: %.4f\n", fastest);
printf("Slowest: %.4f\n", slowest);
printf("Average: %.4f\n", average);
cleanup:
if (con_out != INVALID_HANDLE_VALUE) CloseHandle(con_out);
if (buffer1) free(buffer1);
if (buffer2) free(buffer2);
if (screen_buffer) free(screen_buffer);
if (measurements) free(measurements);
}Is there anything I can do to make this run fast enough for animation (at least 30 FPS)? Techniques like only writing what changed are not an option, although I doubt that doing that would be faster than just writing the whole buffer to the console at once. I know about WriteConsoleOutput() and double-buffering with SetConsoleActiveScreenBuffer() but I don't know if they could be made to work with VT escape sequences.