a programmer
a programmer

Reputation: 353

How to draw while resizing GLFW window?

Whenever I resize a GLFW window it doesn't draw while I'm resizing the window. The newly exposed part of the window only gets drawn on after I finish resizing the window. You can see it for yourself in the picture below:

image

Here is the code for my application. I am running Windows 10 on Visual Studio 2015

#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);
    }
}

Explain any solutions to this problem.

Upvotes: 22

Views: 26177

Answers (5)

Alan Birtles
Alan Birtles

Reputation: 36389

glfwPollEvents blocks during window moving or resizing. The window refresh callback offers a partial solution but doesn't fully solve the problem as (at least on Windows) it's only called each time the window size changes, it isn't called whilst the user is holding down the mouse button during a resize but not moving the mouse.

The only full solution I found was to move all rendering onto a separate thread:

glfwMakeContextCurrent(nullptr);
std::thread renderThread([&] {
  glfwMakeContextCurrent(window);
  while (!glfwWindowShouldClose(window))
  {
    renderFrame();
  }
  glfwMakeContextCurrent(nullptr);
  });
while (!glfwWindowShouldClose(window))
{
  glfwWaitEvents();
}
renderThread.join();
glfwMakeContextCurrent(window);

You'll need to make sure that your event handling and rendering code is thread safe as events are now handled on a different thread to the rendering.

Upvotes: 0

Eyad Ahmed
Eyad Ahmed

Reputation: 112

Edit: the following answer is nice and all and works, but sadly it is not the perfect solution for this, however it is still a solution if you want to use it

I'm surprised no one mentioned window refresh callback, which is the method documented by GLFW for this specific issue, from documentation of glfwPollEvents() link :

On some platforms, a window move, resize or menu operation will cause event processing to block. This is due to how event processing is designed on those platforms. You can use the window refresh callback to redraw the contents of your window when necessary during such operations.

to be fair it is a bit buried, and I myself wasn't able to find it a while ago

void window_refresh_callback(GLFWwindow *window)
{
    render();
    glfwSwapBuffers(window);
    glFinish(); // important, this waits until rendering result is actually visible, thus making resizing less ugly
}

note that for this to work properly glViewport() must be called somewhere

void framebuffer_size_callback(GLFWwindow *window, int width, int height)
{
    glViewport(0, 0, width, height);
}

in your GLFW setup code:

// rest of code
glfwSetWindowRefreshCallback(window, window_refresh_callback);
glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
// rest of code

with previous edits I found that resizing is more stable, even more stable than rendering in the window resize callback, even with glFinish there, you can also compare the behavior of your application with software like Blender

I also have the following in my GLFW initialization code as instructed by GLFW documentation to reduce tearing: https://www.glfw.org/docs/latest/quick.html#quick_swap_buffers

glfwSwapInterval(1);

Upvotes: 5

Kotauskas
Kotauskas

Reputation: 1374

Event processing (glfwPollEvents) stalls whenever the window is resized, but while doing so, it constantly emits resize events, which are processed on the fly by the resize callback that you already use. You can redraw your scene from there and call glfwSwapBuffers to render even when in glfwPollEvents. Practically speaking, this can be achieved by the following code:

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();
}

and move your rendering (glClearColor, glClear and glfwSwapBuffers) from the main loop into draw. Leave a call to draw in your main loop and make window a global variable or pass it to draw when calling it, else it'll be out of scope in draw, where it's needed for glfwSwapBuffers.

That only works when the user moves the mouse while holding - just holding left-click on the resize window part still stalls. To fix that, you need to render in a separate thread in addition to this. (No, you can't do that without threading. Sorry, this is how GLFW works, no one except them can change it.)

Upvotes: 26

Beeeaaar
Beeeaaar

Reputation: 1035

You do not need to set up a separate thread.

You just need to handle the WM_SIZE message and repaint when it happens, which can be accessed by using the glfwSetWindowSizeCallback function. You can call everything inside the loop, or just call the render and swap buffers parts.

Windows does not return from message processing while a drag operation is going on, but will continue to process events. So you just need to do whatever you would have done each frame inside the callback, plus enough to reconfigure your viewports and matrices and whatnot for any new client size. This principle also works for window moves or anything similar.

For Win32 or glfw the basic flow is:

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();
}

Upvotes: -2

Jonathan Olson
Jonathan Olson

Reputation: 1186

It's the windows event handler not returning control to the main thread. Its how windows works and you can't change it.

You can however move all your rendering and glfw commands to a different thread, which won't be stalled by windows.

Upvotes: -1

Related Questions