Переключение между оконным и полноэкранным режимами в OpenGL/GLFW 3.2

Я нахожусь в процессе изучения OpenGL в Linux, но не могу заставить работать режим переключения (окно в полноэкранный режим и обратно).

Похоже, что окно открывается на весь экран, но выглядит не правильно. Для переключения режимов создается новое окно, а старое разрушается.

void OpenGLWindow::FullScreen(bool fullScreen, int width, int height)
{
    GLFWwindow *oldHandle = m_window;

    m_fullscreen = fullScreen;
    m_width = width;
    m_height = height;

    m_window = glfwCreateWindow(width, height, m_caption.c_str(),
        fullScreen ? m_monitor : NULL, m_window);

    if (m_window == NULL)
    {
        glfwTerminate();
        throw std::runtime_error("Failed to recreate window.");
    }

    glfwDestroyWindow(oldHandle);

    m_camera->Invalidate();

    // Use entire window for rendering.
    glViewport(0, 0, width, height);

    glfwMakeContextCurrent(m_window);
    glfwSwapInterval(1);

    if (m_keyboardHandler) SetKeyboardHandler(m_keyboardHandler);
}

Начальное окно

Полный экран (неверно)

Вернуться в окно

Обновления Вопроса

Я обновил код, чтобы использовать ваш код и получить ту же проблему. По твоему предложению я сейчас обновляю камеру, но опять безрезультатно:(

void OpenGLCamera::Invalidate()
{
    RecalculateProjection(m_perspProjInfo->Width(), m_perspProjInfo->Height());
    m_recalculateViewMatrix = true;
    m_recalculatePerspectiveMatrix = true;
    m_recalculateProjectionMatrix = true;
}

void OpenGLCamera::RecalculateProjection(int width, int height)
{
    float aspectRatio = float(width) / height;
    float frustumYScale = cotangent(degreesToRadians(
        m_perspProjInfo->FieldOfView() / 2));

    float frustumXScale = frustumYScale;

    if (width > height) 
    {
        // Shrink the x scale in eye-coordinate space, so that when geometry is
        // projected to ndc-space, it is widened out to become square.
        m_projectionMatrix[0][0] = frustumXScale / aspectRatio;
        m_projectionMatrix[1][1] = frustumYScale;
    }
    else {
        // Shrink the y scale in eye-coordinate space, so that when geometry is
        // projected to ndc-space, it is widened out to become square.
        m_projectionMatrix[0][0] = frustumXScale;
        m_projectionMatrix[1][1] = frustumYScale * aspectRatio;
    }
}

Rabbid: Когда я изменяю размер:

Раббид: Когда я выхожу на полный экран:

3 ответа

Решение

Далее я опишу небольшой, но удобный класс, который имеет дело с изменением размера окна GLFW и ручками включает и выключает полноэкранное окно.
Все используемые функции GLFW хорошо документированы в документации GLFW.

#include <GL/gl.h>
#include <GLFW/glfw3.h>
#include <array>
#include <stdexcept>

class OpenGLWindow
{
private:

    std::array< int, 2 > _wndPos         {0, 0};
    std::array< int, 2 > _wndSize        {0, 0};
    std::array< int, 2 > _vpSize         {0, 0};
    bool                 _updateViewport = true;
    GLFWwindow *         _wnd            = nullptr;
    GLFWmonitor *        _monitor        = nullptr;

    void Resize( int cx, int cy );

public:

    void Init( int width, int height );
    static void CallbackResize(GLFWwindow* window, int cx, int cy);
    void MainLoop ( void );
    bool IsFullscreen( void );
    void SetFullScreen( bool fullscreen );
};

При создании окна указатель пользовательской функции (glfwSetWindowUserPointer) устанавливается в класс управления окнами. И обратный вызов изменения размера устанавливается glfwSetWindowSizeCallback, После того, как окно создано, его текущий размер и положение могут быть получены glfwGetWindowPos а также glfwGetWindowSize,

void OpenGLWindow::Init( int width, int height )
{
    _wnd = glfwCreateWindow( width, height, "OGL window", nullptr, nullptr );
    if ( _wnd == nullptr )
    {
        glfwTerminate();
        throw std::runtime_error( "error initializing window" ); 
    }

    glfwMakeContextCurrent( _wnd );

    glfwSetWindowUserPointer( _wnd, this );
    glfwSetWindowSizeCallback( _wnd, OpenGLWindow::CallbackResize );

    _monitor =  glfwGetPrimaryMonitor();
    glfwGetWindowSize( _wnd, &_wndSize[0], &_wndSize[1] );
    glfwGetWindowPos( _wnd, &_wndPos[0], &_wndPos[1] );
    _updateViewport = true;
}

Когда происходит уведомление об изменении размера, указатель на класс управления окнами может быть получен glfwGetWindowUserPointer:

static void OpenGLWindow::CallbackResize(GLFWwindow* window, int cx, int cy)
{
    void *ptr = glfwGetWindowUserPointer( window );
    if ( OpenGLWindow *wndPtr = static_cast<OpenGLWindow*>( ptr ) )
        wndPtr->Resize( cx, cy );
}

Любое изменение размера окна уведомляется, и новый размер окна сохраняется (glfwGetWindowSize):

void OpenGLWindow::Resize( int cx, int cy )
{
    _updateViewport = true;
}

Когда размер окна изменился, тогда область просмотра должна соответствовать размеру окна (glViewport). Это можно сделать в основном цикле приложения:

void OpenGLWindow::MainLoop ( void )
{
    while (!glfwWindowShouldClose(_wnd))
    {
        if ( _updateViewport )
        {
            glfwGetFramebufferSize( _wnd, &_vpSize[0], &_vpSize[1] );
            glViewport( 0, 0, _vpSize[0], _vpSize[1] );
            _updateViewport = false;
        }

        // ..... render the scene

        glfwSwapBuffers(_wnd);
        glfwPollEvents();
    }
}  

Если текущее окно находится в полноэкранном режиме, это можно сделать, запросив монитор, который окно использует для полноэкранного режима (glfwGetWindowMonitor):

bool OpenGLWindow::IsFullscreen( void )
{
    return glfwGetWindowMonitor( _wnd ) != nullptr;
} 

Чтобы включить и выключить полноэкранный режим, glfwSetWindowMonitor должен вызываться либо с монитора для полноэкранного режима, либо с nullptr:

void SetFullScreen( bool fullscreen )
{
    if ( IsFullscreen() == fullscreen )
    return;

    if ( fullscreen )
    {
        // backup windwo position and window size
        glfwGetWindowPos( _wnd, &_wndPos[0], &_wndPos[1] );
        glfwGetWindowSize( _wnd, &_wndSize[0], &_wndSize[1] );

        // get reolution of monitor
        const GLFWvidmode * mode = glfwGetVideoMode(glfwGetPrimaryMonitor());

        // swithc to full screen
        glfwSetWindowMonitor( _wnd, _monitor, 0, 0, mode->width, mode->height, 0 );
    }
    else
    {
        // restore last window size and position
        glfwSetWindowMonitor( _wnd, nullptr,  _wndPos[0], _wndPos[1], _wndSize[0], _wndSize[1], 0 );
    }

    _updateViewport = true;
}

Я рекомендую вам не создавать новое окно с glfwCreateWindow когда вы просто хотите переключиться между оконным и полноэкранным режимом. использование glfwSetWindowMonitor вместо.

Когда вы создаете окно с включенным полноэкранным режимом, вы должны передать на монитор аргументы, которые совместимы с режимом видео. Вы можете получить стандартный режим видео на основном мониторе следующим образом:

GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);

и переключиться в полноэкранный режим:

glfwSetWindowMonitor(window, mode, 0, 0, v->width, v->height, v->refreshRate);

Просто передайте nullptr-режим и ваши собственные ценности конечно:

glfwSetWindowMonitor(window, nullptr, 0, 0, windowWidth, windowHeight, windowRefreshRate);

И не забудьте изменить размер окна просмотра и обновить камеру.


Вы изменяете размер окна просмотра и обновляете камеру, когда пользователь изменяет размер окна?

Есть несколько проблем с вашим кодом:

  1. При условии, что glfwCreateWindow установит разрешение на width * height в полноэкранном режиме это не правильно. Документация GLFW гласит (выделено мое):

    Для полноэкранных окон указанный размер становится разрешением требуемого режима видео окна. Пока полноэкранное окно не обозначено значком, для указанного монитора устанавливается поддерживаемый режим видео, наиболее точно соответствующий желаемому режиму видео.

  2. Предполагать, что размер окна указан в "пикселях", тоже неверно. Снова цитируем соответствующую часть документации:

    В то время как размер окна измеряется в экранных координатах, OpenGL работает с пикселями. Размер, в который вы переходите glViewportНапример, должно быть в пикселях. На некоторых машинах координаты экрана и пиксели одинаковы, а на других их не будет. Существует второй набор функций для получения размера в пикселях кадрового буфера окна.

Проблемы 1 и 2 можно решить, просто позвонив glfwGetFramebufferSize после того, как окно было создано. Это оставляет нас с проблемой 3:

  1. Ты звонишь glViewport без текущего контекста GL - что приводит к неопределенному поведению, и особенно к тому, что вообще не задает область просмотра. Теперь это действительно интересно, потому что начальный видовой экран для нового контекста будет полностью новым окном, так что ваши ошибки 1 и 2 не будут иметь прямого влияния. Они все еще могут иметь некоторый эффект позже, если ваш код полагается на m_width а также m_height содержащие полезные значения, хотя
Другие вопросы по тегам