0

I am creating a billiards game and am having major problems with tunneling at high speeds. I figured using linear interpolation for animations would help quite a bit, but the problem persists. To see this, I drew a circle at the previous few positions an object has been. At the highest velocity the ball can travel, the path looks like this: enter image description here

Surely, these increments of advancement are much too large even after using linear interpolation.

At each frame, every object's location is updated based on the amount of time since the window was last drawn. I noticed that the average time for the window to be redrawn is somewhere between 70 and 80ms. I would really like this game to work at 60 fps, so this is about 4 or 5 times longer than what I am looking for.

Is there a way to change how often the window is redrawn? Here is how I am currently redrawing the screen

#include "pch.h"
#include "framework.h"
#include "ChildView.h"
#include "DoubleBufferDC.h"

const int FrameDuration = 16;

void CChildView::OnPaint()
{
    CPaintDC paintDC(this);     // device context for painting
    CDoubleBufferDC dc(&paintDC); // device context for painting
    Graphics graphics(dc.m_hDC);    // Create GDI+ graphics context
    mGame.OnDraw(&graphics);

    if (mFirstDraw)
    {
        mFirstDraw = false;
        SetTimer(1, FrameDuration, nullptr);
        LARGE_INTEGER time, freq;
        QueryPerformanceCounter(&time);
        QueryPerformanceFrequency(&freq);
        mLastTime = time.QuadPart;
        mTimeFreq = double(freq.QuadPart);
    }

    LARGE_INTEGER time;
    QueryPerformanceCounter(&time);
    long long diff = time.QuadPart - mLastTime;
    double elapsed = double(diff) / mTimeFreq;
    mLastTime = time.QuadPart;

    mGame.Update(elapsed);
}

void CChildView::OnTimer(UINT_PTR nIDEvent)
{
    RedrawWindow(NULL, NULL, RDW_UPDATENOW);
    Invalidate();
    CWnd::OnTimer(nIDEvent);
}

EDIT: Upon request, here is how the actual drawing is done:

void CGame::OnDraw(Gdiplus::Graphics* graphics)
{

    // Draw the background
    graphics->DrawImage(mBackground.get(), 0, 0,
        mBackground->GetWidth(), mBackground->GetHeight());

    mTable->Draw(graphics);

    Pen pen(Color(500, 128, 0), 1);
    Pen penW(Color(1000, 1000, 1000), 1);

    this->mCue->Draw(graphics);

    for (shared_ptr<CBall> ball : this->mBalls)
    {
        ball->Draw(graphics);
    }

    for (shared_ptr<CBall> ball : this->mSunkenSolidBalls)
    {
        ball->Draw(graphics);
    }

    for (shared_ptr<CBall> ball : this->mSunkenStripedBalls)
    {

        ball->Draw(graphics);

    }
    this->mPowerBar->Draw(graphics);
}

Game::OnDraw will call Draw on all of the game items, which draw on the graphics object they receive as an argument.

Justin
  • 107
  • 1
  • 10
  • Your approach using linear interpolation is correct. To reduce the drawing time, it better to see how you actually draw. Can you post this code ? – Erwan Daniel May 26 '20 at 01:56
  • Sure thing. The post has been updated. If that's not what you were looking for let me know and I'll change it. – Justin May 26 '20 at 02:17
  • Your drawing loop doesn't look *super* expensive, but could be optimized. Take a look on this really interesting answer : https://stackoverflow.com/a/1961966/5032541 – Erwan Daniel May 26 '20 at 02:31
  • Reducing the time spent on each frame is always difficult, consider disabling/enabling some part of your code just for test, to see what really impact your performance – Erwan Daniel May 26 '20 at 02:33
  • I'll search around. Do you think it's for sure an optimization issue with my code? Or could it be something else? Perhaps the nature of an MFC app or Gdiplus? If I have to stray away from Gdiplus I will, but I don't want to if I don't have to since I've sunk quite a bit of time into this. I can't imagine my drawing is so expensive that it's slowing the program down by this much since it's a billiards game with a small number of objects to draw. – Justin May 26 '20 at 02:38
  • You need to add some timing/tracing to measure how much each part of your draw call takes, so that you know where the low hanging fruit is. The simple way is to use something like this: https://aishack.in/tutorials/timing-macros/ (just replace the cvGetTickCount() with native Windows functions), write the output with OutputDebugString() and use DbgView to look at the results (you'll have to include something to only write the outputs every say 60 frames or so of course) – Roel May 26 '20 at 02:54
  • I'd really be surprised if this is really an optimization issue on my end. I really have such a small number of items being drawn. I will definitely look, but is there a good chance it could be anything else? – Justin May 26 '20 at 02:59
  • So I've noticed that when I draw absolutely nothing and I just show a blank white screen, each draw still takes about 25ms. Is getting a high frame rate like 60fps just not possible with this approach? – Justin May 26 '20 at 03:52
  • You are creating a `CDoubleBufferDC` object on **every** redraw. Presumably, that also creates a bitmap the size of your destination. That's a lot of bits that get shoved around, needlessly, on every frame. That's a resource you should create exactly once, and only ever update on window resize operations. Also note that `shared_ptr`'s have non-trivial costs. That's another bunch of wasted cycles for no apparent reason. – IInspectable May 26 '20 at 06:40
  • Thank you for pointing that out. I'm currently looking into this. My biggest bottleneck was drawing the table. I've narrowed it down to just drawing the polygons that make up the table and it still takes 16ms. How can other games get away with drawing so much more than I am with no problem? – Justin May 26 '20 at 09:33
  • Games get their performance by being conservative in their resource consumption, and using an API that's better suited to communicating with the hardware. For 2D graphics you could evaluate Direct2D, a library built on top of Direct3D tailored to the needs of 2D rendering. You'll find an introduction here: [Creating a Simple Direct2D Application](https://learn.microsoft.com/en-us/windows/win32/direct2d/direct2d-quickstart). – IInspectable May 26 '20 at 10:04
  • Also make sure to understand, that a significant portion of [GDI+](https://learn.microsoft.com/en-us/windows/win32/gdiplus/-gdiplus-gdi-start) is implemented in *software* only, with virtually no hardware acceleration. This is particularly true with anti-aliasing. Direct2D produces comparable image quality, but offloads the expensive calculations onto the graphics chip. – IInspectable May 26 '20 at 10:08
  • I see. That makes sense. Unfortunately I'm working on a machine without a dedicated graphics card, so I don't think I'll see much difference. Still, I don't understand, because I can still play games that are much, much higher demanding that the one I'm making even without a graphics card. I will definitely look into rewriting it with D2D. – Justin May 26 '20 at 18:43

0 Answers0