• 博客园logo
  • 会员
  • 周边
  • 新闻
  • 博问
  • 闪存
  • 众包
  • 赞助商
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
LOFLY
终其一生,编织快乐
博客园    首页    新随笔    联系   管理    订阅  订阅

DXIL之一般问题

DXIL 之一般问题

DXIL之一般问题(General Issues)

  一个重要的目标是使HLSL更接近于严格的C/C++子集。这对DXIL的设计以及下面提到的将来硬件功能请求有着影响。

术语

   资源(Resource)指的是以下之一:
  • SRV - 着色器资源视图(只读)
  • UAV - 无序的访问视图(读写)
  • CBV - 常量缓冲(buffer)视图(只读)
  • Sampler (采样器)

   Intrinsics通常指的是在核心LLVM IR中缺失的操作。DXIL将HLSL内置函数(也称为intrinsic)表示为外部函数调用,而不是LLVM intrinsics。

就是说,在DXIL中, Intrinsic是HLSL的的一些操作,没法表示成LLVM IR , 而这些操作在LLVM 看来是外部函数调用。

DXIL 抽象级别

  DXIL具有类似于“标量化”的DXBC的抽象级别。DXIL是一个较低级别的中间表示(IR),可方便在驱动程序编译器中进行快速且稳健的即时编译(JIT)。

  特别地,以下步骤用于将HLSL抽象降低到DXIL:

  • 优化函数参数的复制
  • 内联函数
  • 分配(allocate)和转换(tranform)着色器签名
  • 降低矩阵复杂度,优化中间存储
  • 线性化多维数组和用户定义类型的访问
  • 标量化向量

标量IR

   DXIL操作使用标量数量。可以将多个标量数量组合在一起形成结构体,以表示多个返回值,这在内存操作(例如加载/存储、采样等)中非常有用,因为它可以提高访问的连续性。

   元数据(Metadata)、资源声明(Resource)和调试(Debugging)信息可能包含向量,以更接近地传达源代码的结构给工具和调试器。

  将来版本的IR可能包含向量或分组提示,用于处理小于32位的数据类型,例如half和i16。

内存访问

  从概念上讲,DXIL与DXBC在访问不同类型的内存时保持了一致性。越界行为和各种限制也得以保留。

  可索引的线程本地变量和组共享变量被表示为变量,并通过类似LLVM C的指针进行访问。

  从DXIL的角度来看,经过重新排序的资源(如纹理)具有不透明的内存布局。对这些资源的访问是通过内部函数进行的。

  常量缓冲区内存有两种布局方式:(1)legacy(遗留布局),与DXBC的布局匹配; (2)线性布局。SM6 DXIL使用内部函数(intrinsics)来读取任一布局下的常量缓冲区。

  着色器签名需要进行打包,并位于一种特殊类型的内存中,无法按线性方式查看。通过DXIL中的特殊内部函数(intrinsics)来访问签名值。如果需要将签名参数传递给函数,则首先在线程本地内存中创建一个副本,然后将副本传递给函数。

  类型化缓冲区(typed buffer)表示具有数据转换的内存。通过DXIL中的特殊函数以元素粒度索引来进行类型化缓冲区的加载/存储/原子操作。

支持以下指针类型:

  • 非可索引的线程本地变量。
  • 可索引的线程局部变量(DXBC x-寄存器).
  • Groupshared变量(DXBC g-寄存器).
  • 设备内存指针.
  • 类似于常量缓冲区的内存指针。

   DXIL 指针的类型是通过 LLVM addrspace 构造进行区分的。HLSL 编译器会尽最大努力推断出准确的指针 addrspace,以便驱动程序编译器可以发出最有效的指令。

  一个指针可以通过多种方式产生:

  • 全局变量

  • AllocaInst(Alloca Instruction)是LLVM的一种指令,用于在函数栈帧上为局部变量分配内存空间.

  • 作为某些指针算术运算的结果进行合成。

DXIL在其表示中使用32位指针。

Out-of-bounds behavior 越界行为

  可索引的线程本地访问是通过LLVM指针进行的,并具有类似C的越界语义。

   Groupshared 访问也是通过LLVM指针进行的。Groupshared指针的来源必须是单个TGSM(Thread Group Shared Memory)分配。如果Groupshared指针使用内部GEP(GetElementPtr)指令,它应该不会发生越界。对于内部指针的越界访问行为是未定义的。对于来自常规GEP的Groupshared指针,越界访问的行为与DXBC相同。对于越界访问,加载操作会返回0;越界存储操作会被静默丢弃。

  资源访问在越界行为方面与 DXBC 保持一致。对于越界的加载操作,返回值为 0;越界的存储操作会被静默(slightly)丢弃。

  在 SM6.0 及之后的版本中,越界指针访问具有未定义的(类似于 C 语言的)行为。可以使用 LLVM 内存优化传递来优化此类访问。如果需要指定越界行为,可以使用内部函数来访问内存。

内存访问粒度 (Memory access granularity)

  Intrinsic 和资源访问可能会涵盖比指令要求的更宽的访问范围。DXIL 定义了对于线程本地内存的 i1、i16、i32、i64、f16、f32、f64 类型的内存访问,以及对于内存 I/O(即组共享内存(groupshared memory)和通过常量缓冲区(CBs)、无序访问视图(UAVs)和纹理采样器视图(SRVs)等资源访问的内存)的 i32、f32 和 f64 类型的访问。

虚拟值的数量

  在 DXIL 中,虚拟值的数量没有限制。IR 保证处于静态单赋值(SSA)形式。对于经过优化的着色器,优化器会运行 -mem2reg LLVM pass果有好处的话,还会执行其他的内存到寄存器提升操作。

控制流限制

   DXIL 的控制流图必须是可还原的,通过 T1-T2 测试进行检查。DXIL 不保留 DXBC 的结构化控制流。保留结构化控制流属性将对通过 LLVM 对 DXIL 进行优化的第三方工具造成重大负担,并降低 DXIL 的吸引力。    DXIL 允许 switch 标签块的 fall-through。这与 DXBC 不同,DXBC 中是禁止 fall-through 的。

   DXIL 不支持 DXBC 的 label 和 call 指令;可以使用 LLVM 函数代替(参见下文)。这些指令的主要用途是:(1)不支持 HLSL 接口,和(2)注释为 [call] 的 switch 语句中的 case-body 的提取,这不是一个关注的情况。

函数

   DXIL 支持函数和调用指令,而不是 DXBC 的标签(labels)和调用(calls)。不允许递归调用;DXIL 验证器会强制执行此规则。

   这些函数是常规的 LLVM 函数。参数可以通过值传递或引用传递。这些函数旨在为大型复杂着色器的分离编译提供便利。然而,驱动程序编译器可以根据需要选择性地进行函数内联。

标识符

   DXIL 的标识符必须符合 LLVM IR 的标识符规则。

   标识符重整(mangling )规则是使用 Clang 3.7 与 HLSL 目标时采用的规则。

   保留了以下标识符前缀:

  • dx., dxil.
  • llvm.dx., llvm.dxil.

地址宽度

   DXIL 只会使用 32 位地址来表示指针。字节偏移量也是 32 位。

着色器限制

   DXIL 不支持以下内容:
  • 递归
  • 异常
  • 间接函数调用和动态分派(类似于多态,在运行时确定类型)

入口点 (Entry Point)

   dx.entryPoints 元数据指定了一个入口点记录的列表,每个入口点对应一个记录。在模块中,库可以为每个模块指定多个入口点,但目前此功能不在 DXIL 规范中;其他着色器模型必须且只能指定一个入口点。    例如:
define void @"\01?myfunc1@@YAXXZ"() #0 { ... }
define float @"\01?myfunc2@@YAMXZ"() #0 { ... }

!dx.entryPoints = !{ !1, !2 }

!1 = !{ void  ()* @"\01?myfunc1@@YAXXZ", !"myfunc1", !3, null, null }
!2 = !{ float ()* @"\01?myfunc2@@YAMXZ", !"myfunc2", !5, !6, !7 }

  每个入口点元数据记录指定以下内容:

  • 对入口点函数全局符号的引用
  • 非修饰名称(没有经过重载处理的原始名称)
  • 签名列表
  • 资源列表
  • 着色器功能和其他属性的标签-值对(tag-value pair)列表

'null' 值表示特定节点的缺失。

着色器功能(Shader capabilities )是除了着色器模型指定的属性之外的附加属性。这个列表以i32标签和紧随其后的值的形式组织。

细分着色器表示

   Hull shader 被表示为两个函数,通过元数据相关联:(1) 控制点阶段函数,它是 hull shader 的入口点,和 (2) 片元常量阶段函数。

例如:

!dx.entryPoints = !{ !1 }
!1 = !{ void ()* @"ControlPointFunc", ..., !2 }  ; shader entry record
!2 = !{ !"HS", !3 }
!3 = !{ void ()* @"PatchConstFunc", ... }        ; additional hull shader state

   片元常量函数表示原始的HLSL计算,并且没有像DXBC中那样分为fork和join阶段。 驱动程序编译器可以在目标GPU上获得性能提高时执行此类分离操作。

   在从 DXBC 转换为 DXIL 的过程中,原始的片元常量函数无法在 DXBC 到 DXIL 的转换过程中恢复。相反,每个 fork 和 join 阶段的指令都会被一个循环“包裹”,该循环迭代相应数量的 phase-instance-count 次数。因此,fork/join 实例 ID 变成了循环的归纳变量。LoadPatchConstant 内置函数(见下文)表示从 DXBC vpc 寄存器加载数据。

   下表总结了加载 hull shader 和 domain shader 的输入以及存储输出的内置函数名称。CP 代表控制点,PC 代表片元常量。

Operation Control Point (Hull) Patch Constant Domain
Store Input CP
Load Input CP LoadInput LoadInput
Store Output CP StoreOutput
Load Output CP LoadOutputControlPoint LoadInput
Store PC StorePatchConstant
Load PC LoadPatchConstant LoadPatchConstant
Store Output Vertex StoreOutput

  在片元常量阶段中,LoadPatchConstant 函数仅由 DXBC-to-DXIL 转换器生成,用于访问 DXBC 的 vpc 寄存器。HLSL 编译器产生的中间表示 (IR) 直接引用了 LLVM IR 值。

下一节

posted @ 2023-06-25 16:01  编织快乐  阅读(589)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2026
浙公网安备 33010602011771号 浙ICP备2021040463号-3