分岔之前:先转一次 RGBA,两条路共用

解码器给出来的是一张平面纹理,8bit 片源是 NV12,10bit 是 P010。窗口认 RGBA,Vulkan 那一侧的对接面也是 RGBA,这张纹理得先转一次。
转换在平台的像素处理层里做,Windows 上是一条计算着色器:平面纹理进,RGBA8 出,产物不回内存,而是一张自己建的共享纹理。这张共享纹理同时就是对外交出的 GPU 上下文,两个消费者都从它身上取东西,取法不同。
分岔点在它之后:一张已经转好的 RGBA 共享纹理,交给 Vulkan,还是交给窗口。
去处一:导进 Vulkan 管线
开关开着时,窗口渲染那一层把这张共享纹理交给 VK 那一路。VK 的输入层拿到的是一个 NT 句柄。
句柄在创建那张共享纹理的时候就准备好了:创建标志里带共享与 NT 句柄两项,建完把句柄导出。VK 这边先加载取句柄的两个入口,开一组外部内存相关的设备扩展;接着建图,建的时候声明这是一张外部图、句柄类型是 D3D11 纹理;再查这块内存的属性,按句柄把内存导进来,绑到图上。到这里,同一块显存有了两个 API 各自的视图。
VK 管线自己也有一台 D3D11 设备,用来把这个句柄打开成自己设备上的一张纹理与视图;输入层每帧把这张导入的图拷进管线自己的图,之后怎么做处理、怎么呈现,是渲染分层那一篇讲过的环节。
这一步不是无条件做的。只有当前这一帧确实是硬解帧(缓冲类型不是 cpu)时,才把平台像素层的上下文交出去。软解帧由 VK 自己上传平面,不需要它;而轨道跨媒体复用的时候,那个上下文可能还挂着上一场硬解留下的旧纹理,无条件喂进去,新场的输入槽就被翻成旧尺寸、旧格式,管线协商失败,整场黑屏。硬解切软解的时候真撞到过。
跨 API 的等法:读方在 CPU 上等一手
纹理能访问,不等于能读。GPU 上的写入是异步的,写的一方(计算着色器那一步)出图之后给共享栅栏发一个信号,信号值单调递增。
读的一方是 VK 输入层,它在导入之后等这一手。等的是写方最后一次提交的那个值:已经追上就直接往下走,不引入停顿;没追上才真等,而且这个等待发生在 CPU 上——把栅栏和一个事件挂上,然后等那个事件,带一个超时兜底,免得设备异常时把渲染线程卡死。
这一篇里的两个去处,只有这一条在等。
去处二:不进 Vulkan,由窗口那条链呈现
开关关掉时,窗口换成了另一个类:它自己建 D3D11 设备与交换链,把那张共享纹理当一张全屏四边形贴到后缓冲上,再提交呈现。

它拿纹理的方式和 VK 那边同源:先把共享纹理的互操作设备设成自己这一台,再把句柄在自己设备上打开,得到一张本地纹理和它的视图。之后每帧的动作很短,顺序见配图;视口按画面的真实比例算,只有全屏时才铺满窗口。呈现那一句带垂直同步。
这条路留着,是为了少一个会失败的环节:VK 那条要建面、建链、协商格式,窗口尺寸一变整条链还要重建;窗口这条只有取纹理、画四边形、呈现三步。
它不等写方那道栅栏。节奏靠提交时的垂直同步与交换链轮转错开,取纹理之前不做等待。
窗口尺寸变了只动交换链:换掉后缓冲就行,设备不重建。设备一重建,解码那个实现复用的那台设备就分家了(见硬解接入那一篇)。
第一帧之前还有一处判断:共享纹理还没下来时,窗口这一帧直接早退,不呈现空白画面。
软解帧也走这条路。窗口那条路对两种帧都收:硬解帧经平台像素层转出那张共享纹理,软解帧就地让平台渲染器吃掉、把转好的那张纹理下发窗口。少了后半句,切到软解之后窗口会因为手里没有共享纹理而一直早退,屏幕就是黑的。
换片与换分辨率:残块的来源
两张图尺寸不一样的时候,GPU 上的整图拷贝不报错,它只覆盖左上角那一块。换片或者换分辨率时若直接拷,屏幕上就是新画面缩在左上角、其余地方留着上一帧。
VK 输出那一侧的处理是分两路:尺寸对得上才直拷,对不上改走带缩放的那条路,把画面铺满目标。输入那一侧的对应动作更靠前:每帧读一次外部纹理的尺寸,跟上一层比,变了就重建整张图——老写法不重检,新场进来的还是旧尺寸的输入槽,画面从第一帧起就是错的。窗口这一路则是上一节那句后缓冲重建。
互操作能力也是先探一次
共享这条路上的格式是有限的一小撮:计算着色器的输入侧认 NV12、P010 与 YUV422P10;跨过 API 那道面对接的恒是 RGBA8,只在与 HDR 直通相关的那一档才走 RGBA10。这份互操作能力本身要探:启动时用一张 RGBA8 探针纹理试一次「按 D3D11 纹理的句柄类型导入」能不能成,不成就不做这条导入,落到软解帧那条路上。
探在启动时而不是等到某一帧,是为了让格式协商的失败发生在一个能被看见的时刻,而不是播放中途某张片子突然黑掉。
到这里两条路都通了:一条把硬解帧送进 Vulkan 的统一处理管线,一条绕开 VK 直接贴窗口。共用的是一次 RGBA 转换和那张共享纹理,不同的地方是呈现那一步由谁做。
Android 与 Apple 上,这两条路各自换一套显存对象——一边是 OES 纹理与 AHardwareBuffer,一边是 CVPixelBuffer 与 IOSurface,下面两章各写一遍。
引擎 avox 在 GitHub 开源:github.com/xxxzhou/avox,共享纹理、那道栅栏与窗口这一路都在里面。
音视频开发 播放器 硬件解码 Vulkan D3D11 零拷贝 C++