0

As the title suggests I'm having an issue with my windows console application in c++. So far I've created a class, Console, to represent all of the functions I repeatedly use to construct a windows console. I implemented a line drawing algorithm and had up to 500 fps when filling the screen with magenta characters and drawing a line in white. However, I implemented a triangle drawing algorithm next (just three line drawing calls consecutively) and was surprised to find out that the frame rate dropped to about 20. I removed this code again but the bad frame rate persisted. I really don't know why this has happend because I've essentially ended up not changing anything.

For reference, here is the Console code (without header):

Console::Console(int width, int height):
    m_handle(GetStdHandle(STD_OUTPUT_HANDLE)),
    m_width(width),
    m_height(height),
    m_screen({ 0, 0, 1, 1 }),
    m_title(L"Demo"),
    m_buffer(new CHAR_INFO[(size_t)m_width * (size_t)m_height])
{
    memset(m_buffer, 0, sizeof(CHAR_INFO) * m_width * m_height);
}

Console::~Console()
{
    if (m_buffer)
        delete[] m_buffer;
}

int Console::Construct(int char_w, int char_h)
{
    if (m_handle == INVALID_HANDLE_VALUE)
        return BAD_HANDLE;

    if (!SetConsoleWindowInfo(m_handle, true, &m_screen))
        return WINDOW_INFO_ERROR;

    COORD screen_coord = { (short)m_width, (short)m_height };

    if (!SetConsoleScreenBufferSize(m_handle, screen_coord))
        return SCREEN_BUFFER_SIZE_ERROR;


    CONSOLE_FONT_INFOEX cinfo;
    cinfo.cbSize = sizeof(cinfo);
    cinfo.dwFontSize.X = (short)char_w;
    cinfo.dwFontSize.Y = (short)char_h;
    cinfo.FontFamily = FF_DONTCARE;
    cinfo.FontWeight = FW_NORMAL;
    cinfo.nFont = 0;

    wcscpy_s(cinfo.FaceName, L"Consolas");

    if (!SetCurrentConsoleFontEx(m_handle, false, &cinfo))
        return CONSOLE_FONT_ERROR;

    CONSOLE_SCREEN_BUFFER_INFO cbuffinfo;

    if (!GetConsoleScreenBufferInfo(m_handle, &cbuffinfo))
        return GET_BUFFER_INFO_ERROR;

    if (cbuffinfo.dwMaximumWindowSize.X < m_width)
        return HORIZONTAL_SIZE_TOO_LARGE_ERROR;

    if (cbuffinfo.dwMaximumWindowSize.Y < m_height)
        return VERTICAL_SIZE_TOO_LARGE_ERROR;

    m_screen = { 0, 0, (short)m_width - 1, (short)m_height - 1 };

    if (!SetConsoleWindowInfo(m_handle, true, &m_screen))
        return WINDOW_INFO_ERROR;

    if (!SetConsoleCtrlHandler((PHANDLER_ROUTINE)HandleClose, true))
        return CLOSE_HANDLER_ERROR;

    return OK;
}

void Console::Start()
{
    active = true;

    std::thread t(&Console::DoLoop, this);

    t.join();
}

void Console::DoLoop()
{
    if (!OnCreate())
        active = false;

    std::chrono::high_resolution_clock::time_point t1, t2;

    t1 = std::chrono::high_resolution_clock::now();
    t2 = t1;

    while (active)
    {
        t2 = std::chrono::high_resolution_clock::now();
        std::chrono::duration<float> diff = t2 - t1;
        t1 = t2;

        float dt = diff.count();

        if (!OnUpdate(dt))
            active = false;

        wchar_t title_buff[256];
        swprintf_s(title_buff, L"Console Application - %s - FPS: %3.2f", m_title.c_str(), 1.0f / dt);
        SetConsoleTitle(title_buff);
        WriteConsoleOutput(m_handle, m_buffer, { (short)m_width, (short)m_height }, { 0, 0 }, &m_screen);
    }

    finished.notify_one();
}

void Console::Fill(int x, int y, short glyph, short color)
{
    if (x < 0 || y < 0 || x >= m_width || y >= m_height) return;

    CHAR_INFO& ci = m_buffer[y * m_width + x];
    ci.Char.UnicodeChar = glyph;
    ci.Attributes = color;
}

void Console::Fill(int x1, int y1, int x2, int y2, short glyph, short color)
{
    if (x1 < 0) x1 = 0;
    if (y1 < 0) y1 = 0;

    if (x1 >= m_width) return;
    if (y1 >= m_height) return;

    if (x2 >= m_width) x2 = m_width - 1;
    if (y2 >= m_height) y2 = m_height - 1;

    if (x2 < 0) return;
    if (y2 < 0) return;

    for (int x = x1; x <= x2; x++)
    {
        for (int y = y1; y <= y2; y++)
        {
            CHAR_INFO& ci = m_buffer[y * m_width + x];
            ci.Char.UnicodeChar = glyph;
            ci.Attributes = color;
        }
    }
}

void Console::Clear(short glyph, short color)
{
    for (int x = 0; x < m_width; x++)
    {
        for (int y = 0; y < m_height; y++)
        {
            CHAR_INFO& ci = m_buffer[y * m_width + x];
            ci.Char.UnicodeChar = glyph;
            ci.Attributes = color;
        }
    }
}

void Console::Line(int x1, int y1, int x2, int y2, short glyph, short color)
{
    int dx = x2 - x1;
    int dy = y2 - y1;

    int adx = dx > 0 ? dx : -dx;
    int ady = dy > 0 ? dy : -dy;

    int dy2 = dy + dy;
    int dx2 = dx + dx;

    int adx2 = dx2 > 0 ? dx2 : -dx2;
    int ady2 = dy2 > 0 ? dy2 : -dy2;

    if (adx > ady)
    {
        if (x1 > x2)
        {
            int x = x1;
            x1 = x2;
            x2 = x;
            dx = -dx;
            dx2 = -dx2;

            int y = y1;
            y1 = y2;
            y2 = y;
            dy = -dy;
            dy2 = -dy2;
        }

        int sy = dy > 0 ? 1 : dy < 0 ? -1 : 0;

        int err = ady;

        for (int x = x1, y = y1; x <= x2; x++)
        {
            Fill(x, y, glyph, color);

            err += ady2;

            if (err > adx2)
            {
                err -= adx2;
                y += sy;
            }
        }
    }
    else
    {
        if (y1 > y2)
        {
            int x = x1;
            x1 = x2;
            x2 = x;
            dx = -dx;
            dx2 = -dx2;

            int y = y1;
            y1 = y2;
            y2 = y;
            dy = -dy;
            dy2 = -dy2;
        }

        int sx = dx > 0 ? 1 : dx < 0 ? -1 : 0;

        int err = adx;

        for (int x = x1, y = y1; y <= y2; y++)
        {
            Fill(x, y, glyph, color);

            err += adx2;

            if (err > ady2)
            {
                err -= ady2;
                x += sx;
            }
        }
    }
}

void Console::Triangle(int x1, int y1, int x2, int y2, int x3, int y3, short glyph, short color)
{
    Line(x1, y1, x2, y2, glyph, color);
    Line(x2, y2, x3, y3, glyph, color);
    Line(x3, y3, x1, y1, glyph, color);
}

BOOL Console::HandleClose(DWORD evt)
{
    if (evt == CTRL_CLOSE_EVENT)
    {
        active = false;

        std::unique_lock<std::mutex> ul(lock);
        finished.wait(ul);
    }

    return true;
}

Here is the very short main code:

class Game : public Console
{
public:
    Game(int width, int height):
        Console(width, height)
    {

    }

    bool OnCreate() override
    {
        return true;
    }

    bool OnUpdate(float dt) override
    {
        Clear(GLYPH_SOLID, FG_BLACK);

        //Triangle(20, 20, 300, 40, 150, 200);

        return true;
    }
};

int main()
{
    Game game(400, 300);
    int err = game.Construct(2, 2);

    if (err == Console::OK)
        game.Start();
}

The error constants are just defined integer codes in the header file. If any additional code is needed please let me know. Thanks in advance!

EDIT:

Screenshot of profiling session

J. Lengel
  • 570
  • 3
  • 16
  • Step 1) make sure you compile your code with optimizations enabled. Step 2) run it through a profiler. – Jesper Juhl Aug 26 '19 at 15:07
  • That gave me an error because /Ox and /RTC1 cannot be defined at the same time – J. Lengel Aug 26 '19 at 15:10
  • @J.Lengel, Then check without optimizations in a profiler. Although the profiler might just show you that you need to enable optimization. For using the Visual Studio profiler you'll need to use the `/PROFILE` linker option. – Romen Aug 26 '19 at 15:18
  • I just tried that but the profiler didn't give me anything useful. I did check task manager while the program was running and the cpu only hit about 40% most of the time (it hit about 90% while starting the program but that's not surprising) – J. Lengel Aug 26 '19 at 15:27
  • @Romen Profiling a non-optimized/non-release build is usually fairly pointless. Your profiler will usually just point to debug and other stuff that are either not enabled at all in release builds or stuff that the compilers optimizer would remove from consideration (optimize out entirely or improve performance of enough to make it a non-issue) anyway.. – Jesper Juhl Aug 26 '19 at 15:28
  • @JesperJuhl, That's what I tried to say in my comment above. I thought that J. Lengel's profiler would only work with optimizations disabled due to their comment about `/Ox` and `/RTC1` being mutually exclusive. – Romen Aug 26 '19 at 15:30
  • I hear what your saying. I also ran the release build with optimizations but it gave me the same dismal 20 fps. I also feel like the issue is unrelated since the fps dropped from 500 to 20 without really doing anything – J. Lengel Aug 26 '19 at 15:35
  • 1
    @J.Lengel "Task manager" is too high level and too course grained to be worth anything at all when investigating performance issues. A *much* better tool is [Windows Performance Analyzer](https://learn.microsoft.com/en-us/windows-hardware/test/wpt/windows-performance-analyzer). But do make sure to figure out how to get a release (aka [optimized](https://learn.microsoft.com/en-us/cpp/build/reference/o1-o2-minimize-size-maximize-speed?view=vs-2019)) build of your application first. – Jesper Juhl Aug 26 '19 at 15:38
  • Like I said above I've done that already and running the executable gave me the same results. I'm adding a screenshot of the profiling session now. – J. Lengel Aug 26 '19 at 15:40
  • @J.Lengel Running under a profiler is not expected to give you *different* results. It's expected to give you lots of data about what your application is spending its time on, so that you can identify *where* the performance issues are (when you drill into the collected data and analyze it - it won't just magically *tell you*). – Jesper Juhl Aug 26 '19 at 15:44
  • Well I haven't used the profiler much so could you maybe make sense of the data for me? It's much appreciated :) – J. Lengel Aug 26 '19 at 15:47
  • @J.Lengel You could start by *reading* the link about Windows Performance Analyzer, that I already gave you. – Jesper Juhl Aug 26 '19 at 16:07
  • @J.Lengel, With that screenshot the best we can do is guess about which function in KernelBase.dll is at 15%. My best guess is that it's Console related and it's possible that the windows console just isn't designed to update as fast as you want. – Romen Aug 26 '19 at 16:09
  • @JesperJuhl that link is for the windows analyzer. I was doing my tests on the built in profiler in VS. I'll check that out though too. – J. Lengel Aug 26 '19 at 16:13
  • @J.Lengel, [This SO question](https://stackoverflow.com/questions/14295570/why-is-console-animation-so-slow-on-windows-and-is-there-a-way-to-improve-spee) might be helpful. It seems like the windows console's "FPS" is heavily dependent on exactly *what* and *how* you're writing to it. – Romen Aug 26 '19 at 16:16
  • It's funny you mention that because I've looked at that already and tried out the things that were suggested but they didn't help. I have noticed though that (obviously) on lower resolutions the frame rate goes up to about 400 again. But this doesn't explain why (at the same resolution) my frame rate dropped so much – J. Lengel Aug 26 '19 at 18:20

0 Answers0