首页 > 解决方案 > 为什么我的 UWP 游戏在发布时比在调试模式下慢?

问题描述

我正在尝试制作 UWP 游戏,但遇到了一个问题,即我的游戏在发布模式下比在调试模式下慢得多。

我的游戏将绘制一个 3D 视图(Dungeon Master 风格),并将有一个 UI 部分在 3D 视图上绘制。因为 3D 视图可以减慢到每秒帧数 (FPS) 的少量,我决定让我的游戏以 60 FPS 运行 UI 部分。

以下是主游戏循环的样子,在一些伪代码中:

Gameloop start
  Update game datas
  copy actual finished 3D view from buffer to screen
  draw UI part
  3D view loop start
    If no more time to draw more textures on the 3D view exit 3D view loop
    Draw one texture to 3D view buffer
  3D view loop end --> 3D view loop start
Gameloop end --> Gameloop start

以下是实际的更新和渲染函数:

    void Dungeons_of_NargothMain::Update() 
    {
        m_ritonTimer.startTimer(static_cast<int>(E_RITON_TIMER::UI));

        m_ritonTimer.frameCountPlusOne((int)E_RITON_TIMER::UI_FRAME_COUNT);
        m_ritonTimer.manageFramesPerSecond((int)E_RITON_TIMER::UI_FRAME_COUNT);

        m_ritonTimer.manageFramesPerSecond((int)E_RITON_TIMER::LABY_FRAME_COUNT);

        if (m_sceneRenderer->m_numberTotalOfTexturesToDraw == 0 ||
            m_sceneRenderer->m_numberTotalOfTexturesToDraw <= m_sceneRenderer->m_numberOfTexturesDrawn)
        {
            m_sceneRenderer->m_numberTotalOfTexturesToDraw = 150000;
            m_sceneRenderer->m_numberOfTexturesDrawn = 0;
        }

    }

    // RENDER
    bool Dungeons_of_NargothMain::Render() 
    {
        //********************************//
        // Render UI part here            //
        //********************************//


        //**********************************//
        // Render 3D view to 960X540 screen //
        //**********************************//
        m_sceneRenderer->setRenderTargetTo960X540Screen(); // 3D view buffer screen

        bool screen960GotFullDrawn = false;
        bool stillenoughTimeLeft = true;

        while (stillenoughTimeLeft && (!screen960GotFullDrawn))
        {
            stillenoughTimeLeft = m_ritonTimer.enoughTimeForOneMoreTexture((int)E_RITON_TIMER::UI);
            screen960GotFullDrawn = m_sceneRenderer->renderNextTextureTo960X540Screen();
        }

        if (screen960GotFullDrawn)
            m_ritonTimer.frameCountPlusOne((int)E_RITON_TIMER::LABY_FRAME_COUNT);

        return true;
    }

我删除了不重要的内容。

这是计时器部分(RitonTimer):


    #pragma once

    #include "pch.h"
    #include <wrl.h>
    #include "RitonTimer.h"

    Dungeons_of_Nargoth::RitonTimer::RitonTimer()
    {
        initTimer();
        if (!QueryPerformanceCounter(&m_qpcGameStartTime))
        {
            throw ref new Platform::FailureException();
        }
    }

    void Dungeons_of_Nargoth::RitonTimer::startTimer(int timerIndex)
    {
        if (!QueryPerformanceCounter(&m_qpcNowTime))
        {
            throw ref new Platform::FailureException();
        }
        m_qpcStartTime[timerIndex] = m_qpcNowTime.QuadPart;
        m_framesPerSecond[timerIndex] = 0;
        m_frameCount[timerIndex] = 0;
    }

    void Dungeons_of_Nargoth::RitonTimer::resetTimer(int timerIndex)
    {
        if (!QueryPerformanceCounter(&m_qpcNowTime))
        {
            throw ref new Platform::FailureException();
        }
        m_qpcStartTime[timerIndex] = m_qpcNowTime.QuadPart;
        m_framesPerSecond[timerIndex] = m_frameCount[timerIndex];
        m_frameCount[timerIndex] = 0;
    }

    void Dungeons_of_Nargoth::RitonTimer::frameCountPlusOne(int timerIndex)
    {
        m_frameCount[timerIndex]++;
    }

    void Dungeons_of_Nargoth::RitonTimer::manageFramesPerSecond(int timerIndex)
    {
        if (!QueryPerformanceCounter(&m_qpcNowTime))
        {
            throw ref new Platform::FailureException();
        }
        m_qpcDeltaTime = m_qpcNowTime.QuadPart - m_qpcStartTime[timerIndex];

        if (m_qpcDeltaTime >= m_qpcFrequency.QuadPart)
        {
            m_framesPerSecond[timerIndex] = m_frameCount[timerIndex];
            m_frameCount[timerIndex] = 0;
            m_qpcStartTime[timerIndex] += m_qpcFrequency.QuadPart;
            if ((m_qpcStartTime[timerIndex] + m_qpcFrequency.QuadPart) < m_qpcNowTime.QuadPart)
                m_qpcStartTime[timerIndex] = m_qpcNowTime.QuadPart - m_qpcFrequency.QuadPart;
        }
    }

    void Dungeons_of_Nargoth::RitonTimer::initTimer()
    {
        if (!QueryPerformanceFrequency(&m_qpcFrequency))
        {
            throw ref new Platform::FailureException();
        }

        m_qpcOneFrameTime = m_qpcFrequency.QuadPart / 60;
        m_qpc5PercentOfOneFrameTime = m_qpcOneFrameTime / 20;
        m_qpc10PercentOfOneFrameTime = m_qpcOneFrameTime / 10;
        m_qpc95PercentOfOneFrameTime = m_qpcOneFrameTime - m_qpc5PercentOfOneFrameTime;
        m_qpc90PercentOfOneFrameTime = m_qpcOneFrameTime - m_qpc10PercentOfOneFrameTime;
        m_qpc80PercentOfOneFrameTime = m_qpcOneFrameTime - m_qpc10PercentOfOneFrameTime - m_qpc10PercentOfOneFrameTime;
        m_qpc70PercentOfOneFrameTime = m_qpcOneFrameTime - m_qpc10PercentOfOneFrameTime - m_qpc10PercentOfOneFrameTime - m_qpc10PercentOfOneFrameTime;
        m_qpc60PercentOfOneFrameTime = m_qpc70PercentOfOneFrameTime - m_qpc10PercentOfOneFrameTime;
        m_qpc50PercentOfOneFrameTime = m_qpc60PercentOfOneFrameTime - m_qpc10PercentOfOneFrameTime;
        m_qpc45PercentOfOneFrameTime = m_qpc50PercentOfOneFrameTime - m_qpc5PercentOfOneFrameTime;
    }


    bool Dungeons_of_Nargoth::RitonTimer::enoughTimeForOneMoreTexture(int timerIndex)
    {
        while (!QueryPerformanceCounter(&m_qpcNowTime));

        m_qpcDeltaTime = m_qpcNowTime.QuadPart - m_qpcStartTime[timerIndex];

        if (m_qpcDeltaTime < m_qpc45PercentOfOneFrameTime)
            return true;
        else
            return false;
    }

在调试模式下,游戏的 UI 以 60 FPS 的速度运行,而我的 PC 上的 3D 视图大约为 1 FPS。但即使在那里,我也不确定为什么我必须在一个游戏时间的 45% 处停止我的纹理绘制并调用存在,以获得 60 FPS,如果我等待更长时间,我只能获得 30 FPS。(此值设置在 RitonTimer 的“enoughTimeForOneMoreTexture()”中。

在发布模式下,它急剧下降,UI 部分为 10 FPS,3D 部分为 1 FPS。我试图找出过去2天的原因,但没有找到。

另外我还有一个小问题:我如何告诉视觉工作室我的游戏实际上是游戏而不是应用程序?或者当我将游戏发送到他们的商店时,微软是否会进行“切换”?

这里我把我的游戏放到了我的 OneDrive 上,这样大家就可以下载源文件并尝试编译它,看看你是否得到和我一样的结果:

OneDrive 链接: https ://1drv.ms/f/s!Aj7wxGmZTdftgZAZT5YAbLDxbtMNVg

在 x64 调试或 x64 发布模式下编译。

更新:

我想我找到了为什么我的游戏在发布模式下速度较慢的解释。CPU 可能不会等待绘制指令完成,而只是将其添加到列表中,该列表将在单独的任务中按照自己的速度转发给 GPU(或者 GPU 自己进行缓存)。这将解释这一切。

我的计划是先绘制 UI,然后从 3D 视图中绘制尽可能多的纹理,直到经过 1/60 秒帧时间的 95%,然后将其呈现给交换链。UI 将始终为 60 FPS,并且 3D 视图将与系统允许的一样快(如果可以在 95% 的帧时间内全部绘制,也可以为 60FPS)。这不起作用,因为它可能在一帧时间内缓存了我的 3D 视图的所有指令(我正在测试 3D 视图的 150000 BIG 纹理绘制指令),所以当然 UI 和 3D 视图一样慢结束,或接近。

这也是为什么即使在调试模式下,当我等待 95% 的帧时间时我也没有获得 60FPS,我必须等待 45% 的帧时间才能获得我想要的 UI 的 60 FPS。

我在发布模式下使用较低的值对其进行了测试以验证该理论,实际上,当我仅在帧时间的 15% 处停止绘图时,我的 UI 也获得了 60 FPS。

我认为它只在 DirectX12 中这样工作。

标签: c++uwpdirectx-11query-performance

解决方案


“我如何告诉视觉工作室我的游戏实际上是游戏而不是应用程序” - 没有区别,游戏就是应用程序。

我现在在调试模式下以 300-400 FPS 的速度运行您的代码。

首先,我注释掉了检查您是否有时间渲染另一个纹理的代码。不要那样做。玩家看到的所有东西都应该在一个帧内渲染。如果您的帧花费超过 16 毫秒(目标为 60 fps),请寻找昂贵的操作,或重复进行的调用,可能会增加一些意想不到的成本。当每帧或每次调整大小只需要做一次时,寻找可能重复做某事的代码。ETC

所以问题是你正在渲染非常大的纹理和很多纹理。您想避免过度绘制(在已经渲染像素的地方渲染像素)。你可以有一点透支,这有时比迂腐更可取。但是你一遍又一遍地绘制 1000x2000 的纹理。所以你绝对是在扼杀像素着色器。它只是无法渲染那么多像素。我没有费心查看试图根据剩余帧时间控制纹理渲染的代码。对于您正在尝试做的事情,这没有帮助。

在您的渲染方法中注释掉 while 和 if/else 部分,并使用它来绘制纹理数组..

// set sprite dimensions
int w = 64, h = 64;
for (int y = 0; y < 16; y++)
{
    for (int x = 0; x < 16; x++)
    {

        m_sceneRenderer->renderNextTextureTo960X540Screen(x*64, y*64, w, h);
    }
}

并在 RenderNextTextureToScreen(int x, int y, int w, int h) ..

    m_squareBuffer.sizeX = w; // 1000;
    m_squareBuffer.sizeY = h; // 2000;
    m_squareBuffer.posX = x; // (float)(rand() % 1920);
    m_squareBuffer.posY = y; // (float)(rand() % 1080);

看看这段代码如何渲染更小的纹理,纹理是 64x64 并且没有过度绘制。

请注意,GPU 并非全功能强大,如果使用得当,它可以做很多事情,但如果你只是对它进行疯狂的操作,你可以将它磨得停止,就像使用 CPU 一样。所以尝试渲染“看起来很正常”的东西,你可以想象在游戏中。你会及时了解什么是明智的,什么是不明智的。

对于在发布模式下运行速度较慢的代码,最可能的解释是您的计时和渲染限制器代码已损坏。它不能正常工作,因为 3d 视图以 1fps 运行,所以谁知道它的行为是什么。通过我所做的更改,该程序似乎在发布模式下运行得更快,正如预期的那样。现在对我来说,您的时钟代码在发布模式下显示 600-1600fps。


推荐阅读