OpenGL 学习笔记 II:初始化 API,第一个黑窗,游戏循环和帧率,OpenGL 默认垂直同步,glfw 帧率

本文主要是介绍OpenGL 学习笔记 II:初始化 API,第一个黑窗,游戏循环和帧率,OpenGL 默认垂直同步,glfw 帧率,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

前情提要:

上一篇: OpenGL 学习笔记 I:OpenGL glew glad glfw glut 的关系,OpenGL 状态机,现代操作系统的窗口管理器,OpenGL 窗口和上下文 OpenGL context_我说我谁呢 --CSDN博客 讲解了 OpenGL glew glad glfw glut 的关系,一笔带过 OpenGL 状态机,几句话讲解了现代操作系统的窗口管理器,理解了 OpenGL 窗口和上下文 OpenGL context 的概念。

重新备注一下,这系列笔记可以认为是基于 learnOpenGL 的个人复习补充笔记,主要是补充一些非机械式的直觉理解内容,比如为什么这样设置,我为什么要这样调 API,底层做了什么,以及一些他没有讲到的点,供我自己钻牛角尖用


这一段其实和图形学 OpenGL 没什么关系,复习可以跳过看下面的。如果复习 C/C++ 可以看一看

动静态库的复习:为什么要宏定义 GLEW_STATIC

  • GLEW 的实现有 static 版本和 shared 版本,不过公用一个头文件. 对于 shared 版本,一般需要链接到 so 文件(lib 在 windows)。实际 so 或者 lib 是怎么共享的呢?
  • 实际这个宏定义 is actually only needed for Windows platforms。下面解释这个事实。
  • 间接引用:ELF (PE)读入的时候,首先会解析符号表,一般来说,一层的引用(指针)很容易想到直接 map 好了共享库的地址空间之后,去把用到 shared 库的空位的给根据共享库的符号表给填好就行了,但是前面也说的,对于层层引用,根本是做不到直接写死的,比如你在dll的头文件里面 extern 了一个别的共享库的变量。因为链式加载库也是有顺序的,所以必须要根据 GOT 表来进行间接跳转。所以间接跳转是必要的开销。
  • extern 关键字:默认所有的非 static 函数都是 extern 的,即能被其他翻译单元访问。对于共享库,external linkage 保证引用了这个库(当然要经过头文件)的源码能够访问这些函数。这样的符号查询会推迟到链接时刻。对于共享库来说,链接的时候也要链接到 so 作为输入,只不过实际填写的时候是运行时。对于静态库来说,他直接被做掉了,因为静态库和生成的可执行文件一起包含翻译了。
  • extern 的查询顺序:ld.so 链接在 gcc 里面是 -l xxx 或者 -lxxx 对于在当前翻译单元能找到的符号,就会用在翻译单元里面。然后首先考虑(先绝对当前目录在走环境变量和系统目录)共享库的,最后考虑静态库的。默认的共享库以 so 后缀,静态库以 a 后缀。
  • 运行时:链接了之后 elf 里面就会有一个段存了他运行需要些什么共享库。这样 os 执行 exec 的时候,解析 elf,就能把共享库读进来然后 map 进虚拟地址空间。期间要做一些地址随机化的事情这里就不复习了。
  • M$ dll : 在微软这里的 PE 格式,类似 GOT 的东西是 IAT(import address table)实际来说就是 shared library(在微软是 dll)的 extern 都要坐在 GOT 表里面,然后运行通过间接访问来进行,至于怎么定义的就是加载库的 elf 段文件有定义一个 extern 变量(.data 数据段)的地方就把他地址写到整个程序的 GOT 里面。extern 这里其实是两用的, 导出定义用 extern,引入声明也用 extern。dll 比较特殊他的 dll 不是像 so 共享库那样默认全部都 export 的,而是要用 def 表来定义,或者通过__declspec(dllimport) 和 dllexport 对来实用。对于函数的 dllimport 默认的,对于数据来说必须指定使用 dllimport 因为 dll 的数据他不是放在 IAT 里面的,这是因为 windows 下的 dll 数据做了 CoW 处理。
  • 参考阅读:
    • dllimport 一定要用于变量(Window 下):LNK4217 - Russ Keldorph's WebLog - Site Home - MSDN Blogs (archive.org)
    • 全局变量机制不同:c - Why does Windows require DLL data to be imported? - Stack Overflow
    • dllimport 用于函数不一定要这样做:c++ - Why/when is __declspec( dllimport ) not needed? - Stack Overflow
    • linux 下共享库如何实现相似的可见性控制:Visibility - GCC Wiki (gnu.org)
  • 虽然讲了这么多,但是我并不打算用 glew,前面说了新的项目(不过这哪算什么项目啊...)可能使用 glad 是不错的。

使用 GLAD,轻装上阵

  • 前一篇文章没有解释为什么采用 glad,因为 glad 是一个你想要什么就生成什么的静态源码,默认自带所有 OpenGl 特定版本的 spec,然后还能自定义显卡厂商的 extension。https://glad.dav1d.de 通过这个网站就能生成 glad.c 和 .glad.h 文件,然后直接放进项目里配置好 CMake 就能用了,不需要安装,不需要考虑平台,下载 dll、lib 还是 so、a 文件。
  • GLAD 是一个纯纯静态源码使用的,非常好,我们直接用他。由此在不用区分什么 glew3s 和 glew3 以及要不要 define static 的区别了。
  • 需要注意的是我们说过 glew 和 glad 都是 loading library,所以实际使用是不会用到大量的 glad 函数的,而是就是正统的 glXXXX 的 opengl 函数
  • 不过 glad 就是太轻量级了,所以他必须(不是,实际你可以调用 wgl 或者 glx 的)配合 GLFW 使用。因为他要用到一个 GetProcAddress 函数(下面会讲为什么他在 glfw 里面),这个函数里面就是进行动态链接库查询的,然后返回函数指针。
  • glad 不需要文档,因为他的实际用途只有一开始 load,之后用的都是 glxxx 了,所以每次用只需要根据 glad 的 github README 把那几行 usage 给抄过来就行了

这里给出第一个 OpenGL 黑色窗口的代码

直接抄 LearnOpenGL 的和 glad README 的

// cpp 文件
//
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
GLFWwindow *window;
void glfw_chore() {// initglfwInit();glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);// create windowwindow = glfwCreateWindow(800, 600, "LearnOpenGL", nullptr, nullptr);if (window == nullptr) {std::cout << "Failed to create GLFW window" << std::endl;glfwTerminate();abort();}glfwMakeContextCurrent(window);
}
void glad_chore() {// glad: load all OpenGL function pointersif (!gladLoadGLLoader((GLADloadproc) glfwGetProcAddress)) {std::cout << "Failed to initialize GLAD" << std::endl;abort();}
}int main() {glfw_chore();glad_chore();while (!glfwWindowShouldClose(window)) {glfwPollEvents();std::cout << "One" << std::endl;glfwSwapBuffers(window);}glfwTerminate();return 0;
}

GLFW Window OpenGL context

  • 上下文:上一篇说过了对于 OpenGL 来说,状态机的状态是针对一个Context 来设置的,这个一个 Context 需要包含一个窗口的缓冲区,从而让 GPU 渲染到窗口的 framebuffer 上去。存在形式是一个全局变量挂到那个上下文上,所以是通过 MakeContextCurrent 这种名字的函数(实际 OpenGL 没有规定这个接口,由操作系统和显卡厂商共同实现)指定了之后就针对那个上下文进行操作。
  • 管理窗口的钩子:对于 GLFW 来说,他管理的是一个操作系统的窗口的交互然后架起到 OpenGL 的桥梁。所以实际他管理的对象是窗口,存在形式是通过一个窗口指针来指定要设置的窗口。实际一个窗口在 glfw 里面是一个很复杂的结构体,包括了窗口的各种属性。
  • : 因为我们不直接访问暴露的 framebuffer,实际因为我们通过 glfw 管理缓冲区(一个窗口一个),所以实际调用的 glfwMakeContextCurrent 来设置,里面会调用 OS、驱动 的 makecontextcurrent 的。这么看来,glfw 窗口就是我们以后管理 OpenGL 上下文的东西了,之后操作上下文的行为通过 glfwxxx 函数进行调用。而涉及在当前上下文上进行渲染、数据传送等操作就都通过 glxxx 函数进行
  • 配置与 getProcAddress而为什么对整体的 OpenGL 参数进行设置(比如核心立即模式和最小最大版本号这种设置)以及加载函数指针也通过 glfw 提供呢?我们其实知道 getProcAddress 是 x window (linux)和 wgl (win)提供的,这是因为整个 GUI 都是基于 OS 的,就算 OpenGL 想要直接写显卡framebuffer 使用像素图形模式功能,也需要透过 OS 查询显存映射地址,查询驱动本身也走 OS 查询,所以这部分直接就做在 OS 的窗口管理器接口下面了,所以归入 glx 和 wgl 函数簇下,对于使用跨平台的 glfw 的我们来说,自然这些工作就通过调用 glfw 来做了。

VSYNC 垂直同步

  • 垂直同步的好处和坏处,对不同游戏的适用范围玩游戏的时候其实肯定都学习过了,这里就不说了。我们从 OS  调度的方向来讲,VSYNC 有一个好处是很明显的,那就是减少 CPU 的 usage。至于具体的 gameloop 和各方面的分析,还涉及 gameloop 的写法,游戏逻辑时间、逻辑帧和渲染帧等概念,如果是网游还涉及网络质量问题,这个前面有三篇学游戏理论的时候的笔记我分析过了(其中一篇:游戏开发基础笔记:逻辑帧和物理帧辨析 | Gameloop | 游戏循环),不赘述。
  • 我们观察基本代码的运行结果,可以发现,打印的速度好像是一卡一卡的,这就是因为他启动了垂直同步,这样 CPU 就不会 100%(指一个 core ,实际可能 25% 或者 12.5%)了(主要是我回想 SDL 写的 RoboCat 游戏 gameloop 必100%,而 learnOpenGL 教程又没讲这一点,我忍不住)。
  • 最简单的 gameloop 里面,PollEvent 做的事是简单调用 windows 系统的消息模块,非阻塞 peek 全部已经 queued 的事件,然后调用 callback 之后返回。实际 callback 的事情可能什么都不做,或者不会太复杂,我们一般会把复杂的逻辑放到逻辑帧里面用一个 processInput()  wrapper 来处理所有我们关心的事件(比如通过 getKey 这种名字的函数)。这一点和网络编程是差不多的,因为 callback 一般是 stateless 的,不可能让他处理完全部东西,这个和 js 那种恶魔语言不同。对于需要急速捕获用户连击事件的话,用 callback 应该是更适用。总之一种(callback)是异步无状态的,一种是 backlog 自己 dispatch 输入,还是看用途。
  • 所以实际 vsync 部分发生在 swap buffer 里面。首先glfw的文档的确表明这个 If the swap interval is greater than zero, the GPU driver waits the specified number of screen updates before swapping the buffers. 在  window 下,我 step into glfw_swapbuffers 最后发现调用的是 wingdi 的 swapbuffers。查阅我没有看到具体实现为什么会引发 interval,而且他另外还有一个 swapinterval 函数,结果发现 swapinterval 底层 wglSwapIntervalEXT 这个函数其实是设置 interval 的意思,而不是和 swapbuffers 并列的。所以是通过  swapinterval 影响 swapbuffers 。吐槽 glfw 的文档搜索功能稀巴烂(可能是我不会用)。
  • 关闭 VSYNC需要注意 OpenGL (实际是 OS 和驱动实现)默认开启了垂直同步。我们当然是直接操作 glfw 设置各种和 context 有关的参数,所以就调用这个函数就行了:glfwSwapInterval。附带的参考连接是:I have a question about fps - support - GLFW opengl - My limited FPS : 60 - Stack Overflow
  • 实现一个 FPS 显示:An FPS counter (opengl-tutorial.org) 这种做法应该所有游戏里面通行的做法,实际显卡驱动提供的或者某些第三方插件(游戏盒子什么的)则应该是在操作系统、驱动里面进入显卡 render (每个渲染帧)之前挂上这段代码就能实现帧数显示了。

这篇关于OpenGL 学习笔记 II:初始化 API,第一个黑窗,游戏循环和帧率,OpenGL 默认垂直同步,glfw 帧率的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/970551

相关文章

c++ 类成员变量默认初始值的实现

《c++类成员变量默认初始值的实现》本文主要介绍了c++类成员变量默认初始值,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录C++类成员变量初始化c++类的变量的初始化在C++中,如果使用类成员变量时未给定其初始值,那么它将被

Knife4j+Axios+Redis前后端分离架构下的 API 管理与会话方案(最新推荐)

《Knife4j+Axios+Redis前后端分离架构下的API管理与会话方案(最新推荐)》本文主要介绍了Swagger与Knife4j的配置要点、前后端对接方法以及分布式Session实现原理,... 目录一、Swagger 与 Knife4j 的深度理解及配置要点Knife4j 配置关键要点1.Spri

Java中的for循环高级用法

《Java中的for循环高级用法》本文系统解析Java中传统、增强型for循环、StreamAPI及并行流的实现原理与性能差异,并通过大量代码示例展示实际开发中的最佳实践,感兴趣的朋友一起看看吧... 目录前言一、基础篇:传统for循环1.1 标准语法结构1.2 典型应用场景二、进阶篇:增强型for循环2.

Python循环结构全面解析

《Python循环结构全面解析》循环中的代码会执行特定的次数,或者是执行到特定条件成立时结束循环,或者是针对某一集合中的所有项目都执行一次,这篇文章给大家介绍Python循环结构解析,感兴趣的朋友跟随... 目录for-in循环while循环循环控制语句break语句continue语句else子句嵌套的循

HTML5 getUserMedia API网页录音实现指南示例小结

《HTML5getUserMediaAPI网页录音实现指南示例小结》本教程将指导你如何利用这一API,结合WebAudioAPI,实现网页录音功能,从获取音频流到处理和保存录音,整个过程将逐步... 目录1. html5 getUserMedia API简介1.1 API概念与历史1.2 功能与优势1.3

nginx启动命令和默认配置文件的使用

《nginx启动命令和默认配置文件的使用》:本文主要介绍nginx启动命令和默认配置文件的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录常见命令nginx.conf配置文件location匹配规则图片服务器总结常见命令# 默认配置文件启动./nginx

PostgreSQL 默认隔离级别的设置

《PostgreSQL默认隔离级别的设置》PostgreSQL的默认事务隔离级别是读已提交,这是其事务处理系统的基础行为模式,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价... 目录一 默认隔离级别概述1.1 默认设置1.2 各版本一致性二 读已提交的特性2.1 行为特征2.2

C++中RAII资源获取即初始化

《C++中RAII资源获取即初始化》RAII通过构造/析构自动管理资源生命周期,确保安全释放,本文就来介绍一下C++中的RAII技术及其应用,具有一定的参考价值,感兴趣的可以了解一下... 目录一、核心原理与机制二、标准库中的RAII实现三、自定义RAII类设计原则四、常见应用场景1. 内存管理2. 文件操

Python实例题之pygame开发打飞机游戏实例代码

《Python实例题之pygame开发打飞机游戏实例代码》对于python的学习者,能够写出一个飞机大战的程序代码,是不是感觉到非常的开心,:本文主要介绍Python实例题之pygame开发打飞机... 目录题目pygame-aircraft-game使用 Pygame 开发的打飞机游戏脚本代码解释初始化部

canal实现mysql数据同步的详细过程

《canal实现mysql数据同步的详细过程》:本文主要介绍canal实现mysql数据同步的详细过程,本文通过实例图文相结合给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的... 目录1、canal下载2、mysql同步用户创建和授权3、canal admin安装和启动4、canal