• Using OpenGL in a SFML window


    Introduction

    本教程不是关于OpenGL本身,而是关于如何使用SFML作为OpenGL的环境,以及如何将它们混合在一起。

    正如您所知,OpenGL最重要的特性之一是可移植性。但是单靠OpenGL还不足以创建完整的程序:您需要一个窗口、渲染上下文、用户输入等。您别无选择,只能编写特定于操作系统的代码来自行处理这些内容。这就是sfml窗口模块发挥作用的地方。让我们看看它如何允许您使用OpenGL。


    Including and linking OpenGL to your application

    OpenGL头文件在每个操作系统上都不相同。因此,SFML提供了一个“抽象”头文件,负责为您包含正确的文件。

    #include <SFML/OpenGL.hpp>
    
    • 1

    此标题包括OpenGL函数,而不包括其他内容。人们有时认为SFML自动包含OpenGL扩展头,因为SFML本身加载扩展,但这是一个实现细节。从用户的角度来看,必须像处理任何其他外部库一样处理OpenGL扩展加载。

    然后需要将程序链接到OpenGL库。与头文件不同,SFML不能提供统一的链接OpenGL的方法。因此,您需要根据所使用的操作系统(“Windows上的opengl32”、“Linux上的GL”等)知道要链接到哪个库。

    OpenGL函数以“gl”前缀开头。请记住,当您遇到链接器错误时,它将帮助您找到忘记链接的库。


    Creating an OpenGL window

    因为SFML是基于OpenGL的,所以它的窗口可以无需任何额外的努力就可以进行OpenGL调用。

    sf::Window window(sf::VideoMode(800, 600), "OpenGL");
    
    // it works out of the box
    glEnable(GL_TEXTURE_2D);
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5

    如果您认为它太自动化,sf::Window的构造函数有一个额外的参数,允许您更改底层OpenGL上下文的设置。此参数是sf::ContextSettings结构的一个实例,它提供对以下设置的访问:

    1. depthBits是用于深度缓冲区的每像素位数(0表示禁用)
    2. stencilBits是用于模具缓冲区的每像素位数(0表示禁用)
    3. antialiasingLevel是多重采样级别
    4. majorVersion和minorVersion构成所需的OpenGL版本
    sf::ContextSettings settings;
    settings.depthBits = 24;
    settings.stencilBits = 8;
    settings.antialiasingLevel = 4;
    settings.majorVersion = 3;
    settings.minorVersion = 0;
    
    sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, settings);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    如果图形卡不支持这些设置中的任何一个,SFML将尝试查找最接近的有效匹配。例如,如果4x抗锯齿太高,它会尝试2x,然后回落到0。

    在任何情况下,您都可以检查SFML在getSettings函数中实际使用了哪些设置:

    sf::ContextSettings settings = window.getSettings();
    
    std::cout << "depth bits:" << settings.depthBits << std::endl;
    std::cout << "stencil bits:" << settings.stencilBits << std::endl;
    std::cout << "antialiasing level:" << settings.antialiasingLevel << std::endl;
    std::cout << "version:" << settings.majorVersion << "." << settings.minorVersion << std::endl;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    SFML支持OpenGL 3.0以上版本(只要图形驱动程序能够处理)。SFML 2.3中添加了对选择3.2以上上下文的概要文件以及是否设置了上下文调试标志的支持。不支持前向兼容性标志。默认情况下,SFML使用兼容性配置文件创建3.2以上的上下文,因为图形模块使用传统的OpenGL功能。如果要使用图形模块,请确保在没有core profile设置的情况下创建上下文,否则图形模块将无法正常工作。在OS X上,SFML仅支持使用核心概要文件创建OpenGL 3.2以上的上下文。如果要在OS X上使用图形模块,则只能使用传统上下文,这意味着OpenGL版本2.1。


    A typical OpenGL-with-SFML program

    #include <SFML/Window.hpp>
    #include <SFML/OpenGL.hpp>
    
    int main()
    {
        // create the window
        sf::Window window(sf::VideoMode(800, 600), "OpenGL", sf::Style::Default, sf::ContextSettings(32));
        window.setVerticalSyncEnabled(true);
    
        // activate the window
        window.setActive(true);
    
        // load resources, initialize the OpenGL states, ...
    
        // run the main loop
        bool running = true;
        while (running)
        {
            // handle events
            sf::Event event;
            while (window.pollEvent(event))
            {
                if (event.type == sf::Event::Closed)
                {
                    // end the program
                    running = false;
                }
                else if (event.type == sf::Event::Resized)
                {
                    // adjust the viewport when the window is resized
                    glViewport(0, 0, event.size.width, event.size.height);
                }
            }
    
            // clear the buffers
            glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
            // draw...
    
            // end the current frame (internally swaps the front and back buffers)
            window.display();
        }
    
        // release resources...
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    这里我们不使用window.isOpen()作为主循环的条件,因为我们需要窗口保持打开状态,直到程序结束,以便我们仍然有一个有效的OpenGL上下文用于循环的最后一次迭代和清理代码。

    请毫不犹豫地查看SFML SDK中的“OpenGL”和“Window”示例。如果您还有其他问题,它们更完整,并且很可能包含您的问题的解决方案。


    Managing OpenGL contexts

    在SFML中创建的每个窗口都会自动附带一个OpenGL上下文。调用任何OpenGL函数时,它们都会在当前活动的上下文中操作。因此,每当调用OpenGL函数时,都需要上下文处于活动状态。如果调用OpenGL函数时上下文未处于活动状态,则函数调用将不会产生所需的效果,因为没有状态可对其产生影响。

    要激活窗口的上下文,请使用window.setActive(),与window.setActive(true)相同。当另一个上下文当前处于活动状态时激活该上下文将导致当前处于活动状态的上下文在激活新上下文之前被隐式停用。要显式停用窗口的上下文,请使用window.setActive(false)。如下文所述,如果要在另一个线程上激活上下文,则需要这样做。但是,通常,建议您在每次完成一批OpenGL操作时,只需停用上下文。按照这个建议,每一批操作都将明显地包装在激活和停用调用之间。可以为此编写一个RAII助手类。

    // activate the window's context
    window.setActive(true);
    
    // set up OpenGL states
    // clear framebuffers
    // draw to the window
    
    // deactivate the window's context
    window.setActive(false);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在SFML中调试OpenGL问题时,第一步始终是确保调用OpenGL函数时上下文处于活动状态。不要假设SFML将隐式激活上下文,或者在调用库时SFML将保留当前活动的上下文。提供的唯一保证是,当前线程上的活动上下文在调用window之间不会更改。设置活动(true)和窗口。setActive(false),只要其间没有对库进行其他调用。在所有其他情况下,必须假设当前上下文可能已更改,因此需要显式重新激活先前活动的上下文,以确保先前活动的上下文再次处于活动状态。还应确保在调用OpenGL函数时,正确的上下文处于活动状态。活动上下文不仅为OpenGL操作提供执行环境,还指定任何绘图命令的目标帧缓冲区。在没有可见帧缓冲区的上下文处于活动状态时调用OpenGL绘图函数将导致这些绘图命令不生成任何可见输出。在多个上下文中拆分OpenGL操作也会导致状态更改在上下文中传播。如果后续的任何绘制操作都假定设置了某些状态,则在这种情况下不会产生正确的结果。

    编写OpenGL代码时,强烈建议始终检查每次调用OpenGL函数后是否产生任何OpenGL错误。这是通过glGetError()函数完成的。在每次函数调用后检查错误将有助于缩小可能发生错误的范围,并显著提高调试效率。


    Managing multiple OpenGL windows

    管理多个OpenGL窗口并不比管理一个更复杂,只需记住几件事。

    OpenGL调用是在活动上下文(即活动窗口)上进行的。因此,如果要在同一程序中绘制到两个不同的窗口,则必须先选择哪个窗口处于活动状态,然后才能绘制某些内容。这可以通过设置活动功能完成:

    // activate the first window
    window1.setActive(true);
    
    // draw to the first window...
    
    // activate the second window
    window2.setActive(true);
    
    // draw to the second window...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    一个线程中只能有一个上下文(窗口)处于活动状态,因此在激活另一个窗口之前无需停用窗口,它会自动停用。这就是OpenGL的工作原理。

    要知道的另一件事是,由SFML创建的所有OpenGL上下文都共享它们的资源。这意味着您可以在任何上下文处于活动状态的情况下创建纹理或顶点缓冲区,并将其与任何其他上下文一起使用。这也意味着您在重新创建窗口时不必重新加载所有OpenGL资源。只有可共享的OpenGL资源才能在上下文之间共享。不可共享资源的一个示例是顶点数组对象。


    OpenGL without a window

    有时可能需要在没有活动窗口的情况下调用OpenGL函数,因此没有OpenGL上下文。例如,从单独的线程加载纹理时,或在创建第一个窗口之前。SFML允许您使用sf::Context类创建无窗口上下文。您所要做的就是实例化它以获得有效的上下文。

    int main()
    {
        sf::Context context;
    
        // load OpenGL resources...
    
        sf::Window window(sf::VideoMode(800, 600), "OpenGL");
    
        ...
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    Rendering from threads

    多线程程序的典型配置是在一个线程(主线程)中处理窗口及其事件,并在另一个线程中渲染。如果这样做,有一条重要的规则需要记住:如果上下文(窗口)在另一个线程中处于活动状态,则不能激活它。这意味着您必须在启动渲染线程之前停用窗口。

    void renderingThread(sf::Window* window)
    {
        // activate the window's context
        window->setActive(true);
    
        // the rendering loop
        while (window->isOpen())
        {
            // draw...
    
            // end the current frame -- this is a rendering function (it requires the context to be active)
            window->display();
        }
    }
    
    int main()
    {
        // create the window (remember: it's safer to create it in the main thread due to OS limitations)
        sf::Window window(sf::VideoMode(800, 600), "OpenGL");
    
        // deactivate its OpenGL context
        window.setActive(false);
    
        // launch the rendering thread
        sf::Thread thread(&renderingThread, &window);
        thread.launch();
    
        // the event/logic/whatever loop
        while (window.isOpen())
        {
            ...
        }
    
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    Using OpenGL together with the graphics module

    本教程介绍了OpenGL与sfml窗口的混合,这非常简单,因为这是本模块的唯一目的。与图形模块的混合有点复杂:sfml图形也使用OpenGL,因此必须格外小心,以使sfml和用户状态不会相互冲突。

    如果您还不知道图形模块,那么只需要知道sf::Window类被sf::RenderWindow替换,它继承了它的所有函数并添加了用于绘制SFML特定实体的特性。

    避免SFML和您自己的OpenGL状态之间发生冲突的唯一方法是每次从OpenGL切换到SFML时保存/恢复它们。

    - draw with OpenGL
    
    - save OpenGL states
    
    - draw with SFML
    
    - restore OpenGL states
    
    - draw with OpenGL
    
    ...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    最简单的解决方案是让SFML通过pushGLStates/popGLStates函数为您完成这项工作:

    glDraw...
    
    window.pushGLStates();
    
    window.draw(...);
    
    window.popGLStates();
    
    glDraw...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    由于SFML不了解OpenGL代码,因此无法优化这些步骤,因此它会保存/恢复所有可用的OpenGL状态和矩阵。这对于小型项目来说可能是可以接受的,但对于需要最大性能的大型项目来说,这可能太慢了。在这种情况下,您可以使用glPushAttrib/glpopatrib、glPushMatrix/glPopMatrix等自己处理OpenGL状态的保存和恢复。

    如果这样做,在绘制之前仍然需要恢复SFML自己的状态。这是通过resetGLStates函数完成的。

    glDraw...
    
    glPush...
    window.resetGLStates();
    
    window.draw(...);
    
    glPop...
    
    glDraw...
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    通过自己保存和恢复OpenGL状态,您可以只管理真正需要的状态,从而减少不必要的驱动程序调用的数量。

  • 相关阅读:
    JMeter做http接口功能测试
    基于bert_bilstm_crf的命名实体识别
    .NET 8 RC 2 发布,将在11月14日发布正式版
    Unity可视化Shader工具ASE介绍——6、通过例子说明ASE节点的连接方式
    2022牛客暑期多校训练营3 个人题解
    【单片机】13-实时时钟DS1302
    【JGit】 AddCommand 新增的文件不能添加到暂存区
    【Mybatis】搭建一个Mybatis框架需要做什么
    Shifu+WasmEdge:物联网数据轻松“瘦身”
    LoRA大模型加速微调和训练算法解读
  • 原文地址:https://blog.csdn.net/Phantom_matter/article/details/125534822