显卡和 DirectX 简史(二)DX 版本和兼容性

本文是对之前的新晚安短信 - Day 2的扩充。

感谢 @Swung0x48 的指正,较长的加粗部分是修改后的内容。另外也欢迎大家批评指正后续内容和博客其他文章!

图形 API 简介

说到显卡,也不得不提图形 API,OpenGL/Vulkan 和 Direct3D。

D3D 是 DX 中最重要、最活跃的部分。和 CPU x86 大统一不同,各家的显卡可以采用完全不同的架构和 ISA,因此一个统一的硬件抽象层就必不可少。现代的 DX 版本与 Windows 版本基本绑定,例如 Windows 10 的普及有部分归功于 DX12 吧。

与 DX 仅支持 Windows 和 Xbox 不同,OpenGL 是跨平台的(基本所有平台都支持)。在早年乘 Quake 东风在游戏界也风靡一时,专业软件也广泛使用 OpenGL(比如 Blender)。

不过由于开放标准的缘故,OpenGL 没能很好跟上硬件发展潮流,最终在游戏界中被 D3D 所取代。D3D 虽然在早年有效率和易用性问题,但毕竟是微软亲儿子,随着不断的改进,在 DX9 时代全面超越 OpenGL 也是必然的了。

Vulkan 作为 OpenGL 的直接继任者(GLNext),和 DX12 一样是底层 API。

由于 DX 在 PC 游戏中还是具有压倒性优势的,因此本考古系列也就从 DX 开始介绍。其实是我自己也只深入研究了 DX。

Direct3D 版本简介

这部分就是对Wikipedia - Direct3D的翻译了,有删减;另外也参考了各版本 DirectX SDK 的文档

DirectX 1.0 (1995) 并没有提供 3D 部分,因此不存在 Direct3D 1.0。

Direct3D 2.0 和 3.0 (1996) 之间没有区别,是最早的 D3D。D3D2/3 提供了立即模式(Immediate Mode)和保留模式(Retained Mode)两种渲染方式。RM 是建立在 IM 上的,它不够灵活,没有多少游戏使用,所以最终在 DX10 中被移除了。而 D3D2/3 IM 采用的是执行缓冲区(execute buffer)模型,比较难用而且效率不高,因此早期的 D3D 并不太受欢迎。

由于种种原因,DirectX 4.0 是不存在的。

Direct3D 5.0 (1997) 改进了立即模式,模仿当时更流行的 OpenGL 和 Glide 加入了 DrawPrimitive 模式,不过还是保留了执行缓冲区模式。从本质上讲,D3D5 相比 D3D3 并没有加入新的硬件支持,当时的 3D 加速卡还处于很早期的阶段。

Direct3D 6.0 (1998) 则加入了很多硬件特性,例如顶点缓冲区(vertex buffer)、多重纹理(multitexture)、模板缓冲区(stencil buffer)、凹凸贴图(bump mapping)、纹理压缩等。很容易感受到,3D 加速卡厂商开始堆功能了。D3D6 同时也优化了 CPU 部分,并提供了软件渲染,可以说是后面 Windows Advanced Rasterization Platform (WARP) 的原型了。

Direct3D 7.0 (1999) 进一步加入了硬件加速 T&L(transform and lighting)、立方体环境贴图(cubic environment mapping)、几何混合(geometry blending)等,改进了顶点缓冲区、纹理管理和软件渲染等。上图是几何混合的官方示意图。D3D7 是最后一个只支持固定管线的版本,不过 D3D8/9 仍然支持固定管线。同时 GeForce 256 代表 3D 加速卡正式获得了 GPU 这个名字。

Fun Fact: 虽然硬件 T&L 听起来很高级,支持顶点变换和光照计算(包括逐像素光照),但实际上仍然属于固定管线,因为其本质上仍然是不可编程的。

Direct3D 8.0 (2000) 最重要的变化是引入了顶点着色器(vertex shader)和像素着色器(pixel shader),对应的着色器模型(Shader Model)是 1.x。另外 D3D8 也整合了 DirectDraw,支持多重采样(multisampling)来提供抗锯齿、动态模糊和景深等效果。此外还支持高性能粒子系统、3D 纹理、LOD(level of detail)、高阶图元等。

Fun Fact: SM 1.x 是用 D3D 规定的硬件无关汇编编程的,类似于中间代码表示。有很多 D3D8/D3D9 的游戏主体仍然使用固定管线,只用着色器实现一些特效。这个中间代码形式在现在仍然存在(可以参考著名的A trip through the Graphics Pipeline),不过有了 HLSL 几乎没人会去手写了。

Direct3D 9.0 (2002) 是更加重大的更新,支持 SM 2.0 和 3.0(9.0c),加入 HLSL,对于着色器的功能更新后面单列。支持多渲染目标(MRT)、多元素纹理(multielement textures)、位移贴图(displacement mapping)、HDR 等。DX9.0c 也是 Windows XP、2000、98 等支持的最高版本。

Direct3D 10.0 (2006) 最主要的是让 GPU 架构实质上进入了统一着色器架构时代。统一着色器(硬件一般称为流处理器)能完成原来的顶点着色器和像素着色器的功能,另外还加入了几何着色器(geometry shader)。对应的是 SM 4.x。同时移除了保留模式和固定管线,并开始用 FL(feature level)来区分硬件支持的功能。

Direct3D 11.0 (2009) 带来了 SM 5,加入了曲面细分(tessellation)和对应的着色器、多线程渲染、计算着色器(compute shader)等。其实曲面细分很早就在硬件中实现 了,但使用不多,到 D3D11 才正式加入,但不兼容以前的各种硬件实现。

Direct3D 12.0 (2015) 就是底层 API 了,加入了很多改进,但比较广为人知的可能就是后面加入的光追了。

Fun Fact: 尽管 D3D2/3 中用执行缓冲区实现的立即模式最终被移除了,现在发现这种思想确实能有效减少 API 调用的 overhead,因此在现代 API(D3D12/Vulkan/Metal)中都使用原理类似的 command list。不过与 D3D2/3 不同的是,现代的实现解决了当年的效率问题,一次提交大量的 draw call,从而提高渲染效率。

Direct3D 版本兼容性

本部分同时还参考了Direct3D versioning and compatibility

和 Windows、x86 一样,D3D 显然是向上兼容设计的。也就是说,支持新的 D3D 运行时的系统理论上也能运行旧版本的 D3D 程序。当然也有例外,比如上面提到的保留模式支持就在现代的 D3D 中被移除了。例如,我现在支持 D3D12 的系统,理论上能运行所有使用立即模式 D3D2~D3D12 的程序。但我的系统无法运行基于 D3D8 的 3DMark 2001 SE,因为它会认为 Ryzen 处理器不支持 MMX……这个 bug 现在似乎消失了,不会是 AMD 显卡驱动更新的结果吧?但我可以运行同样基于 D3D8 的上古卷轴 3(TES Morrowind)。所以理论上旧游戏的不兼容问题一般不是 D3D 带来的。

但麻烦的是,D3D 还有有限的向下兼容,或者说新版本 D3D 程序也能运行在旧版本 D3D 运行时的系统上。毕竟,你总不希望你的显卡在下一个 DX 版本广为使用之后就没法运行了吧。

D3D 的向下兼容,以 D3D9 为分界线,D3D9 及以下采用的是 Caps,而 D3D10 及以上使用的是 FL。Caps 允许程序查询显卡是否支持某个特性,而 FL 则更简单的给出显卡实际支持的最高功能等级。也就是说,在早期很多特性都是显卡实现可选的,这一点和 OpenGL 扩展有点相似。Caps 虽然灵活,但加大了 D3D 开发者(通常是引擎开发者)的负担,因此 FL 的引入将大量「功能扩展」转变成了少数几个「等级」,牺牲灵活度来换取易用性。

向下兼容的具体机制就是,新版本的 D3D 运行时可以调用为旧版本 DX 开发的驱动,从而使旧显卡能运行新游戏。为什么要强调驱动呢?因为在 Caps 时代,设备驱动接口(DDI)版本可以与显卡实际支持的 D3D 版本不同!DDI 版本可以运行 dxdiag 查看。

下表是我比较熟悉的显卡示例:

名称 D3D 版本 DDI 版本
S3 Virge D3D3 DDI 5
Riva 128 D3D5 DDI 6
Riva TNT2 M64 D3D6 DDI 7
GeForce 2 MX400 D3D7 DDI 9
Radeon 9250 D3D8 DDI 9

下表是 D3D 运行时向下兼容 DDI / FL 的情况:

D3D 版本 最低兼容的 DDI / FL
D3D<=7 所有
D3D8 DDI 6
D3D9 DDI 7
D3D10 FL 10
D3D11 FL 9
D3D12 FL 11

当然这只是理论上的兼容性,实际中可能还会受游戏本身的兼容要求限制。例如采用 D3D9 开发,要求最低 PS 1.x 的游戏,显然没法在 D3D7 的硬件上运行,因为其不支持可编程着色器,尽管显卡的驱动可能是 DDI 9 的。

如果要 push 下限,Riva TNT2 M64 看起来是很不错的选择。尽管它只支持 D3D6,但驱动是 DDI 7,因此可以兼容 D3D9 的游戏。测试也证实了这一点:标注最低支持 GeForce 2 的 WolfQuest 2.5,实际上使用 TNT2 M64 也能运行,因为它是没有其他限制的 D3D9 游戏。不过某些集显,例如 Intel GMA 950 似乎也有这样的特性,但可惜我家带 GMA 950 的主板坏了……不过从年份上看,TNT2 M64 不是那么突出,毕竟传奇的 GeForce 256 也是同年发布的,就完全支持 D3D7 了。

DirectX 与 Windows 兼容性

解决了 DDI 兼容性,还有其他一些兼容性限制,其中最主要的就是 Windows 版本的兼容性问题了。

下表是各版本 Windows 最高支持的 DX 版本:

Windows 版本 最高支持的 DX 版本
Windows 95 DX 8.0
Windows 98 / ME / 2000 / XP DX 9.0c
Windows Vista DX 10.1 / 11*
Windows 7 DX 11 / 11.1* / 12*
Windows 8 DX 11.1
Windows 8.1 DX 11.2
Windows 10 DX 12
Windows 11 DX 12

* 非原生支持,需要平台升级等

另外,尽管 Windows 98 / ME / 2000 / XP 都最高支持 DX 9.0c,但停止支持的时间不同,有一些不明显的差异。

除了 DX 版本与 Windows 版本有关联外,显卡驱动程序可能也对 Windows 版本有要求。例如,Riva 128 最高只支持 Windows 9x,而 GeForce 7 开始不支持 Windows 9x,GeForce 8 以上才支持 Windows 10,等等。游戏对于 Windows 版本(如果是 9x 倒是可以用 KernelEx,有人因此成功在 Windows 98 上运行 Minecraft)和硬件也会有要求,因此满足 DX 版本要求只是最基本的要求。


请问如果需要开发一个游戏,能支持使用任何版本 D3D 的硬件加速,并且充分发挥硬件的特性,至少要开发几个 D3D 版本?

答案应该是 D3D7(最后一个完全向下兼容的版本)、D3D9、D3D11 以及 D3D12。恰好这几个版本还是比较流行的,于是我接下来的计划就是,开发这样一个游戏