逻辑的残影

作为一个棋力不高,但尚可作为消遣的普通人,我认为棋力的高低体现在头脑中能预先演算多少步棋。不过,听说即使是高手演算时也常会犯一种错误。举例来说,当演算到第三步的时候把一个子移开,但是演算到第五步的时候会下意识觉得那个子还在第二步的位置。这叫做『残影』棋子。

在做编程这样的复杂工作时,也经常在逻辑概念的变换中构建『残影』。最近就因为一个『残影』两天没有睡好觉。先不说犯错的经历,讲讲相关的概念本来应该是什么样子。计算机图形系统的终极目标是生成二维图像,这个图像可能会显示在显示器上,或者存储成文件,可能是一幅静态图片,也可能是一组图片构成的动画,可能是一幅照片,图案,也可能是一个虚拟 3D 场景的影像,总之终归是一个(或者多个)二维图像。生成二维图像需要预先开辟绘制的空间。这种空间一般显卡的显存中创建,因为这样才能使用 GPU 硬件加速。OpenGL 里对这种空间的抽象叫做 Framebuffer Object( FBO )。

我在《 OpenGL 随想》系列里说过 OpenGL 是一个重度依赖 context 的系统( Cocoa 的 Quartz 和 Windows 的 GDI+ 也依赖 context 。Quartz 的概念模型来自 PDF ,所以文档和打印系统也依赖 context 。由此可见一切图形相关系统都建立在 context 这个基础概念之上)。Context 这个概念有多重要,多基础,就体现出我后面犯的那个错误多愚蠢。FBO 是 OpenGL context 中的一部分,一个 context 可以拥有多个 FBO 。其中 ID 为 0 的 FBO 用于屏幕显示。

如果严格如上所述,那么这个模型是优美的。可是,OpenGL 设计之初只考虑到屏幕显示,FBO 在 OpenGL 2.1 和之前都不是标准的一部分,只是一个扩展( FBO EXT )。所以屏幕显示并不是真的被称为 0 号 FBO 。创建所谓『 0 号 FBO 』的过程和创建其它 FBO 的过程大不相同,所需的参数都直接传给创建 context 的函数。就是这个陷阱构建了逻辑『残影』的陷阱 —— 我把 context 当成了对『 0 号 FBO 』的封装。

正在开发的程序对 OpenGL 的应用都是 offscreen 渲染。所有的 onscreen 显示都在 OpenGL offscreen 渲染之后用其它方式完成。因为开发刚开始的时候在 Windows 平台上没来得及搞清如何使用 FBO EXT ,所以在 Windows 上的 offscreen 渲染也用的是『 0 号 FBO 』( Mac OS X 版本从一开始就使用了 FBO EXT ),必须创建一个不可见的窗口并将其 DC 句柄作为『 0 号 FBO 』传给 wglCreateContext() 。由于『 0 号 FBO 』和显示硬件紧密绑定,所以对高级需求有一些不可接受的限制。比如,普通 PC 显示器的单个色彩通道深度不超过 8 位,用『 0 号 FBO 』就没法生成 16 位色彩深度的位图。这时我开始在 Windows 上研究使用『真正的』FBO EXT 。

工作了一夜之后,我开始认为『真正的』FBO 解决了问题,而最初构建的那个逻辑『残影』从此开始作祟。因为把 context 当成了 『 0 号 FBO 』的封装,同时有了『真正的』FBO,我毫不犹豫的删掉了创建 context 的代码,包括在 Windows 上创建隐藏窗口和调用 wglCreateContext() 的代码,和在 OS X 上创建 CGL context 的代码。

结果当然是悲剧了。为了让过程更悲壮,删掉创建 context 代码之后的程序居然在一台 Windows PC 上和所有 Mac 上运行正常(除了一个不怎么被测试到的 case )。这个结果当然让我的『 context 不过是 0 号 FBO 的封装』的歪曲理论更加坚固。随着程序在其它 Windows PC 上产生垃圾结果,以及那个失败 case 被发现,我的头脑也跟着崩塌了。早上还在和同事夸耀删掉了创建 context 的『无用』代码,晚上就陷入了呆滞。

第二天早上醒来,迷迷糊糊中突然想起了什么是 context 的真实含义。早晨同事见到我的第一句话是『我觉得是删掉 context 的缘故』。知道这件事的美国同事也来信说他猜想是 context 的问题,尽管他只看到了运行结果没有看过代码。看来大家晚上都在思考啊(虽然美国是白天)!

回头分析,凭空构建出『残影』固然在于我因为时间紧迫没有仔细思考概念,但是很大程度上也是因为 OpenGL 的 API 由于历史原因没能清晰和正交的反应真正的概念。各个 FBO 本该在概念上平等,而且完全不用『 0 号 FBO 』的情况也是合理的,那么从 OpenGL 的状态机概念来讲,FBO 应该在 context 创建之后挂靠其上,而不应该和创建 context 的函数有任何语法上的直接关系。但是实际创建 context 的函数( CGLCreateContext()NSOpenGLContextwglCreateContext() )都需要一个事前创建的『 0 号 FBO 』作为参数,哪怕这个『 0 号 FBO 』完全没有用处。

发表评论

Fill in your details below or click an icon to log in:

WordPress.com 徽标

您正在使用您的 WordPress.com 账号评论。 注销 /  更改 )

Facebook photo

您正在使用您的 Facebook 账号评论。 注销 /  更改 )

Connecting to %s


%d 博主赞过: