在 OpenGL/GLFW 3.2 中在窗口和全屏之间切换
Switching Between windowed and full screen in OpenGL/GLFW 3.2
我正在 Linux 上学习 OpenGL,但我无法进行模式切换(window 切换到全屏并返回)。
window 似乎进入了全屏模式,但看起来不正确。要切换模式,正在创建新的 window 并销毁旧的。
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);
}
初始Window
全屏(不正确)
Return 到 Windowed
问题更新
我已更新代码以使用您的代码并遇到同样的问题。根据您的建议,我现在正在更新相机,但仍然无济于事:(
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;
}
}
兔子:当我调整大小时:
兔子:当我进入全屏时:
在下文中,我将描述一个小而方便的 class,它处理调整 GLFW window 的大小并处理开关全屏 window 打开和关闭。
GLFW documentation.
中详细记录了所有使用的 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 );
};
创建window时,用户函数指针(glfwSetWindowUserPointer
)被设置为window管理class。调整大小回调由 glfwSetWindowSizeCallback
设置。在 window 创建后,它的当前大小和位置可以通过 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;
}
当调整大小通知发生时,那么window管理class的指针可以通过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 );
}
window 大小的任何更改都会收到通知,并会存储新的 window 大小 (glfwGetWindowSize
):
void OpenGLWindow::Resize( int cx, int cy )
{
_updateViewport = true;
}
当 window 大小发生变化时,视口必须适合 window 大小 (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();
}
}
如果当前window处于全屏模式,可以通过请求window用于全屏模式(glfwGetWindowMonitor
)的显示器来实现:
bool OpenGLWindow::IsFullscreen( void )
{
return glfwGetWindowMonitor( _wnd ) != nullptr;
}
要打开和关闭全屏模式,必须调用 glfwSetWindowMonitor
,或者使用全屏模式的监视器,或者使用 nullptr
:
void OpenGLWindow::SetFullScreen( bool fullscreen )
{
if ( IsFullscreen() == fullscreen )
return;
if ( fullscreen )
{
// backup window position and window size
glfwGetWindowPos( _wnd, &_wndPos[0], &_wndPos[1] );
glfwGetWindowSize( _wnd, &_wndSize[0], &_wndSize[1] );
// get resolution of monitor
const GLFWvidmode * mode = glfwGetVideoMode(_monitor);
// switch 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
会在全屏模式下将分辨率设置为 width
* height
是不正确的。 GLFW documentation 状态(强调我的):
For full screen windows, the specified size becomes the resolution of the window's desired video mode. As long as a full screen window is not iconified, the supported video mode most closely matching the desired video mode is set for the specified monitor.
假设在"pixels"中指定的window大小不正确either.Quoting再次relevant part of the documentation:
While the size of a window is measured in screen coordinates, OpenGL works with pixels. The size you pass into glViewport
, for example, should be in pixels. On some machines screen coordinates and pixels are the same, but on others they will not be. There is a second set of functions to retrieve the size, in pixels, of the framebuffer of a window.
问题 1 和 2 可以通过在创建 window 之后简单地调用 glfwGetFramebufferSize
来解决。这给我们留下了问题 3:
- 您在没有当前 GL 上下文的情况下调用
glViewport
-
导致未定义的行为,尤其是根本不设置视口。这实际上是一个有趣的问题,因为新上下文的初始视口将是全新的 window,因此您的错误 1 和 2 没有直接影响。不过,如果您的代码依赖于包含有用值的 m_width
和 m_height
,它们以后仍可能会产生一些影响。
当您只想在 windowed 和全屏之间切换时,我建议您不要使用 glfwCreateWindow
创建新的 Window。请改用 glfwSetWindowMonitor
。
当您创建启用全屏的 window 时,您必须传递与监视器上的视频模式兼容的参数。您可以像这样在主显示器上获得标准视频模式:
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
并切换到全屏:
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
只需传递一个 nullptr
-mode 和您自己的值当然:
glfwSetWindowMonitor(window, nullptr, 0, 0, windowWidth, windowHeight, windowRefreshRate);
别忘了调整视口大小和更新相机。
您是否在用户调整 window 大小时调整视口大小并更新相机?
我正在 Linux 上学习 OpenGL,但我无法进行模式切换(window 切换到全屏并返回)。
window 似乎进入了全屏模式,但看起来不正确。要切换模式,正在创建新的 window 并销毁旧的。
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);
}
初始Window
全屏(不正确)
Return 到 Windowed
问题更新
我已更新代码以使用您的代码并遇到同样的问题。根据您的建议,我现在正在更新相机,但仍然无济于事:(
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;
}
}
兔子:当我调整大小时:
兔子:当我进入全屏时:
在下文中,我将描述一个小而方便的 class,它处理调整 GLFW window 的大小并处理开关全屏 window 打开和关闭。
GLFW documentation.
#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 );
};
创建window时,用户函数指针(glfwSetWindowUserPointer
)被设置为window管理class。调整大小回调由 glfwSetWindowSizeCallback
设置。在 window 创建后,它的当前大小和位置可以通过 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;
}
当调整大小通知发生时,那么window管理class的指针可以通过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 );
}
window 大小的任何更改都会收到通知,并会存储新的 window 大小 (glfwGetWindowSize
):
void OpenGLWindow::Resize( int cx, int cy )
{
_updateViewport = true;
}
当 window 大小发生变化时,视口必须适合 window 大小 (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();
}
}
如果当前window处于全屏模式,可以通过请求window用于全屏模式(glfwGetWindowMonitor
)的显示器来实现:
bool OpenGLWindow::IsFullscreen( void )
{
return glfwGetWindowMonitor( _wnd ) != nullptr;
}
要打开和关闭全屏模式,必须调用 glfwSetWindowMonitor
,或者使用全屏模式的监视器,或者使用 nullptr
:
void OpenGLWindow::SetFullScreen( bool fullscreen )
{
if ( IsFullscreen() == fullscreen )
return;
if ( fullscreen )
{
// backup window position and window size
glfwGetWindowPos( _wnd, &_wndPos[0], &_wndPos[1] );
glfwGetWindowSize( _wnd, &_wndSize[0], &_wndSize[1] );
// get resolution of monitor
const GLFWvidmode * mode = glfwGetVideoMode(_monitor);
// switch 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
会在全屏模式下将分辨率设置为width
*height
是不正确的。 GLFW documentation 状态(强调我的):For full screen windows, the specified size becomes the resolution of the window's desired video mode. As long as a full screen window is not iconified, the supported video mode most closely matching the desired video mode is set for the specified monitor.
假设在"pixels"中指定的window大小不正确either.Quoting再次relevant part of the documentation:
While the size of a window is measured in screen coordinates, OpenGL works with pixels. The size you pass into
glViewport
, for example, should be in pixels. On some machines screen coordinates and pixels are the same, but on others they will not be. There is a second set of functions to retrieve the size, in pixels, of the framebuffer of a window.
问题 1 和 2 可以通过在创建 window 之后简单地调用 glfwGetFramebufferSize
来解决。这给我们留下了问题 3:
- 您在没有当前 GL 上下文的情况下调用
glViewport
- 导致未定义的行为,尤其是根本不设置视口。这实际上是一个有趣的问题,因为新上下文的初始视口将是全新的 window,因此您的错误 1 和 2 没有直接影响。不过,如果您的代码依赖于包含有用值的m_width
和m_height
,它们以后仍可能会产生一些影响。
当您只想在 windowed 和全屏之间切换时,我建议您不要使用 glfwCreateWindow
创建新的 Window。请改用 glfwSetWindowMonitor
。
当您创建启用全屏的 window 时,您必须传递与监视器上的视频模式兼容的参数。您可以像这样在主显示器上获得标准视频模式:
GLFWmonitor *monitor = glfwGetPrimaryMonitor();
const GLFWvidmode *mode = glfwGetVideoMode(monitor);
并切换到全屏:
glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
只需传递一个 nullptr
-mode 和您自己的值当然:
glfwSetWindowMonitor(window, nullptr, 0, 0, windowWidth, windowHeight, windowRefreshRate);
别忘了调整视口大小和更新相机。
您是否在用户调整 window 大小时调整视口大小并更新相机?