之前在开发 GGE Vulkan Rendering Backend 的过程中,有时仅仅通过 API 文档难以深入理解其背后的运行机制,于是产生了深入 Vulkan 驱动内部的想法,本文通过深入解析 AMD Vulkan 驱动源码,尝试了解 Vulkan 以及 GPU 硬件的运行机制。同时本文也是 Vulkan 探秘系列的第一篇。

AMD 在 2017年底放出了针对 AMD GPU 的 Linux 平台的 Vulkan 驱动程序源码:AMDVLK,github 源码地址:

https://github.com/GPUOpen-Drivers/AMDVLK​github.com

通过阅读 AMDVLK 源码,可以更深入的了解 Vulkan API 的实现机制和 GPU 内部细节,尽管 AMDVLK 是针对的是 linux 平台,但对移动平台的 Vulkan 深入学习也有一定的帮助作用。

本文分为 2 个大部分,第一部分讲解 AMDVLK 的整体架构以及核心接口说明,第二部分以 VkCmdDraw API 为切入点,深入 AMDVLK 源码进行解析。

声明:由于本人不是驱动开发人员,在解析过程中难免会可能产生误解甚至错误,如理解有误,望请及时指出,不吝赐教。

AMDVLK 整体架构及核心接口

下面是 AMDVLK 整体的架构图:

AMDVLK 架构图

XGL

其中 XGL 主要负责将 Vulkan API 命令转换为 PAL 命令。除了转换指令之外,它还使用基于 LLVM Pipeline Compiler(LLPC)库将 VkPipeline 的 Shader 编译为与 PAL 管道 ABI 兼容的代码对象。从 XGL 的职责可以看出,XGL 是比较薄的层次,驱动中大部分的工作代码都集中在 PAL 中。

PAL

PAL 是 AMD Radeon™ (GCN、RDNA及以上) 架构的硬件和操作系统的抽象层,运行在用户模式驱动中(UMD)中,是 UMD 的重要组成部分。PAL 接受来自于 XGL 转换过来的命令,在其内部根据所处硬件平台执行相应的硬件和操作系统指令。PAL 是相对较厚的驱动层,占据了基于 PAL 的 UMD 中的大部分代码量(除 LLPC 之外)。

PAL 内部的抽象级别并不是完全一致的,在功能相近的部分,抽象级别相对较高,在和硬件相关的不同部分,抽象级别相对较低。总体的原则是在不影响驱动性能的前提下尽可能共享更多的代码。

在架构上 XGL 是 PAL 的 Client,PAL 是 XGL 的 Server。

PAL 接口源码目录结构

  • PAL 使用 C++ 定义接口,.../pal/inc 目录定义了公共接口, Client 端如 XGL 必须包含此目录。在结构上基本上是一个接口对应一个头文件。.../pal/inc 包含三个子目录,分别如下:
  • inc/core 定义 PAL Core 核心接口,下文详述。
  • inc/util 定义 PAL 工具集,除了其特定于GPU的核心功能外,PAL还在Util名称空间中提供了许多通用的,操作系统抽象的软件实用程序。 PAL核心依赖于这些实用程序,但是它们的Client 也可以使用它们。 Util中提供的功能包括内存管理,调试打印和声明,通用容器,多线程和同步原语,文件系统访问以及加密算法实现。
  • inc/gpuUtil 定义 PAL GPU 工具集,除了通用的,不受操作系统限制的软件实用程序外,PAL还在GpuUtil命名空间中提供了GPU特定的实用程序。 这些实用程序提供了建立在核心Pal接口之上的通用,有用的功能。 一些示例包括用于使用GPU编写文本的接口,MLAA实现以及位于Pal :: IPerfExperiment之上的包装器,以简化性能数据收集。

PAL Core

PAL 的核心接口定义在 Pal namespace 中。以面向对象的形式与 GPU 和 OS 进行交互。PAL Core 的核心功能主要有:

  • 将所有的 Shader Stage,以及一些附加的“shader adjacent”状态整合成一个单一的 Pipeline Object。
  • 显示的、free-threaded 的 Command Buffer 生成。
  • 支持多个、异步引擎来执行 GPU 任务(GraphicsComputeTransfer)。
  • 显示的 System 和 GPU 的内存管理。
  • 灵活的 Shader 资源绑定模型。
  • 显示的同步管理,包括 Pipeline Stalls、Cache Flushes 以及 State Changes 压缩等等。

以上这些核心功能说明 PAL Core 是实现现代图形 API 特性的最重要部分。

PAL Core 核心接口

PAL Core 核心接口按照抽象类别分为三个大类,分别为 OS 抽象接口、硬件 IP 抽象接口以及公共基础类,具体如下:

OS 抽象接口

  • IPlatform:由与 PAL 交互的 Client 创建的根级对象。 主要负责枚举连接到系统的设备和屏幕,并返回所有系统范围的属性。
  • IDevice:用于查询特定 GPU 的属性并与其进行交互的可配置上下文。也是所有其他 PAL 对象的工厂类。
  • IQueue:一台设备具有一个或多个能够发布某些类型的工作的引擎。 例如,Tahiti 硬件拥有1个通用引擎(支持图形,计算或复制命令),2个计算引擎(支持计算或复制命令)和2个DMA引擎(仅支持复制命令)。 IQueue对象是用于在特定引擎上提交工作的上下文。 这主要采取提交命令缓冲区并将图像显示到屏幕的形式。 在队列中执行的工作将按顺序开始,但是在不同的队列(即使队列引用相同的引擎)上执行的工作也不能保证在没有显式同步的情况下进行排序。
  • IQueueSemaphore:可以从IQueue发出信号并等待队列信号,以便控制队列之间的执行顺序。
  • IFence:用于粗粒度CPU / GPU同步。 围栏可以作为队列中命令缓冲区提交的一部分从GPU发出信号,然后从CPU等待。
  • IGpuMemory:表示GPU可访问的内存分配。 可以是虚拟的(只能是必须通过IQueue操作显式映射的VA分配)或物理的。 Client 必须通过全局管理设备的物理分配的驻留时间(IDevice :: AddGpuMemoryReferences),或通过指定提交时命令缓冲区引用的分配来管理物理分配的驻留。
  • ICmdAllocator:用于支持ICmdBuffer的GPU内存分配池。 Client 可以自由地为每个设备创建一个分配器,或为每个线程创建一个分配器以消除线程争用。
  • IScreen:代表连接到系统的显示器。 用于将画面呈现到屏幕上。
  • IPrivateScreen:表示OS不可见的显示器,通常是VR头戴式显示器。

硬件 IP 抽象接口

  • 所有 IP

    • ICmdBuffer:Command Buffer 的抽象接口。
    • IImage:Image 的抽象接口。
  • 仅限 GFXIP
    • IPipeline:包含所有着色器阶段(用于计算的CS,用于图形的VS / HS / DS / GS / PS),描述着色器如何使用用户数据条目的资源映射,以及其他一些固定功能状态,例如depth / 颜色格式,混合启用,MSAA启用等。
    • IColorTargetView:允许将图像绑定为颜色目标(即RTV)。
    • IDepthStencilView:允许将图像绑定为深度/模板目标(即DSV)。
    • IGpuEvent:用于CPU和GPU之间的细粒度(命令内缓冲区)同步。 可以从CPU或GPU设置/重置GPU事件,然后从两者中等待。
    • IQueryPool:用于跟踪遮挡或管道统计信息查询结果。
    • 动态状态对象:IColorBlendState,IDepthStencilState 和 IMsaaState 定义与 DX11 类似的相关固定功能图形状态的逻辑集合。
    • IPerfExperiment:用于收集性能计数器和线程跟踪数据。
    • IBorderColorPalette:提供可索引颜色的集合,供钳制到任意边框颜色的采样器使用。

公共基础类

  • IDestroyable:所有 PAL 核心接口的基类,定义Destroy 方法。 调用 Destroy 将释放该对象的所有内部分配资源,但是该对象的系统内存需要由 Client 负责释放。
  • IGpuMemoryBindable:定义用于将 GPU 内存绑定到对象的一组方法。 继承IGpuMemoryBindable 的接口需要 GPU 内存才能被 GPU 使用。Client 必须查询请求的信息(例如,对齐,大小,堆),并为对象分配/绑定GPU内存。 IGpuMemoryBindable继承自 IDestroyable。

下图是在某个渲染帧中 PAL 的一些核心接口相互之间的交互和关系:

如图所示,IPlatform 中包含了多个 IDevice,每个 IDevice 对应一个物理 GPU 设备。在其中的一个 IDevice 中(左上),存在 4 个执行引擎,有4个 IQueue 对应这 4 个执行引擎,1个 Universal Queue,一个 Compute Queue,两个 DMA Queue,每个 Queue 都在独立的线程中提交。在图的右侧是一些存储 GPU 任务的 ICmdBuffer 接口,其中有执行场景渲染、后处理、纹理上传等等。不同的任务的 ICmdBuffer 在对应类型的 IQueue 中执行,如图中,Scene Rendering 在 Universal Queue 中执行,Post Processing 在 Compute Queue 中执行,而 Texture Upload 在 DMA Queue 中执行。在 Queue 中执行的除了 Command Buffer 以外,还有用于同步的 IQueueSemaphore,如图在 Universal Queue 中先 wait 了前两个 IQueueSemaphore,在执行完 Scene Rendering 之后,signal 最后一个 IQueueSemaphore。在 Compute 的 Queue 中,先 wait 最后一个IQueueSemaphore,在执行 Post Processing,最后 present。以此类推其他两个 Queue 也是如此。


VkCmdDraw 函数驱动源码解析

架构和核心接口讲完,我们从一个具体的 Vulkan API 切入到 AMDVLK 中来解析源码。这里我们从最常见的绘制 API VkCmdDraw 开始。

按照架构,首先分析的是 XGL 层。不过在讲解之前,先介绍下 Vulkan 函数声明格式中的调用约定宏。

如上所示,vkCmdDraw 函数声明中的 VKAPI_ATTR 和 VKAPI_CALL 是平台相关的调用约定宏,根据 Vulkan 规范描述,相应的 Vulkan 实现平台负责定义这些宏,以便 Vulkan 调用方以 Vulkan 实现期望的相同调用约定来调用 Vulkan 命令。

调用约定宏有3种,分别是 VKAPI_ATTR、VKAPI_CALL、VKAPI_PTR,下面分别说明:

  • VKAPI_ATTR:放在函数声明的返回类型之前。用于 C++11 和 GCC/Clang 风格的函数属性语法。
  • VKAPI_CALL:放在函数声明中的返回类型之后。用于 MSVC 样式的调用约定语法。
  • VKAPI_PTR:放在函数指针类型的“(”和 "*" 之间。

根据上述规则,一般的函数声明如下所示:

函数声明:  VKAPI_ATTR void VKAPI_CALL vkCommand(void);
函数指针类型: typedef void (VKAPI_PTR *PFN_vkCommand)(void);

在 XGL 中 Vulkan API 内部路由分发是 DispatchTable 辅助类完成,不过分发机制不是本文讨论的内容,所以在这里不再阐述。在 XGL 的 vk_cmdbuffer.cpp 中定义了 vkCmdDraw 函数,函数签名与 Vulkan API 完全一致,代码如下:

namespace vk
{namespace entry
{...
VKAPI_ATTR void VKAPI_CALL vkCmdDraw(VkCommandBuffer                             cmdBuffer,uint32_t                                    vertexCount,uint32_t                                    instanceCount,uint32_t                                    firstVertex,uint32_t                                    firstInstance)
{ApiCmdBuffer::ObjectFromHandle(cmdBuffer)->Draw(firstVertex,vertexCount,firstInstance,instanceCount);
}
...

vkCmdDraw 的实现非常简单,只有一行代码,只是将 Vulkan API 转发给 XGL 内部 CmdBuffer 对象的 Draw 函数调用。这里的 ApiCmdBuffer 是用于实现转发 VkCommandBuffer API 的辅助类,由 VK_DEFINE_DISPATCHABLE 宏定义,代码如下:

VK_DEFINE_DISPATCHABLE(CmdBuffer);
...
#define VK_DEFINE_DISPATCHABLE(a) class Dispatchable##a : public Dispatchable<a> {};    // 从 Dispatchable 模板类继承typedef Dispatchable##a Api##a; // typedef 定义 ApiXXX 类型,在这里就是 ApiCmdBuffer

从代码可以看出,ApiCmdBuffer 继承自 Dispatchable 模板类,Dispatchable 是用于封装 Vulkan 可调度的核心对象(VkPhysicalDevice、VkDevice、VkCommandBuffer、VkQueue)的辅助模板类,主要用于实现 Vulkan 对象到 XGL 内部对象之间的转换,在 Dispatchable 内部保存指向 XGL 内部对象的指针,例如这里的 CmdBuffer。Dispatchable 模板类定义如下:

template <typename C>
class Dispatchable
{private:VK_LOADER_DATA m_reservedForLoader;unsigned char  m_C[sizeof(C)];...public:...// Given pointer to const C, returns the containing Dispatchable<C>.static VK_FORCEINLINE const Dispatchable<C>* FromObject(const C* it){return reinterpret_cast<const Dispatchable<C>*>(reinterpret_cast<const uint8_t*>(it) - (sizeof(Dispatchable<C>) - sizeof(C)));}// Non-const version of above.static VK_FORCEINLINE Dispatchable<C>* FromObject(C* it){return reinterpret_cast<Dispatchable<C>*>(reinterpret_cast<uint8_t*>(it) - (sizeof(Dispatchable<C>) - sizeof(C)));}// Converts a "Vk*" dispatchable handle to the driver internal object pointer.static VK_FORCEINLINE C* ObjectFromHandle(typename C::ApiType handle){return reinterpret_cast<C*>(reinterpret_cast<Dispatchable<C>*>(handle)->m_C);}
};

不难看出,成员 m_C 实际保存了指向 XGL 内部对象的指针,模版参数 C 则是 XGL 对象类型,在这里就是 CmdBuffer。而 FromObject、ObjectFromHandle 静态函数,就是用于实现从 Vulkan 对象到 XGL 内部对象的转换函数,在这里就是 VkCommandBuffer 到 CmdBuffer 的转换。

在 Dispatchable 类定义的下面是几个用于初始化 XGL 内部对象的宏,以及用于建立 Vulkan 对象和 XGL 内部对象的关联的宏,为了简化阐述,而且也不是本文讨论的内容,这里就不再详述,后续再另发文解析。

下面我们来看 CmdBuffer 的定义:

class CmdBuffer
{public:typedef VkCommandBuffer ApiType;
...
void CmdBuffer::Draw(uint32_t firstVertex,uint32_t vertexCount,uint32_t firstInstance,uint32_t instanceCount)
{DbgBarrierPreCmd(DbgBarrierDrawNonIndexed);ValidateStates();PalCmdDraw(firstVertex,vertexCount,firstInstance,instanceCount);DbgBarrierPostCmd(DbgBarrierDrawNonIndexed);
}
...

注意 ApiType 这个类型定义,在每个从 Dispatchable 派生的类都必须定义此类型,因为需要在 Dispatchable 中用于对象转换。同样的在其它可调度的核心对象类如 PhysicalDevice、Device、Queue 中也有这样的类型定义。

CmdBuffer 的 Draw 函数中的 DbgBarrierPreCmd、DbgBarrierPostCmd 是用于在调试模式下插入 PAL Barrier,用于在 PAL 层进行校验,为了简化阐述,关于 PAL 层 Barrier 这里不再详述,后续再另发文详解。

ValidateStates 函数主要用于检查 GPU 状态,如果为 Dirty 则在内部遍历每个 GPU 设备,调用 PAL 层 CommandBuffer 设置 Viewport 和 Scissor Rect,最后重置 Dirty 标志。

PalCmdDraw 函数是真正调用 PAL 层的绘制命令所在。代码如下:

void CmdBuffer::PalCmdDraw(uint32_t firstVertex,uint32_t vertexCount,uint32_t firstInstance,uint32_t instanceCount)
{// Currently only Vulkan graphics pipelines use PAL graphics pipeline bindings so there's no need to// add a delayed validation check for graphics.VK_ASSERT(PalPipelineBindingOwnedBy(Pal::PipelineBindPoint::Graphics, PipelineBindGraphics));utils::IterateMask deviceGroup(m_curDeviceMask);do{const uint32_t deviceIdx = deviceGroup.Index();PalCmdBuffer(deviceIdx)->CmdDraw(firstVertex,vertexCount,firstInstance,instanceCount);}while (deviceGroup.IterateNext());
}

PalPipelineBindingOwnedBy 校验 XGL 和 PAL 层之间的 Pipeline Binding 是否一致。接下来通过 IterateMask 用于遍历每个 Vulkan Device( Command Buffer 可以运行在多个 Vulkan Device 上),依次调用 PAL 层的核心接口 ICmdBuffer 的 CmdDraw 函数。

PalCmdBuffer 函数用于根据 Device Index 返回对应的 PAL 层的核心接口 ICmdBuffer:

VK_INLINE Pal::ICmdBuffer* PalCmdBuffer(int32_t idx) const
{if (idx == 0){VK_ASSERT((uintptr_t)m_pPalCmdBuffers[idx] == (uintptr_t)this + sizeof(*this));return (Pal::ICmdBuffer*)((uintptr_t)this + sizeof(*this));}VK_ASSERT((idx >= 0) && (idx < static_cast<int32_t>(MaxPalDevices)));return m_pPalCmdBuffers[idx];
}

m_pPalCmdBuffers 是 ICmdBuffer 接口指针数组,保存最多 4 个 ICmdBuffer。从代码中可以看出,XGL CmdBuffer 和 ICmdBuffer 的内存分布是紧致排布的,这样是 Cache friendly 的,强化了驱动性能。

得到 ICmdBuffer 指针之后,就调用 ICmdBuffer 的 CmdDraw 函数,从这里开始以下的分析就进入了 PAL 层。

    PAL_INLINE void CmdDraw(uint32 firstVertex,uint32 vertexCount,uint32 firstInstance,uint32 instanceCount){m_funcTable.pfnCmdDraw(this, firstVertex, vertexCount, firstInstance, instanceCount);}

m_funcTable 是一系列 DrawDispatch 函数指针的结构,pfnCmdDraw 是这个结构中指向 非顶点索引绘制的函数指针。

为了利于后面的讲解,这里先讲下 PAL 核心接口 ICmdBuffer 的 GfxIP 实现。由前面 PAL 层的硬件 IP 抽象接口的阐述可知,ICmdBuffer 是个抽象类,不同 GCN 架构的硬件图形计算的 IP 模块需要实现这个抽象接口,在 PAL 中主要实现了 2 个 GfxIP 的接口,分别是 GfxIP6,对应的是 gfx678 GfxIP;Gfx9 对应的是 gfx910 GfxIP。具体实现代码在 src/core/hw/gfxip/gfx6 和 src/core/hw/gfxip/gfx9 目录下。同理,对于其他 PAL 层的硬件 IP 抽象接口也是如此,可以按照这个路数来查看其他的核心对象代码。GfxIP level 和各个 ISA 的详细对应关系请参考下面这个链接:

https://www.x.org/wiki/RadeonFeature/​www.x.org

说回 m_funcTable,这个结构中的函数指针就是由各个具体的 GfxIP 实现来设置的。以 GfxIP6 为例,在 src/core/hw/gfxip/gfx6/gfx6UniversalCmdBuffer.h 中声明了 UniversalCmdBuffer 类,对应的是 Graphics Command Buffer,这个类中的 SwitchDrawFunctions 函数用于根据当前芯片的 gfx level 来切换,SwitchDrawFunctions 函数在 UniversalCmdBuffer 创建时初始化为非 view instance 的函数,以及在 Bind Pipeline 时如果 Pipeline 的 view instance 发生变化则再次调用来切换绘制函数。以下是这个函数的部分实现代码:

switch (m_device.Parent()->ChipProperties().gfxLevel)
{case GfxIpLevel::GfxIp6:m_funcTable.pfnCmdDraw                     = CmdDraw<GfxIpLevel::GfxIp6, false, false, false>;m_funcTable.pfnCmdDrawOpaque               = CmdDrawOpaque<GfxIpLevel::GfxIp6, false, false, false>;m_funcTable.pfnCmdDrawIndexed              = CmdDrawIndexed<GfxIpLevel::GfxIp6, false, false, false>;m_funcTable.pfnCmdDrawIndirectMulti        = CmdDrawIndirectMulti<GfxIpLevel::GfxIp6, false, false, false>;m_funcTable.pfnCmdDrawIndexedIndirectMulti =CmdDrawIndexedIndirectMulti<GfxIpLevel::GfxIp6, false, false, false>;break;
case GfxIpLevel::GfxIp7:m_funcTable.pfnCmdDraw                     = CmdDraw<GfxIpLevel::GfxIp7, false, false, false>;m_funcTable.pfnCmdDrawOpaque               = CmdDrawOpaque<GfxIpLevel::GfxIp7, false, false, false>;m_funcTable.pfnCmdDrawIndexed              = CmdDrawIndexed<GfxIpLevel::GfxIp7, false, false, false>;m_funcTable.pfnCmdDrawIndirectMulti        = CmdDrawIndirectMulti<GfxIpLevel::GfxIp7, false, false, false>;m_funcTable.pfnCmdDrawIndexedIndirectMulti =CmdDrawIndexedIndirectMulti<GfxIpLevel::GfxIp7, false, false, false>;break;
case GfxIpLevel::GfxIp8:m_funcTable.pfnCmdDraw                     = CmdDraw<GfxIpLevel::GfxIp8, false, false, false>;m_funcTable.pfnCmdDrawOpaque               = CmdDrawOpaque<GfxIpLevel::GfxIp8, false, false, false>;m_funcTable.pfnCmdDrawIndexed              = CmdDrawIndexed<GfxIpLevel::GfxIp8, false, false, false>;m_funcTable.pfnCmdDrawIndirectMulti        = CmdDrawIndirectMulti<GfxIpLevel::GfxIp8, false, false, false>;m_funcTable.pfnCmdDrawIndexedIndirectMulti =CmdDrawIndexedIndirectMulti<GfxIpLevel::GfxIp8, false, false, false>;break;
case GfxIpLevel::GfxIp8_1:m_funcTable.pfnCmdDraw                     = CmdDraw<GfxIpLevel::GfxIp8_1, false, false, false>;m_funcTable.pfnCmdDrawOpaque               = CmdDrawOpaque<GfxIpLevel::GfxIp8_1, false, false, false>;m_funcTable.pfnCmdDrawIndexed              = CmdDrawIndexed<GfxIpLevel::GfxIp8_1, false, false, false>;m_funcTable.pfnCmdDrawIndirectMulti        = CmdDrawIndirectMulti<GfxIpLevel::GfxIp8_1, false, false, false>;m_funcTable.pfnCmdDrawIndexedIndirectMulti =CmdDrawIndexedIndirectMulti<GfxIpLevel::GfxIp8_1, false, false, false>;break;
default:PAL_ASSERT_ALWAYS();break;
}

可以看出,m_funcTable.pfnCmdDraw 指向的是 UniversalCmdBuffer::CmdDraw 静态模版函数,这是 VkCmdDraw 中最底层的调用函数,代码如下(为了方便说明,只列出关键代码段,并在前面标注了序号):

template <GfxIpLevel gfxLevel, bool issueSqttMarkerEvent, bool viewInstancingEnable, bool DescribeDrawDispatch>
void PAL_STDCALL UniversalCmdBuffer::CmdDraw(ICmdBuffer* pCmdBuffer,uint32      firstVertex,uint32      vertexCount,uint32      firstInstance,uint32      instanceCount)
{if ((gfxLevel >= GfxIpLevel::GfxIp8) || (instanceCount > 0)){auto* pThis = static_cast<UniversalCmdBuffer*>(pCmdBuffer);ValidateDrawInfo drawInfo;drawInfo.vtxIdxCount   = vertexCount;drawInfo.instanceCount = instanceCount;drawInfo.firstVertex   = firstVertex;drawInfo.firstInstance = firstInstance;drawInfo.firstIndex    = 0;drawInfo.useOpaque     = false;1      pThis->ValidateDraw<false, false>(drawInfo);....2      uint32* pDeCmdSpace = pThis->m_deCmdStream.ReserveCommands();....{3          pDeCmdSpace += pThis->m_cmdUtil.BuildDrawIndexAuto(vertexCount, false,pThis->PacketPredicate(),pDeCmdSpace);}....4      pDeCmdSpace  = pThis->m_workaroundState.PostDraw(pThis->m_graphicsState, pDeCmdSpace);5      pDeCmdSpace  = pThis->IncrementDeCounter(pDeCmdSpace);6      pThis->m_deCmdStream.CommitCommands(pDeCmdSpace);....}
}

序号 1 代码调用了 ValidateDraw 函数,主要用于在执行硬件绘制命令之前,根据 dirty state 进行一系列 GPU Pipeline State 的检查和设置。其内部使用的是 CmdStream 类代表单个硬件命令流,在 AMDVLK 中,使用的是 PM4 格式的硬件指令,但由于 PM4 格式未公开,无法了解其内部结构。在 UniversalCmdBuffer 中有两个 CmdStream 成员,分别是 Primary 命令流(DE),和用于并行绘制引擎的常量更新命令流(CE)。

序号 2 代码是分配了一个 PM4 格式的硬件命令流空间,在接下来的流程中用于构建实际的硬件命令。

序号 3 代码调用 CmdUtil 工具类的 BuildDrawIndexAuto 函数用于构建 PM4 格式的非索引绘制命令,CmdUtil 工具类主要用于构建 PM4 命令包。

序号 4 代码用于一些特定硬件上的特殊 draw 后处理,例如针对 Gfx 7/8 上 stream-output 绘制之后所有的 Vertex Grouper Tessellator 都需要挂起等待 stream out 的同步信号,这时需要发出 VGT_STREAMOUT_SYNC 事件同步命令。

序号 5 代码用于增加 DE 指令的计数,其内部通过调用 CmdUtil 工具类的 BuildIncrementDeCounter 函数增加统计 DE 指令计数的指令。

序号 6 代码调用 Primary 的 CmdStream 提交上述所有的硬件指令。

至此 VkCmdDraw 完整的驱动内部流程就分析完了,但其实这其中还有许多内容,出于便于阐述的原因,在文中没有列出代码以及详细说明,比如 ValidateDraw 函数,其内部也是比较复杂的流程。另外在 UniversalCmdBuffer::CmdDraw 中也只是创建了 PM4 的 GPU 硬件命令,并没有真正发送到 GPU 上执行,而真正执行则要通过 vkQueueSubmit API 才会提交到硬件中,这部分的内容更加复杂,因此本文只是对庞大的 AMDVLK 源码的初探,希望通过本文对 Vulkan 驱动能了解一些基本概念,同时也为深入阅读 AMDVLK 源码提供了一些思路和参考。

性能计数器驱动_Vulkan 探密:AMD Vulkan 开源驱动源码解析-零相关推荐

  1. 【移动开发】Checkout开源库源码解析

    Checkout开源库的源码解析 1.功能介绍 1.1Checkout是什么 Checkout是Android In-App Billing API(v3 +)的一个封装库.In-App Billin ...

  2. Android 常用开源框架源码解析 系列 (四)Glide

    一.定义  Glide 一个被google所推荐的图片加载库,作者是bumptech.对Android SDk 最低要求是 API 10  与之功能类似的是Square公司的picasso  二.基本 ...

  3. Android 常用开源框架源码解析 系列 (九)dagger2 呆哥兔 依赖注入库

    一.前言 依赖注入定义 目标类中所依赖的其他的类的初始化过程,不是通过手动编码的方式创建的. 是将其他的类已经初始化好的实例自动注入的目标类中. "依赖注入"也是面向对象编程的 设 ...

  4. ubuntu下amd超频工具_Ubuntu使用ATI(AMD)开源驱动及优化

    通常所谓ATI开源驱动就是非ATI官方开发的驱动,Linux下大多数的驱动由爱好者写成而不是硬件厂商,故大多开放程序源代码,称为开源驱动.官方驱动往往是闭源驱动- 开源驱动比官方驱动问题少, 而且现在 ...

  5. 百度智能手环方案开源(含源码,原理图,APP,通信协议等)...

    分享一个百度智能手环开源项目的设计方案资料. 项目简介 百度云智能手环的开源方案是基于Apache2.0开源协议,开源内容包括硬件设计文档,原理图.ROM.通讯协议在内的全套方案,同时开放APP和云服 ...

  6. 《OV4689摄像头模组驱动源码解析》

    <OV4689摄像头模组驱动源码解析> OV4689是一款广泛应用于工业.安防等领域的高清图像传感器.在这篇文章中,我们将介绍如何在单片机上使用OV4689模组,并提供相应的驱动源码. O ...

  7. 《OpenHarmony开源鸿蒙学习入门》-- 系统相机应用源码解析(一)

    OpenHarmony开源鸿蒙学习入门–系统相机应用源码解析(一) 一.源码解析的目的: 为什么要去做源码解析这件事?我个人认为,首先可以提高我们对代码书写的能力,毕竟官方系统级的应用,会比demo的 ...

  8. 开源项目实例源码_今年我读了四个开源项目的源码,来分享下心得

    今年来看了 RocketMQ.Kafka.Dubbo .Tomcat 的源码,之前也有读者询问过如何读源码,索性就来分享一下. 其实还看了一点点 Linux.Redis.jdk8,这几个阅读的目的和上 ...

  9. 驱动编程中的头文件与内核源码的关系

    前言 在编写嵌入式LINUX驱动程序时,需要添加很多头文件,这些头文件均位于嵌入式LINUX源码中,因此编译驱动前,应先安装嵌入式LINUX源码,并至少对嵌入式LINUX内核编译一次. 这些头文件根据 ...

  10. 分析开源项目源码,我们该如何入手分析?(授人以渔)

    点击上方 好好学java ,选择 星标 公众号 重磅资讯.干货,第一时间送达 今日推荐:牛人 20000 字的 Spring Cloud 总结,太硬核了~ 1 前言 本文接上篇文章跟大家聊聊我们为什么 ...

最新文章

  1. 元宇宙深度研究报告:元宇宙是互联网的终极形态?
  2. 输入法大战,你用哪家输入法? | 每日趣闻
  3. 制作一本《First Love, Last Rites》之二
  4. python中的列表是指针吗_Python中的指针——到底指什么(二)
  5. 写入接口c语言_PYNQ: 使用CFFI嵌入C语言
  6. pandas之groupby分组与pivot_table透视
  7. pluto实现分析(22)
  8. VS2005 出现warning C4996: strcpy was declared deprecated
  9. JDK的下载、安装和配置
  10. 还在为运维烦恼?体验云上运维服务,提意见赢好礼!【华为云分享】
  11. 提高python 程序运行速度_3个Python函数帮程序员们避免编写循环,提高运行速度...
  12. X.509,RSA,PKCS 普及
  13. 倒计时器c语言,在线倒计时器
  14. 永中集成Office 2013 简体中文免费版
  15. Android系统开发——WiFi Hotspot限速2M每秒
  16. ccf公共钥匙盒python_CCF/CSP 公共钥匙盒
  17. linux pg启动日志查看,pg日志分析
  18. 【模拟器】华为模拟器eNSP安装注意事项及常见报错处理
  19. 消防安全监测模块,筑牢工厂消防安全屏障
  20. lisp判断选区是否有对象_cad如何快速删除矩形框之外的图形?比如:

热门文章

  1. 【C语言入门教程】4.7 指针的地址分配 - mallocl(), free()
  2. 服务器部署_nginx报错: [warn] conflicting server name www.test.com on 0.0.0.0:80, ignored
  3. autoit选中图标无反应_ps图标教学,使用小技巧。
  4. java mvc接收json_java相关:SpringMVC中controller接收json数据的方法
  5. python去除列表中的重复元素,简单易理解,超详细解答,步骤分析
  6. xml简单理解,xml增删改操作,仅作笔记,不作为学习借鉴
  7. idea 背景色修改_IDEA使用调优配置
  8. gin上传文件服务器,gin-上传文件
  9. 用自定义IHttpModule实现URL重写 1
  10. Redis.conf 详解