如何在调整GLFW窗口大小时进行绘制?

20
无论何时我调整GLFW窗口大小,只要我正在调整窗口大小,就不会绘制。 窗口的新暴露部分只有在我完成调整窗口大小后才会被绘制。 你可以在下面的图片中看到:

image

这是我的应用程序代码。 我在Visual Studio 2015上运行Windows 10。

#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);
void get_resolution(int* window_width, int* window_height);
void initGlfwSettings();
GLFWwindow* initGlfwWindow();
void initGlad();

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

int main()
{
    initGlfwSettings();

    GLFWwindow* window = initGlfwWindow();

    initGlad();

    // glad: load all OpenGL function pointers
    // ---------------------------------------


    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        int width, height;
        glfwGetWindowSize(window, &width, &height);


        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
        // input
        // -----
        processInput(window);

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

void get_resolution(int* window_width, int* window_height) {
    const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());

    *window_width = mode->width;
    *window_height = mode->height;
}

void initGlfwSettings()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);


    #ifdef __APPLE__
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // uncomment this statement to fix compilation on OS X
    #endif
}

GLFWwindow* initGlfwWindow()
{
    /*GLFWmonitor* monitor = glfwGetPrimaryMonitor();
    int width;
    int height;

    get_resolution(&width, &height);*/



    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "learning opengl", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        exit(1);
    }

    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
    glfwSwapInterval(1);

    return window;
}

void initGlad()
{
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        exit(1);
    }
}

解释此问题的任何解决方案。


这个回答解决了你的问题吗?GLFW轮询等待窗口调整大小完成,如何修复? - Andreas detests censorship
@Andreasdetestscensorship 在我看来,最好是请求关闭另一个问题而不是这个,因为这个问题有更多的投票和更好的答案。你应该更仔细地检查你标记的帖子,而不是简单地要求关闭多个帖子的重复。 - Elikill58
@Elikill58 我认为另一个问题更好,而这个问题只有一个好答案。因此,我选择了最早的问题作为重复目标。这些问题可以合并。 - Andreas detests censorship
3个回答

23

当窗口调整大小时,事件处理(glfwPollEvents)会停顿,但在此过程中,它会不断发出resize事件,并通过已经使用的resize回调函数进行实时处理。你可以从这里重新绘制场景并调用glfwSwapBuffers进行渲染,即使在glfwPollEvents中也可以。实际上,可以通过以下代码实现:

void draw()
{
    // Rendering code goes here
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    glfwSwapBuffers(window);
}

void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and 
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
    // Re-render the scene because the current frame was drawn for the old resolution
    draw();
}

将您的渲染(glClearColorglClearglfwSwapBuffers)从主循环中移至draw函数。在主循环中保留对draw的调用,将window变量设置为全局变量或在调用时将其传递给draw函数,否则它将超出draw函数的作用域,在需要glfwSwapBuffers的位置上将无法使用。

仅当用户同时按住鼠标左键并移动鼠标时,此方法才有效-只是在缩放窗口部分单击并按住鼠标左键仍会导致程序停顿。为了解决这个问题,您需要在一个单独的线程中进行渲染,需要额外添加代码实现。(不,您不能在没有线程的情况下完成这个任务。抱歉,这是GLFW的工作方式,除了他们之外没有人可以更改它。)


为了解决这个问题,你需要在另一个线程中进行渲染。这是否意味着每次在任一线程上绘制时都要使GL上下文成为当前上下文并将其分离?否则,你如何能够在渲染循环和调整大小回调中绘制? - The Most Curious Thing
从一些测试来看,似乎最好让调整大小回调从渲染循环请求ctx切换,这样渲染循环就不必担心在“正常”操作(即没有调整大小)下进行ctx切换。这种解决方案在调整大小期间可能会消耗大量CPU周期,但是...谁在乎呢。调整大小不是正常操作,而此解决方案提供了完美平滑的调整大小,无需担心SIZEMOVE循环的特殊性。 - The Most Curious Thing
我想在这里添加一条评论:至少在我的机器上(Windows 10),帧缩放回调将在窗口缩放回调之前被调用。这可能会导致两者之间非常短暂的差异,直到调用窗口缩放回调为止。因此,如果有任何绘图代码利用了帧缓冲区大小和窗口大小之间的比率,它可能会导致奇怪的图形故障。也许最好在窗口缩放回调中绘制,同时从帧缩放回调中获取上次保存的帧缓冲区大小? - Andrej Mitrović

-1

这是窗口事件处理程序未将控制权返回到主线程。 这就是Windows的工作方式,您无法更改它。

但是,您可以将所有渲染和glfw命令移动到不受Windows阻塞的不同线程中。


通常情况下,当调整大小完成时才返回控制权是一件好事,因为您经常会遇到与窗口相同大小的帧缓冲区。这些帧缓冲区只需要在调整大小完成时调整一次大小即可。 - dari
我一直想要在我所构建的所有东西中保持更新。例如运行视频或实时数据,至少捕获需要有自己的线程以避免停顿。 - Jonathan Olson
@JonathanOlson 你会如何实现这个?我尝试了两次自己实现,第一次所有内容都显示出来,但是一直显示加载光标。第二次尝试时只显示了黑屏。 - a programmer
这里有一个简短的讨论可能会有所帮助。https://sourceforge.net/p/glfw/discussion/247562/thread/2e6e361e/ - Jonathan Olson
@JonathanOlson 我将他的代码转换成了使用 std::thread 的代码。它会显示一个三角形,但是当改变窗口大小时窗口不会更新。源代码在这里 - a programmer
除了不完整之外,看起来那段代码只运行一次渲染步骤,而不是循环运行。它不会在调整大小时更新,因为它不会更新(除非缺失的类成员函数中有更多的渲染代码)。 - Jonathan Olson

-2

你不需要设置独立的线程。

你只需要处理 WM_SIZE 消息并在其发生时重新绘制,可以使用 glfwSetWindowSizeCallback 函数访问它。你可以在循环中调用一切,或者仅调用渲染和交换缓冲区部分。

Windows 在进行拖动操作时不会返回消息处理,但将继续处理事件。因此,你只需在回调中执行每个帧应该做的事情,以及针对任何新客户端大小重新配置视口和矩阵等内容所需的足够的工作。这个原则也适用于窗口移动或任何类似的操作。

对于 Win32 或 glfw 基本流程是:

void MyStart()
{
  for glfw set the callback to MyCallback via glfwSetWindowSizeCallback

  while(!ShouldExit)
  {
    some code
    MyFrame()
  }
}

void MyFrame()
{
  do things I normally do per frame

  paint this and that

  swap buffers
}

void MyCallback()
{ 
    get new client area size
    reset viewport or fov
    MyFrame();   
}

// for windows without glfw
void WndProc()
{
  WM_SIZE:
    MyCallback();
}

1
在 Windows 7 上使用 GLFW 3.2.1 时,仍会在调整大小时鼠标不移动时停止更新/绘制。 - genpfault
1
@genpfault 检查一下拖动调整大小的手柄时回调函数是否真正触发。其余部分应该只是绘图。看看这个是否有帮助:http://forum.lwjgl.org/index.php?topic=6090.0 - Beeeaaar

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接