【问题标题】:GPU usage jumps to 100% when closing program after drawing a lot of frames with OpenGL使用 OpenGL 绘制大量帧后关闭程序时 GPU 使用率跃升至 100%
【发布时间】:2021-07-02 18:27:28
【问题描述】:

所以我偶然发现了一些相当奇怪的东西(在完成了一个项目并修复了内存泄漏之后,一旦你关闭程序就会导致 100% 的 CPU 峰值),这似乎会导致一个非常短的 100% 的 GPU 使用峰值,同样,一旦你关闭程序。

这是我运行的最少代码:

#undef UNICODE
#include <Windows.h>

#define GLEW_STATIC
#include "include/GL/glew.h"

#include <string>
#include <chrono>
#include <thread>

//
// window
//

HWND window_hwnd = { };
WNDCLASS window_wndclass = { };

std::string wndclass_name = "class";
std::string window_name = "class";

LRESULT CALLBACK WindowProc(
    _In_ HWND hWnd,
    _In_ UINT uMsg,
    _In_ WPARAM wParam,
    _In_ LPARAM lParam
)
{
    switch (
        uMsg
        )
    {
    default:
        return DefWindowProc(
            hWnd,
            uMsg,
            wParam,
            lParam
        );
    }

    return 0;
}

void window_create_class()
{
    window_wndclass = { };
    window_wndclass.lpfnWndProc = WindowProc;

    window_wndclass.lpszClassName = wndclass_name.c_str();

    window_wndclass.cbClsExtra = 0;
    window_wndclass.cbWndExtra = 0;
    window_wndclass.hInstance = 0;

    RegisterClass(
        &window_wndclass
    );
}

void window_create()
{
    window_create_class();

    window_hwnd = CreateWindow(
        wndclass_name.c_str(),
        window_name.c_str(),

        WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX,

        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,
        CW_USEDEFAULT,

        0,
        0,
        0,
        0
    );
}

int window_loop()
{
    MSG msg;
    BOOL get_message;
    while (
        (
            get_message = GetMessage(
                &msg,
                window_hwnd,
                0,
                0
            ) > 0
            ) != 0
        )
    {
        if (
            get_message == -1
            )
        {
            // error handling

            break;
        }
        else
        {
            TranslateMessage(
                &msg
            );
            DispatchMessage(
                &msg
            );
        }
    }

    return get_message;
}

//
// opengl
//

HDC gl_hdc = { };
HGLRC gl_hglrc = { };

PIXELFORMATDESCRIPTOR gl_pixel_format_descriptor = {
    sizeof(
        PIXELFORMATDESCRIPTOR
    ),
    1,
    // Flags
    PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER,
    // The kind of framebuffer. RGBA or palette.
    PFD_TYPE_RGBA,
    // Colordepth of the framebuffer.
    32,
    0, 0, 0, 0, 0, 0,
    0,
    0,
    0,
    0, 0, 0, 0,
    // Number of bits for the depthbuffer
    24,
    // Number of bits for the stencilbuffer
    8,
    // Number of Aux buffers in the framebuffer.
    0,
    PFD_MAIN_PLANE,
    0,
    0, 0, 0
};

int gl_pixel_format = -1;

void gl_pixel_format_configure()
{
    gl_pixel_format = ChoosePixelFormat(
        gl_hdc,
        &gl_pixel_format_descriptor
    );
    SetPixelFormat(
        gl_hdc,
        gl_pixel_format,
        &gl_pixel_format_descriptor
    );
}

void gl_create()
{
    gl_pixel_format_configure();

    gl_hglrc = wglCreateContext(
        gl_hdc
    );

    wglMakeCurrent(
        gl_hdc,
        gl_hglrc
    );

    GLenum state = glewInit();
    if (
        state != GLEW_OK
        )
    {
        throw;
    }
}

void gl_draw()
{
    RECT client_rect = { };
    GetClientRect(
        window_hwnd,
        &client_rect
    );

    int window_width = client_rect.right - client_rect.left;
    int window_height = client_rect.bottom - client_rect.top;

    glViewport(
        0,
        0,
        window_width,
        window_height
    );
    glClearColor(
        0.0f,
        0.0f,
        0.0f,
        1.0f
    );
    glClear(
        GL_COLOR_BUFFER_BIT
    );

    wglSwapLayerBuffers(
        gl_hdc,
        WGL_SWAP_MAIN_PLANE
    );
}

int main()
{
    window_create();

    gl_hdc = GetDC(
        window_hwnd
    );

    gl_create();

    ShowWindow(
        window_hwnd,
        SW_SHOW
    );

    // simulate drawing frames

    auto now = std::chrono::steady_clock::now();
    auto interval = std::chrono::duration<
        double,
        std::chrono::seconds::period
    >(
        1.0 / 60.0
        );
    auto next = now + interval;

    for (
        int i = 0;
        i < 400;
        i++
        )
    {
        gl_draw();

        std::this_thread::sleep_until(
            next
        );
        next += interval;
    }

    return window_loop();
}

在 Windows 上,使用 GLEW 库。使用 Visual Studio 的编译器编译,具有发布优化。我也在我的笔记本电脑上测试过,它有一个完全不同的 GPU(实际上是英特尔集成显卡),效果相同。

wglSwapLayerBuffers 未被调用时,不会发生这种情况。

注意它甚至没有绘制任何东西,甚至没有创建任何 OpenGL 对象,也没有在任何地方使用指针。这让我想知道,我是否使用了 Windows 的 OpenGL 上下文错误?或者这是 GLEW 的一些特性? (顺便说一句,我假设 400 个绘制帧的效果(这不是导致这种情况的确切数量,我没有费心试图准确地确定它,但它不会发生在 300 帧中,例如)可能GPU 与 GPU 不同。虽然将其设置为 1000+ 之类的值肯定可以工作;这甚至不是一个不切实际的数量)。

老实说,这并不是一个问题(毕竟,它发生在main 返回之后),但它仍然很奇怪,我想避免它。那么为什么会发生这种情况,或者至少我该如何解决呢?


编辑#1: 我对 OpenGL 上下文创建进行了一些尝试,创建了一个适当的上下文 as described on the official OpenGL Wiki,而不是我提供的示例中的简单上下文,但没有成功。

好奇心也战胜了我,这让我浏览了我的 Steam 库并启动了大约十几个游戏,在主菜单中等待几秒钟,让它们绘制几百帧,如果不是几千帧的话.

大多数游戏在您退出游戏后都会出现 100% 的 GPU 峰值。然而,值得注意的是,一些 AAA 游戏(即 DOOM Eternal 和 GTA V)没有表现出这种行为。

我想,一方面,这进一步说明了这并不是真正值得关注的问题。然而这也证明它是可以避免的,虽然我仍然不知道具体如何实现。

我认为运行这些 AAA 游戏的引擎有自己的 OpenGL 包装器,甚至可能有自己的低级别 OpenGL-OS 接口,完全避免了 WGL,问题似乎源于 WGL。

但这只是猜测,我肯定没有找到具体的答案。

【问题讨论】:

  • glGetError 返回的是什么?如果在每帧基础上累积一些错误,则 GL 错误缓冲区可能会导致一些延迟,但这纯粹是推测。我没有看到任何 On Close 事件,也没有在应用程序退出之前强制关闭你的线程......坦率地说,你的代码架构很奇怪(但我习惯于 VCL,所以我有很多偏见)所以我可能会忽略它.. .
  • @Spektre 这是一个非常简单、可重现的最小示例。不使用用于创建窗口的外部库。只需 GLEW 处理与 OpenGL 相关的所有内容(我怀疑问题出在哪里,因为 GLEW 实现了wglSwapLayerBuffers)。没有其他线程,一切都在主线程上运行。 /// glGetError 也无济于事,毕竟在main 返回之后会出现峰值。无论如何我都调用了它(每帧调用一次,绘制完所有 400 帧后调用一次),但它返回 GL_NO_ERROR (0),正如我认为的那样,在这两种情况下都会返回。

标签: c++ windows opengl glew


【解决方案1】:

据我所知,在关闭使用它的程序(包括浏览器和编译器)后,GPU 将立即飙升至 100%。我想这只是在使用程序完成后清理 GPU 的效果。

至于为什么您的程序与其他 win32 程序相比会发生这种情况,我很确定这只是因为您使用的是 GLEW(不过我可能错了)。

【讨论】:

  • 我同意你的看法,几乎可以肯定正在执行某种清理工作。但我不认为这是 GLEW。或者至少它不是由 GLEW 直接引起的,正如其他具有相同问题的程序(如我测试的基于 allegro 构建的游戏 Factorio,它有自己的自定义 OpenGL 包装器)应该可以看出这一点。 / CPU 峰值不是问题。如果我没记错的话,它们是由于动态分配内存而没有再次释放它,迫使 Windows 在退出时介入。我怀疑睡眠本身会导致任何这样的飙升。这没有任何意义。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-07-20
  • 1970-01-01
  • 2019-04-09
  • 2019-01-13
  • 1970-01-01
  • 2014-03-22
  • 1970-01-01
相关资源
最近更新 更多