首页 > 代码库 > 游戏开发人员使用CodeXL:如何为GCN分析HLSL

游戏开发人员使用CodeXL:如何为GCN分析HLSL

AMD Radeon? R7R9系列,几乎所有的HD 7000系列显卡,包括史上最快显卡AMD Radeon? R9 295X2与流行的AMD Radeon? R9 290X显卡均采用了下一代图形架构GCN。下一代游戏主机也将采用GCN,对游戏开发人员而言,首要任务是为此架构优化着色器。

 

 以前,游戏开发人员可以使用GPU ShaderAnalyzerGSA),分析DirectX? HLSL着色器性能。但目前GSA不支持GCN。最近发布的CodeXL 1.4通过CodeXL的命令行工具提供了此项功能。具体来说,CodeXLAnalyzer命令行工具输出所生成的GCN硬件着色器的反汇编,提供有用的着色器数据,如通用寄存器(GPR)用量。因为GCN的指令集架构(ISA)是公开的(ASIC家族代号“Southern Islands”“Sea Islands”),游戏开发人员可以深入了解着色器是如何执行并优化HLSL,以获得更佳效果。

 

CodeXLAnalyzer概览

 

CodeXLAnalyzer是一款命令行工具,支持离线编译HLSL着色器成GCN硬件着色器。它运用实时驱动模式,即它利用了安装在本地计算机的AMD驱动及DirectX运行库的编译器。首先,它利用D3DCompile API  HLSL编译成DirectX二进制文件。有了这一步骤,CodeXLAnalyzer需要对应于D3DCompile函数形参的实参。例如,它需要着色器函数名(D3DCompile的入口参数)与配置文件(D3DCompile的目标参数,如ps_5_0)。D3DCompile API产生的错误和警告信息通过控制台输出。

 

编译成DirectX二进制文件后,CodeXLAnalyzer运用AMDDirectX驱动,将此结果编译至GCN硬件着色器,并将反汇编的GCN代码列表写入一个文件。你可以指定独立于本地机器安装的显卡的GCN ASIC目标。也可让工具输出着色器资源使用数据逗号分隔值文件(CSV)。下面举几个例子。

 

1:打印帮助文字

 

使用选项-h,获得可用选项列表

 

CodeXLAnalyzer.exe -h

 

;CodeXL同样支持OpenCL。对HLSL而言,可忽略OpenCL部分,直达下面标记“DX指南以及选项(仅限Windows)”部分。

 

2:基础

 

 

struct PsInput

{

   float4 v4Pos : SV_POSITION;

   float2 v2Tex : TEXTURE0;

};

Texture2D   g_Texture : register( t0 );

SamplerState g_Sampler : register( s0 );

float4 PsExample( PsInput Input ) : SV_Target

{

   return g_Texture.Sample( g_Sampler, Input.v2Tex );

}

 

 

假定上例简单的像素着色器位于源文件Example.hlsl中,下列命令将为Hawaii ASIC( R9 290X)编译该像素着色器。

 

 

CodeXLAnalyzer.exe -c Hawaii -f PsExample -s HLSL -p ps_5_0 -a PsExampleStats.csv --isa PsExampleISA.txt Example.hlsl

 

输出如下图所示:

 

接下来我们逐一分解选项:

 

-c Hawaii

Compile for the Hawaii ASIC. This does not have to match the card in your machine.

用于Hawaii ASIC编译。无需匹配机器安装的显卡

-f PsExample

Compile the function named PsExample.

编译名为PsExample的函数

-s HLSL

Specify that the source is HLSL.

指定源为HLSL

-p ps_5_0

Use the ps_5_0 profile for D3DCompile.

D3DCompile使用ps_5_0配置文件

-a PsExampleStats.csv

Perform shader analysis and write resulting shader statistics to the specified file.

执行着色器分析,写入分析结果至指定文件

--isa PsExampleISA.txt

Write the GCN disassembly to the specified file.

写入GCN反编译文件至指定文件

Example.hlsl

Specify the HLSL source file.

 

 

3D3DCompile DLL位置

  

请记住,CodeXLAnalyzer默认使用D3DCompiler_*.DLL库,其安装在 %WINDIR%\System32 ( %WINDIR%\SysWow64),这也是以前DirectX SDK安装D3DCompile的位置。要使用其它版本(如Win8.x SDK),需用到--DXLocation命令选项。

 

CodeXLAnalyzer.exe -c Hawaii -f PsExample -s HLSL -p ps_5_0 --DXLocation "C:\Program Files (x86)\Windows Kits\8.0\bin\x86\?d3dcompiler_46.dll" -a PsExampleStats.csv --isa PsExampleISA.txt Example.hlsl

 

 

4D3DCompile标记

 

你可以在CodeXLAnalyzer中使用选项--DXFlags,编译使用了与引擎相同标记的HLSL源码。选项--DXFlags的实参直接传递给D3DCompileFlags1形参。你需要决定所使用的标记整形值。这在Visual Studio调试工具中是比较容易的。一种办法就是标识变量,然后设置断点,看下最终的值。例如:

 

 

UINT CompileFlags = ( D3DCOMPILE_OPTIMIZATION_LEVEL3 | D3DCOMPILE_WARNINGS_ARE_ERRORS );

 

 

你也可直接在监视窗口,利用d3dcompiler.h中的D3DCOMPILE defines使用左移表达式。例如,下面是直接从d3dcompiler.h中找的例子,定义了上例的标记。

 

 

#define D3DCOMPILE_OPTIMIZATION_LEVEL3 (1 << 15)

#define D3DCOMPILE_WARNINGS_ARE_ERRORS (1 << 18)

 

 以下为监视表达式(以及上例CompileFlags变量的值)

 

 

 

最后,在命令行加入DXFlags选项:

 

CodeXLAnalyzer.exe -c Hawaii -f PsExample -s HLSL -p ps_5_0 --DXLocation "C:\Program Files (x86)\Windows Kits\8.0\bin\x86\d3dcompiler_46.dll" --DXFlags 294912 -a PsExampleStats.csv --isa PsExampleISA.txt Example.hlsl

 

如果你不使用DXFlags选项,CodeXLAnalyzer传递0值至D3DCompile,对应D3DCOMPILE_OPTIMIZATION_LEVEL1

 

5:预编译器定义

 

cbuffer cbPerFrame : register( b0 )

{

   matrix g_mProjectionInv : packoffset( c0 );

}

// convert a depth value from post-projection space into view space

float ConvertProjDepthToViewSpace( float z )

{

   z = 1.f / (z*g_mProjectionInv._34 + g_mProjectionInv._44);

   return z;

}

Texture2D  g_DepthTexture : register( t0 );

RWTexture2D g_RWDepthMax  : register( u0 );

groupshared uint  ldsZMax;

[numthreads(NUM_THREADS_X, NUM_THREADS_Y, 1)]

void CsExample( uint3 globalIdx : SV_DispatchThreadID, uint3 localIdx : SV_GroupThreadID,

               uint3 groupIdx : SV_GroupID )

{

   uint localIdxFlattened = localIdx.x + localIdx.y*NUM_THREADS_X;

   if( localIdxFlattened == 0 )

   {

       ldsZMax = 0;

   }

   GroupMemoryBarrierWithGroupSync();

   // use LDS atomics to find the max depth per screen tile

   float maxZ = 0.f;

   float viewPosZ = ConvertProjDepthToViewSpace( g_DepthTexture.Load( uint3(globalIdx.x,globalIdx.y,0) ) );

   InterlockedMax( ldsZMax, asuint( viewPosZ ) );

   GroupMemoryBarrierWithGroupSync();

   maxZ = asfloat( ldsZMax );

   if( localIdxFlattened == 0 )

   {

       // write once per tile to lower-res buffer

       g_RWDepthMax[groupIdx.xy] = maxZ;

   }

}

 

假设上述运算着色器位于源文件ExampleCS.hlsl中。类似于TiledLighting11Radeon SDK sample中的运算着色器代码,利用深度缓冲作为输入,计算每屏贴图深度范围(最小与最大深度)。但是,为简便起见,上例只计算了最大深度。

 

对这个着色器来说,屏幕贴图尺寸映射线程数属性XY参数。请注意,NUM_THREADS_X NUM_THREADS_Y并未在着色器文件中定义,需要在编译过程中指定。你可以在CodeXLAnalyzer使用选项-D,为D3DCompile指定XY参数:

 

 

CodeXLAnalyzer.exe -c Hawaii -f CsExample -s HLSL -p cs_5_0 --DXLocation "C:\Program Files (x86)\Windows Kits\8.0\bin\x86\d3dcompiler_46.dll" -D NUM_THREADS_X=16 -D NUM_THREADS_Y=16 -a CsExampleStats.csv --isa CsExampleISA.txt ExampleCS.hlsl

 

GCN硬件着色器数据

 

选项-a可将某些着色器数据写入逗号分隔文件(CSV)。对于CodeXL 1.4而言,给出了以下硬件资源的详情:标量通用寄存器(SGPRs)、矢量通用寄存器 (VGPRs)、本地数据共享 (LDS),以及暂时存储器。

以下是例2中简单像素着色器的结果:

 

 

所使用的VGPR

 

对于像素着色器,你通常只需要关心使用了多少VGPR。要了解其中缘由,先简单回顾下基本的GCN架构。GCN GPU是由几个并行运行的运算单元(CU)组成。1CU包含4SIMD,每个 SIMD1个矢量ALU64K矢量GPR存储组成。在SIMD中按164个工作项(即64线程)执行工作,称作波前。

 

 

GCN Compute Unit:  GCN 运算单元

Branch & Message unit

分支及消息单元

Vector registers(4*64kb)

矢量寄存器

Scheduler:

调度程序

Local data share(64kb)

本地数据共享

Vector units:

矢量单元

Scalar registers(8kb)

标量寄存器

Scalar unit

标量单元

L1 cache(16kb)

L1缓存

Texture filter unit(4)

纹理过滤单元

 

 

Texture fetch load/store units(16)

纹理拾取加载/存储单元

 

 

 

SIMD同时通过拥有几个波前来隐藏内存延迟,这样运算单元调度程序可以转换不同波前。例如,当1个波前正待从存储获取结果,其它波前也可以发出存储请求。每个SIMD最大支持10个同步波前同时运行。但是,某个内核(即着色器)能否获得该最大值,取决于几个因素。对HLSL像素着色器而言,限制因素通常是VGRP用量。

 

每个SIMD64KB的当地存储用于VGPR。着色器使用的每个VGPRCodeXLAnalyzer着色器数据中所使用的VGPR)保留单个32位浮点值(对应于4位矢量值)。64KB加上每VGPR4字节,以及64线程波前,意味着每线程极限为256VGPR(即每着色器)。但在该256VGPR极限,整个64KB VGPR存储被单一波前占用,因此你不能同时运行多个波前。这也限制了隐藏延迟,以致效能较低。

 

以下表格显示VGPR用量对应同步波前数:

 

 

例如,如果CodeXLAnalyzer着色器数据中所使用的VGPR49-64间,着色器可同步运行4个波前/SIMD。但如果着色器当前接近下一波VGPR高峰阀值,那么即歙便HLSL中看似微小的变化也可以将其发送至下一区间,降低每SIMD的波前数量。这将使得性能显著下降,让你手足无措,为何看似微小的变化会对性能产生如此大的影响。CodeXLAnalyzer可以帮助你诊断此类情形。

 

所使用的SGPR

 

VGPR只是决定最大同步波前/SIMD的因素之一。SGPR也是一个潜在的限制因素。每个SIMD2KBSGPR存储(见GCN运算单元图表中单个8KB存储)。某SGPR的值在波前所有线程中共享(各线程VGPR有唯一的值)。就象VGPR,每个SGPR保留132位浮点数。每SIMD2KBSGPR,每SGPR4字节,即每个线程的限值为512SGPR。但是,其中只有104个可供着色器使用(其中AMD驱动的着色器编译器保留2个,剩余102个)。见下表:

 

 

还有其它一些因素决定了着色器同步波前的数量。首先是本地数据共享(LDS)用量,即CodeXLAnalyzer着色器数据中的UsedLDS。按DirectX术语,LDS映射至线程组共享内存(TGSM)。也就是说,标记为组共享修饰符的着色器变量形成数据中非0UsedLDS。我们将会在下一部分讨论UsedLDS

 

最后一个因素是工作组中的波前数量。1个工作组是执行运算单元的工作项目的集合,由一个或多个波前构成。这与HLSL运算着色器中的线程组是一个概念。因此,线程数属性的形参X, Y,Z决定了运算着色器工作组大小。每个工作组有多个波前,受限于同步运行的硬件障碍数量,每GCN运算单元最多有16个工作组。当每个工作组只有1个波前时,这种障碍就无所谓了,障碍限制即不可用。但是,如果运算着色器致每个工作组有2个波前,也即每个运算单元最大32个波前(216个),或每SIMD8个波前。

 

SIMD的最大波前数量受这4种因素(VGPR, SGPR, LDS,及工作组大小)影响,其中的最少波前也决定了着色器同步波前的数量。对像素着色器(以及其它HLSL着色器类,运算着色器除外)而言,你无法控制LDS用量或工作组大小。因此,分析这些类型的着色器时,你只需要把重点放在VGPRSGPR即可。实践中,SGPR很少情况下会成为影响HLSL像素着色器的因素,这样你只需关注VGPR用量。

 

所使用的LDS

 

前面讲过,带有组共享标记的运算着色器变量将致数据中UsedLDS0。以下是例5中的运算着色器结果:

 

 

 

请注意,例中的着色器只有一个单元声明了组共享,不过LDS能分配到的最低值是16字节。着色器数据中MaxLDS值为32768,代表D3D11中的组共享存储类最大为32KB

 

要确定LDS 用量如何影响同步波,着色器使用的LDS为工作组中所有工作项(或按HLSL说法,线程组中所有线程)共享。GCN运算单位有 64KBLDS 64 KB (不是32 KB D3D11的限值)除以UsedLDS值,然后将结果下取整值。得到可以放进LDS的工作组的最大数量。此简单示例中取16个字节UsedLDS不是很有趣,所以假设使用了8 KB。也即有8个工作组放进LDS。将其转换成波前,需要知道工作组大小(即每个工作组波前数量)。

 

5中声明的线程数:

 

[numthreads(NUM_THREADS_X,NUM_THREADS_Y,1)]

 

在示例的命令行中,NUM_THREADS_XNUM_THREADS_Y都设为16,即每个线程组256 线程(每工作组的工作项)。每波前 64线程,每个工作组4个波前。8个工作组放进LDS,每工作组4波前,即运算单元 (CU)最大32 波前,或每SIMD8个同步波前(每运算单元4SIMD,所以32除以4 )。因此,即使GPR用量允许每个SIMD10个最大波前,着色器使用8KBLDS也致最大数量减少至8

 

所使用的临时字节数:

 

着色器数据中最后一项是usedScratchBytes。如果AMD驱动程序的着色器编译器需分配的VGPR超过256个,就会溢出到临时存储。借助L1 L2 缓存,主视频内存用作临时存储。

 

实际操作中,HLSL着色器并非经常碰到VGPRs溢出至临时存储的情况。不过,如果出现这种情况,几乎肯定会导致性能打折,你需要修改着色器,减少 VGPR压力,释放临时存储使用。例如,临时数组(即本地数组)使用 VGPRs,可能会导致VGPR 用量过高。(题外话:GCN中索引到本地数组是个很昂贵的操作,除了可能导致VGPR用量过高,你还需警惕不能过分依赖本地数组。)

 

更多有关GPR压力:

 

每个SIMD的波前越多,更有利于隐藏延迟,这通常是通过较低的VGPR用量来实现的,问题是:为何AMD驱动程序着色编译器不直接调度指令,要求最小化VGPR用量呢?请看下面简单的伪代码示例,显示有两种方法来调度指令:

 

 

Schedule1        Schedule 2

-----------       -----------

Load v1           Load v1

Load v2           VALU use v1

Load v3           Load v1

VALU use v1       VALU use v1

VALU use v2       Load v1

VALU use v3       VALU use v1

 

 

 调度1需要3VGPR,而调度2使用只有单个VGPR 来做相同的工作。然而,虽然调度2使用的寄存器较少,但并一定就好,因为在数据可供VALU使用前,你必须等待每个加载完成。

 

调度1中分组负载可以在某特定波前内更好的适应延迟隐藏,但调度2较有利于减少GPR压力,也即意味着每个SIMD有更多波前,这样同样可以隐藏延迟(通过波前切换)。不过它更加复杂。

 

尽管如此,在优化HLSL着色器时,尝试减少GPR压力还是有益的,尤其是着色器正好超过每个SIMD的波前阈值(例如 68 VGPR)GPR压力增加的潜因包括嵌套太深、本地数组声明、持久的临时变量。和以前一样,记录任何变化,确保真正性能改善。

 

要求

 

HLSL新的CodeXLAnalyzer命令功能支持CodeXL 1.4或更高版本,可从CodeXL面下载。

 还需要AMD催化剂beta版驱动程序14.4 RC v1.0 或更高版本。或者你可使用 AMD 催化剂14.4 (或更高版本)的WHQL驱动程序。

 

更多信息

 

  Southern Islands Sea Islands 指令集架构文档包含各GCN着色器指令的解释、以及VGPR, SGPR,LDS更多详情。

 

更多AMDGCN信息可查看以下链接:

 

· GCN性能推文

·   AMD GCN架构:速成

 

对非AMD用户,以下是一些有关GCN的材料:

 

 Michal Drobot: GCN低水平优化:pdf,pptx

 Emil Persson: Next-Gen DX11的低水平着色器优化:pdf,pptx

 Bart Wronski: GCN –隐藏延迟及波前占用的两种方法: blog

 

另外,CodeXL还包括帮助文件,虽然目前主要重点在OpenCL,也有一些章节涉及DirectX。首先,其中有一页关于内核占用(搜索关键词占用)。提供了更多有关每SIMD波前的各种限制因素。

 

帮助文件也包含了DirectX新增功能的说明文件。最简单的办法就是搜索关键词DirectX 。帮助文件位于:%CodeXLDir%\Help (. C:\Program Files (x86)\AMD\CodeXL\Help).

 

 

原文链接:http://developer.amd.com/community/blog/2014/05/16/codexl-game-developers-analyze-hlsl-gcn/