本节摘要:DirectX 的诞生不是一次技术升级,而是一次秩序重建。本节拆解 HAL 与 HEL 这对行话,复盘九十年代驱动混沌如何逼出统一抽象层,并讲清 DirectDraw 与 Direct3D 早期版本各自的分工与局限。
图形行业的老兵聊天时,常冒出两个缩写:HAL 和 HEL。HAL,Hardware Abstraction Layer,硬件抽象层——把显卡的真实能力直接暴露给程序的那层薄壳;HEL,Hardware Emulation Layer,硬件模拟层——硬件做不了或没接上的功能,由 CPU 用软件兜底。这对词是理解 DirectX 设计哲学的钥匙:契约优先在硬件上兑现,兑现不了的由软件补齐,开发者代码不用改。听起来很美?它的代价与兴衰,正是本节的主线。
时间拨回九十年代中期。Windows 95 刚把图形界面推给大众,游戏开发者却在经历一段噩梦:想在这个系统上做出高速动画,得绕开 GDI——它为办公软件设计,慢得让游戏没法活。绕开的办法是直接怼硬件:S3 的显卡有一套寄存器,Trident 是另一套,ATI 又是一套。每支持一款芯片,就要重写一段底层代码;发行商说"这个月装机的机器用的是新芯片",你的移植排期就全乱。
彼时的游戏主机却是另一番景象:主机硬件固定,开发者贴着金属写代码,性能榨到极致。PC 阵营的问题不是硬件不行,而是没有契约——程序与硬件之间缺少一份双方都认的协议。微软看到了这个空档,1995 年推出 Windows Game SDK, shortly 后更名 DirectX。名字起得直白:Direct,绕过中间层直接够到硬件;X,从 DirectDraw、DirectSound 到 DirectInput,多媒体的一切都装进来。
DirectX 初代的架构答案就是那对行话。运行时在程序与驱动之间立了一份能力清单:程序问"能不能做这个效果",运行时查硬件支持情况——支持,走 HAL,直通驱动,性能拉满;不支持,走 HEL,CPU 软件模拟,画面对但速度打折。开发者写一份代码,高级显卡上飞驰,入门显卡上缓行,至少都能跑。
这套设计的聪明与妥协都值得细看。聪明在于它把"硬件碎片化"这个市场问题转译成了"性能分层"这个工程问题——碎片还在,但程序不用感知。妥协在于 HEL 的模拟成本极高:用 CPU 软画一帧复杂场景,帧率会难看到没法交付,所以实际上开发者很快就把"软模拟"当成"功能缺失"处理,能力检测成了每份 DirectX 代码的必修课。
早期 DirectX 的能力查询是一个巨大的标志位集合。下面的伪代码展示了那个年代的典型写法(接口名以 Direct3D 6 时代为参照):
// 上古风格的能力检测:查表决定走哪条代码路径 D3DDEVICEDESC7 desc; device->GetCaps(&desc); if (desc.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_POW2) { // 只支持2的幂尺寸纹理:贴图一律按 128、256 对齐 usePow2Textures = true; } if (desc.dwMaxVertexBlendMatrices >= 4) { // 硬件支持四矩阵骨骼混合:走硬件路径 useHardwareSkinning = true; } else { // 兜底:CPU 算完再提交 useCpuSkinning = true; }
这段代码的气味你会在后面章节反复闻到:能力决定路径,路径分裂成多份。DirectX 后来每一代演化,都在想办法让开发者少写这种分叉——直到 D3D 12 用功能级别(第二章 2.3 节)给出一份更优雅的答卷。
初代 DirectX 里,2D 与 3D 是两个平行世界。DirectDraw 管 2D:表面(Surface)概念、翻页(Flip)、位块传输(Blt),目标是让动画上屏够快。Direct3D 管 3D:早期分成立即模式(Immediate Mode)与保留模式(Retained Mode)两条线——前者给你顶点和矩阵自己拼,后者打包一个场景图替你管。保留模式想降低门槛,结果抽象漏风、性能不济,几年后悄无声息地死了;立即模式反而活成了后来 Direct3D 的正身。
这个分家留下了两个影响深远的遗产。其一,表面与翻页模型:DirectDraw 的 Flip 机制是今天交换链(Swap Chain)概念的直系祖先,第二章 2.2 节会沿着它讲帧的闭环。其二,2D 与 3D 的API割裂:DirectDraw 管不了 3D 场景里的 2D 元素,Direct3D 画个 UI 文本又费劲,这处裂缝直到 Direct2D/DirectWrite 时代(第四章 4.2 节)才真正弥合。

把镜头放到一家九十年代末的中小工作室:团队在主机上做完一款赛车游戏,要移植到 Windows。发行商要求覆盖当时主流的三款显卡。没有 DirectX 的年代,团队要写三套贴着寄存器的渲染后端,工期按月算;有了 Direct3D 6,他们写一套代码,启动时查能力表:三款卡都支持硬件纹理映射,主路径一致;其中一款不支持硬件雾化,程序走 HEL 软雾化——菜单界面没问题,比赛画面帧率掉到没法看。最后团队的选择是:把雾化设为可开关的画质选项,检测到不支持就默认关掉。
这个案例里藏着那个年代的全部要素:一份代码、能力检测、性能降级、以及最终依然要人工分叉的现实。DirectX 把"三套驱动接口"压成了"一份能力清单",这是质的飞跃;但清单之内的差异处理,仍是开发者的活。这场人与契约的持续博弈,正是后续每一代 DirectX 演化的推动力——1.2 节我们就沿着版本阶梯,把这场博弈的每一回合看清楚。
聊到这儿,值得把一个纠缠了几代人的困惑摊开:为什么有人说我装的是 DirectX 9,有人说我装的是 DirectX 12,可游戏盒子上又印着"需要 Direct3D 9 兼容显卡"——这三句话说的是一回事吗?不是。DirectX 是整个多媒体运行时的版本号,Direct3D 只是其中负责 3D 渲染的那个子集,两者的版本步调并不一致。DirectX 7 时代的 Direct3D 7、DirectX 9.0c 里的 Direct3D 9,是一一对应;但 DirectX 9.0c 这个版本号在 Windows XP 时代服役了近十年,靠不断追加的小版本号(9.0a、9.0b、9.0c)区分能力增量,"9.0c"于是成了那个年代游戏配置门槛的代名词。
还有一层历史遗留:早期 Direct3D 配套的辅助函数库(D3DX 系列)不随 Windows 发放,只随 SDK 安装或随游戏打包。于是出现了"系统 DirectX 版本够、游戏还是起不来"的经典工单——缺的不是运行时,是那颗没打包进去的辅助库。微软后来干脆把这类工具从系统契约里剥离,到 D3D 12 时代更是把辅助代码开源化、头文件化,让"契约"回归纯粹:系统只承诺核心运行时,工具链归工具链。理解了这个分野,你就能读懂为什么第 2.3 节要单独讲功能级别——那才是 Direct3D 自己的能力标尺。