Keen的博客 欢迎来到我的个人站~ http://Keenjin.github.io/ Sat, 11 Mar 2023 13:50:28 +0000 Sat, 11 Mar 2023 13:50:28 +0000 Jekyll v3.9.3 【调试技术】Linux桌面客户端C++开发 <h1 id="1-兼容性">1. 兼容性</h1> <h2 id="11-glibc版本号">1.1. glibc版本号</h2> <table> <thead> <tr> <th style="text-align: center">系统家族</th> <th style="text-align: center">系统大版本</th> <th style="text-align: center">系统子版本</th> <th style="text-align: center">linux内核版本</th> <th style="text-align: center">内嵌gcc版本</th> <th style="text-align: center">glibc最高支持版本</th> </tr> </thead> <tbody> <tr> <td style="text-align: center">Ubuntu</td> <td style="text-align: center">14.04</td> <td style="text-align: center">14.04</td> <td style="text-align: center">4.14</td> <td style="text-align: center">gcc 4.8</td> <td style="text-align: center">2.19</td> </tr> <tr> <td style="text-align: center">Ubuntu</td> <td style="text-align: center">16.04 LTS</td> <td style="text-align: center">16.04 LTS</td> <td style="text-align: center">4.14</td> <td style="text-align: center">gcc 5.4.0</td> <td style="text-align: center">2.23</td> </tr> <tr> <td style="text-align: center">Ubuntu</td> <td style="text-align: center">18.04 LTS</td> <td style="text-align: center">18.04 LTS</td> <td style="text-align: center">4.14</td> <td style="text-align: center">gcc 7.5.0</td> <td style="text-align: center">2.27</td> </tr> <tr> <td style="text-align: center">Ubuntu</td> <td style="text-align: center">20.04 LTS</td> <td style="text-align: center">20.04 LTS</td> <td style="text-align: center">5.4</td> <td style="text-align: center">gcc 9.3.0</td> <td style="text-align: center">2.31</td> </tr> <tr> <td style="text-align: center">国产系统</td> <td style="text-align: center">KylinV10</td> <td style="text-align: center">KylinV10</td> <td style="text-align: center">5.4</td> <td style="text-align: center">gcc 9.3.0</td> <td style="text-align: center">2.31</td> </tr> <tr> <td style="text-align: center">国产系统</td> <td style="text-align: center">UOS</td> <td style="text-align: center">UOS</td> <td style="text-align: center">4.19</td> <td style="text-align: center">gcc 8.2.0</td> <td style="text-align: center">2.27</td> </tr> <tr> <td style="text-align: center">CentOS</td> <td style="text-align: center">CentOS7</td> <td style="text-align: center">CentOS7</td> <td style="text-align: center">3.10.0</td> <td style="text-align: center">gcc 4.8.5</td> <td style="text-align: center">2.17</td> </tr> <tr> <td style="text-align: center">CentOS</td> <td style="text-align: center">CentOS8</td> <td style="text-align: center">CentOS8</td> <td style="text-align: center">4.18.0</td> <td style="text-align: center">gcc 8.2.0</td> <td style="text-align: center">2.27</td> </tr> <tr> <td style="text-align: center">RHEL</td> <td style="text-align: center">RHEL7</td> <td style="text-align: center">RHEL7</td> <td style="text-align: center">3.10.0</td> <td style="text-align: center">gcc 4.8.5</td> <td style="text-align: center">2.17</td> </tr> <tr> <td style="text-align: center">RHEL</td> <td style="text-align: center">RHEL8</td> <td style="text-align: center">RHEL8</td> <td style="text-align: center">4.18.0</td> <td style="text-align: center">gcc 8.2</td> <td style="text-align: center">2.27</td> </tr> <tr> <td style="text-align: center">RHEL</td> <td style="text-align: center">RHEL9</td> <td style="text-align: center">RHEL9</td> <td style="text-align: center">5.14</td> <td style="text-align: center">gcc 11</td> <td style="text-align: center">2.31</td> </tr> </tbody> </table> <h1 id="2-多架构编译">2. 多架构编译</h1> Sat, 11 Mar 2023 00:00:00 +0000 http://Keenjin.github.io/2023/03/linux_desktop_client/ http://Keenjin.github.io/2023/03/linux_desktop_client/ Linux 【调试技术】gdb调试 <h1 id="1-准备工作">1. 准备工作</h1> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 查看coredump路径。若命令产生的coredump是由apport接管,则crash存在于/var/crash目录</span> <span class="nb">cat</span> /proc/sys/kernel/core_pattern <span class="c"># 查看coredump生成大小限制</span> <span class="nb">ulimit</span> <span class="nt">-a</span> <span class="c"># 设置coredump生成</span> vim /etc/profile <span class="nb">ulimit</span> <span class="nt">-c</span> ulimited <span class="nb">source</span> /etc/profile <span class="c"># 由apport产生的crash,并非直接coredump文件,而是需要解压出来</span> <span class="nb">sudo </span>apt <span class="nb">install </span>apport apport-unpack xxxx.crash xxxx <span class="c"># 调试上述解压后的CoreDump</span> <span class="nb">cd </span>xxxx <span class="c"># 若非本机,则无法使用如下命令,需要将对应的版本中的二进制提取出来 gdb $exe CoreDump</span> gdb <span class="sb">`</span><span class="nb">cat </span>ExecutablePath<span class="sb">`</span> CoreDump <span class="c"># 如果是systemd启动的服务进程,可能无法产生dump,原因是systemd设置的服务的coredump限制没打开,可以采用两种方法:</span> <span class="c"># 方法一:针对服务自身/lib/systemd/system/xxx.service,添加dump生成设置</span> <span class="o">[</span>Service] <span class="nv">User</span><span class="o">=</span>root <span class="nv">LimitNOFILE</span><span class="o">=</span>1000000 <span class="nv">LimitCORE</span><span class="o">=</span>infinity <span class="c"># 方法二:直接修改systemd的全局配置/etc/systemd/system.conf</span> <span class="nv">DefaultLimitCORE</span><span class="o">=</span>infinity <span class="c"># 注意:方法一和二修改完后,都需要通过systemctl daemon-reload重新加载配置才能生效</span> <span class="c"># 测试dump生成</span> <span class="nb">kill</span> <span class="nt">-11</span> <span class="k">${</span><span class="nv">pid</span><span class="k">}</span> </code></pre></div></div> <h1 id="2-调试命令">2. 调试命令</h1> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 查看栈</span> bt </code></pre></div></div> Wed, 23 Mar 2022 00:00:00 +0000 http://Keenjin.github.io/2022/03/gdb_debug/ http://Keenjin.github.io/2022/03/gdb_debug/ 调试技术 【架构设计】curl原理剖析 <ul> <li><a href="#1-源码编译">1. 源码编译</a> <ul> <li><a href="#11-win下源码编译">1.1. Win下源码编译</a> <ul> <li><a href="#111-准备工作">1.1.1. 准备工作</a></li> <li><a href="#112-openssl编译">1.1.2. OpenSSL编译</a></li> <li><a href="#113-libssh2编译">1.1.3. libssh2编译</a></li> <li><a href="#114-libcurl静态库动态库编译">1.1.4. libcurl静态库/动态库编译</a></li> </ul> </li> </ul> </li> <li><a href="#2-源码分析">2. 源码分析</a></li> </ul> <h1 id="1-源码编译">1. 源码编译</h1> <h2 id="11-win下源码编译">1.1. Win下源码编译</h2> <h3 id="111-准备工作">1.1.1. 准备工作</h3> <blockquote> <p>环境准备</p> </blockquote> <ul> <li>Windows10 20H2系统</li> <li>Microsoft Visual 2019 C++ (MSVC14.2)</li> <li>purl工具(编译openssl的时候需要):<a href="https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-64bit.msi">https://strawberryperl.com/download/5.32.1.1/strawberry-perl-5.32.1.1-64bit.msi</a></li> <li>源代码: <ul> <li>curl: <a href="https://github.com/curl/curl">https://github.com/curl/curl</a></li> <li>libssh2[curl可选项]:<a href="https://github.com/libssh2/libssh2">https://github.com/libssh2/libssh2</a></li> <li>openssl[curl可选项]: <a href="https://github.com/openssl/openssl">https://github.com/openssl/openssl</a></li> </ul> </li> </ul> <blockquote> <p>注意</p> </blockquote> <ul> <li>源码目录结构需要保证正确,如下: ```txt</li> <li>curl |- docs |- …</li> <li>openssl |- apps |- …</li> <li>libssh2 |- docs |- … ```</li> </ul> <h3 id="112-openssl编译">1.1.2. OpenSSL编译</h3> <p>比较好的是,curl将openssl的编译也支持到了,参考:curl\projects\build-openssl.bat(可以先看curl\projects\README),使用build-openssl.bat -help查看使用帮助。</p> <blockquote> <p>普通编译命令行示例</p> </blockquote> <div class="language-batch highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">rem 注意,我们这里的代码路径是C:\Program Files (x86)\Microsoft Visual Studio 14.0,由于我们内部使用了多版本vs(2019和2015),但实际上主程序是一个2019版本,前面的参数vc14不能写成vc14.2(这个会查找C:\Program Files (x86)\Microsoft Visual Studio 2019这个路径)</span> <span class="c">rem 整个编译,会生成动态库、静态库、debug版本、release版本</span> <span class="kd">build</span><span class="na">-openssl </span><span class="kd">vc14</span> <span class="kd">x86</span> <span class="nb">debug</span> <span class="s2">"E:\Code\openssl"</span> <span class="na">-VSpath </span><span class="s2">"C:\Program Files (x86)\Microsoft Visual Studio 14.0"</span> <span class="na">-perpath </span><span class="s2">"C:\Strawberry\perl"</span> <span class="kd">build</span><span class="na">-openssl </span><span class="kd">vc14</span> <span class="kd">x86</span> <span class="kd">release</span> <span class="s2">"E:\Code\openssl"</span> <span class="na">-VSpath </span><span class="s2">"C:\Program Files (x86)\Microsoft Visual Studio 14.0"</span> <span class="na">-perpath </span><span class="s2">"C:\Strawberry\perl"</span> </code></pre></div></div> <p>注意:</p> <ul> <li>多编译器环境下,编译会出现rc.exe找不到的问题(实际是冲突了,不知道使用哪个),因此,运行命令行需要使用vs2019的专用命令行运行上述脚本;而无多编译器场景下,一般上述脚本会自动查找对应的运行环境</li> <li>若上述选择vs2005版本等,编译中还会出现CreateFiber等链接错误(vs2019不会出现问题),是由于NT版本问题,需要修改build-openssl.bat,增加-D_WIN32_WINNT=0x0501 <div class="language-batch highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">perl</span> <span class="s2">"</span><span class="nv">%SOURCE_PATH%</span><span class="s2">\Configure"</span> <span class="nv">%options%</span> <span class="na">-D</span>_WIN32_WINNT<span class="o">=</span><span class="mh">0x0501</span> <span class="s2">"--prefix=</span><span class="nv">%TMP_INSTALL_PATH%</span><span class="s2">"</span> </code></pre></div> </div> </li> </ul> <p>上述编译完成后,会生成:</p> <ul> <li>openssl\build\Win32\VC14\LIB Debug</li> <li>openssl\build\Win32\VC14\DLL Debug</li> <li>openssl\build\Win32\VC14\LIB Release</li> <li>openssl\build\Win32\VC14\DLL Release</li> </ul> <h3 id="113-libssh2编译">1.1.3. libssh2编译</h3> <p>由于libssh2在curl中没有方便的编译脚本,需要自行编译。通过尝试,发现libssh2-1.10.0支持openssl1.1.1i(cmake中会提示找openssl这个版本,同时前面用最新的1.1.1l会出错),这里需要先通过上述流程编译一遍OpenSSL1.1.1i,然后将依赖库,组成如下结构(参考:<a href="https://www.libssh2.org/mail/libssh2-devel-archive-2019-02/0007.shtml">https://www.libssh2.org/mail/libssh2-devel-archive-2019-02/0007.shtml</a>):</p> <pre><code class="language-txt">- openssl |- include |- openssl libssl.lib libcrypto.lib </code></pre> <p>libssh2当前版本(1.10.0)使用cmake工具进行编译,配合openssl需要设置几个参数:</p> <pre><code class="language-txt">CMAKE_INSTALL_PREFIX=xxxx\libssh2\build\libssh2 CRYPTO_BACKEND=OpenSSL OPENSSL_ROOT_DIR=xxxx\libssh2\build\openssl(注意,这里就上述组成的一个文件结构,按照上述结构会自动寻找include、lib) </code></pre> <p>编译完毕后,这里生成了libssh2,为了能让curl顺利编译,还需要重建目录结构(实际build-openssl.bat也是这样处理的)</p> <pre><code class="language-txt">- libssh2 |- build |- Win32 |- VC14 |- Lib Release |- libssh2.lib </code></pre> <h3 id="114-libcurl静态库动态库编译">1.1.4. libcurl静态库/动态库编译</h3> <p>前面的过程完毕后,这里libcurl编译就非常容易了</p> <ul> <li>找到curl\projects\generate.bat,生成工程文件</li> <li>找到curl\projects\Windows\VC14\curl-all.sln,用vs2019打开,选择对应的版本编译即可</li> </ul> <h1 id="2-源码分析">2. 源码分析</h1> Thu, 03 Jun 2021 00:00:00 +0000 http://Keenjin.github.io/2021/06/curl/ http://Keenjin.github.io/2021/06/curl/ 架构设计 【网络技术】P2P技术原理浅析 <!-- TOC --> <ul> <li><a href="#1-概述">1. 概述</a> <ul> <li><a href="#11-p2p的概念">1.1. P2P的概念</a></li> <li><a href="#12-p2p产生的背景">1.2. P2P产生的背景</a></li> <li><a href="#13-p2p的优劣势">1.3. P2P的优劣势</a></li> </ul> </li> <li><a href="#2-p2p分类">2. P2P分类</a> <ul> <li><a href="#21-根据中央化程度">2.1. 根据中央化程度</a></li> <li><a href="#22-根据网络拓扑结构">2.2. 根据网络拓扑结构</a></li> </ul> </li> <li><a href="#3-nat技术详解">3. NAT技术详解</a> <ul> <li><a href="#31-概述">3.1. 概述</a></li> <li><a href="#32-nat的优劣势">3.2. NAT的优劣势</a></li> <li><a href="#33-nat穿透常见解决方案">3.3. NAT穿透常见解决方案</a></li> <li><a href="#34-探针打洞基本原理">3.4. 探针打洞基本原理</a> <ul> <li><a href="#341-基本原理">3.4.1. 基本原理</a></li> <li><a href="#342-同一个nat网关内">3.4.2. 同一个NAT网关内</a></li> <li><a href="#343-不通nat网关内">3.4.3. 不通NAT网关内</a></li> <li><a href="#344-多层不通nat网关内">3.4.4. 多层不通NAT网关内</a></li> <li><a href="#345-保活">3.4.5. 保活</a></li> <li><a href="#346-tcp打洞">3.4.6. TCP打洞</a></li> </ul> </li> <li><a href="#35-常用的打洞协议框架">3.5. 常用的打洞协议框架</a></li> </ul> </li> <li><a href="#4-p2p下载技术原理">4. P2P下载技术原理</a> <ul> <li><a href="#41-概述">4.1. 概述</a></li> <li><a href="#42-两种p2p下载技术方案">4.2. 两种P2P下载技术方案</a> <ul> <li><a href="#421-bittorrent技术方案">4.2.1. BitTorrent技术方案</a></li> <li><a href="#422-kademlia技术方案">4.2.2. Kademlia技术方案</a></li> </ul> </li> <li><a href="#43-基于bt技术的p2p下载实现案例">4.3. 基于BT技术的P2P下载实现案例</a> <ul> <li><a href="#431-p2p技术所遇到的问题">4.3.1. P2P技术所遇到的问题</a></li> <li><a href="#432-解决方案">4.3.2. 解决方案</a></li> <li><a href="#433-整体架构">4.3.3. 整体架构</a></li> </ul> </li> </ul> </li> </ul> <!-- /TOC --> <h1 id="1-概述">1. 概述</h1> <h2 id="11-p2p的概念">1.1. P2P的概念</h2> <p>P2P,英文全称:Peer To Peer,点对点技术,是一种区别于C/S的网络模型,没有统一的中心服务器结点,各个Client也是提供服务的Server。最早1969年4月就记录在RFC1。</p> <h2 id="12-p2p产生的背景">1.2. P2P产生的背景</h2> <p>P2P理念最初是在互联网上兴起的,这得从IPv4说起。</p> <p>互联网是什么?就是各种硬件设备互联,比如电脑、手机、各种联网设备等,用于信息和服务共享。互联网采用TCP/IP架构模型,而本质上是一种C/S模型。通过五元组信息(源IP+源端口+目标IP+目标端口+协议类型)来标记通信双方(Client和Server),按照标准协议,向对方发送数据包,对方根据标准协议解析数据包,来实现信息资源共享。如果说,我们按照原来这个理想的互联模型,不考虑其他任何东西,是根本不需要P2P的,因为本来任何对象都可以作为Server或者Client来提供服务,彼此之间是可以互联。IP是用来标记设备的,目前广泛使用IPv4版本,而端口是用来标记设备上运行的软件服务。IP和端口,是有限的,最初设计者也是没想到发展如此迅速,整个IPv4的地址范围,完全不够互联网设备来分配,那为了解决地址不够用的问题,就引入了NAT。也是自从有了NAT,Client和Server的界限,就变得非常明显,导致了P2P的出现(当然后面IPv6的出现,将这个地址容量扩大到非常大的一个数目,不过终究不是无限的,而且NAT还有其他额外好处)。</p> <p>NAT(Net Address Translate,网络地址转换)是一种IP复用的一种技术,将有限的IP扩展成无限,让IPv4起死回生。这里就不说什么ABCDE类IP分类了。简单概括NAT的原理,首先是将互联网上网络进行了划分:互联网上的任意可以访问的地址组成的网络,称为公有网络,而互联网上不可访问的地址组成的网络,称为私有网络,其中,私有网络侧,一般放置客户端,而公有网络侧,一般放置服务端(先简化不考虑反向代理反向链接等概念)。NAT作为公有网络和私有网络之间的一个桥梁,所起的作用,就是打通公有网络和私有网络的通信,具体而言,就是对于NAT设备自身,作为公有网络的一部分,其他人可以访问,而NAT设备又直接与私有网络之间形成硬件互联。一般而言,请求是由客户端发起的,而客户端是可以触达服务端的(因为服务端是在公有网络上,地址可以访问),服务端是不能直接触达客户端的,因为客户端是在私有网络。这样,当客户端发起请求,请求经过NAT设备出去,我们知道请求是通过五元组信息进行定位,NAT设备此时就负责将请求源(也就是客户端)的IP修改为NAT设备的公有IP,目的是让服务端收到请求后,可以往一个有效地址回复数据,同时记录一下这一次请求的客户端IP到公有IP的转换到一个映射表。当服务端收到数据包,就会往NAT的公有IP回复数据包,数据到达NAT,会再次通过前面记录的映射表,查询到是哪个客户端请求的数据包,进而将数据包中转给对应的客户端。这样的话,私有网络的IP,对公网而言,就没有太大意义,可以任意定制。通常,各个组织内部会组成这样一个私有网络,而各个组织的这些私有网络IP,都是可重复的,所以NAT本质上,就是将NAT公有IP复用给了私有网络各个客户端。</p> <p>通过NAT技术的公私网络隔离,可以实现IP复用,解决了IPv4不够用的问题,但是也同时带来了新问题,直接导致TCP/IP通信困难(由于NAT导致IP成为虚拟IP,外网无法针对内网某台主机进行直连通信,因为没有真实地址可用)。由于所有服务都不得不采用C/S架构,对于提供Server的企业,带宽成本相当高,为了节省带宽成本,将NAT设备内外通信打通,利用Client端作为资源服务提供方来减少直接对Server的访问,因此产生了P2P技术。</p> <h2 id="13-p2p的优劣势">1.3. P2P的优劣势</h2> <p>P2P的优势:</p> <ul> <li>较佳的并行处理能力(相比于C/S架构,随着C越来越多,速度越来越慢,但是p2p网络不会有很明显的变化,所有客户端都能提供资源,包括带宽、存储空间、计算能力)</li> <li>无中心服务器、依靠用户群(peers)交换信息,减低网路传输节点</li> <li>不用投资大量金钱在服务器的软、硬设备</li> </ul> <p>P2P的劣势:</p> <ul> <li>架构较为复杂,节点之间通常存在NAT隔离,无法直接通信,导致需要额外的通路建设过程,以及各个节点的资源协调管理</li> <li>P2P节点遍布整个互联网,给开发者、组织、政府带来监控难题</li> <li>用在大规模网路,资源分享紊乱,管理较难</li> <li>安全性较低 <ul> <li>中毒攻击:提供内容与描述不同的文件</li> <li>拒绝服务攻击:使网络运行非常慢甚至完全崩溃</li> <li>背叛攻击(吸血):用户或软件使用网路却没有贡献出自己的资源</li> <li>数据中插入病毒:下载或传递的文件可能被感染病毒木马</li> <li>P2P软件本身的木马:软件可能含有间谍软件</li> <li>过滤:网络运营商可能会试图阻止传递来自P2P网络上的数据</li> <li>身份攻击:跟踪网络上用户并且进行不断骚扰式或者合法性攻击</li> <li>垃圾信息:在网路上发送未请求的信息</li> </ul> </li> </ul> <p>当然,针对P2P的安全性的一些问题,都有对应的解决方案。</p> <h1 id="2-p2p分类">2. P2P分类</h1> <h2 id="21-根据中央化程度">2.1. 根据中央化程度</h2> <p>一般型P2P</p> <ul> <li>节点同时作为客户端和服务器端</li> <li>没有中心服务器</li> <li>没有中心路由器</li> <li>典型代表:Gnutella,一种文件共享网络,泛滥式查询搜索所有节点</li> </ul> <p>特殊型P2P</p> <ul> <li>一个中心服务器保存节点信息</li> <li>节点负责发布这些信息,让中心服务器知道它们想共享什么文件</li> <li>路由终端使用地址,被一组索引引用来获取绝对地址</li> <li>典型代表:Napster,一种音乐共享网络,依赖中央服务器协调网络中的查询</li> </ul> <p>混合型P2P</p> <ul> <li>同时含有一般型和特殊型的特点</li> <li>典型代表:Skype</li> </ul> <h2 id="22-根据网络拓扑结构">2.2. 根据网络拓扑结构</h2> <p>结构P2P</p> <ul> <li>点对点之间互有链接咨询,彼此形成特定规则拓扑结构</li> <li>需要请求资源时,依该拓扑结构规则寻找,若存在则一定找得到</li> <li>典型代表:Chord、YaCy(分布式开源免费网页搜索引擎系统,保护用户隐私,无中央服务器搜索,保证内容不被审查)、Kademlia(一种协议算法,规定网络的结构同时,规定通过节点查询进行信息交换的方式,快速定位期望的节点)</li> </ul> <p>无结构P2P</p> <ul> <li>点对点之间互有链接咨询,彼此形成无规则拓扑结构</li> <li>需要请求资源时,以广播方式寻找,通常会设TTL,即使存在也不一定找得到</li> <li>典型代表:Gnutella</li> </ul> <p>松散结构P2P</p> <ul> <li>点对点之间互有链接咨询,彼此形成无规则拓扑结构</li> <li>需要请求资源时,依现有咨询推测寻找,介于P2P结构和无结构型之间</li> <li>典型代表:Freenet(一个软件,匿名互联网领域)</li> </ul> <h1 id="3-nat技术详解">3. NAT技术详解</h1> <p>P2P中一个最为关键的技术之一,就是能穿透NAT设备。因此有必要对NAT技术有一个全貌的了解。</p> <h2 id="31-概述">3.1. 概述</h2> <p>NAT本质为解决IPv4地址不够用而诞生的,通过IP复用达到地址几乎无限扩充。</p> <p>常见的NAT有以下两种:</p> <ul> <li>基本网络地址转换(Basic NAT) <ul> <li>也称“静态NAT”,仅支持地址转换,不支持端口转换(一般拥有多个公网IP池可用)</li> <li>宽带路由器使用此方法(宽带路由器有时候被标记为DMZ主机)</li> </ul> </li> </ul> <p><img src="/images/post/network/nat_basic.png" alt="png" /></p> <ul> <li>网络地址端口转换(NAPT) <ul> <li>允许多台主机共享一个公网IP</li> <li>通常由以下两种同时工作组成: <ul> <li>源地址转换类型(内网主机发出的数据包能够到达外网主机)</li> <li>目标地址转换类型(外网主机发出的数据包能够到达内网主机)</li> </ul> </li> </ul> </li> </ul> <p><img src="/images/post/network/napt.png" alt="png" /></p> <h2 id="32-nat的优劣势">3.2. NAT的优劣势</h2> <p>NAT的优势:</p> <ul> <li>负载均衡:通过重定向讲一些服务器的连接转向其他选定的服务器(切换IP)</li> <li>失效终结:提供高可靠性服务,一旦路由器检测服务器宕机,自动转移到备份服务器</li> <li>透明代理:将连接到因特网的HTTP连接重定向到HTTP代理服务器以缓存数据和过滤请求,减少带宽使用而无需配置浏览器代理</li> </ul> <p>NAT的劣势:</p> <ul> <li>使IP会话的保持时效变短 <ul> <li>会话建立会在NAT网关上创建关联表,消耗IP端口号,而这些资源是有限的,需要能资源回收机制</li> <li>实时回收资源(tcp连接等,可以根据解析包,判断是会话结束时再回收资源)</li> <li>定时器回收(udp等无连接状态,只能根据定时,来决定数据包存活期),不同协议有效期举例: <ul> <li>DNS协议:10s</li> <li>FTP-ctrl:300s</li> <li>ICMP协议:10s</li> <li>TCP协议(通常300s,fin、rst为10s,syn为10s)</li> <li>UDP协议:240s</li> </ul> </li> </ul> </li> <li>内部主机复用IP,使得依赖IP进行主机跟踪的机制都失效 <ul> <li>基于网络流量分析的应用无法跟踪到终端用户与流量的具体行为关系</li> <li>基于IP的用户行为日志分析变得困难,难以定位恶意攻击的主机</li> <li>基于IP的用户授权不再可靠</li> <li>服务器的连接限制,使得用户之间的服务抢占排队(本来是防止DOS攻击,防止一个用户的大量连接请求,但是这里就造成了误杀)</li> </ul> </li> <li>对IP端到端模型的破坏 <ul> <li>NAT无法保证多个会话的关联性,当NAT网关拥有多个公有IP地址时,一组关联会话会被分配到不同的公网地址(那就需要不同的域名,很麻烦)。</li> <li>当公网侧需要主动向私网侧发数据,因为NAT网关未建立连接,通常数据包无法到达</li> </ul> </li> <li>修改IP包头信息,会妨碍一些安全协议的工作 <ul> <li>认证协议无法正常工作(NAT篡改了IP地址、传输层端口、校验和导致的,因为认证的目的是要保证这些信息在传输中不变)</li> <li>隧道协议存在问题(隧道协议通常用外层地址标识隧道实体,穿越NAT隧道会有IP复用关系,另一端需要小心处理,例如添加特殊标记标识自己身份)</li> <li>ICMP协议解复用失败(因为IP对应关系被重新映射,ICMP需要复用和解复用。当ICMP的报文载荷无法提供足够的信息时,解复用会失败)</li> <li>IP分片(通常IP分片的传输层信息(包含IP和端口号,特别是端口号)只包含在第一个分片,NAT通常依赖端口号做映射,若无传输层信息,NAT难以识别后续分片与关联表的对应关系,这就需要单独针对每个分片增加额外的端口号信息)</li> </ul> </li> </ul> <h2 id="33-nat穿透常见解决方案">3.3. NAT穿透常见解决方案</h2> <ul> <li>应用层网关(ALG):根据传输层端口号识别协议,然后查询协议对应的IP地址和端口,转换为NAT转换表中的IP和端口,并将转换前的记录起来,形成查询关联表。这样,发送给私网侧的所有IP和端口,都是经过转换的NAT网关的IP和端口,然后私网侧发送数据包到公网侧,就会通过查询前一次记录的转换前的表,找到对应的,还原回去 <ul> <li>必须跟踪所有应用协议的升级,以及新协议</li> <li>有些协议加密了无法解析,也就无法转换</li> </ul> </li> <li>探针技术STUN:需要各个服务增加代码支持,同时部署STUN服务,利用NAT临时映射表的时效性进行打洞穿越</li> <li>中间件技术(UPnP):客户端参与网关公网映射信息的维护,通过UPnP向网关请求映射表,保存在客户端上,在需要发送数据的时候,直接先改写协议中涉及到的IP端口,改写为NAT网关的公网IP端口发送数据出去,NAT网关在收到外网数据时则通过UPnP建立的映射表只转换IP和端口信息(传输层)发数据到内网 <ul> <li>UPnP,通用即插即用,是一个网络终端与网关的通信协议</li> <li>需要NAT网关、内部主机、应用程序同时支持UPnP,且组网时需要允许NAT网关和内网主机交换UPnP信令</li> </ul> </li> <li>中继代理技术(TURN):一个公网的应用代理服务器做中转</li> <li>特定协议的自穿越技术(IKE、IPSec技术):用UDP在报文外面加一层封装,内部的报文就不再受到影响 <ul> <li>因为协议本身考虑安全新,有报文防修改的鉴别能力,其他方法都无效</li> </ul> </li> </ul> <h2 id="34-探针打洞基本原理">3.4. 探针打洞基本原理</h2> <p>这里先以基于UDP协议打洞技术来叙述,最后拿TCP打洞进行对比</p> <h3 id="341-基本原理">3.4.1. 基本原理</h3> <p>基本原理:通过中间服务器的协助,在各自的NAT网关上建立映射表,使得P2P连接双方的报文能够直接穿透对方的NAT网关。客户端分别与中间服务器连接,中间服务器会记录下客户端的内网IP端口和外网IP端口。</p> <p>P2P的Session建立原理:</p> <ul> <li>客户端A和客户端B分别向集中服务器登录,集中服务器记录下他们各自的内外网IP端口(这里集中服务器需要保活和刷新各自IP端口信息)</li> <li>客户端A最初不知道如何向客户端B发起连接,于是A向集中服务器发送消息,请求集中服务器帮助建立与客户端B的UDP连接</li> <li>集中服务器将含有B的内外网IP端口发送给A,同时也将A的内外网IP端口发送给B</li> <li>A开始向B的内外网地址二元组发送UDP数据包,并且A会自动锁定第一个给出响应的B的地址二元组(为何需要同时发两组数据包?因为并不清楚B是与A在同一个NAT网关下,还是不同NAT网关下)</li> <li>B开始向A的内外网地址二元组发送UDP数据包,并且B会自动锁定第一个给出相应的A的地址二元组</li> </ul> <h3 id="342-同一个nat网关内">3.4.2. 同一个NAT网关内</h3> <p>通常为一个局域网。</p> <p>UDP打洞过程:<br /> <img src="/images/post/network/p2p0.png" alt="png" /></p> <p>打洞前</p> <ul> <li>A(10.0.0.1:4321)经过NAT网关(155.99.25.11)向S(18.181.0.31:1234)注册登录 <ul> <li>数据包经过NAT后,会给A的这次连接分配一个随机端口号62005</li> <li>S所看到的就是(155.99.25.11:62005向它发送信息),记录到S的映射表</li> </ul> </li> <li>B(10.1.1.3:4321)经过NAT网关(155.99.25.11)向S(18.181.0.31:1234)注册登录 <ul> <li>数据包经过NAT后,会给B的这次连接分配一个随机端口号62000</li> <li>S所看到的就是(155.99.25.11:62000向它发送信息),记录到S的映射表</li> </ul> </li> </ul> <p>打洞中</p> <ul> <li>A(10.0.0.1:4321)经过NAT网关(155.99.25.11)向S(18.181.0.31:1234)请求B的地址二元组信息,数据包包含A的内网地址(10.0.0.1:4321)</li> <li>A(10.0.0.1:4321)的这次请求,经过NAT网关变成了已经建立的NAT映射表上的A’(155.99.25.11:62005)请求,向S发送</li> <li>S(18.181.0.31:1234)向它所看到的A’(155.99.25.11:62005)发送数据包,包含了B的内外网地址信息(10.1.1.3:4321、155.99.25.11:62000),A保存了B的地址信息</li> <li>S(18.181.0.31:1234)随后又向它所看到的B’(155.99.25.11:62000)发送数据包,包含了A的内外网地址信息(10.0.0.1:4321、155.99.25.11:62005),B保存了A的地址信息</li> <li>随后相互通信,A向B发送数据,B向A发送数据,均是先尝试内网地址通信,若数据包相互收到,则记住对方的第一个回包地址信息,识别出对方身份,确认是内网可通信</li> </ul> <p>打洞后</p> <ul> <li>A和B直接利用内网地址通信</li> </ul> <p>注意:</p> <ul> <li>A和B发往对方公网地址二元组信息的UDP数据包,不一定会被对方接收到,取决于当前NAT设备是否支持不同端口之间的UDP数据包送达(Hairpin回环转换特性)</li> <li>不过如果NAT设备支持Hairpin特性,也尽量优先以内网进行尝试</li> </ul> <h3 id="343-不通nat网关内">3.4.3. 不通NAT网关内</h3> <p>UDP打洞过程:<br /> <img src="/images/post/network/p2p1.png" alt="png" /></p> <p>打洞前</p> <ul> <li>A(10.0.0.1:4321)经过NAT网关(155.99.25.11)向S(18.181.0.31:1234)注册登录 <ul> <li>数据包经过NAT后,会给A的这次连接分配一个随机端口号62000</li> <li>S所看到的就是(155.99.25.11:62000向它发送信息),记录到S的映射表</li> </ul> </li> <li>B(10.1.1.3:4321)经过NAT网关(138.76.29.7)向S(18.181.0.31:1234)注册登录 <ul> <li>数据包经过NAT后,会给B的这次连接分配一个随机端口号31000</li> <li>S所看到的就是(138.76.29.7:31000向它发送信息),记录到S的映射表</li> </ul> </li> </ul> <p>打洞中</p> <ul> <li>A(10.0.0.1:4321)经过NAT网关(155.99.25.11)向S(18.181.0.31:1234)请求B的地址二元组信息,数据包包含A的内网地址(10.0.0.1:4321)</li> <li>A(10.0.0.1:4321)的这次请求,经过NAT网关变成了已经建立的NAT映射表上的A’(155.99.25.11:62000)请求,向S发送</li> <li>S(18.181.0.31:1234)向它所看到的A’(155.99.25.11:62000)发送数据包,包含了B的内外网地址信息(10.1.1.3:4321、138.76.29.7:31000),A保存了B的地址信息</li> <li>S(18.181.0.31:1234)随后又向它所看到的B’(138.76.29.7:31000)发送数据包,包含了A的内外网地址信息(10.0.0.1:4321、155.99.25.11:62000),B保存了A的地址信息</li> <li>随后相互通信,A向B发送数据,B向A发送数据,均是先尝试内网相互通信,若数据包相互收到,则记住对方的第一个回包地址信息,识别出对方身份,确认是内网不可通信,后续的所有包都走B的外网地址通信</li> </ul> <p>打洞后</p> <ul> <li>A和B直接利用内网地址通信</li> </ul> <p>注意:如果A发给B的包,在B发给A的包之前到达B的NAT设备之前到达,就会丢弃该包,因为默认不允许外网往内发包</p> <h3 id="344-多层不通nat网关内">3.4.4. 多层不通NAT网关内</h3> <p>互联网ISP部署到个人客户端的一般模型</p> <p>UDP打洞过程:<br /> <img src="/images/post/network/p2p2.png" alt="png" /></p> <p>打洞前</p> <ul> <li>A(10.0.0.1:4321)经过NAT-A网关(10.0.1.1)发数据,后又经过NAT-C网关(155.99.25.11)将数据包送达S(18.181.0.31:1234),进行注册登录 <ul> <li>数据包经过NAT-A后,会给A的这次连接分配一个随机端口号45000</li> <li>数据包经过NAT-C后,会给这次连接分配一个随机端口号62000</li> <li>S所看到的就是(155.99.25.11:62000向它发送信息),记录到S的映射表</li> </ul> </li> <li>B(10.1.1.3:4321)经过NAT-B网关(10.0.1.2)发数据,后又经过NAT-C网关(155.99.25.11)将数据包送达S(18.181.0.31:1234),进行注册登录 <ul> <li>数据包经过NAT-B后,会给B的这次连接分配一个随机端口号55000</li> <li>数据包经过NAT-C后,会给这次连接分配一个随机端口号62005</li> <li>S所看到的就是(155.99.25.11:62005向它发送信息),记录到S的映射表</li> </ul> </li> </ul> <p>打洞中</p> <ul> <li>A(10.0.0.1:4321)经过NAT-A网关(10.0.1.1)和NAT-C网关(155.99.25.11)向S(18.181.0.31:1234)请求B的地址二元组信息,数据包包含A的内网地址(10.0.0.1:4321)</li> <li>S(18.181.0.31:1234)向它所看到的A’(155.99.25.11:62000)发送数据包,包含了B的内外网地址信息(10.1.1.3:4321、155.99.25.11:62005),A保存了B的地址信息</li> <li>S(18.181.0.31:1234)随后又向它所看到的B’(155.99.25.11:62005)发送数据包,包含了A的内外网地址信息(10.0.0.1:4321、155.99.25.11:62000),B保存了A的地址信息</li> <li>随后相互通信,A向B发送数据,B向A发送数据,均是先尝试内网相互通信,若数据包相互收到,则记住对方的第一个回包地址信息,识别出对方身份,确认是内网不可通信,后续的所有包都走B的外网地址通信</li> </ul> <p>打洞后</p> <ul> <li>A和B直接利用内网地址通信</li> </ul> <h3 id="345-保活">3.4.5. 保活</h3> <ul> <li>由于NAT设备针对UDP有空闲超时计数,所有穿越会有有效期</li> <li>双方需要向对方发送心跳包,而且这个心跳包必须是双方都发送,保证双方Session都能正常工作</li> </ul> <h3 id="346-tcp打洞">3.4.6. TCP打洞</h3> <p>与UDP打洞的区别:</p> <ul> <li>一般NAT设备对UDP支持更友好一些,TCP更复杂(因为NAT上的TCP协议的映射表有效期,可以根据TCP连接情况,主动失效,而UDP只能定时失效;NAT映射表会依据TCP发送端的端口号,也就是必须与前面注册登录时保持一致,否则发送给另一端的端口地址不一样,大概率会失败)</li> <li>UDP只需要处理一个套接字的收发通信,而TCP需要用多个套接字绑定同一个端口(端口复用)</li> <li>TCP打洞成功率远远没有UDP高(NAT防火墙策略对TCP协议不是很友好)</li> </ul> <p>TCP打洞Session建立原理:</p> <ul> <li>客户端A使用其与服务器的连接,向服务器发送请求,要求服务器协助其连接客户端B</li> <li>服务器将客户端B的内外网的TCP地址二元组信息返回给A,同时,服务器将A的内外网TCP地址二元组发送给B</li> <li>客户端A和B各自使用连接服务器的端口异步发起向对方的内外网地址二元组的TCP连接,同时监听各自本地TCP端口是否有外部的连接接入</li> <li>A和B开始等待向外的连接是否成功,检查是否有新连接接入。如果向外的连接,由于某种网络错误而失败,客户端可以延迟重试</li> <li>TCP连接建立起来以后,客户端之间应该开始鉴权操作,确保目前连入的连接就是所希望的连接(比对双方IP端口)。如果鉴权失败,客户端将关闭连接,等待新的连接接入。客户端只接受第一个通过鉴权的客户端,然后将进入P2P通信过程不再继续等待是否有新的连接接入</li> </ul> <h2 id="35-常用的打洞协议框架">3.5. 常用的打洞协议框架</h2> <ul> <li>STUN(P2P解决方案,支持TCP穿透和UDP穿透)</li> <li>TURN(中继技术)</li> <li>ICE(整合了STUN、TURN)</li> </ul> <h1 id="4-p2p下载技术原理">4. P2P下载技术原理</h1> <h2 id="41-概述">4.1. 概述</h2> <p>下载的本质,就是向资源服务器请求资源数据,通常是有限的Server作为资源服务器。<br /> P2P下载,则认为所有参与下载的人,也都是资源服务器,提供内容供其他人下载。下载方,需要知道找谁下载,就是靠种子文件来提供资源服务器索引的。</p> <p>P2P对比C/S下载:</p> <ul> <li>原始的C/S模式,下载的人越多,个人分得的带宽越少</li> <li>P2P下载模式,下载的人越多,提供的带宽越大</li> </ul> <h2 id="42-两种p2p下载技术方案">4.2. 两种P2P下载技术方案</h2> <h3 id="421-bittorrent技术方案">4.2.1. BitTorrent技术方案</h3> <p>BitTorrent协议,是2001年美国程序员发布的,是一种基于P2P文件传输通信协议,协议要求资源发布者根据文件生成提供一个.torrent文件,就是种子文件。</p> <p>种子组成:</p> <ul> <li>Tracker信息:BT下载中需要用到的Tracker服务器地址和针对Tracker服务器的设置</li> <li>文件信息:将目标文件虚拟分成大小相等的块,块大小是2K的整数次方,这里的文件信息就包含了每个块的索引信息和Hash信息</li> </ul> <p>P2P种子运行基本原理:</p> <ul> <li>下载用户,先需要取得种子信息,拿到Tracker服务器地址及相关设置</li> <li>当用户下载了某个资源,本地资源分片落地时,就会告知Tracker服务器,Tracker服务器会记录已下载的用户信息,存储到服务器缓存(Tracker服务器所起的作用类似服务发现)</li> <li>当有其他人需要下载时,会先连接Tracker服务器,从其中获取下载资源的Peer列表,也会包括前面已经下载过的用户</li> <li>当Peer与Peer下载准备开始前,会通过BT软件进行打洞(比如通过NAT设备端口有效期机制等),让彼此能够进行通信,然后进行文件分片下载。BT软件有可能同时对多个分片向多个人请求资源下载,如果下载后校验Hash失败,则会尝试其他Peer</li> </ul> <p>BT方案的缺陷:</p> <ul> <li>如果Tracker服务器异常了,则可能拿不到Peer列表(单点故障)</li> <li>如果各个Peer失效,则可能下载超时</li> </ul> <h3 id="422-kademlia技术方案">4.2.2. Kademlia技术方案</h3> <p>Kademlia准确点讲,是一种P2P协议,用于DHT(Distributed Hash Table)去中心化的P2P解决方案,解决BT方案中的单点故障问题,区块链就是应用的这种解决方案。</p> <p><img src="/images/post/network/p2p3.png" alt="png" /></p> <p>基本约定:</p> <ul> <li>每个结点都有一个ID(很长),这个ID是DHT网络中某个文件某种计算方式的HASH</li> <li>每个结点需要负责存储文件索引、文件分片等,但是每一个结点没有一个完整的知识,只有一个片段,也不知道所有的文件片段保存在哪里</li> <li>每个文件计算出来HASH,那么结点ID与此HASH相同或者相似,这个结点就需要知道该文件存储在哪里</li> </ul> <p>Session建立原理:</p> <ul> <li>node new上线,需要下载文件1</li> <li>首先,node new需要获取文件1的种子文件,这个种子文件中,会包含一部分结点的地址以及文件1的Hash</li> <li>node new根据文件1Hash,在种子文件中这些已知的结点中问询,查找与Hash相同或相似的结点信息</li> <li>各个结点,收到node new的请求,会首先看自身是否满足要求,如果不满足要求,就会进一步广播此请求(有种类似社交网络中找人)</li> <li>一旦找到了结点(我们这里是node C,包含了文件1的索引),就会回复告诉node new,需要分别去B、D、F查询</li> <li>node new先与B进行Peer连接,开始下载,自己本地也有文件1了,就会告诉node C和ID相似的结点,自己也存在文件1了,可以加入那个文件的拥有者列表</li> <li>node C和其他文件1索引拥有者会将node new也加入到文件1的索引列表</li> </ul> <h2 id="43-基于bt技术的p2p下载实现案例">4.3. 基于BT技术的P2P下载实现案例</h2> <h3 id="431-p2p技术所遇到的问题">4.3.1. P2P技术所遇到的问题</h3> <p>最大的问题是P2P率并不高,常常都直接向Server寻求资源,导致无法降低很多Server带宽成本。我们可以设想一下,最常见的比如我们在推送某些更新文件时,通常是统一时刻,大家更新的时间段若比较重合,就很容易引发Peer节点不足。</p> <h3 id="432-解决方案">4.3.2. 解决方案</h3> <p>通过预推文件,让下载资源提前触达到用户,主动增加Peer节点数。为此,也需要不能影响用户自身的体验,一般需要达成以下目标:</p> <ul> <li>在用户带宽允许的前提下,提升下载速度</li> <li>还需要严格控制用户的上行带宽,不要影响用户上网</li> <li>保证下载质量,同时做到对大文件下载的差错控制</li> </ul> <h3 id="433-整体架构">4.3.3. 整体架构</h3> <p><img src="/images/post/network/p2p4.png" alt="png" /></p> Thu, 22 Apr 2021 00:00:00 +0000 http://Keenjin.github.io/2021/04/p2p/ http://Keenjin.github.io/2021/04/p2p/ 网络技术 【安全技术】关于零信任安全的思考 Sat, 13 Mar 2021 00:00:00 +0000 http://Keenjin.github.io/2021/03/security_thinking/ http://Keenjin.github.io/2021/03/security_thinking/ 安全技术 优秀资源合集 <h2 id="网站">网站</h2> <ul> <li><a href="http://www.woshipm.com/">人人都是产品经理</a>:这里有许多新产品的特性分析,可以通过搜索,帮助深入对一些产品的体验分析</li> <li><a href="https://app.diagrams.net/">draw.io</a>:一个开源且完全免费的绘图工具,可以在线,也可以下载app,类似processon,只不过processon只有可怜的9个使用限制,这里可以无限制使用</li> <li><a href="https://stackshare.io/">stackshare</a>:帮助了解各大公司技术栈,可以知道一些趋势</li> <li><a href="https://simple-tracker.com/">simple</a>:这个有点类似番茄钟,非常精简的界面体验,让一天的时间消耗都去哪儿了,展现非常明显</li> </ul> <h2 id="新产品体验">新产品体验</h2> <ul> <li>slack:一种国外的社交工具,区别于微信和qq,具备md文本编辑特性,有类似企业微信的bot工具等,国内好多软件特性都有这里的缩影</li> <li>discord:类似slack,有点专门的游戏概念</li> </ul> Sun, 28 Feb 2021 00:00:00 +0000 http://Keenjin.github.io/2021/02/new_sights/ http://Keenjin.github.io/2021/02/new_sights/ 产品 【操作指令】代码阅读可视化 <h2 id="背景">背景</h2> <p>最近看一些开源代码,代码量之大需要较多的阅读时间,在梳理开源代码调用关系的时候,通常要花费大量时间,一直想能有一套可视化工具,将阅读的代码关系连接起来,将极大提升阅读效率。</p> <p>开源社区中普遍使用graphviz来进行图形绘制,使用其他工具,生成调用链。</p> <h2 id="codevizgraphviz不好用没有成功过">codeviz+graphviz(不好用,没有成功过)</h2> <p>网址:<a href="https://github.com/petersenna/codeviz">https://github.com/petersenna/codeviz</a></p> <p>通过介绍可知,它本来是作者为了分析linux内核源码所设计的,有着与大家一样的困惑的同时,不甘于把时间耗费在巨大的代码量中,而设计了这么个代码调用关系可视化工具。这个工具的实现原理,是采用定制gcc编译器(给gcc源码打patch),在生成c代码编译关系之时,生成调用信息,交给graphviz来绘制。也能对c++调用关系做处理。正因如此实现机制,在分析任何源码前,都需要明确对应的gcc使用版本,然后针对处理。通过实践来操作,发现无论如何都无法生成调用关系cdepn文件,实践环境:centos7 x64环境,使用gcc 7.4.0。有兴趣的同学,可以参考petersenna介绍的方式,安装试试。可以使用如下步骤:</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 下载源码</span> git clone hhttps://github.com/petersenna/codeviz.git <span class="nb">cd </span>codeviz <span class="c"># 配置7.4.0版本安装</span> ./configure <span class="nt">--gcc</span><span class="o">=</span>7.4.0 <span class="nt">--gccgraph</span><span class="o">=</span>/usr/local/gccgraph <span class="c"># 如果下载失败,修改compilers/install_gcc-7.4.0.sh脚本中的FTP工具,为wget工具,当然,请确保已安装</span> <span class="c"># 这里会先下载gcc源码,然后打patch,然后安装gcc,并使用gcc编译codeviz。如果发现gcc编译失败,请先去compilers/gcc-graph/gcc-7.4.0/,然后运行contrib/download_pre.....脚本,下载依赖,然后重新make。这个时间非常久,建议睡前编译,当然,可以选择并行编译,请自行修改脚本。</span> make make <span class="nb">install</span> </code></pre></div></div> <p>最后依旧没有成功,先记录一下,以后有需要再研究吧。</p> <h2 id="doxygengraphviz非常赞">doxygen+graphviz(非常赞)</h2> <p>doxygen是一个工程化工具,使用后,会感觉非常棒,不仅支持c/c++,还支持java等其他流行编程语言。基本使用方式非常简单。</p> <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"># 安装</span> yum <span class="nb">install </span>graphviz <span class="nt">-y</span> yum <span class="nb">install </span>doxygen <span class="nt">-y</span> <span class="c"># 选择源码目录</span> <span class="nb">cd </span>squid-5 <span class="c"># 生成Doxyfile标准文件</span> doxygen <span class="nt">-g</span> <span class="c"># 编辑Doxyfile,文件末尾增加如下:</span> HAVE_DOT <span class="o">=</span> YES EXTRACT_ALL <span class="o">=</span> YES EXTRACT_PRIVATE <span class="o">=</span> YES EXTRACT_STATIC <span class="o">=</span> YES CALL_GRAPH <span class="o">=</span> YES CALLER_GRAPH <span class="o">=</span> YES DISABLE_INDEX <span class="o">=</span> YES GENERATE_TREEVIEW <span class="o">=</span> YES RECURSIVE <span class="o">=</span> YES <span class="c"># 重新运行</span> doxygen Docxfile <span class="c"># 生成html文件,在html文件夹,配合latex使用。我在虚拟机中生成的,为了外层方便使用,需要建立一个http server,使用python3非常方便</span> yum <span class="nb">install </span>python3 <span class="nt">-y</span> <span class="nb">cd </span>squid-5/html/ python3 <span class="nt">-m</span> http.server 8080 <span class="c"># 在主机浏览器中,输入ip:8080,即可访问</span> </code></pre></div></div> <p>效果如下:</p> <p><img src="/images/post/squid/squid-viz.png" alt="png" /></p> Sat, 22 Aug 2020 00:00:00 +0000 http://Keenjin.github.io/2020/08/code_visualize/ http://Keenjin.github.io/2020/08/code_visualize/ 操作指令 【音视频】多媒体研究思考 <h1 id="多媒体简述">多媒体简述</h1> <p>何为媒体?英文Media,也可以理解为媒介,表示连接两个物体之间的手段,在现如今,更多的理解为信息通过信息源传递到接收者的通路。我们生活在这个世界,能接触到的信息有限,有时候我们需要了解外面的世界,就需要借助媒体获得外面世界的信息。传统的媒体通路,包括报纸、杂志、书籍、电视等,甚至在更久远的历史年代,还依靠邮件、火炬等传递。在互联网兴起之后,整个媒体技术手段变得更加数字化,作为媒体的通路,还包括计算机应用,例如:PC时代门户网站、微博等,移动时代的微信、抖音等,让信息的实时性和受众程度更高了。</p> <p>何为多媒体?多,是指媒体传递着不止一种的信息内容,在现代,多媒体传输包括:音频、视频、图像、文本等。这里,主要是视觉和听觉的传递,在未来,或许还能对触觉进行传递,让信息更真实。</p> <h1 id="从音频出发延伸去看世界的本质">从音频出发,延伸去看世界的本质</h1> <p>本来是想研究一下音频相关技术,一不小心,就沉浸在世界的本质研究乐趣中不可自拔,姑且简单谈谈我的理解。</p> <p>音频是什么?或者问,我们听到的声音是什么?我们听到的音乐、路边的车辆行驶声、人们说话声、打雷声、走路声、大风呼啸刮过的声音、建房挖路切割敲打声、狗叫声……我们生活的世界,充斥着各种各样的声音种类,而且相互交杂。咦?各种各样?我们是怎么辨别出来的呢?似乎我们人类能轻松辨别出声音的种类。</p> <p>声音的本质到底是什么?这一点,科学家已经做足了研究。声音本质是振动产生的一种波,称为机械波。人们说话,通过肚子振动控制呼吸气流+喉咙肌肉控制,带动喉咙中声带振动,再通过口型舌头等,一连串控制,发出不同的声音。不同人发出的声音各不相同,本质是声带振动频率各不相同,不同频率振动,就产生不同频率的机械波,声音进入耳膜,产生不同频率的振动,人类大脑就可以通过不同频率的微小差别,区分开是谁发出的声音,而大脑仅需要记住“频率-&gt;物”的映射关系即可。频率就可以区分声音,人可以发出多种频率,大脑可以自动学习进行音频的模式匹配,找到最准确的那个物。</p> <p>始终相信,大自然造物的规律,是简单的,科学家也在追求终极规律的道路上,不断靠近真相。科学家到底是怎么思考的呢?我想,也不全然是天马行空的思绪,必然也是对已存在的合理性的深入追问和探索,我想以这样的姿态,来探索音频,探索世界。当我再继续思考声音是一种波的时候,也在继续询问自己:波,到底是什么?一种能量?一种物质?还是其他什么呢?到底为什么可以传递?是如何衰减的?科学家们在终极发问的道路上,通过探索,得出的一个规律:能量守恒。或许可以说,正是有了这个规律,才更加让人深信不疑:造物主造物规律,必然是简单而又精美的。能量守恒这条规律,在各个方面,都得到印证。比如:机械能守恒定律(在只有重力和弹力做功的情况下,动能势能之和,也就是机械能,保持不变。经典实验:钟摆,在没有其他损耗的情况下,会永久摆动下去);热力学第一定律(热量可以从一个物体传递到另一个物体,也可以与机械能或其他能量相互转换,转换过程中,能量的总和保持不变。经典应用:发动机);质能守恒定律(封闭系统内,一个物质的能量减少,其质量必然减少,而另外的物质能量增加,质量也会增加,所有粒子的相对论静能与动能之和,在相互作用过程中保持不变。);电磁转换定律(电能和磁能,可以相互转换,封闭系统内,总体能量保持不变)。</p> <p>明白封闭系统内能量的守恒,那就可以继续进行探索。此时还无法解释,波是什么。通过爱因斯坦质能守恒,明白物质质量和能量是关联的。质量,由密度*体积来计算,这是经典物理学中的一个规律,本质上是存在误差的,但是误差不会太大。物质的密度,一般而言,是稳定的一个值的范围,这就说明,物质形成本身,应该是满足一定规律的。这就涉及到物质组成的最小单元的研究,这一点,也是上个世纪科学家研究的重点。通过以前化学知识的认知,我们知道,物质是由各种元素排列组合而成,元素周期表就是目前所发现的所有元素。元素周期表中的元素,是什么呢?是一种原子结构,由原子核(质子 + 中子) + 电子组成。这句话是什么意思呢?也就是说,物质都是由微粒组成,其中基本单元可以理解为原子,而差异性则体现在原子外层电子层分布,这就是元素周期表中的周期的概念,它表示电子在原子外层分布的层级情况。那这个层级,是什么意思呢?这里层级,表示电子能级层,是量子物理学中的概念了。原子外层被电子围绕旋转,这个模型只是一种物质单元的描述,实际上,科学家对于原子外层的电子,是无法测量它的轨迹的,这就是量子学中测不准原则,但是科学家通过光学实验,发现这些电子虽然无法测量轨迹,但也不是混乱的运转,而是在特定能量层级运转。通过不同频率的光照射,才有可能让它的能级发生跃迁,而不在于电子吸收光的强度。这是一个非常反常理的设定,电子依靠的是光频率,而非光强度,进行能量转移,这个在热力学中是不可想像的。这里边可以回到音频本身,可以看到,频率是区分音频的本质内容,这里的元素,是通过电子能级层分布来区分开,而电子能级层分布,又与频率相关联。大自然果然不是胡乱构造的。说了半天电子能级层,那我到底想表达什么呢?刚刚说到,元素由原子和电子不同能层分布决定,电子能层又可以通过不同频率的光照射,来实现跃迁。电子能级层,就元素周期表而言,目前只发现到7层,层数越高,电子包含的数量也越大,元素为达到稳定,原子核中包含的质子也就越多,质子个数与电子保持一致(现在用正电和负电来表示),才能保持稳定。这里边,我不想做深入研究,继续肤浅的解释。爱因斯坦的质能方程,描述的是质量和能量的关系,以及可以互转。能量到底是什么?力是一种能量,热是一种能量,重量是一种能量,但本质的能量到底是什么呢?如果用原子来解释,那就只能解释为,能量是一种物质属性,可以说质量本身就是能量,质子进一步是由胶子和夸克组成,研究发现,胶子质量为0,夸克质量只有质子的5%,剩下95%的质量,是由胶子和夸克互相运动造成的,也就是质量和能量是相互转换的,这也是质能方程的根基,所以能量是一种物质属性,就是这么解释的。物质密度的稳定性,也是由这些运动的稳定性来决定的。电子没有质量,但是电子带有能量,是不是本质是因为它的运动导致的呢?假设我们这么理解,这个世界本身就是由无质量的粒子,旋转产生能量,进而产生质量组成的呢?大到天体运动(挂在宇宙中,也不会掉下去),小到电子原子。元素周期表中的元素,则是不同电子运动方式。脑补了一下你看到的人都是一坨最小粒子运动组成,真的很神奇。回到能量说上来,我们再看电子,前面说电子分布在不同能级层,可以通过光子,把它“撞”到其他能级,但前提是满足一定频率。这一点跟我们的认知,是完全不一样的,按照经典物理学,我们如果要撞,是要用更大的力,才能撞得动,但是这里,无论光有多强,只要频率不满足,就无法撞动电子。经典物理,是以物质粒子性来描述撞击力,光子如果想象为粒子,确实跟经典物理相矛盾,那如果光子本身是一种波呢?</p> Sat, 20 Jun 2020 00:00:00 +0000 http://Keenjin.github.io/2020/06/%E5%A4%9A%E5%AA%92%E4%BD%93%E7%A0%94%E7%A9%B6%E6%80%9D%E8%80%83/ http://Keenjin.github.io/2020/06/%E5%A4%9A%E5%AA%92%E4%BD%93%E7%A0%94%E7%A9%B6%E6%80%9D%E8%80%83/ 音视频 【音视频】直播的思考 <h1 id="直播的思考">直播的思考</h1> <p>进入直播行业已有2个月,在此之前,并没有使用过任何直播工具,一直对直播的概念比较模糊,以为它是一个不正经的网络视频聊天交友工具。为了正式给直播的印象定型,决定来梳理一下这2个月对直播学习的心得。</p> <h2 id="直播行业的历史">直播行业的历史</h2> <h3 id="人无法逾越空间规则吗">人无法逾越空间规则吗?</h3> <p>还记得曾经2003年,那会儿我还在上初中,《传奇》网游的火爆,也将我带入游戏世界。那会儿跟小时候的玩伴儿一起跑去网吧,就为了打怪升级。大家并排坐,组团组队一起攻入祖玛,行动的一致性靠的是吼。后来又出现公会,大家在公会内组团组队去打怪、攻沙城,跨地域的交流变得困难。好在游戏中推出了聊天框,大家在游戏前,通过聊天框中输入的文字信息,来约定集合时间、地点、目的地。在进行游戏娱乐活动中,通过文字的沟通,让空间,不再成为束缚。</p> <p>人类没办法瞬间移动到其他空间,但是,信息确可以做到。不禁感叹,通信科技是一项多么伟大的进步。直播,更是使得信息能够实时传达,此时此刻此事的想法,跨距离障碍传递给远方。如果说这个时候给直播下一个定义,我会说:“直播,就是实时传递信息的方式”。</p> <h3 id="更便捷有效的表达方式">更便捷有效的表达方式</h3> <p>通过文本,可以传达想法,但是文字传达信息存在弊端。一方面,在游戏中,双手就是命,特别是沙城决战,根本就没法腾出手来去打字发信息,文本传递信息的方式,便难以满足要求。另一方面,文字想要表达清楚真正的想法,存在理解成本和时间成本,这是由于文字先天性承载信息的不足,文字能表达概念内容,但是需要比较清楚的逻辑描述,在上下文的承接上,需要你来我往的相互信息,才能清楚的让信息在双方之间互通。同时,打字本身,也会比较耗时。</p> <p>2003年到2007年,相继出现了“聊聊语音”、“IS语音”、“新浪UT”、“盛大ET”语音聊天工具,解放双手的语音聊天方式,让跨地域的队员,就像是坐在一起一样,在喊着指令。分享此时此刻在游戏世界的想法的方式,变得更便捷了。</p> <p>语音相比于文字,除了解放双手的便捷性之外,更重要的是它所能承载的信息量以及实时通信双方沟通的效率的提升。人与人沟通,最原始本质的方式,就是语音,人的大脑对语音的处理能力,是比对文字要强得多,不仅能表达概念内容,还能从语音上的细微音调变化,来体现人的情感变化,本质其实就是语音所能承载的信息量更多了。而且,即时通信最大的问题,是双方信息交换,语音使得信息交换的上下文更清晰,也可以在对方还未完全说完之时打断对方,立刻表达自己已经理解对方接下来的意图,并告知自己实际的意图,信息交换产生的冗余更低了。</p> <p>通信技术的出现,使人类打破空间规则,建立了一条基本的人与人传达信息的隧道。而语音技术的使用,更是建立的人的意志力传递的隧道。此时再给直播下个定义,我会说:“直播,就是实时互动,交换信息”。</p> <p>2008年,“YY语音”、“QT语音”也参与到游戏语音直播行业,与此同时,它们也相继推出了K歌和聊吧,让语音沟通交流方式应用到游戏之外的娱乐行业。而K歌、聊吧这些内容的出现,也逐渐萌芽产生真正的直播。</p> <h3 id="真正意义上的直播出现">真正意义上的直播出现</h3> <p>语音,本质上是通信技术借鉴人类表达方式而产生的,而视频的出现,则就像拉近人与人的直接距离,面对面交流,这种交换信息的方式,完全模拟人类的原始社交。从这之后,人类彻底实现跨空间更逼真的交流。视频所能承载的信息,也更多了,从听到的声音、看到的表情动作、看到的周围环境,也能更容易理解双方说的内容。</p> <p>2009年,“9158”、“六间房”、“呱呱”等相继出现,运用音、视频通信技术,重新定义了直播。K歌、聊吧等娱乐方式,设计成了房间、聊天室模式,房间的主人,称为主播。在初期,房间内可以有多个主播。观看房间的人,称为游客,而关注这些主播的人,称为粉丝。</p> <p>这种变化,不仅丰富了语音的意义,也是现代直播真正的雏形。从这之后,无论是文字、语音形式的直播工具,开始真正发挥价值。以前在游戏中使用的语音工具,更多的是用于沟通传达指令,或者游戏之外用于交友互动,沟通主体双方是同等的,大多是一对一、多对多的关系。</p> <p>«未完待续»</p> Fri, 12 Jun 2020 00:00:00 +0000 http://Keenjin.github.io/2020/06/%E7%9B%B4%E6%92%AD%E7%9A%84%E6%80%9D%E8%80%83/ http://Keenjin.github.io/2020/06/%E7%9B%B4%E6%92%AD%E7%9A%84%E6%80%9D%E8%80%83/ 音视频 【音视频】DX渲染学习总结 <!-- TOC --> <ul> <li><a href="#1-环境准备">1. 环境准备</a> <ul> <li><a href="#11-兼容性">1.1. 兼容性</a> <ul> <li><a href="#111-硬件兼容性">1.1.1. 硬件兼容性</a></li> <li><a href="#112-软件兼容性">1.1.2. 软件兼容性</a></li> </ul> </li> <li><a href="#12-安装说明">1.2. 安装说明</a></li> </ul> </li> <li><a href="#2-计算机图像显示原理">2. 计算机图像显示原理</a> <ul> <li><a href="#21-图像显示基本原理">2.1. 图像显示基本原理</a></li> <li><a href="#22-windows图形系统架构">2.2. Windows图形系统架构</a></li> <li><a href="#23-渲染管线">2.3. 渲染管线</a> <ul> <li><a href="#231-渲染管线的基本作用">2.3.1. 渲染管线的基本作用</a></li> <li><a href="#232-渲染架构">2.3.2. 渲染架构</a></li> <li><a href="#233-各个阶段简单说明">2.3.3. 各个阶段简单说明</a> <ul> <li><a href="#2331-顶点数据输入">2.3.3.1. 顶点数据输入</a></li> <li><a href="#2332-顶点着色器">2.3.3.2. 顶点着色器</a></li> <li><a href="#2333-曲面细分过程">2.3.3.3. 曲面细分过程</a></li> <li><a href="#2334-几何着色器">2.3.3.4. 几何着色器</a></li> <li><a href="#2335-图元组装">2.3.3.5. 图元组装</a></li> <li><a href="#2336-光栅化">2.3.3.6. 光栅化</a></li> <li><a href="#2337-片段着色器">2.3.3.7. 片段着色器</a></li> <li><a href="#2338-测试混合">2.3.3.8. 测试混合</a></li> </ul> </li> </ul> </li> </ul> </li> <li><a href="#3-dx基本使用流程">3. DX基本使用流程</a> <ul> <li><a href="#31-基于窗口的消息循环">3.1. 基于窗口的消息循环</a></li> <li><a href="#32-dx8">3.2. DX8</a></li> <li><a href="#33-dx9">3.3. DX9</a></li> <li><a href="#34-dx10">3.4. DX10</a></li> <li><a href="#35-dx11">3.5. DX11</a></li> <li><a href="#36-dx12">3.6. DX12</a></li> <li><a href="#37-工程开源">3.7. 工程开源</a></li> </ul> </li> </ul> <!-- /TOC --> <h1 id="1-环境准备">1. 环境准备</h1> <h2 id="11-兼容性">1.1. 兼容性</h2> <h3 id="111-硬件兼容性">1.1.1. 硬件兼容性</h3> <p>这里的硬件,指的是显卡及显卡驱动。本身DirectX是一组ring3层和ring0层配合使用的工具组,它需要显卡及显卡驱动的支持,每一个DirectX版本,都需要相关显卡驱动版本的支持。而市面上的显卡及驱动程序,一般都设计成向下兼容,也就是对低版本具有良好的兼容性,DirectX的低版本也可以运行在最新的显卡上。</p> <p>而驱动程序与Windows版本是对应的,因为DX不仅需要显卡硬件特性的支持,同时也需要图形驱动的支持,显卡自身的驱动与操作系统的图形驱动Win32K.sys联动,构成DX底层的技术支持,所以,考虑硬件的同时,也需要考虑操作系统版本上对DX的支持性。同样,操作系统对于DX的支持,也是向下兼容的</p> <p>Windows中查看显卡硬件对DirectX版本特性的支持,可以通过dxdiag.exe来查看显卡对dx版本支持范围,如下:</p> <p><img src="/images/post/GPU/dxdiag.png" alt="png" /></p> <p>右侧的功能级别,即表示支持的DirectX版本范围(这里是9.1~12.1)。注意,虽然这里最低只显示到9.1,但是实际上更低版本比如DX8,都是支持的。通常,需要关注这里版本范围的上限,我的parallel desktop就只支持到DX10。</p> <p>所以,当我们编写代码时若想使用DX12最新特性,需要注意考虑显卡硬件设备对DX版本的支持,从代码层面增加if else分支逻辑。</p> <p>市场上支持DX各种版本硬件及系统出现的时间,大致汇总一下如下:</p> <ul> <li><a href="http://vga.zol.com.cn/281/2813838.html">2002年8月,ATI公司推出全球第一款支持DX9显卡</a>,此时还是WinXP时代</li> <li><a href="https://3c.3dmgame.com/show-14-5266-1.html">2006年11月,NVIDIA推出第一款DX10显卡</a>,与此同时,2007年微软推出Vista系统,开始支持DX10</li> <li><a href="http://news.mydrivers.com/1/163/163556_all.htm">2009年9月,AMD-ATI推出全球首款DX11显卡</a>,与此同时,2009年10月微软推出Win7系统</li> <li><a href="https://pad.mydrivers.com/1/538/538872.htm">2015年7月,微软推出Win10,支持DX12,而硬件的话说是老卡也支持</a>,此时是win10时代</li> </ul> <p>总结一下:</p> <ul> <li>2002年,WinXP,支持DX9</li> <li>2006年,WinVista,支持DX9、DX10</li> <li>2009年,Win7,支持DX9、DX10、DX11</li> <li>2015年,Win10,支持DX9、DX10、DX11、DX12</li> </ul> <h3 id="112-软件兼容性">1.1.2. 软件兼容性</h3> <p>由于DX版本是随着硬件和操作系统而逐步升级的,软件要想做到稳定运行在各种不同DX版本上,就需要拥有良好的兼容性。而软件自身如何做到兼容性的呢?</p> <p>首先,DX9是覆盖现如今计算机系统xp ~ win10的,范围是最广的,所以大多数游戏厂商为了涉及更多的客户,选择DX9版作为游戏的渲染引擎(这也是为何市面上DX9的游戏编程的书最为广泛),最为简单方便,兼容性最好。<br /> 其次是DX11,因为市面上Win7以上用户,已经占比70%以上了,而从DX9跨越到DX11,性能上的优势肯定也是跨越级的,所以综合最佳选择是DX11。 <br /> 当然,也会存在多版本并行的情况,为了兼顾XP~Win10,软件也会选择同时支持DX9、DX10、DX11、DX12全部或部分,兼容方式,采用随软件携带d3d9.dll、d3d10.dll、d3d11.dll、d3d12.dll,代码中根据环境检测来选择最佳引擎,代码调用层抽象出一层DX版本无关性逻辑层,这样也方便不同DX版本都能使用相同接口。而做到这个的,就是市面上的游戏引擎,如下表:</p> <table> <thead> <tr> <th>引擎名称</th> <th>Ogre</th> <th>Unreal</th> <th>Unity</th> <th>Gamebryo</th> <th>Bigworld</th> </tr> </thead> <tbody> <tr> <td>作者</td> <td>Steve streeting</td> <td>Epic Games</td> <td>Unity technology</td> <td>Emegent game technology</td> <td>Big World</td> </tr> <tr> <td>图形API</td> <td>OpenGL DirecX</td> <td>OpenGL DirectX Software</td> <td>DirectX OpenGL</td> <td>DirectX OpengGL</td> <td>DirectX OpenGL</td> </tr> <tr> <td>操作系统</td> <td>Windows Linux MacOS</td> <td>Windows Linux MacOS Xbox PlayStation GameCube</td> <td>Windows Linux PlayStation iPhone Wii</td> <td>Windows MaxOS Xbox360 PS3</td> <td>Windows 支持64位操作系统</td> </tr> <tr> <td>开发语言</td> <td>C/C++</td> <td>C/C++</td> <td>C/C++</td> <td>C/C++</td> <td>C/C++</td> </tr> <tr> <td>开源与否</td> <td>公开</td> <td>不公开</td> <td>不公开</td> <td>不公开</td> <td>部分源代码开源</td> </tr> <tr> <td>文档状况</td> <td>充份</td> <td>充份</td> <td>提供逐步的指导、文档和实例方案</td> <td>充份</td> <td>充份</td> </tr> <tr> <td>特征概貌</td> <td>面向对象的设计 插件式结构 可存/取系统</td> <td>面向对象的存取 插件式结构 可存取系统</td> <td>面向对象设计</td> <td>面向对象设计 开放式跨平台游戏引擎 插件式结构 可存取系统</td> <td>面向对象的存取 插件式结构 可存取系统</td> </tr> <tr> <td>支持脚本语言</td> <td>脚本材质语言 允许代码外进行材质属性的设置 支持脚本控制的多遍渲染</td> <td>内嵌的Unreal-Script语言 以及C++支持的所有形象华文本,支持非程序员编译器</td> <td>Unity支持3种脚本语言:JavaScript,C#,Boo</td> <td>LUA脚本</td> <td>Python脚本</td> </tr> <tr> <td>内嵌编辑器</td> <td>不支持</td> <td>UnrealED:基于结构几何表示的实时设计工具,对光源、纹理、和几何体进行所见即所得的操作 脚本配置方式的编辑</td> <td>完整优秀的编辑工具,包括地形,材质,动画,声音,模型等等</td> <td>不支持</td> <td>不支持</td> </tr> </tbody> </table> <h2 id="12-安装说明">1.2. 安装说明</h2> <p>安装方法:</p> <ul> <li>DX11及以前版本,有独立安装包,下载链接<a href="https://www.microsoft.com/en-us/download/confirmation.aspx?id=6812">https://www.microsoft.com/en-us/download/confirmation.aspx?id=6812</a></li> <li>DX12包含在Win10SDK中,一般下载WinSDKSetup进行安装,如果使用VS2015之后的版本,可以在VS2015插件集合中,选择Windows SDK 10版本进行安装,这里默认自带了DX的动态链接库和头文件。</li> </ul> <p>注意:虽然第一种方法中同时包括DX9、DX10、DX11的开发SDK,但是会发现与Win10SDK中包含的内容不一样,比如可能winsdk中不包括d3dx9.h等,而且要注意,Win10 SDK各个不同版本对于DX同一个版本的sdk也会有不一样的地方,这点极度坑爹。</p> <p>代码示例:</p> <ul> <li>在上面第一种安装方法里,代码示例可以通过独立安装包中的应用程序:DirectX Sample Browser浏览。</li> <li>DX12的代码示例,则在github上:<a href="https://github.com/microsoft/DirectX-Graphics-Samples">https://github.com/microsoft/DirectX-Graphics-Samples</a></li> <li>DX11的代码示例,虽然也可以通过DirectX Sample Browser浏览,但是在github上也存在新一份的代码<a href="https://github.com/walbourn/directx-sdk-samples">https://github.com/walbourn/directx-sdk-samples</a>,而且采用的写法与DX12相似</li> </ul> <h1 id="2-计算机图像显示原理">2. 计算机图像显示原理</h1> <h2 id="21-图像显示基本原理">2.1. 图像显示基本原理</h2> <p>无论是讨论DX,还是OpenGL,还是GDI,本质是CPU、GPU、内存、显存、计算机总线、显示器相互作用来使数据进行呈现。</p> <p>从最终端的显示屏来看,它要能显示数据,无非是显示屏有一个控制芯片,对应需要有一段显示屏内存数据区,以及一段与显示屏芯片通信的驱动程序。驱动程序负责将内存数据区的数据,发送往显示屏,而这段内存数据区,就是待显示的图像,相当于画板,由应用程序通过编码来填充绘制。与显示屏交互的驱动程序,只是做简单的动作:接收刷新信号(有图像已经准备好了,准备显示,由应用程序发出的通知),与显示屏控制器通过数据总线进行数据传递(显示屏控制芯片的特殊时钟信号来实现的),显示屏即有数据展示。它只做简单的事情,就意味着,只是数据的搬运工,而实际渲染数据内存区,还得是CPU或者GPU来绘制出来的。</p> <p>那么如何产生一帧渲染数据呢?简单的一张静态图,当作一个二维数组,只需要CPU往二维数组里按位存储数据即可。而计算机通常是多任务的,也就是往往会有很多并行的渲染数据需要绘制,而显示屏资源实际只有一个,显示驱动同一时刻只负责运输一段数据帧,也就是前面说的那段内存数据区。计算机的多任务图像显示,也只是共用内存数据区,都往这段内存区写数据。如果每个人都想争做显示屏的展示内容,我写一段数据,然后你又写一段数据,那么很可能就相互冲突,展示的内容非你也非我。所以,要么同一时刻只有一个人写数据来展示,要么需要一个专门做数据最终输出的对象,由它来掌控最终产生到显示屏的内容应该是什么,它来接收多任务图像显示的数据输入。很明显是后者,因为前者只能让一幅图展示出来,相当于显示屏是被独占的,实际上,显示屏往往可以展示多个进程的窗口内容。既然是后者,就说明有一个专门的显示输出控制器(Vista之后实际就是DWM桌面窗口管理器),不同应用程序的数据输出,都作为它的输入,由它根据一定的规则,来展示到显示屏上。什么规则呢?简单的,就是像窗口程序一样,谁是顶层置顶窗口,谁就有优先显示权,这个置顶窗口就会将自我的尺寸全部展示出来。但是,通常窗口都不是占满全屏的,那么其他窗口,就会按照一个先后队列,依次展示出来(也可以理解为深度测试)。最终,根据队列中窗口区域,组合成最终的显示帧数据,供驱动传输给显示屏进行展示。</p> <p><img src="/images/post/GPU/图像显示原理.png" alt="png" /></p> <h2 id="22-windows图形系统架构">2.2. Windows图形系统架构</h2> <p>参考文章:<a href="https://docs.microsoft.com/zh-cn/previous-versions/ee921514(v=msdn.10)?redirectedfrom=MSDN">https://docs.microsoft.com/zh-cn/previous-versions/ee921514(v=msdn.10)?redirectedfrom=MSDN</a></p> <p>不仅DX在逐步升级,Windows图形架构也是逐步演变的。先总结一下基础流程架构的变化,如下:</p> <p><img src="/images/post/GPU/XP图形架构.png" alt="png" /><br /> <img src="/images/post/GPU/Vista图形架构.png" alt="png" /><br /> <img src="/images/post/GPU/Win7图形架构.png" alt="png" /></p> <p>有必要强调的几点:</p> <ul> <li>自从Vista转换到WDDM图形驱动模型后,引入DWM,桌面整体不再是2D显示区了,因为DWM本身使用DirectX技术,桌面实际上是一个全屏的3D程序,所以我们使用alt+tab来切换其他窗格时,看到来一个3D的炫酷切换界面。同时,渲染的窗口更加细腻,Aero毛玻璃效果也就很自然的引入其中。</li> <li>另外,原来XP中的显示屏缓存为所有应用程序直接绘制,所以实际都是在一个缓冲区的不同区域绘制,如果拖动窗口很快,就会看到阴影。</li> <li>XP原来针对GDI使用XPDM进行硬件加速,后来换成Vista,GDI的绘制直接交由CPU来执行了,所以实际上,传统窗口程序在Vista上会比XP上更慢,Vista的CPU消耗也必然比XP更高</li> <li>Win10的DX12新架构本质上,与Win7的类似,最大的改变,是为了将性能提升到极致而升级WDDM驱动模型,接口操作引入更多更偏向硬件的操作</li> </ul> <p>驱动架构演变如下:</p> <p><img src="/images/post/GPU/XP驱动架构.jpg" alt="png" /><br /> <img src="/images/post/GPU/Vista驱动架构.jpg" alt="png" /><br /> <img src="/images/post/GPU/Win7驱动架构.jpg" alt="png" /></p> <h2 id="23-渲染管线">2.3. 渲染管线</h2> <h3 id="231-渲染管线的基本作用">2.3.1. 渲染管线的基本作用</h3> <p>应用程序绘制一个窗口显示到桌面显示器,实际流程是比较简单的。所有显卡硬件、高清分辨率显示器以及Windows图形架构的升级,都只是为了趋向更逼真的电脑仿真,改善运行性能,让图形视频更流畅。而这个流畅,本质是,生产传输给Primary显示帧缓存一幅图像帧的速度更快,GPU的目的,就是为来使生产和传输过程变得更快。</p> <p>何为传输更快?当然是为了更少的拷贝和更少的CPU操作(因为CPU是一个通用计算资源,不止图形在用,整个计算机都依赖它,所以资源是被所有应用共享)。</p> <p>何为生产更快?简单的了解来一下图形学原理,在3D世界,我们依赖非常多的数学计算,包括:坐标系变换、批量像素点的位置矩阵变换、插值平滑变换等。一般在3D图形学编程中,是将由3D世界的众多三角形网格模型通过着色、变换、透视、裁剪、融合等,输出成2D显示器上的每一个像素点,整个流程是非常复杂,必将也需要非常多的计算资源。GPU的作用,简单来说,就是为了辅助CPU来做这些专门为图形数学变换而做的计算,整个过程,就是一次渲染过程,而这个过程是由渲染管线完成。</p> <h3 id="232-渲染架构">2.3.2. 渲染架构</h3> <p>为了有一个全面的认知,这里我总结了一下整个渲染管线与应用程序以及显示输出的关系,如下:</p> <p><img src="/images/post/GPU/渲染管线.jpg" alt="jpg" /></p> <p>值得强调说明的点:</p> <ul> <li>渲染管线是被复用的,但是渲染的资源存储位置不同</li> <li>渲染管线输出的缓冲区,是跟窗口绑定的,因此逃脱不了DWM合成器的组合,但是因为窗口只是简单粗暴的根据Z序来消隐,所以它的计算量并不是很大。当然DWM同样也是使用DirectX来操作的,因此这里的性能就不是问题</li> <li>我们的应用程序在执行一系列DirectX的调用,本质是往GPU的指令队列中打入一堆指令,GPU根据这些指令顺序执行某些事情,这些指令也都关联到具体的资源</li> </ul> <h3 id="233-各个阶段简单说明">2.3.3. 各个阶段简单说明</h3> <p>这里有一篇文章<a href="https://positiveczp.github.io/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF.pdf">https://positiveczp.github.io/%E7%BB%86%E8%AF%B4%E5%9B%BE%E5%BD%A2%E5%AD%A6%E6%B8%B2%E6%9F%93%E7%AE%A1%E7%BA%BF.pdf</a>,介绍渲染管线非常详细。</p> <h4 id="2331-顶点数据输入">2.3.3.1. 顶点数据输入</h4> <p>这里的顶点数据,包括:顶点坐标、顶点颜色、顶点法线、纹理坐标等数据,以及顶点图元信息:点(GL_POINTS)、线(GL_LINES)、线条(GL_LINE_STRIP)、三角面(GL_TRIANGLES)</p> <h4 id="2332-顶点着色器">2.3.3.2. 顶点着色器</h4> <p>主要是进行一系列的坐标变换,用下面一张图可以描述其作用</p> <p><img src="/images/post/GPU/坐标变换.png" alt="png" /></p> <p>此阶段可编程,可以使用Vertex Shader.hlsl来进行着色器编码,主要是操作一堆坐标变换。</p> <h4 id="2333-曲面细分过程">2.3.3.3. 曲面细分过程</h4> <p>图形学中,3D世界是由一堆三角形组成的网格模型来模拟的,而这个网格模型中三角形的数量决定了显示的质量。但是,三角形的数量越多,需要消耗的存储空间也就越大,设计也会更耗时。因此,往往取一个中间程度,剩下的过程,是交由曲面细分过程,在计算时通过插值平滑算法,自动细分三角形。整个过程可以通过下述图来表达作用</p> <p><img src="/images/post/GPU/曲面细分过程.png" alt="png" /></p> <p>此阶段也是可编程的,可以细分为:外壳着色器阶段(Hull Shader)、镶嵌器阶段(Tessellator)和域着色器阶段(Domain Shader)。并且,它是可选阶段。</p> <h4 id="2334-几何着色器">2.3.3.4. 几何着色器</h4> <p>它的输入是图元,可以将图元扩展成多边形,也是可选阶段,可编程,使用Geomitry Shader.hlsl编写。</p> <h4 id="2335-图元组装">2.3.3.5. 图元组装</h4> <p>将输入的顶点,组装成指定的图元,组装过程会进行裁剪和背面剔除,还会进行屏幕映射操作:透视除法和视口变换。这个阶段是硬件实现的。</p> <h4 id="2336-光栅化">2.3.3.6. 光栅化</h4> <p>主要是将前面阶段映射成的屏幕2D物体模型离散化成像素点,可用一张图表征作用:</p> <p><img src="/images/post/GPU/光栅化.png" alt="png" /></p> <p>此过程也是硬件自动实现的。</p> <h4 id="2337-片段着色器">2.3.3.7. 片段着色器</h4> <p>片段着色器,又称像素着色器,可编程,通过Pixel Shalder.hlsl编写。这个阶段主要是进行光照变换和阴影处理,决定每一个像素的颜色值。</p> <h4 id="2338-测试混合">2.3.3.8. 测试混合</h4> <p>这个阶段分为测试和混合。</p> <p>测试包括:裁切测试、Alpha测试、模板测试和深度测试,测试不通过的就会丢弃。<br /> 混合是指alpha混合,表示半透明效果,Aero毛玻璃效果就是利用了此alpha混合来实现的。</p> <h1 id="3-dx基本使用流程">3. DX基本使用流程</h1> <h2 id="31-基于窗口的消息循环">3.1. 基于窗口的消息循环</h2> <pre><code class="language-C++">HWND g_hWnd = NULL; LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch (message) { case WM_PAINT: { Render(); // 这个表示清空绘制区,这样就不会重复产生WM_PAINT消息了 //(GetMessage或PeekMessage会将无绘制区的WM_PAINT从消息队列中移除) ValidateRect(hWnd, NULL); break; } break; case WM_DESTROY: PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; } ATOM MyRegisterClass(HINSTANCE hInstance) { WNDCLASSEXW wcex; wcex.cbSize = sizeof(WNDCLASSEX); wcex.style = CS_HREDRAW | CS_VREDRAW; wcex.lpfnWndProc = WndProc; wcex.cbClsExtra = 0; wcex.cbWndExtra = 0; wcex.hInstance = hInstance; wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_D3D9HELLOWORLD)); wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_D3D9HELLOWORLD); wcex.lpszClassName = szWindowClass; wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); return RegisterClassExW(&amp;wcex); } BOOL InitInstance(HINSTANCE hInstance, int nCmdShow) { hInst = hInstance; // 将实例句柄存储在全局变量中 g_hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr); if (!g_hWnd) { return FALSE; } ShowWindow(g_hWnd, nCmdShow); UpdateWindow(g_hWnd); return TRUE; } int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow) { MyRegisterClass(hInstance); // 执行应用程序初始化: if (!InitInstance (hInstance, nCmdShow)) { return FALSE; } InitD3D(g_hWnd); MSG msg; // 主消息循环: while (GetMessage(&amp;msg, nullptr, 0, 0)) { TranslateMessage(&amp;msg); DispatchMessage(&amp;msg); } UnInitD3D(); return (int) msg.wParam; } </code></pre> <h2 id="32-dx8">3.2. DX8</h2> <p>DX8示例:渲染一个三角形</p> <ul> <li>Step1:调用Direct3DCreate8获取d3d8 sdk接口对象</li> <li>Step2:获取IDirect3DDevice8某个屏幕显示器的设备对象</li> <li>Step3:设置渲染状态</li> <li>Step4:准备顶点数据,或者texture纹理数据</li> <li>Step5:BeginScene准备渲染</li> <li>Step6:SetStreamSource将顶点数据进行装配,SetVertexShader设置顶点格式</li> <li>Step7:DrawPrimitive传递顶点拓扑结构并绘制顶点图元,或者通过获取BackBuffer的表明,通过CopyRect可以拷贝纹理数据等,这一步就是往渲染目标后备缓存BackBuffer绘制数据</li> <li>Step8:EndScene结束渲染</li> <li>Step9:Present将BackBuffer交换到FrontBuffer,并调用视频流控制器,将FrontBuffer输出到屏幕</li> </ul> <pre><code class="language-C++">IDirect3D8* g_d3d8 = NULL; IDirect3DDevice8* g_d3d8Device = NULL; IDirect3DVertexBuffer8* g_pVB = NULL; struct CUSTOMVERTEX { float x, y, z, rhw; // The position D3DCOLOR color; // The color }; #define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZRHW|D3DFVF_DIFFUSE) void InitDevice(HWND hWnd) { g_d3d8 = Direct3DCreate8(D3D_SDK_VERSION); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&amp;d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_X8R8G8B8; d3dpp.hDeviceWindow = hWnd; HRESULT hr = g_d3d8-&gt;CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, &amp;d3dpp, &amp;g_d3d8Device); g_d3d8Device-&gt;SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE); g_d3d8Device-&gt;SetRenderState(D3DRS_LIGHTING, FALSE); g_d3d8Device-&gt;SetRenderState(D3DRS_ZENABLE, FALSE); // 定义顶点 g_d3d8Device-&gt;CreateVertexBuffer(3 * sizeof(CUSTOMVERTEX), 0, D3DFVF_CUSTOMVERTEX, D3DPOOL_DEFAULT, &amp;g_pVB); CUSTOMVERTEX* pVertices; if (FAILED(g_pVB-&gt;Lock(0, 0, (BYTE**)&amp;pVertices, 0))) { return; } pVertices[0].x = 100.0f; pVertices[0].y = 150.0f; pVertices[0].z = 1.0f; pVertices[0].rhw = 1.0f; pVertices[0].color = D3DCOLOR_XRGB(255, 0, 0); pVertices[1].x = 250.0f; pVertices[1].y = 150.0f; pVertices[1].z = 1.0f; pVertices[1].rhw = 1.0f; pVertices[1].color = D3DCOLOR_XRGB(0, 255, 0); pVertices[2].x = 150.0f; pVertices[2].y = 250.0f; pVertices[2].z = 1.0f; pVertices[2].rhw = 1.0f; pVertices[2].color = D3DCOLOR_XRGB(0, 0, 255); g_pVB-&gt;Unlock(); } void Render() { if (g_d3d8Device &amp;&amp; g_pVB) { g_d3d8Device-&gt;Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0); g_d3d8Device-&gt;BeginScene(); g_d3d8Device-&gt;SetStreamSource(0, g_pVB, sizeof(CUSTOMVERTEX)); g_d3d8Device-&gt;SetVertexShader(D3DFVF_CUSTOMVERTEX); g_d3d8Device-&gt;DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 1); g_d3d8Device-&gt;EndScene(); g_d3d8Device-&gt;Present(0, 0, 0, 0); } } void UnInitDevice() { if (g_pVB) g_pVB-&gt;Release(); if (g_d3d8Device) g_d3d8Device-&gt;Release(); if (g_d3d8) g_d3d8-&gt;Release(); } </code></pre> <h2 id="33-dx9">3.3. DX9</h2> <p>DX9示例:加载绘制一张图</p> <ul> <li>Step1:调用Direct3DCreate9获取d3d9 sdk接口对象</li> <li>Step2:获取IDirect3DDevice9某个屏幕显示器的设备对象</li> <li>Step3:D3DXGetImageInfoFromFile获取文件信息</li> <li>Step4:CreateOffscreenPlainSurface创建一个离屏表面,有两种方法,一种是创建D3DPOOL_DEFAULT类型,存储在显卡,后面使用StrechRect到渲染目标RenderTarget,离屏表面大小可以不与渲染目标也即窗口客户区大小一致;另一种是创建D3DPOOL_SYSTEMMEM类型,存储在系统内存,后面使用UpdateSurface到渲染目标RenderTarget,离屏表面必须与渲染目标大小一致</li> <li>Step5:BeginScene准备渲染</li> <li>Step6:GetRenderTarget获取渲染目标</li> <li>Step7:根据Step4中创建离屏表面的类型,来决定使用StrechRect还是UpdateSurface方法渲染到RenderTarget</li> <li>Step8:EndScene结束渲染</li> <li>Step9:Present将BackBuffer交换到FrontBuffer,并调用视频流控制器,将FrontBuffer输出到屏幕</li> </ul> <pre><code class="language-C++"> IDirect3D9Ex* g_pD3D9Ex = NULL; IDirect3DDevice9Ex* g_pD3D9DeviceEx = NULL; IDirect3DSurface9* g_pImgFromFile = NULL; void InitDevice(HWND hWnd) { HRESULT hr = Direct3DCreate9Ex(D3D_SDK_VERSION, &amp;g_pD3D9Ex); if (FAILED(hr)) { ATL::CAtlString strErr; strErr.Format(L"errcode: 0x%0x", hr); MessageBoxW(hWnd, L"Direct3DCreate9Ex failed. " + strErr, NULL, MB_OK); return; } RECT rcWnd; GetClientRect(hWnd, &amp;rcWnd); D3DPRESENT_PARAMETERS d3dpp; ZeroMemory(&amp;d3dpp, sizeof(d3dpp)); d3dpp.Windowed = TRUE; d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD; d3dpp.BackBufferFormat = D3DFMT_UNKNOWN; d3dpp.hDeviceWindow = hWnd; //d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER; if (FAILED(hr = g_pD3D9Ex-&gt;CreateDeviceEx(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING, // 软件顶点处理。如果显卡支持硬件顶点处理,最好是使用显卡 &amp;d3dpp, NULL, &amp;g_pD3D9DeviceEx))) { return; } D3DXIMAGE_INFO ii; D3DXGetImageInfoFromFile(L"G:\\Solution\\Debug\\test.jpg", &amp;ii); g_pD3D9DeviceEx-&gt;CreateOffscreenPlainSurface(rcWnd.right-rcWnd.left, rcWnd.bottom-rcWnd.top, ii.Format, D3DPOOL_SYSTEMMEM, &amp;g_pImgFromFile, NULL); // g_pD3D9DeviceEx-&gt;CreateOffscreenPlainSurface(ii.Width, ii.Height, ii.Format, D3DPOOL_DEFAULT, &amp;g_pImgFromFile, NULL); D3DXLoadSurfaceFromFileW(g_pImgFromFile, NULL, NULL, L"G:\\Solution\\Debug\\test.jpg", NULL, D3DX_FILTER_NONE, 0, NULL); } void Render() { if (g_pD3D9DeviceEx &amp;&amp; g_pImgFromFile) { g_pD3D9DeviceEx-&gt;Clear(0, 0, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 0, 255), 1.0f, 0); if (SUCCEEDED(g_pD3D9DeviceEx-&gt;BeginScene())) { IDirect3DSurface9* pBackBuffer = NULL; //g_pD3D9DeviceEx-&gt;GetBackBuffer(0,0, D3DBACKBUFFER_TYPE_MONO, &amp;pBackBuffer); g_pD3D9DeviceEx-&gt;GetRenderTarget(0, &amp;pBackBuffer); //g_pD3D9DeviceEx-&gt;StretchRect(g_pImgFromFile, NULL, pBackBuffer, NULL, D3DTEXTUREFILTERTYPE::D3DTEXF_NONE); g_pD3D9DeviceEx-&gt;UpdateSurface(g_pImgFromFile, NULL, pBackBuffer, NULL); pBackBuffer-&gt;Release(); g_pD3D9DeviceEx-&gt;EndScene(); } g_pD3D9DeviceEx-&gt;PresentEx(0, 0, 0, 0, 0); } } void UnInitDevice() { if (g_pImgFromFile) g_pImgFromFile-&gt;Release(); if (g_pD3D9DeviceEx) g_pD3D9DeviceEx-&gt;Release(); if (g_pD3D9Ex) g_pD3D9Ex-&gt;Release(); } </code></pre> <h2 id="34-dx10">3.4. DX10</h2> <p>DX10示例:绘制一个三角形</p> <ul> <li>Step1:D3D10CreateDeviceAndSwapChain创建设备和交换链系统。</li> <li>Step2:CreateRenderTargetView和OMSetRenderTargets创建渲染目标视图缓存,绑定到交换系统上,作为渲染目标</li> <li>Step3:设置ViewPort</li> <li>Step4:HLSL语言定义顶点着色器和像素着色器</li> <li>Step5:D3DReadFileToBlob和CreateVertexShader加载顶点着色器,D3DReadFileToBlob和CreatePixelShader加载像素着色器,VSSetShader和PSSetShader分别设置将顶点着色器和像素着色器绑定到渲染管线</li> <li>Step6:定义输入布局结构D3D10_INPUT_ELEMENT_DESC,CreateInputLayout和IASetInputLayout装配输入布局结构。输入布局结构可以包括顶点属性(包括坐标位置、颜色等)、纹理属性等</li> <li>Step7:定义顶点数据,CreateBuffer创建Buffer资源,用于存储顶点数据</li> <li>Step8:通过IASetVertexBuffers装配顶点缓存,并通过IASetPrimitiveTopology设置顶点拓扑结构</li> <li>Step9:Draw绘制</li> </ul> <pre><code class="language-C++">// Header.hlsli struct VertexIn { float3 pos : POSITION; float4 color : COLOR; }; struct VertexOut { float4 posH : SV_POSITION; float4 color : COLOR; }; </code></pre> <pre><code class="language-C++">// VertexShader.hlsl #include "Header.hlsli" // 顶点着色器 VertexOut VS(VertexIn vIn) { VertexOut vOut; vOut.posH = float4(vIn.pos, 1.0f); vOut.color = vIn.color; // 这里alpha通道的值默认为1.0 return vOut; } </code></pre> <pre><code class="language-C++">// PixelSHader.hlsl #include "Header.hlsli" // 像素着色器 float4 PS(VertexOut pIn) : SV_Target { return pIn.color; } </code></pre> <pre><code class="language-C++">// CPP #include &lt;d3d10.h&gt; #include &lt;d3dx10.h&gt; #include &lt;d3dcompiler.h&gt; #include &lt;directxmath.h&gt; ID3D10Device* g_pd3dDevice = NULL; IDXGISwapChain* g_pSwapChain = NULL; ID3D10RenderTargetView* g_pRenderTargetView = NULL; ID3D10InputLayout* g_pVertexLayout = NULL; ID3D10Buffer* g_pVertexBuffer = NULL; ID3D10VertexShader* g_pVertexShader = NULL; ID3D10PixelShader* g_pPixelShader = NULL; struct SimpleVertex { D3DXVECTOR3 pos; D3DXVECTOR4 color; }; void InitDevice(HWND hWnd) { DXGI_SWAP_CHAIN_DESC sd; ZeroMemory(&amp;sd, sizeof(sd)); sd.BufferCount = 1; sd.BufferDesc.Width = 640; sd.BufferDesc.Height = 480; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.RefreshRate.Numerator = 60; sd.BufferDesc.RefreshRate.Denominator = 1; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.OutputWindow = hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.Windowed = TRUE; if (FAILED(D3D10CreateDeviceAndSwapChain(NULL, D3D10_DRIVER_TYPE_REFERENCE, NULL, 0, D3D10_SDK_VERSION, &amp;sd, &amp;g_pSwapChain, &amp;g_pd3dDevice))) { return; } // Create a render target view ID3D10Texture2D* pBackBuffer; if (FAILED(g_pSwapChain-&gt;GetBuffer(0, __uuidof(ID3D10Texture2D), (LPVOID*)&amp;pBackBuffer))) return; HRESULT hr = g_pd3dDevice-&gt;CreateRenderTargetView(pBackBuffer, NULL, &amp;g_pRenderTargetView); pBackBuffer-&gt;Release(); if (FAILED(hr)) return; g_pd3dDevice-&gt;OMSetRenderTargets(1, &amp;g_pRenderTargetView, NULL); D3D10_VIEWPORT vp; vp.Width = 640; vp.Height = 480; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; g_pd3dDevice-&gt;RSSetViewports(1, &amp;vp); // Define the input layout D3D10_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D10_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D10_INPUT_PER_VERTEX_DATA, 0 }, }; UINT numElements = sizeof(layout) / sizeof(layout[0]); // Create the input layout ID3DBlob* blob; D3DReadFileToBlob(L"G:\\Solution\\Debug\\VertexShader.cso", &amp;blob); g_pd3dDevice-&gt;CreateVertexShader(blob-&gt;GetBufferPointer(), blob-&gt;GetBufferSize(), &amp;g_pVertexShader); g_pd3dDevice-&gt;CreateInputLayout(layout, numElements, blob-&gt;GetBufferPointer(), blob-&gt;GetBufferSize(), &amp;g_pVertexLayout); blob-&gt;Release(); // Set the input layout g_pd3dDevice-&gt;IASetInputLayout(g_pVertexLayout); D3DReadFileToBlob(L"G:\\Solution\\Debug\\PixelShader.cso", &amp;blob); g_pd3dDevice-&gt;CreatePixelShader(blob-&gt;GetBufferPointer(), blob-&gt;GetBufferSize(), &amp;g_pPixelShader); blob-&gt;Release(); g_pd3dDevice-&gt;VSSetShader(g_pVertexShader); g_pd3dDevice-&gt;PSSetShader(g_pPixelShader); // Create vertex buffer SimpleVertex vertices[] = { { D3DXVECTOR3(0.0f, 0.5f, 0.5f), D3DXVECTOR4(1.0f, 0.0f, 0.0f, 1.0f) }, { D3DXVECTOR3(0.5f, -0.5f, 0.5f), D3DXVECTOR4(0.0f, 1.0f, 0.0f, 1.0f) }, { D3DXVECTOR3(-0.5f, -0.5f, 0.5f), D3DXVECTOR4(0.0f, 0.0f, 1.0f, 1.0f) }, }; D3D10_BUFFER_DESC bd; bd.Usage = D3D10_USAGE_DEFAULT; bd.ByteWidth = sizeof(SimpleVertex) * 3; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; D3D10_SUBRESOURCE_DATA InitData; InitData.pSysMem = vertices; if (FAILED(g_pd3dDevice-&gt;CreateBuffer(&amp;bd, &amp;InitData, &amp;g_pVertexBuffer))) return; // Set vertex buffer UINT stride = sizeof(SimpleVertex); UINT offset = 0; g_pd3dDevice-&gt;IASetVertexBuffers(0, 1, &amp;g_pVertexBuffer, &amp;stride, &amp;offset); // Set primitive topology g_pd3dDevice-&gt;IASetPrimitiveTopology(D3D10_PRIMITIVE_TOPOLOGY_TRIANGLELIST); } void Render() { if (g_pd3dDevice &amp;&amp; g_pSwapChain) { float ClearColor[4] = { 0.0f, 0.125f, 0.6f, 1.0f }; // RGBA g_pd3dDevice-&gt;ClearRenderTargetView(g_pRenderTargetView, ClearColor); // Render a triangle g_pd3dDevice-&gt;Draw(3, 0); g_pSwapChain-&gt;Present(0, 0); } } void UnInitDevice() { if (g_pVertexBuffer) g_pVertexBuffer-&gt;Release(); if (g_pVertexLayout) g_pVertexLayout-&gt;Release(); if (g_pVertexShader) g_pVertexShader-&gt;Release(); if (g_pRenderTargetView) g_pRenderTargetView-&gt;Release(); if (g_pd3dDevice) g_pd3dDevice-&gt;Release(); if (g_pSwapChain) g_pSwapChain-&gt;Release(); } </code></pre> <h2 id="35-dx11">3.5. DX11</h2> <p>DX11示例:绘制一个三角形</p> <p>同DX10,只是Device与渲染阶段相关的内容,全部挪到DeviceContext中</p> <pre><code class="language-C++">// CPP #include &lt;d3d11.h&gt; #include &lt;d3dcompiler.h&gt; #include &lt;directxmath.h&gt; ID3D11Device* g_pd3dDevice = NULL; IDXGISwapChain* g_pSwapChain = NULL; ID3D11DeviceContext* g_pd3dImmediateContext = NULL; ID3D11RenderTargetView* g_pRenderTargetView = NULL; ID3D11VertexShader* g_pVertexShader = NULL; ID3D11PixelShader* g_pPixelShader = NULL; ID3D11InputLayout* g_pVertexLayout = NULL; ID3D11Buffer* g_pVertexBuffer = NULL; struct SimpleVertex { DirectX::XMFLOAT3 pos; DirectX::XMFLOAT4 color; }; void InitDevice(HWND hWnd) { DXGI_SWAP_CHAIN_DESC sd; ZeroMemory(&amp;sd, sizeof(sd)); sd.BufferCount = 1; sd.BufferDesc.Width = 640; sd.BufferDesc.Height = 480; sd.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; sd.BufferDesc.RefreshRate.Numerator = 60; sd.BufferDesc.RefreshRate.Denominator = 1; sd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; sd.OutputWindow = hWnd; sd.SampleDesc.Count = 1; sd.SampleDesc.Quality = 0; sd.Windowed = TRUE; D3D11CreateDeviceAndSwapChain(NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, 0, NULL, 0, D3D11_SDK_VERSION, &amp;sd, &amp;g_pSwapChain, &amp;g_pd3dDevice, NULL, &amp;g_pd3dImmediateContext); // Create a render target view ID3D11Texture2D* pBackBuffer; if (FAILED(g_pSwapChain-&gt;GetBuffer(0, __uuidof(ID3D11Texture2D), (LPVOID*)&amp;pBackBuffer))) return; HRESULT hr = g_pd3dDevice-&gt;CreateRenderTargetView(pBackBuffer, NULL, &amp;g_pRenderTargetView); pBackBuffer-&gt;Release(); if (FAILED(hr)) return; g_pd3dImmediateContext-&gt;OMSetRenderTargets(1, &amp;g_pRenderTargetView, NULL); D3D11_VIEWPORT vp; vp.Width = 640; vp.Height = 480; vp.MinDepth = 0.0f; vp.MaxDepth = 1.0f; vp.TopLeftX = 0; vp.TopLeftY = 0; g_pd3dImmediateContext-&gt;RSSetViewports(1, &amp;vp); // Define the input layout D3D11_INPUT_ELEMENT_DESC layout[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 }, }; UINT numElements = sizeof(layout) / sizeof(layout[0]); // Create the input layout ID3DBlob* blob; D3DReadFileToBlob(L"G:\\Solution\\Debug\\VertexShader.cso", &amp;blob); g_pd3dDevice-&gt;CreateVertexShader(blob-&gt;GetBufferPointer(), blob-&gt;GetBufferSize(), NULL, &amp;g_pVertexShader); // 创建顶点布局 g_pd3dDevice-&gt;CreateInputLayout(layout, numElements, blob-&gt;GetBufferPointer(), blob-&gt;GetBufferSize(), &amp;g_pVertexLayout); blob-&gt;Release(); // Set the input layout g_pd3dImmediateContext-&gt;IASetInputLayout(g_pVertexLayout); D3DReadFileToBlob(L"G:\\Solution\\Debug\\PixelShader.cso", &amp;blob); g_pd3dDevice-&gt;CreatePixelShader(blob-&gt;GetBufferPointer(), blob-&gt;GetBufferSize(), NULL, &amp;g_pPixelShader); blob-&gt;Release(); g_pd3dImmediateContext-&gt;VSSetShader(g_pVertexShader, NULL, 0); g_pd3dImmediateContext-&gt;PSSetShader(g_pPixelShader, NULL, 0); // Create vertex buffer SimpleVertex vertices[] = { { DirectX::XMFLOAT3(0.0f, 0.5f, 0.5f), DirectX::XMFLOAT4(1.0f, 0.0f, 0.0f, 1.0f) }, { DirectX::XMFLOAT3(0.5f, -0.5f, 0.5f), DirectX::XMFLOAT4(0.0f, 1.0f, 0.0f, 1.0f) }, { DirectX::XMFLOAT3(-0.5f, -0.5f, 0.5f), DirectX::XMFLOAT4(0.0f, 0.0f, 1.0f, 1.0f) }, }; D3D11_BUFFER_DESC bd; bd.Usage = D3D11_USAGE_DEFAULT; bd.ByteWidth = sizeof(SimpleVertex) * 3; bd.BindFlags = D3D10_BIND_VERTEX_BUFFER; bd.CPUAccessFlags = 0; bd.MiscFlags = 0; D3D11_SUBRESOURCE_DATA InitData; InitData.pSysMem = vertices; if (FAILED(g_pd3dDevice-&gt;CreateBuffer(&amp;bd, &amp;InitData, &amp;g_pVertexBuffer))) return; // Set vertex buffer UINT stride = sizeof(SimpleVertex); UINT offset = 0; g_pd3dImmediateContext-&gt;IASetVertexBuffers(0, 1, &amp;g_pVertexBuffer, &amp;stride, &amp;offset); // Set primitive topology g_pd3dImmediateContext-&gt;IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST); } void Render() { // // Clear the backbuffer // if (g_pd3dDevice &amp;&amp; g_pSwapChain) { float ClearColor[4] = { 0.0f, 0.125f, 0.6f, 1.0f }; // RGBA g_pd3dImmediateContext-&gt;ClearRenderTargetView(g_pRenderTargetView, ClearColor); // Render a triangle g_pd3dImmediateContext-&gt;Draw(3, 0); g_pSwapChain-&gt;Present(0, 0); } } void UnInitDevice() { if (g_pVertexBuffer) g_pVertexBuffer-&gt;Release(); if (g_pVertexLayout) g_pVertexLayout-&gt;Release(); if (g_pVertexShader) g_pVertexShader-&gt;Release(); if (g_pRenderTargetView) g_pRenderTargetView-&gt;Release(); if (g_pd3dDevice) g_pd3dDevice-&gt;Release(); if (g_pd3dImmediateContext) g_pd3dImmediateContext-&gt;Release(); if (g_pSwapChain) g_pSwapChain-&gt;Release(); } </code></pre> <h2 id="36-dx12">3.6. DX12</h2> <p>DX12示例:绘制一个三角形</p> <ul> <li>Step1:图形调试器支持</li> <li>Step2:创建设备</li> <li>Step3:创建命令队列(GPU和CPU通信,使用一堆命令队列依次执行,是否可以理解为,CPU和GPU通信方式修改成了异步方式了?)</li> <li>Step4:创建交换链。注意,必须至少创建两个FrameBuffer</li> <li>Step5:创建渲染器目标视图(RTV) 描述符堆,类似一个资源池,可以用来分配给渲染目标等。</li> <li>Step6:创建帧资源(每个帧的渲染器目标视图),每次创建,都需要找描述符堆申请资源</li> <li>Step7:创建命令分配器,管理命令列表</li> <li>Step8:创建根签名,类似签名证书根证书一样,资源一旦“打”上这个签名(结构体中指向根签名对象),就表示要进入渲染管道进行渲染使用</li> <li>Step9:编译着色器</li> <li>Step10:定义顶点输入布局、裁剪区、资源屏障</li> <li>Step11:创建图形管道状态对象,图形管道状态对象包括顶点着色器、像素着色器、顶点输入布局、顶点拓扑结构、渲染目标格式等</li> <li>Step12:创建命令列表</li> <li>Step13:创建并加载顶点缓冲区</li> <li>Step14:创建顶点缓冲区视图</li> <li>Step15:创建fence,用于GPU和CPU同步</li> <li>Step16:等待GPU完成准备工作</li> <li>Step17:填充命令列表,包括:绑定根签名、渲染目标、视图、顶点缓存、绘制命令</li> <li>Step18:执行命令列表</li> <li>Step19:触发swapchain,Present显示帧</li> <li>Step20:等待GPU完成动作</li> </ul> <pre><code class="language-C++">struct PSInput { float4 position : SV_POSITION; float4 color : COLOR; }; PSInput VSMain(float4 position : POSITION, float4 color : COLOR) { PSInput result; result.position = position; result.color = color; return result; } float4 PSMain(PSInput input) : SV_TARGET { return input.color; } </code></pre> <pre><code class="language-C++">#include &lt;wrl.h&gt; using Microsoft::WRL::ComPtr; #include "d3dx12.h" #include &lt;dxgi1_4.h&gt; #include &lt;d3dcompiler.h&gt; #include &lt;directxmath.h&gt; #include &lt;stdexcept&gt; ComPtr&lt;ID3D12Device&gt; g_pd3dDevice; ComPtr&lt;ID3D12CommandQueue&gt; g_pd3dCommandQueue; ComPtr&lt;IDXGISwapChain3&gt; g_pd3dSwapChain; ComPtr&lt;ID3D12DescriptorHeap&gt; g_prtvDescHeap; UINT g_rtvDescriptorSize = 0; ComPtr&lt;ID3D12Resource&gt; g_pRenderTarget[2]; ComPtr&lt;ID3D12CommandAllocator&gt; g_pd3dCommandAllocator; ComPtr&lt;ID3D12RootSignature&gt; g_rootSign; ComPtr&lt;ID3D12GraphicsCommandList&gt; g_commandList; ComPtr&lt;ID3D12PipelineState&gt; g_pipelineState; ComPtr&lt;ID3D12Resource&gt; g_pVB; D3D12_VERTEX_BUFFER_VIEW g_vertexBufferView; ComPtr&lt;ID3D12Fence&gt; g_pFence; UINT64 g_fenceValue = 0; HANDLE g_hfenceEvent = NULL; CD3DX12_VIEWPORT g_viewport; CD3DX12_RECT g_scissorRect; UINT g_frameCnt = 2; UINT g_frameIndex = 0; struct Vertex { DirectX::XMFLOAT3 position; DirectX::XMFLOAT4 color; }; inline std::string HrToString(HRESULT hr) { char s_str[64] = {}; sprintf_s(s_str, "HRESULT of 0x%08X", static_cast&lt;UINT&gt;(hr)); return std::string(s_str); } class HrException : public std::runtime_error { public: HrException(HRESULT hr) : std::runtime_error(HrToString(hr)), m_hr(hr) {} HRESULT Error() const { return m_hr; } private: const HRESULT m_hr; }; void ThrowIfFailed(HRESULT hr) { if (FAILED(hr)) { throw HrException(hr); } } void GetHardwareAdapter(IDXGIFactory2* pFactory, IDXGIAdapter1** ppAdapter) { ComPtr&lt;IDXGIAdapter1&gt; adapter; *ppAdapter = nullptr; for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pFactory-&gt;EnumAdapters1(adapterIndex, &amp;adapter); ++adapterIndex) { DXGI_ADAPTER_DESC1 desc; adapter-&gt;GetDesc1(&amp;desc); if (desc.Flags &amp; DXGI_ADAPTER_FLAG_SOFTWARE) { // Don't select the Basic Render Driver adapter. // If you want a software adapter, pass in "/warp" on the command line. continue; } // Check to see if the adapter supports Direct3D 12, but don't create the // actual device yet. if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr))) { break; } } *ppAdapter = adapter.Detach(); } void LoadPipeline(HWND hWnd) { UINT dxgiFactoryFlags = 0; #if defined(_DEBUG) // Step1:图形调试器支持 // Enable the debug layer (requires the Graphics Tools "optional feature"). // NOTE: Enabling the debug layer after device creation will invalidate the active device. { ComPtr&lt;ID3D12Debug&gt; debugController; if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&amp;debugController)))) { debugController-&gt;EnableDebugLayer(); // Enable additional debug layers. dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG; } } #endif // Step2:创建设备 ComPtr&lt;IDXGIFactory4&gt; factory; CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&amp;factory)); ComPtr&lt;IDXGIAdapter1&gt; hardwareAdapter; GetHardwareAdapter(factory.Get(), &amp;hardwareAdapter); D3D12CreateDevice( hardwareAdapter.Get(), D3D_FEATURE_LEVEL_11_0, IID_PPV_ARGS(&amp;g_pd3dDevice) ); // Step3:创建命令队列(GPU和CPU通信,使用一堆命令队列依次执行,是否可以理解为,CPU和GPU通信方式修改成了异步方式了?) // Describe and create the command queue. D3D12_COMMAND_QUEUE_DESC queueDesc = {}; queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE; queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT; g_pd3dDevice-&gt;CreateCommandQueue(&amp;queueDesc, IID_PPV_ARGS(&amp;g_pd3dCommandQueue)); // Step4:创建交换链 // Describe and create the swap chain. DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {}; swapChainDesc.BufferCount = g_frameCnt; // 至少有两个 swapChainDesc.Width = 640; swapChainDesc.Height = 480; swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM; swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT; swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD; swapChainDesc.SampleDesc.Count = 1; ComPtr&lt;IDXGISwapChain1&gt; swapChain; ThrowIfFailed(factory-&gt;CreateSwapChainForHwnd( g_pd3dCommandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it. hWnd, &amp;swapChainDesc, nullptr, nullptr, &amp;swapChain )); swapChain.As(&amp;g_pd3dSwapChain); // Step5:创建渲染器目标视图(RTV) 描述符堆,类似一个资源池,可以用来分配给渲染目标等。 // Describe and create a render target view (RTV) descriptor heap. D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {}; rtvHeapDesc.NumDescriptors = g_frameCnt; rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV; rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE; g_pd3dDevice-&gt;CreateDescriptorHeap(&amp;rtvHeapDesc, IID_PPV_ARGS(&amp;g_prtvDescHeap)); g_rtvDescriptorSize = g_pd3dDevice-&gt;GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV); // Step6:创建帧资源(每个帧的渲染器目标视图),每次创建,都需要找描述符堆申请资源 CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(g_prtvDescHeap-&gt;GetCPUDescriptorHandleForHeapStart()); for (UINT n = 0; n &lt; g_frameCnt; n++) { ThrowIfFailed(g_pd3dSwapChain-&gt;GetBuffer(n, IID_PPV_ARGS(&amp;g_pRenderTarget[n]))); g_pd3dDevice-&gt;CreateRenderTargetView(g_pRenderTarget[n].Get(), nullptr, rtvHandle); rtvHandle.Offset(1, g_rtvDescriptorSize); } // Step7:创建命令分配器,管理命令列表 g_pd3dDevice-&gt;CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&amp;g_pd3dCommandAllocator)); } void LoadAssets() { // Step8:创建根签名,类似签名证书根证书一样,资源一旦“打”上这个签名(结构体中指向根签名对象),就表示要进入渲染管道进行渲染使用 CD3DX12_ROOT_SIGNATURE_DESC rootSignatureDesc; rootSignatureDesc.Init(0, nullptr, 0, nullptr, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT); ComPtr&lt;ID3DBlob&gt; signature; ComPtr&lt;ID3DBlob&gt; error; D3D12SerializeRootSignature(&amp;rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &amp;signature, &amp;error); g_pd3dDevice-&gt;CreateRootSignature(0, signature-&gt;GetBufferPointer(), signature-&gt;GetBufferSize(), IID_PPV_ARGS(&amp;g_rootSign)); // Step9:编译着色器 ComPtr&lt;ID3DBlob&gt; vertexShader; ComPtr&lt;ID3DBlob&gt; pixelShader; #if defined(_DEBUG) // Enable better shader debugging with the graphics debugging tools. UINT compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION; #else UINT compileFlags = 0; #endif ThrowIfFailed(D3DCompileFromFile(L"G:\\Solution\\D3D12Test\\shader.hlsl", nullptr, nullptr, "VSMain", "vs_5_0", compileFlags, 0, &amp;vertexShader, nullptr)); ThrowIfFailed(D3DCompileFromFile(L"G:\\Solution\\D3D12Test\\shader.hlsl", nullptr, nullptr, "PSMain", "ps_5_0", compileFlags, 0, &amp;pixelShader, nullptr)); // Step10:定义顶点输入布局 // Define the vertex input layout. D3D12_INPUT_ELEMENT_DESC inputElementDescs[] = { { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }, { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 } }; g_viewport.TopLeftX = 100.0f; g_viewport.TopLeftY = 10.0f; g_viewport.Width = 400.0f; g_viewport.Height = 250.0f; g_scissorRect.left = 0.0f; g_scissorRect.top = 0.0f; g_scissorRect.right = 640.0f; g_scissorRect.bottom = 480.0f; // Step11:创建图形管道状态对象,图形管道状态对象包括顶点着色器、像素着色器、顶点输入布局、顶点拓扑结构、渲染目标格式等 // Describe and create the graphics pipeline state object (PSO). D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = {}; psoDesc.InputLayout = { inputElementDescs, _countof(inputElementDescs) }; psoDesc.pRootSignature = g_rootSign.Get(); psoDesc.VS = CD3DX12_SHADER_BYTECODE(vertexShader.Get()); psoDesc.PS = CD3DX12_SHADER_BYTECODE(pixelShader.Get()); psoDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT); psoDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT); psoDesc.DepthStencilState.DepthEnable = FALSE; psoDesc.DepthStencilState.StencilEnable = FALSE; psoDesc.SampleMask = UINT_MAX; psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE; psoDesc.NumRenderTargets = 1; psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM; psoDesc.SampleDesc.Count = 1; g_pd3dDevice-&gt;CreateGraphicsPipelineState(&amp;psoDesc, IID_PPV_ARGS(&amp;g_pipelineState)); // Step12:创建命令列表 g_pd3dDevice-&gt;CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, g_pd3dCommandAllocator.Get(), g_pipelineState.Get(), IID_PPV_ARGS(&amp;g_commandList)); // Command lists are created in the recording state, but there is nothing // to record yet. The main loop expects it to be closed, so close it now. g_commandList-&gt;Close(); // Step13:创建并加载顶点缓冲区 Vertex triangleVertices[] = { { { 0.0f, 0.25f, 0.0f }, { 1.0f, 0.0f, 0.0f, 1.0f } }, { { 0.25f, -0.25f, 0.0f }, { 0.0f, 1.0f, 0.0f, 1.0f } }, { { -0.25f, -0.25f, 0.0f }, { 0.0f, 0.0f, 1.0f, 1.0f } } }; const UINT vertexBufferSize = sizeof(triangleVertices); // Note: using upload heaps to transfer static data like vert buffers is not // recommended. Every time the GPU needs it, the upload heap will be marshalled // over. Please read up on Default Heap usage. An upload heap is used here for // code simplicity and because there are very few verts to actually transfer. g_pd3dDevice-&gt;CreateCommittedResource( &amp;CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD), D3D12_HEAP_FLAG_NONE, &amp;CD3DX12_RESOURCE_DESC::Buffer(vertexBufferSize), D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&amp;g_pVB)); // Copy the triangle data to the vertex buffer. UINT8* pVertexDataBegin; CD3DX12_RANGE readRange(0, 0); // We do not intend to read from this resource on the CPU. g_pVB-&gt;Map(0, &amp;readRange, reinterpret_cast&lt;void**&gt;(&amp;pVertexDataBegin)); memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices)); g_pVB-&gt;Unmap(0, nullptr); // Step14:创建顶点缓冲区视图 // Initialize the vertex buffer view. g_vertexBufferView.BufferLocation = g_pVB-&gt;GetGPUVirtualAddress(); g_vertexBufferView.StrideInBytes = sizeof(Vertex); g_vertexBufferView.SizeInBytes = vertexBufferSize; // Step15:创建fence,用于GPU和CPU同步 g_pd3dDevice-&gt;CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&amp;g_pFence)); g_fenceValue = 1; // Create an event handle to use for frame synchronization. g_hfenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr); if (g_hfenceEvent == nullptr) { return; } // Step16:等待GPU完成准备工作 // Wait for the command list to execute; we are reusing the same command // list in our main loop but for now, we just want to wait for setup to // complete before continuing. WaitForPreviousFrame(); } void InitDevice(HWND hWnd) { // 加载管线 LoadPipeline(hWnd); // 记载资产 LoadAssets(); } void WaitForPreviousFrame() { // WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE. // This is code implemented as such for simplicity. The D3D12HelloFrameBuffering // sample illustrates how to use fences for efficient resource usage and to // maximize GPU utilization. // Signal and increment the fence value. const UINT64 fence = g_fenceValue; g_pd3dCommandQueue-&gt;Signal(g_pFence.Get(), fence); g_fenceValue++; // Wait until the previous frame is finished. if (g_pFence-&gt;GetCompletedValue() &lt; fence) { g_pFence-&gt;SetEventOnCompletion(fence, g_hfenceEvent); WaitForSingleObject(g_hfenceEvent, INFINITE); } g_frameIndex = g_pd3dSwapChain-&gt;GetCurrentBackBufferIndex(); } void PopulateCommandList() { // Command list allocators can only be reset when the associated // command lists have finished execution on the GPU; apps should use // fences to determine GPU execution progress. g_pd3dCommandAllocator-&gt;Reset(); // However, when ExecuteCommandList() is called on a particular command // list, that command list can then be reset at any time and must be before // re-recording. g_commandList-&gt;Reset(g_pd3dCommandAllocator.Get(), g_pipelineState.Get()); // Set necessary state. g_commandList-&gt;SetGraphicsRootSignature(g_rootSign.Get()); g_commandList-&gt;RSSetViewports(1, &amp;g_viewport); g_commandList-&gt;RSSetScissorRects(1, &amp;g_scissorRect); // Indicate that the back buffer will be used as a render target. g_commandList-&gt;ResourceBarrier(1, &amp;CD3DX12_RESOURCE_BARRIER::Transition(g_pRenderTarget[g_frameIndex].Get(), D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET)); CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(g_prtvDescHeap-&gt;GetCPUDescriptorHandleForHeapStart(), 0, g_rtvDescriptorSize); g_commandList-&gt;OMSetRenderTargets(1, &amp;rtvHandle, FALSE, nullptr); // Record commands. const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f }; g_commandList-&gt;ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr); g_commandList-&gt;IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST); g_commandList-&gt;IASetVertexBuffers(0, 1, &amp;g_vertexBufferView); g_commandList-&gt;DrawInstanced(3, 1, 0, 0); // Indicate that the back buffer will now be used to present. g_commandList-&gt;ResourceBarrier(1, &amp;CD3DX12_RESOURCE_BARRIER::Transition(g_pRenderTarget[g_frameIndex].Get(), D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT)); g_commandList-&gt;Close(); } void Render() { if (!g_pFence) { return; } // Step17:填充命令列表 PopulateCommandList(); // Step18:执行命令列表 ID3D12CommandList* ppCommandLists[] = { g_commandList.Get() }; g_pd3dCommandQueue-&gt;ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists); // Step19:触发swapchain,显示帧 g_pd3dSwapChain-&gt;Present(1, 0); // Step20:等待GPU完成动作 WaitForPreviousFrame(); } void UnInitDevice() { // Ensure that the GPU is no longer referencing resources that are about to be // cleaned up by the destructor. WaitForPreviousFrame(); CloseHandle(g_hfenceEvent); } </code></pre> <h2 id="37-工程开源">3.7. 工程开源</h2> <p>工程参见:<a href="https://github.com/Keenjin/D3DSample">https://github.com/Keenjin/D3DSample</a></p> Thu, 09 Apr 2020 00:00:00 +0000 http://Keenjin.github.io/2020/04/DXRender/ http://Keenjin.github.io/2020/04/DXRender/ 音视频