[C++][SFML 2.2] 奇怪的性能问题 - 移动鼠标会降低 CPU 使用率

[C++][SFML 2.2] Strange Performance Issues - Moving Mouse Lowers CPU Usage


在过去两周左右的时间里,我一直致力于制作 2D 地图编辑器,但我 运行 遇到了一个非常奇怪的问题。当我试图优化我的代码时,例如,通过为真正应该是函数的函数创建函数,我做了一些让我的 CPU 使用率达到顶峰的事情。我已经尝试注释掉我认为最有可能是罪魁祸首(滚动、渲染、其他计算)的不同代码部分,但无济于事。我认为问题可能只是试图从不同的函数中调用事物,但为了尽可能模块化,我真的很喜欢这个功能。

自从我将其与文件进行比较以来,我所做的另一项重大更改是我将所有变量移植到外部 CPP 文件中,因为将变量设置为全局变量使其更易于从在不同的功能中。这可能是一个非常简单的问题,但我一辈子都无法理解为什么会这样。

使用原始代码,我得到大约 2-3% CPU 的使用率。使用新代码,我得到大约 20-35%,但是,如果我移动鼠标,它会下降到大约 6-8%。

tl;dr:在尝试使用函数优化我的代码后,我以某种方式降低了我的性能,并发现了一个奇怪的怪癖,即移动鼠标会大大减少 CPU 使用率。 .

//Most variables are declared in an external file
void editorLoop()
{
/***************************/
//See what user wants to do
/***************************/
std::cout << "Would you like to [O]pen a file, make [N]ew one, or [E]xit?\n";
std::cin >> openOrNewFile;
/************************/
//Create a new file
/***********************/
if (openOrNewFile == 'n' || openOrNewFile == 'N')
{
    createMap();
}
/***********************/
//Quit the program
/**********************/
else if (openOrNewFile == 'e' || openOrNewFile == 'E')
{
    return;
}
/************************/
//Open an existing file
/***********************/
else if (openOrNewFile == 'o' || openOrNewFile == 'O')
{
    openMap();
}
/**********************/
//Invalid input
/*********************/
else
{
    std::cout << "Please enter a valid input!\n";
    main();
}


/***********************/
//Create the Window
/***********************/
sf::RenderWindow gameWindow(sf::VideoMode(screenSizeX, screenSizeY, mapTileSize), "Game");
//Artificially cap FPS to keep memory and CPU usage low
gameWindow.setFramerateLimit(60);
//view1's size is defined externally, same size as the gameWindow, though
view1.setCenter(screenSizeX / 2, screenSizeY / 2 + mapTileSize);

//Load GUI Elements
//I've found that trying to load these inside the setGUIElements function leads to the 
//texture memory to be deleted, so I'm left with white spaces
sf::Texture guiElements;
if (!guiElements.loadFromFile("Resources/guiElements.png"))
{
    std::cout << "Error loading guiElements.png\n";
}

sf::Font arial;
if (!arial.loadFromFile("Resources/arial.ttf"))
{
    std::cout << "Error loading arial.tff\n";
}
setGUIElements();
activeTileSprite.setTexture(tileTexture);
upArrowX.setTexture(guiElements);
downArrowX.setTexture(guiElements);
upArrowY.setTexture(guiElements);
downArrowY.setTexture(guiElements);
saveButton.setTexture(guiElements);
editBoxX.setFont(arial);
editBoxY.setFont(arial);

//Selector Rectangle
sf::RectangleShape Selected(sf::Vector2f(mapTileSize, mapTileSize));
Selected.setFillColor(sf::Color(0,0,0,0));
Selected.setOutlineThickness(2);
Selected.setOutlineColor(sf::Color(255,0,0));
Selected.setPosition(-1000,-1000);

//Set up toolbox string values
tempXTileValue = std::to_string((_ULonglong)xTileValue);
tempYTileValue = std::to_string((_ULonglong)yTileValue);

while (gameWindow.isOpen())
{   
    /************************************/
    //Mouse Input Defined Here
    /***********************************/
    //Get mouse position relative to the window
    sf::Vector2f mousePositionGlobal = gameWindow.mapPixelToCoords(sf::Mouse::getPosition(gameWindow));
    sf::Vector2i mousePositionLocal = sf::Mouse::getPosition(gameWindow);

    sf::Event event;
    while (gameWindow.pollEvent(event))
    {
        if (event.type == sf::Event::Closed)
        {
            gameWindow.close();
        }
        if (event.type == sf::Event::MouseButtonReleased)
        {
            if (event.key.code == sf::Mouse::Left)
            {
                std::cout << "Global Mouse Position:\n" << "X: " << mousePositionGlobal.x << " Y: " << mousePositionGlobal.y << "\n\n";
                std::cout << "Local Mouse Position:\n" << "X: " << mousePositionLocal.x << " Y: " << mousePositionLocal.y << "\n\n";

                int mouseX = mousePositionGlobal.x;
                int mouseY = mousePositionGlobal.y;

                //Move the selector rectangle to where the mouse clicked
                if ((mouseX >= 0) && (mouseY >= 0))
                {
                    if (mousePositionLocal.x > 900 && mousePositionLocal.x < 1100 && mousePositionLocal.y > 30 && mousePositionLocal.y < 286)
                    {

                    }
                    else
                    {
                        Selected.setPosition((mouseX/32)*32, (mouseY/32)*32);

                        /*****************************************/
                        //These lines define what tile to be placed
                        //NOTE: map[][]'s first [] is the Y axis, and the second [] is the X axis!!
                        /*****************************************/
                        map[mouseY/32*32/32][mouseX/32*32/32].x = yTileValue;
                        map[mouseY/32*32/32][mouseX/32*32/32].y = xTileValue;
                    }
                }

                /********************/
                //Toolbox stuff
                /********************/
                //This can probably be written better ...
                //Up Arrow X
                if ((mousePositionLocal.x > screenSizeX * 0.95) && (mousePositionLocal.x < screenSizeX * 0.95 + 32) && (mousePositionLocal.y > screenSizeY * 0.096) && (mousePositionLocal.y < screenSizeY * 0.096 + 32))
                {
                    if (xTileValue < 10)
                    {
                        xTileValue ++;
                        std::cout << xTileValue << std::endl;
                        tempXTileValue = std::to_string((_ULonglong)xTileValue);
                    }
                    else
                    {
                        xTileValue = 0;
                        std::cout << xTileValue << std::endl;
                        tempXTileValue = std::to_string((_ULonglong)xTileValue);
                    }
                }

                //Down Arrow X
                if ((mousePositionLocal.x > screenSizeX * 0.95) && (mousePositionLocal.x < screenSizeX * 0.95 + 32) && mousePositionLocal.y > 98 && mousePositionLocal.y < 130)
                {                   
                    if (xTileValue > 0)
                    {
                        xTileValue --;
                        std::cout << xTileValue << std::endl;
                        tempXTileValue = std::to_string((_ULonglong)xTileValue);
                    }
                    else
                    {
                        xTileValue = 10;
                        std::cout << xTileValue << std::endl;
                        tempXTileValue = std::to_string((_ULonglong)xTileValue);
                    }
                }

                //Up Arrow Y
                if ((mousePositionLocal.x > screenSizeX * 0.95) && (mousePositionLocal.x < screenSizeX * 0.95 + 32) && mousePositionLocal.y > 148 && mousePositionLocal.y < 180)
                {
                    if (yTileValue < 10)
                    {
                        yTileValue ++;
                        std::cout << yTileValue << std::endl;
                        tempYTileValue = std::to_string((_ULonglong)yTileValue);
                    }
                    else
                    {
                        yTileValue = 0;
                        std::cout << yTileValue << std::endl;
                        tempYTileValue = std::to_string((_ULonglong)yTileValue);
                    }
                }

                //Down Arrow Y
                if ((mousePositionLocal.x > screenSizeX * 0.95) && (mousePositionLocal.x < screenSizeX * 0.95 + 32) && mousePositionLocal.y > 188 && mousePositionLocal.y < 220)
                {
                    if (yTileValue > 0)
                    {
                        yTileValue --;
                        std::cout << yTileValue << std::endl;
                        tempYTileValue = std::to_string((_ULonglong)yTileValue);
                    }
                    else
                    {
                        yTileValue = 10;
                        std::cout << yTileValue << std::endl;
                        tempYTileValue = std::to_string((_ULonglong)yTileValue);
                    }
                }

                //Save Button
                if (mousePositionLocal.x > screenSizeX * 0.886 && mousePositionLocal.x < screenSizeX * 0.886 + 32 && mousePositionLocal.y > screenSizeY * 0.414 - 32 && mousePositionLocal.y < screenSizeY * 0.414)
                {
                    saveMap();
                }
            }

            if (event.key.code == sf::Mouse::Right)
            {
                Selected.setPosition(-1000, - 1000);
            }
        }
        if (event.type == sf::Event::KeyReleased)
        {
            if (event.key.code == sf::Keyboard::X)
            {
                showToolBox = !showToolBox;
            }
        }

        //View Managing
        viewCenter = view1.getCenter();
        viewSize = view1.getSize();

        /*************************/
        //Scrolling
        /************************/
        if ((mousePositionLocal.x > screenSizeX * 0.83) && (mousePositionLocal.x < screenSizeX) && (mousePositionLocal.y > mapTileSize) && (mousePositionLocal.y < screenSizeY * 0.43) && (showToolBox == true))
        {
            enableScroll = false;
        }
        else
        {
            enableScroll = true;
        }
        if ((mousePositionLocal.y < screenSizeY * 0.1) && (enableScroll == true))
        {
            //If the view would leave the map by scrolling; don't scroll
            if (viewCenter.y < mapTileSize * 10.7)
            {

            }
            else
            {
                scroll(0,-scrollSpeed);
            }
        }
        if ((mousePositionLocal.y > screenSizeY * 0.9) && (enableScroll == true))
        {
            if (viewCenter.y > ((mapSizeY * mapTileSize)) - (mapTileSize * 9))
            {

            }
            else
            {
                scroll(0,scrollSpeed);
            }
        }
        if (mousePositionLocal.x < screenSizeX * 0.1 && enableScroll == true)
        {
            if (viewCenter.x < (mapTileSize * 17))
            {

            }
            else
            {
                scroll(-scrollSpeed,0);
            }
        }
        if ((mousePositionLocal.x > screenSizeX * 0.9) && enableScroll == true)
        {
            if (viewCenter.x > (mapSizeX * mapTileSize) - (mapTileSize * 17))
            {

            }
            else
            {
                scroll(scrollSpeed,0);
            }
        }

        //Clear buffer
        gameWindow.clear();

        //The view has to be set in the draw function
        //for a reason that is not yet clear to me.
        gameWindow.setView(view1);

        //Add stuff to new buffer
        //Y loop
        for (int i = 0; i < map.size(); i++)
        {
            //X loop
            for (int j = 0; j < map[i].size(); j++)
            {
                if ((map[i][j].x != -1) && (map[i][j].y != -1))
                {
                    tiles.setPosition(j * mapTileSize, i * mapTileSize);
                    tiles.setTextureRect(sf::IntRect(map[i][j].x * mapTileSize, map[i][j].y * mapTileSize, mapTileSize, mapTileSize));
                    gameWindow.draw(tiles);
                }
            }
        }

        //Draw the Selector rectangle
        gameWindow.draw(Selected);

        if (showToolBox == true)
        {
            activeTileSprite.setTextureRect(sf::IntRect(mapTileSize*yTileValue, mapTileSize*xTileValue, mapTileSize, mapTileSize));

            //Draw toolbox
            gameWindow.draw(toolbarBox);
            gameWindow.draw(activeTileSprite);

            gameWindow.draw(upArrowX);
            gameWindow.draw(downArrowX);

            gameWindow.draw(upArrowY);
            gameWindow.draw(downArrowY);

            //Update the strings for X and Y here
            //Otherwise, it doesn't work for some reason
            editBoxX.setString("Y: " + tempXTileValue);
            editBoxY.setString("X: " + tempYTileValue);

            gameWindow.draw(editBoxX);
            gameWindow.draw(editBoxY);

            gameWindow.draw(saveButton);
        }
        //Render buffer
        gameWindow.display();
    }
}
openfile.close();
return;
}


如果我需要添加(或删除)任何信息,请告诉我。

我已经四处查看但找不到任何类似的问题,所以希望我没有遗漏任何东西,并正确解释了我的问题。
再次感谢,
helpMeLearnC++

P.S。我应该提一下,我不是一个完全的初学者,但我也不是超级高级。

P.S.S.在尝试了 Selbie 的建议之后,我注意到,相当矛盾的是,除非鼠标移动,否则我的游戏不会更新......现在我想知道为什么这会比鼠标不动时产生更好的性能......(或代码我用过没有正常工作)。

P.S.S.S.在进一步修改代码之后,我发现了我认为的预期:当鼠标移动时,gameWindow.pollEvent() 优先于 while(gameWindow.isOpen()) 循环,后者执行绘图很可能是什么扼杀了我的表现。现在的问题是处理这个问题的最佳方法是什么。

P.S.S.S.S.对于那些好奇的人,假设我检查 FPS 的方法是正确的,我每秒渲染大约 27,000 帧(我的测试非常小,所以虽然我认为它看起来有点高,但我不会完全抹黑它)。




通过将 mousePositionGlobal 和 mousePositionLocal 移动到 pollEvent 循环中,我能够将性能从大约 20-35% 降低到大约 8-15%,并在剩下的过程中下降(大约 3% ) 当鼠标移动时。所以,取得了进展,但我仍然不确定是什么原因导致了差异......由于 FPS 已经上升到大约 35,000,我将假设我的计算代码是错误的。还有其他建议吗?

我敢打赌你的 CPU 是四核的。您看到的 CPU 百分比来自 Windows 任务管理器(或来自使用 Windows 性能计数器的其他应用程序)。我怀疑这是因为你有一个单线程游戏循环,它不停地运行,帧之间没有休眠。因此,您的“20-35%”CPU 实际上是您的程序在您的芯片上占用了一个完整的内核。这是任何想要以可能的最高帧速率渲染的游戏循环的典型特征。

我怀疑在您的显卡上,"moving the mouse" 也会产生与其他 windowed 游戏类似的性能特征。当您移动鼠标时,Windows 图形获得比游戏 window 更高的优先级并暂停游戏循环的渲染。因此,当鼠标四处移动时,您的代码仍然是 运行,但帧速率要低得多。由于图形系统必须处理游戏外的绘图请求,.draw() 或 .display() 调用被阻止 window。因此,您的游戏循环块和使用更少 CPU.

我的建议是在您的游戏中添加帧速率计数器 window。显示最后一秒累积的帧数的东西(需要一些额外的整数变量、时间函数和一些除法来计算帧速率)。我猜你会看到当鼠标移动时帧速率计数器变慢。

另外,在互联网上搜索 "code profiling tool"。 Visual Studio 的某些版本内置了 on。它们应该在您的代码花费大部分时间的地方。

一个简单的游戏循环,您不想在其中运行 CPU 可能类似于:

time last_render = now();
while( app_is_running ) {
  Event e;
  // eat all events.  Ideally, more important than rendering,
  // and should take only a little bit of time:
  while ( get_event(&e) ) {
    process_event(e); // <- note, does not render, should be **cheap**
  }
  // frame limit code:
  while (now()-last_render < frame_limit) {
    Event e;
    if (wait_for_event(&e, (frame_limit-delta)/2))
      process_event(e);
  }
  last_render = now(); // stamp before rendering
  render(); // also includes other game state updates.
}

现在,sfml 似乎缺少 wait_for_event(Event*, timeout) 功能。这意味着你不能 "go to sleep while still responding to events"。因此,休眠要么使您的事件处理无响应,要么您必须对其进行管理,要么您需要多个线程。

我们可以建立一个线程安全队列,创建一个线程,其工作是 waitForEvent 并让它填充一个我们可以等待的队列,然后在队列上休眠和超时(基本上解决什么问题似乎是 sfml 缺陷)。

或者我们可以轮询和跟踪 CPU。离我远点 phone!

或者我们可以忍受 1 帧的用户输入响应延迟(我认为这是不可接受的)。

我不是游戏开发者,所以对这个建议持怀疑态度。