利用 Direct3D 绘制几何体—7.编译着色器

news2024/10/23 9:16:04

在 Direct3D 中,着色器程序必须先被编译为一种可移植的字节码。接下来,图形驱动程序将获取这些字节码,并将其重新编译为针对当前系统 GPU 所优化的本地指令 [ATI1]。我们可以在运行期间用下列函数对着色器进行编译。

HRESULT D3DCompileFromFile(
  LPCWSTR pFileName,
  const D3D_SHADER_MACRO *pDefines,
  ID3DInclude *pInclude,
  LPCSTR pEntrypoint,
  LPCSTR pTarget,
  UINT Flags1,
  UINT Flags2,
  ID3DBlob **ppCode,
  ID3DBlob **ppErrorMsgs);

1. pFileName:我们希望编译的以 .hlsl 作为扩展名的 HLSL 源代码文件。

2. pDefines:在本书中,我们并不使用这个高级选项,因此总是将它指定为空指针。关于此参数的详细信息可参见 SDK 文档。

3. pInclude:在本书中,我们并不使用这个高级选项,因而总是将它指定为空指针。关于此参数的详细信息可详见 SDK 文档。

4. pEntrypoint:着色器的入口点函数名。一个 .hlsl 文件可能存有多个着色器程序(例如,一个顶点着色器和一个像素着色器),所以我们需要为待编译的着色器指定入口点。

5. pTarget:指定所用着色器类型和版本的字符串。在本书中,我们采用的着色器模型版本是 5.0 和 5.1。

        a) vs_5_0 与 vs_5_1:表示版本分别为 5.0 和 5.1 的顶点着色器(vertex shader)。

        b) hs_5_0 与 hs_5_1:表示版本分别为 5.0 和 5.1 的外壳着色器(hull shader)。

        c) ds_5_0 与 ds_5_1:表示版本分别为 5.0 和 5.1 的域着色器(domain shader)。

        d) gs_5_0 与 gs_5_1:表示版本分别为 5.0 和 5.1 的几何着色器(geometry shader)。

        e) ps_5_0 与 ps_5_1:表示版本分别为 5.0 和 5.1 的像素着色器(pixel shader)。

        f) cs_5_0 与 cs_5_1:表示版本分别为 5.0 和 5.1 的计算着色器(compute shader)。

6. Flags1:指示对着色器代码应当如何编译的标志。在 SDK 文档里,这些标志列出得不少,但是此书中我们仅用两种。

        a) D3DCOMPILE_DEBUG:用调试模式来编译着色器。

        b) D3DCOMPILE_SKIP_OPTIMIZATION:指示编译器跳过优化阶段(对调试很有用处)。

7. Flags2:我们不会用到处理效果文件的高级编译选项,关于它的信息请参见 SDK 文档。

8. ppCode:返回一个指向 ID3DBlob 数据结构的指针,它存储着编译好的着色器对象字节码。

9. ppErrorMsgs:返回一个指向 ID3DBlob 数据结构的指针。如果在编译过程中发生了错误,它便会储存报错的字符串。

ID3DBlob 类型描述的其实就是一段普通的内存块,这是该接口的两个方法:

        a) LPVOID GetBufferPointer:返回指向 ID3DBlob 对象中数据的 void* 类型的指针。由此可见,在使用此数据之前务必先要将它转换为适当的类型(参考下面的示例)。

        b) SIZE_T GetBufferSize:返回缓冲区的字节大小(即该对象中的数据大小)。

为了能够输出错误信息,我们在 d3dUtil.h/.cpp 文件中实现了下列辅助函数在运行时编译着色器:

// d3dUtil.cpp 第90行
ComPtr<ID3DBlob> d3dUtil::CompileShader(
	const std::wstring& filename,
	const D3D_SHADER_MACRO* defines,
	const std::string& entrypoint,
	const std::string& target)
{
    // 若处于调试模式,则使用调试标志
	UINT compileFlags = 0;
#if defined(DEBUG) || defined(_DEBUG)  
	compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif

	HRESULT hr = S_OK;

	ComPtr<ID3DBlob> byteCode = nullptr;
	ComPtr<ID3DBlob> errors;
	hr = D3DCompileFromFile(filename.c_str(), defines, D3D_COMPILE_STANDARD_FILE_INCLUDE,
		entrypoint.c_str(), target.c_str(), compileFlags, 0, &byteCode, &errors);

    // 将错误信息输出到调试窗口
	if(errors != nullptr)
		OutputDebugStringA((char*)errors->GetBufferPointer());

	ThrowIfFailed(hr);

	return byteCode;
}

以下是一个调用此函数的示例:

ComPtr<ID3DBlob> mvsByteCode = nullptr; // BoxApp.cpp 第65行
ComPtr<ID3DBlob> mpsByteCode = nullptr; // BoxApp.cpp 第66行

// BoxApp.cpp 第354行
mvsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "VS", "vs_5_0");
mpsByteCode = d3dUtil::CompileShader(L"Shaders\\color.hlsl", nullptr, "PS", "ps_5_0");

HLSL 的错误和警告消息将通过 ppErrorMsgs 参数返回。比方说,如果不小心把 mul 函数拼写错误,那么我们便会从调试窗口得到类似于下列的错误输出:

仅对着色器进行编译并不会使它与渲染流水线相绑定以供其使用。

1. 离线编译

我们不仅可以在运行期间编译着色器,还能够以单独的步骤(例如,将其作为构建整个工程过程中的一个独立环节,或是将其视为资源内容流水线(asset  content pipeline)流程的一部分)离线地(offline)编译着色器。这样做有原因若干:

1. 对于复杂的着色器来说,其编译过程可能耗时较长。因此,借助离线编译即可缩短应用程序的加载时间。

2. 以便在早于运行时的构建处理期间提前发现编译错误。

3. 对于 Windows 8 应用商店中的应用而言,必须采用离线编译这种方式。

我们通常用 .cso(即 compiled shader object,已编译的着色器对象)作为已编译着色器的扩展名。

为了以离线的方式编译着色器,我们将使用 DirectX 自带的 FXC 命令行编译工具。为了将 color.hlsl 文件中分别以 VS 和 PS 作为入口点的顶点着色器和像素着色器编译为调试版本的字节码,我们可以输入以下命令:

fxc "color.hlsl" /Od /Zi /T vs_5_0 /E "VS" /Fo "color_vs.cso" /Fc "color_vs.asm"
fxc "color.hlsl" /Od /Zi /T ps_5_0 /E "PS" /Fo "color_ps.cso" /Fc "color_ps.asm"

为了将 color.hlsl 文件中分别以 VS 和 PS 作为入口点的顶点着色器和像素着色器编译为发行版本的字节码,则可以输入以下命令:

fxc "color.hlsl" /T vs_5_0 /E "VS" /Fo "color_vs.cso" /Fc "color_vs.asm"
fxc "color.hlsl" /T ps_5_0 /E "PS" /Fo "color_ps.cso" /Fc "color_ps.asm"
参数描述
/Od禁用优化(对于调试十分有用)
/Zi开启调试信息
/T <string>着色器类型和着色器模型的版本
/E <string>着色器入口点
/Fo <string>经过编译的着色器对象字节码
/Fc <string>输出一个着色器的汇编文件清单(对于调试、检验指令数量、查阅生成的代码细节都是很有帮助的)

如果试图编译一个有语法错误的着色器,则 FXC 会将错误/警告消息输出到命令窗口。

既然已经按离线的方式把顶点着色器和像素着色器编译到 .cso 文件里,也就不需要在运行时对其进行编译(即,无须再调用 D3DCompileFromFile 方法)。但是,我们仍要将 .cso 文件中已编译好的着色器对象字节码加载到应用程序中,这可以由 C++ 的标准文件输入机制来加以实现,如:

// d3dUtil.cpp 第21行
ComPtr<ID3DBlob> d3dUtil::LoadBinary(const std::wstring& filename)
{
  std::ifstream fin(filename, std::ios::binary);

  fin.seekg(0, std::ios_base::end);
  std::ifstream::pos_type size = (int)fin.tellg();
  fin.seekg(0, std::ios_base::beg);

  ComPtr<ID3DBlob> blob;
  ThrowIfFailed(D3DCreateBlob(size, blob.GetAddressOf()));

  fin.read((char*)blob->GetBufferPointer(), size);
  fin.close();

  return blob;
}
...
ComPtr<ID3DBlob> mvsByteCode = d3dUtil::LoadBinary(L"Shaders\\color_vs.cso");
ComPtr<ID3DBlob> mpsByteCode = d3dUtil::LoadBinary(L"Shaders\\color_ps.cso");

2. 生成着色器汇编代码

FXC 程序根据可选参数 /Fc 来生成可移植的着色器汇编代码。通过查阅着色器的汇编代码,既可核对着色器的指令数量,也能了解生成的代码细节——这是为了验证编译器所生成的代码与我们预想的是否一致。例如,如果我们在 HLSL 代码中写了一个条件语句,那么可能会认为汇编代码中将存在一条与之对应的分支指令。在可编程 GPU 发展的初期阶段中,在着色器里使用分支指令的代价是比较高昂的。因此,编译器时常会通过对两个分支展开求值,再对求值结果进行插值来整理条件语句,以避免采用分支指令并计算出正确的结果。例如,下列两组代码是等价的:

条件语句整理后

float x = 0;

// s == 1 (true) or s == 0 (false)

if(s)

        x = sqrt(y);

else

        x = 2*y;

float a = 2*y;

float b = sqrt(y);

float x = a + s*(b-a);

// s == 1: x = a + b - a = b = sqrt(y)

// s == 0: x = a + 0*(b - a) = a = 2*y

因此,若采用这种展开整理方法,我们将得到没有任何分支语句而效果却又与整理前相同的代码。但是,在不查阅着色器汇编代码的情况下,我们无法知道此展开过程是否发生,甚至不能验证生成的分支指令是否正确。有时,查看着色器汇编代码的目的是为了弄清它到底做了什么。下面就是一个由 color.hlsl 文件中顶点着色器生成的汇编代码示例:

//
// 生成自微软(R) HLSL着色器编译器 6.4.9844.0
//
//
// 缓冲区定义
//
// cbuffer cbPerObject
// {
//
//  float4x4 gWorldViewProj;      // 偏移量:  0 大小:  64
//
// }
//
//
// 资源绑定
//
// 名称          类型     格式    维度   槽  元素
// ------------ -------- ------ ----- --- ------- -----------  
// cbPerObject  cbuffer  NA     NA    0    1
//
//
//
// 输入签名
//
// 名称           索引        掩码   寄存器  系统值    格式      使用情况
// --------      ---------- ----- ------ -------- -------- --------- 
// POSITION      0            xyz   0      NONE     float    xyz
// COLOR         0            xyzw  1      NONE     float    xyzw
//
//
// 输出签名
//
// 名称          索引          掩码   寄存器  系统值    格式      使用情况
// --------     -----------  ----- ------ -------- -------- -------
// SV_POSITION  0              xyzw  0      POS      float    xyzw
// COLOR        0              xyzw  1      NONE     float    xyzw
//
vs_5_0
dcl_globalFlags refactoringAllowed | skipOptimization
dcl_constantbuffer cb0[4], immediateIndexed
dcl_input v0.xyz
dcl_input v1.xyzw
dcl_output_siv o0.xyzw, position
dcl_output o1.xyzw
dcl_temps 2
//
// 初始化变量关系
//  v0.x <- vin.PosL.x; v0.y <- vin.PosL.y; v0.z <- vin.PosL.z; 
//  v1.x <- vin.Color.x; v1.y <- vin.Color.y; v1.z <- vin.Color.z; v1.w <- vin.Color.w; 
//  o1.x <- <VS return value>.Color.x; 
//  o1.y <- <VS return value>.Color.y; 
//  o1.z <- <VS return value>.Color.z; 
//  o1.w <- <VS return value>.Color.w; 
//  o0.x <- <VS return value>.PosH.x; 
//  o0.y <- <VS return value>.PosH.y; 
//  o0.z <- <VS return value>.PosH.z; 
//  o0.w <- <VS return value>.PosH.w
//
#第29行"color.hlsl"
mov r0.xyz, v0.xyzx
mov r0.w, l(1.000000)
dp4 r1.x, r0.xyzw, cb0[0].xyzw // r1.x <- vout.PosH.x
dp4 r1.y, r0.xyzw, cb0[1].xyzw // r1.y <- vout.PosH.y
dp4 r1.z, r0.xyzw, cb0[2].xyzw // r1.z <- vout.PosH.z
dp4 r1.w, r0.xyzw, cb0[3].xyzw // r1.w <- vout.PosH.w

#第32行
mov r0.xyzw, v1.xyzw // r0.x <- vout.Color.x; r0.y <- vout.Color.y;
           // r0.z <- vout.Color.z; r0.w <- vout.Color.w
mov o0.xyzw, r1.xyzw
mov o1.xyzw, r0.xyzw
ret 
// 大约使用了10个指令槽

3. 利用 Visual Studio 离线编译着色器

我们可以向工程内添加 .hlsl 文件,而 Visual Studio 会识别它们并提供编译的选项。这些在 UI 中配置的选项就是 FXC 程序的参数。在向 VS 工程中添加 HLSL 文件后,它将成为构建流程的一部分,而着色器也将会被 FXC 程序所编译。

但是,使用 VS 集成的 HLSL 工具却有一个缺点,即它只允许每个文件中仅有一个着色器程序。因此,这条限制将令顶点着色器和像素着色器不能共存于一个文件里。此外,我们有时希望以不同的预处理指令(preprocessor directives)编译同一个着色器程序,从而获取同一着色器的不同编译结果。同样地,如果使用集成的 VS 工具就不可能做到这一点,因为每输入一个 .hlsl 文件则只能输出一个 .cso 文件。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2221484.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

创建型模式-----(单例模式)

目录 基本概念 饿汉式&#xff1a; 懒汉式&#xff1a; 上锁双判空版本 std::call_once版本&#xff1a; C11标准后局部静态变量版本&#xff1a; 项目中单例模板的应用 基本概念 单例模式&#xff1a;在程序运行期间只有一份&#xff0c;与程序生存周期一样&#xff0c;…

对比学习论文随笔 1:正负样本对(Contrastive Learning 基础论文篇)

为了阅读的流畅&#xff0c;当前针对相同的代理任务按时间顺序进行梳理&#xff0c;涉及仅使用正负样本思想且优化目标一致的「基础」论文&#xff08;2018-2020&#xff09;&#xff0c;编码器均采用 ResNet。 文章目录 前言对比学习和代理任务&#xff08;Pretext task&#…

浪潮云启操作系统(InLinux)bcache缓存实践:理解OpenStack环境下虚拟机卷、Ceph OSD、bcache设备之间的映射关系

前言 在OpenStack平台上&#xff0c;采用bcache加速ceph分布式存储的方案被广泛用于企业和云环境。一方面&#xff0c;Ceph作为分布式存储系统&#xff0c;与虚拟机存储卷紧密结合&#xff0c;可以提供高可用和高性能的存储服务。另一方面&#xff0c;bcache作为混合存储方案&…

Java笔试06

在Java中&#xff0c;异常可以分为两大类&#xff1a;编译时异常&#xff08;编译时检查异常&#xff09;和运行时异常&#xff08;非编译时检查异常&#xff09;。 编译时异常&#xff08;Checked Exceptions&#xff09;是指在编译时期必须被捕获或声明抛出的异常。这些异常…

字节流写入文件

一、创建输出流对象表示的文件三种方式 方法一&#xff1a; FileOutputStream fos new FileOutputStream("fos.txt",true);//最简便方法二&#xff1a; FileOutputStream fos new FileOutputStream(new File("fos.txt"));方法三&#xff1b; File f ne…

Python | Leetcode Python题解之第502题IPO

题目&#xff1a; 题解&#xff1a; class Solution:def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:if w > max(capital):return w sum(nlargest(k, profits))n len(profits)curr 0arr [(capital[i], profits[i]…

HTML作业

作业 复现下面的图片 复现结果 代码 <!DOCTYPE html> <html><head><meta charset"utf-8"><title></title></head><body><form action"#"method"get"enctype"text/plain"><…

Java程序设计:spring boot(7)——数据访问操作

目录 1 查询操作 1.1 接口方法定义 1.2 映射文件配置 1.3 UserService 1.4 UserController 2 添加操作 2.1 接口方式定义 2.2 映射文件配置 2.3 添加 commons-lang3 依赖 2.4 AssertUtil ⼯具类 2.5 ParamsException ⾃定义异常 2.6 UserService 2.7 ResultInfo …

UDP传输协议Linux C语言实战

文章目录 1.UDP简介1.1特点1.2 UDP协议头部格式1.2.1 **UDP头部**&#xff1a;1.2.2 **头部意义**&#xff1a;1.2.3 **头部参数**&#xff1a; 1.3 UDP数据长度控制1.4 UDP协议建立框架 2. 函数介绍2.1 sendto函数2.2 recvform函数2.3 其他函数 3.实例3.1 通用结构体、IPV4结构…

算法的学习笔记—(牛客JZ50)

&#x1f600;前言 在处理字符串时&#xff0c;寻找第一个只出现一次的字符是一项常见的任务。本文将探讨几种有效的解法&#xff0c;包括使用 HashMap 和位集&#xff08;BitSet&#xff09;。 &#x1f3e0;个人主页&#xff1a;尘觉主页 文章目录 &#x1f970;第一个只出现…

软件分享丨豆包电脑端 AI 助手

豆包电脑端 AI 助手是由字节跳动推出&#xff0c;旨在为用户提供高效便捷的工作和学习体验。它能在工作、学习等场景中发挥重要作用&#xff0c;为用户提供智能辅助&#xff0c;下面简单介绍它的特点&#xff1a; 高效搜索&#xff1a;像优化后的百度&#xff0c;直接提问就能…

【本科毕业设计】基于单片机的智能家居防火防盗报警系统

基于单片机的智能家居防火防盗报警系统 相关资料链接下载摘要Abstract第1章 绪论1.1课题的背景1.2 研究的目的和意义 第2章 系统总体方案设计2.1 设计要求2.2 方案选择和论证2.2.1 单片机的选择2.2.2 显示方案的选择 第3章 系统硬件设计3.1 整体方案设计3.1.1 系统概述3.1.2 系…

C#通过异或(^)运算符制作二进制加密(C#实现加密)

快速了解异或运算符&#xff1a; 异或运算符在C#中用 “^” 来表示 口诀&#xff1a;相同取0&#xff0c;相异取1 简单加密解密winform示例&#xff1a; /// <summary>/// 异或运算符加密实现/// </summary>/// <param name"p_int_Num">初始值<…

生成式 AI 与向量搜索如何扩大零售运营:巨大潜力尚待挖掘

在竞争日益激烈的零售领域&#xff0c;行业领导者始终在探索革新客户体验和优化运营的新途径&#xff0c;而生成式 AI 和向量搜索在这方面将大有可为。从个性化营销到高效库存管理&#xff0c;二者在零售领域的诸多应用场景中都展现出变革性潜力&#xff0c;已成为保持行业领先…

云电脑的真实使用体验

最近这几年&#xff0c;关于云电脑的宣传越来越多。 小枣君之前曾经给大家介绍过云电脑&#xff08;链接&#xff09;。简单来说&#xff0c;它属于云计算的一个应用。通过在云端虚拟出一些虚拟电脑&#xff0c;然后让用户可以远程使用&#xff08;仍然需要借助本地电脑&#x…

使用爬虫爬取Python中文开发者社区基础教程的数据

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

CANoe_C#调用CDD和CAPL调用CDD方法对比

引言 在汽车电子系统的开发和测试中,CANoe作为一款强大的网络仿真工具,广泛应用于各种通信协议的模拟和验证。为了实现复杂的测试场景,开发者可以使用不同的编程语言和方法来调用CANoe的功能。其中,C#和CAPL(CANoe Programming Language)是两种常用的编程方式。本文将对…

Golang | Leetcode Golang题解之第498题对角线遍历

题目&#xff1a; 题解&#xff1a; func findDiagonalOrder(mat [][]int) []int {m, n : len(mat), len(mat[0])ans : make([]int, 0, m*n)for i : 0; i < mn-1; i {if i%2 1 {x : max(i-n1, 0)y : min(i, n-1)for x < m && y > 0 {ans append(ans, mat[x…

学习笔记——交换——STP(生成树)工作原理

三、工作原理 STP的基本原理是在一个有二层环路的网络中&#xff0c;交换机通过运行STP&#xff0c;自动生成一个没有环路的网络拓扑。这个无环网络拓扑也叫做STP树(STP Tree)&#xff0c;树节点为某些交换机&#xff0c;树枝为某些链路。当网络拓扑发生变化时&#xff0c;STP…

《汇编语言》第15章——实验15安装新的 int 9 中断例程

安装新的 int9 中断例程 安装一个新的 int 9 中断例程&#xff0c;功能:在 DOS 下&#xff0c;按下A键后&#xff0c;除非不再松开如果松开&#xff0c;就显示满屏幕的A&#xff0c;其他的键照常处理。 提示&#xff0c;按下一个键时产生的扫描码称为通码&#xff0c;松开一个…