如何设计 window 渲染循环?
How to design a window render loop?
我正在尝试设计一个 window 对象,其工作是处理 GLFW 的所有功能 window(初始化、回调、输入处理...)
最重要的事情之一是渲染循环。我能想到的最幼稚的设计是让 renderloop 方法接受一个没有参数的函数指针,然后在循环方法中调用它,如下所示:
class Window
{
public:
Window();
~Window();
void WindowLoop(void (*f) (void));
protected:
GLFWwindow* window;
};
void Window::WindowLoop(void (*f) (void))
{
while(!glfwWindowShouldClose(window))
{
f();
glfwPollEvents();
}
}
然而,这带来了很多限制。一方面,它暗示函数不能接受任何参数。这可能是也可能不是问题。
我做了一些研究,显然你可以传递带有任意数量参数的函数指针,但这似乎既困难又不建议。
另一种选择是通用仿函数,然后可以将参数定义为 class/struct 的一部分,避免必须处理它。
可能还有其他我不知道的潜在设计。
在 C++ 中,对于渲染循环,哪个是一个好的设计,首先要考虑使用的多功能性,其次是执行速度?
我的简短回答是:
使用 std::function
而不是原始函数指针。这为您提供了更大的灵活性,因为它可以容纳:
- 函数指针
- 方法指针(当然有对象)
- 仿函数
- lambdas(有或没有实际上重复上述一个或另一个的捕获)。
您仍然需要为调用定义签名,但您可以提供您的回调上下文,这可能就是您所需要的。
所以,这就是它的样子:
#include <functional>
class Window
{
public:
Window();
~Window();
void WindowLoop(std::function<void()> f);
protected:
GLFWwindow* window;
};
void Window::WindowLoop(std::function<void()> f)
{
while(!glfwWindowShouldClose(window))
{
f();
glfwPollEvents();
}
}
(看起来和OP的原样差别不大。)
三思而后行,我发现值得一提的是小部件集及其提供的解决方案(因为有同样的问题需要解决)。
两个通用的解决方案是
- 信号/信号处理程序(signal slot concept)
virtual
可以覆盖的事件处理程序方法。
信号基本上就是一个带有函数指针的容器(或 std::function
或类似的东西)。在某些情况下会发出信号(即调用存储的函数指针)。因此,其他对象可以在这种情况下得到通知(通过在信号中注册它们的信号处理程序)。所以,一个信号实际上和上面类似,只是函数指针不是临时提供的,而是存储在成员变量中。
另一种方法是在某些情况下调用 virtual
方法。要添加自定义行为,resp。必须派生基础 class 并且必须覆盖任务中的 virtual
方法。
在 OP 的情况下,这可能是这样的:
class Window
{
public:
Window();
~Window();
void WindowLoop();
protected:
virtual void step();
protected:
GLFWwindow* window;
};
void Window::WindowLoop()
{
while(!glfwWindowShouldClose(window))
{
step();
glfwPollEvents();
}
}
void Window::step() { /* empty placeholder */ }
要在应用程序中使用它,Window
的派生 class 是强制性的:
class GameWindow: public Window {
protected:
virtual void step() override;
};
void GameWindow::step()
{
// Do the game step stuff (e.g. rendering)
// where this (of type GameWindow) can provide the necessary context.
}
关于Qt,有各种情况的信号和virtual
方法,例如小部件中的事件处理程序。主要是 either/or 的选择——我不记得两者都可以用于某事。例如。可以为 QPushButton::clicked()
注册一个信号处理程序,但要将事件处理程序自定义为 mousePressEvent()
,必须重载 Qt 小部件以覆盖事件处理程序方法。 (也有事件过滤器的概念,但恕我直言,这并不完全相同。)
gtkmm 而不是(至少在我过去使用的版本 2 中)提供了我能记住的所有东西 virtual
方法 和 信号。因此,始终可以选择派生 gtkmm 小部件或 change/extend gtkmm 小部件的行为,只需注册一个信号处理程序即可。这可能会引入很少的额外性能成本,但对于应用程序编程来说非常方便。
我正在尝试设计一个 window 对象,其工作是处理 GLFW 的所有功能 window(初始化、回调、输入处理...)
最重要的事情之一是渲染循环。我能想到的最幼稚的设计是让 renderloop 方法接受一个没有参数的函数指针,然后在循环方法中调用它,如下所示:
class Window
{
public:
Window();
~Window();
void WindowLoop(void (*f) (void));
protected:
GLFWwindow* window;
};
void Window::WindowLoop(void (*f) (void))
{
while(!glfwWindowShouldClose(window))
{
f();
glfwPollEvents();
}
}
然而,这带来了很多限制。一方面,它暗示函数不能接受任何参数。这可能是也可能不是问题。
我做了一些研究,显然你可以传递带有任意数量参数的函数指针,但这似乎既困难又不建议。
另一种选择是通用仿函数,然后可以将参数定义为 class/struct 的一部分,避免必须处理它。
可能还有其他我不知道的潜在设计。
在 C++ 中,对于渲染循环,哪个是一个好的设计,首先要考虑使用的多功能性,其次是执行速度?
我的简短回答是:
使用 std::function
而不是原始函数指针。这为您提供了更大的灵活性,因为它可以容纳:
- 函数指针
- 方法指针(当然有对象)
- 仿函数
- lambdas(有或没有实际上重复上述一个或另一个的捕获)。
您仍然需要为调用定义签名,但您可以提供您的回调上下文,这可能就是您所需要的。
所以,这就是它的样子:
#include <functional>
class Window
{
public:
Window();
~Window();
void WindowLoop(std::function<void()> f);
protected:
GLFWwindow* window;
};
void Window::WindowLoop(std::function<void()> f)
{
while(!glfwWindowShouldClose(window))
{
f();
glfwPollEvents();
}
}
(看起来和OP的原样差别不大。)
三思而后行,我发现值得一提的是小部件集及其提供的解决方案(因为有同样的问题需要解决)。
两个通用的解决方案是
- 信号/信号处理程序(signal slot concept)
virtual
可以覆盖的事件处理程序方法。
信号基本上就是一个带有函数指针的容器(或 std::function
或类似的东西)。在某些情况下会发出信号(即调用存储的函数指针)。因此,其他对象可以在这种情况下得到通知(通过在信号中注册它们的信号处理程序)。所以,一个信号实际上和上面类似,只是函数指针不是临时提供的,而是存储在成员变量中。
另一种方法是在某些情况下调用 virtual
方法。要添加自定义行为,resp。必须派生基础 class 并且必须覆盖任务中的 virtual
方法。
在 OP 的情况下,这可能是这样的:
class Window
{
public:
Window();
~Window();
void WindowLoop();
protected:
virtual void step();
protected:
GLFWwindow* window;
};
void Window::WindowLoop()
{
while(!glfwWindowShouldClose(window))
{
step();
glfwPollEvents();
}
}
void Window::step() { /* empty placeholder */ }
要在应用程序中使用它,Window
的派生 class 是强制性的:
class GameWindow: public Window {
protected:
virtual void step() override;
};
void GameWindow::step()
{
// Do the game step stuff (e.g. rendering)
// where this (of type GameWindow) can provide the necessary context.
}
关于Qt,有各种情况的信号和virtual
方法,例如小部件中的事件处理程序。主要是 either/or 的选择——我不记得两者都可以用于某事。例如。可以为 QPushButton::clicked()
注册一个信号处理程序,但要将事件处理程序自定义为 mousePressEvent()
,必须重载 Qt 小部件以覆盖事件处理程序方法。 (也有事件过滤器的概念,但恕我直言,这并不完全相同。)
gtkmm 而不是(至少在我过去使用的版本 2 中)提供了我能记住的所有东西 virtual
方法 和 信号。因此,始终可以选择派生 gtkmm 小部件或 change/extend gtkmm 小部件的行为,只需注册一个信号处理程序即可。这可能会引入很少的额外性能成本,但对于应用程序编程来说非常方便。