在建立雷达虚拟操作系统或维修训练系统时,显示器的仿真效果直接影响模拟器的训练效果。目前制约余辉实现的主要瓶颈是余辉效果带来的庞大的计算量,使得效果较好的余辉扫描线转速难以超过10转/s,如果要提高转速,则需要以牺牲显示画质为代价。基于光栅扫描余辉模拟的主流方法有画线法、固定扇扫法、逐点消隐法,由于前两者图像易出现辐射状花纹及扫描速率不稳定,因此后者的应用较多,效果也明显强于前者[1]。本文在逐点消隐法的基础上应用CUDA技术,解决了运算量巨大的问题,在光栅显示器上得到了余辉效果逼真、画面流畅的余辉图形。
1 余辉仿真的瓶颈
传统的雷达P显采用示波管作为显示终端,其内部荧光材料具有指数型衰减的余辉效应,电子束扫描线圆周扫过屏幕将留下逐渐消隐的余辉[2]。但光栅显示器无法自动产生荧光粉的余辉效应,因此必须人为地模拟余辉效应。
软模拟通常采用光栅显示器,用计算机编程实现。光栅扫描显示器具有高亮度、高稳定度、大容量显示的图文处理能力、丰富的色彩及多灰度等级的优点。一般采用以下三种方法实现[3-4]。
(1)画线法较容易实现,原理是在屏幕上以画直线的方式画出每一角度的扫描线,形成每次画一个扇面的灰度递减的直线簇。但是当程序运行时,扫描线轨迹不断地在屏幕上转动,该方法不能无缝地覆盖整个扇扫区域,从而产生一个辐射状的固定花纹。
(2)固定扇扫法是在画线法基础上改进的一种仿真方法,控制扇形区域的圆心角,依次使不同扇形区域亮度减少。它虽然消除了辐射状花纹,但在没有目标到有目标信号时,由于数据量的增加会造成扫描线的转速不同。
(3)逐点消隐法,主要原理是将每个方位像素的亮度逐次递减,即每个点都必须被修改,这样整个屏幕画面亮度逐渐衰减。其产生的余辉效果比较逼真,扫描线转速也较稳定。
模拟逼真的余辉效果,一般采用逐点消隐法,十分逼真的余辉仿真需要非常高的数据吞吐率,要求在每一显示帧的时间内(一般为60 Hz的倒数约16 ms)对屏幕中所有像素进行一次衰减运算。以公认的高效算法,即查表法为例:对于一个像素点而言,最少需要1次读和2次写操作,分辨率为1 024×1 024的屏幕中会有1 024×1 024个像素点参与雷达回波的显示,数量约为1 M。即在16 ms的时间内需要进行1 M次读操作和2 M次写操作,分给每个像素点的时间为16 ns。由于Windows属于通用型操作系统,硬件操作过程极其复杂,无论如何也无法在16 ns内完成1次读和2次写操作。需要说明的是,现有的用PC实现的余辉仿真算法都是以牺牲画质为前提条件的,例如有的算法降低角度分辨率,有的算法只运算部分像素。
2 瓶颈的解决方案
为了解决此瓶颈,本文将国外主要应用于3D游戏设计的CUDA技术移植到余辉的模拟上。CUDA(统一计算设备架构)是NVIDIA公司在2007年推出的针对GPGPU(通用计算GPU)的一个全新构想,使专注于图像处理的GPU超高性能在数据处理和科学计算等通用计算领域发挥优势[5]。
GPU特别适合并行数据运算问题,同一个程序可操作许多并行数据元素,并具有高运算密度(算术运算与内存操作的比例),且在高密度运算时,GPU访问内存的延迟可以被掩盖。目前高端GPU计算性能已达到Teraflops(每秒万亿次浮点运算)级别,其运算速度远远高于CPU的速度[6-7]。2008年初国内建成的首套实验系统,其计算性能的理论峰值124 Teraflops,可用峰值82 Teraflops。
但是常规的GPU通用计算还存在以下问题[7]:编程过于繁杂,难以学习与使用,在非图形领域应用很不充分;GPU编程缺乏灵活性,对GPU性能的发挥有很大的限制。
而CUDA采用GPU+CPU的方式,通过标准C语言将GPU的众多的计算特性结合到一起,由线程来创建应用程序。程序代码在实际执行中分为两种,一种是运行在CPU上的主机代码,另一种是运行在GPU上的设备代码。它类似于CPU上的多线程程序,但与仅能有很少线程同时工作的多核CPU相比,GPU可以同时执行成千上万个线程[8-9]。CPU程序以异步的方式调用GPU核程序,GPU作为CPU的协处理器(CoProeessor)提供服务。
当前CUDA提供的主要功能如下[7]:
(1)在GPU上提供标准C编程语言。
(2)为在支持CUDA的NVIDIA GPU的并行计算提供统一的软硬件解决方案。
(3)支持CUDA的GPU能进行并行数据缓存和线程执行管理。
(4)经过优化的,从CPU到支持CUDA的GPU的直接上传、下载通道。
(5)CUDA驱动与DirectX和OpenGL等图形驱动程序兼容。
为了解决巨大计算量的问题,主要采用CPU+GPU的编程模式来模拟余辉,在GPU中为每一个像素点创建一个线程独立进行亮度衰减处理。由于每个像素的线程并行执行,完成整个屏幕像素的数据处理几乎不需要计算时间,真正花费时间的是画面绘制和翻转。因此绘制画面在后台表面进行,绘制完成后翻转到前台显示,这样绘制和显示可以同时进行,既为画面的绘制留足了时间,又能得到流畅不闪烁的画质。
3 采用CUDA技术来实现余辉效果
为了产生不同方位的扫描线,将方位、距离进行量化,由于扫描区域的分辨率为1 024×1 024,因此半径为512像素。由于扫描半径为512个像素,理论上只要角度量化数N大于3 217就不会出现显示死地址的现象[10],方位上量化为4 096个等分。这样初始生成一个4 096×512个像素的圆域。雷达P显中采用的是极坐标系,而在光栅显示器中采用的是直角坐标,通过坐标变换,将建立一张坐标变换表,如表1所示。
通过查表可以避免坐标变换带来的正余弦计算,方便地在极坐标和直角坐标间转换,从而节省大量的运算时间[11]。考虑到近距离区域,多个角度的距离单元会对应相同的像素点,首先为每个像素点定义一个属性的结构体:
typedef struct
{ WORD x;//屏幕直角坐标x
WORD y;//屏幕直角坐标y
WORD SCANlinePtIndex;//该点在扫描线上的
//距离索引
BYTE MapTo2Pt;//该点与同一条扫描上的
//点是否重合
BYTE RadEnd;//标记该条扫描线处理完毕
}RADIUSPOINT;
为圆域内的点分配内存空间:
RADIUSPOINT m_pRadPtToLintPtMap=new RADIUSPOINT[4 096×512]。
对于同一条扫描线上相邻的两点,如果直角坐标相同就把MapTo2Pt设为1,标记为相同的点;如果相邻两点的直角坐标不相同,则把距离索引值赋给ScanlinePtIndex,每条线最后一个点设置RadEnd为1来标记每条线处理已完毕。对于相邻两条线上的点,如果当前线上点与前一条线上相邻4个点的直角坐标相等,设置为m_pPixelOverlap[i]=1,否则设为0。
考虑到余辉呈指数型衰减,而指数运算需要花费大量的时间,对于计算机,其最快的操作是取值和赋值,为了提高光栅扫描雷达显示系统的实时性,需要提高单位时间内能够处理的像素点个数。于是对指数运算采用查表法以提高速度,维护一张按角度划分的指数型衰减因子表m_wAttenuation[4 096]以进行数值的取值和赋值操作。
同时还要建立一个Brightness[4 096×512]的亮度表,来存储每个像素对应的RGB颜色值。
以上这些工作在程序的初始化中即完成,一经完成即可在后续的程序中直接调用。
通过CUDA编程时,GPU可看作为可以并行执行非常多个线程的计算设备,执行并行计算的线程被组织成线程块(BLOCk),每个线程块可以包含多达512个线程,而线程块又组成了栅格(Grid)。GPU可以支持成百上千万个并行线程,于是可以为每个像素点开一个线程,这样每个像素点可以并行处理,能极大地提高对整个屏幕像素的处理速度,为CPU留出足够多的时间去处理其他相关的任务。
定义线程块Block包含的线程维数:
dim3 threads(BLOCK_SIZE,BLOCK_SIZE);
定义栅格Grid包含的线程块数:
dim3 grid(Width/threads.x,Height/ threads.y);
每个像素点对应的线程处理工作如下:
由于某型雷达转速为10转/min,相当于每次更新的扫描线数应为4 096×10/60/1 000=0.683条/ms,像素处理在GPU中并行进行,对CPU的占用率几乎为零,所消耗的时间主要是Direct3D纹理的绘制和表面的翻转,大约为16 ms,因此每次更新的扫描线数目约为16×0.683=10.928,即每次更新11条。将当前要更新的扫描线上的像素点设为初始亮度,其后的每条扫描线上的像素点的亮度按与当前扫描线角度差m_angLEDiff取m_wAttenuation[m_anglediff]的亮度进行衰减。由于近距离区域多个角度的距离单元对应相同的像素点,因此中心部位被消隐的次数明显要比其他部位多,导致效果有些失真。于是需要对这些坐标相同的点进行处理,对于属性MapTo2Pt为1的点,比较坐标相同的点处于不同距离时的亮度,取其大者赋值给亮度表Brightness[4 096×512]。对于属性m_pPixelOverlap为1的点,比较处于各个角度时的亮度,取其大者赋值给亮度表。这样对于同一个点只显示一次且取其最亮者显示,较好地避免了中心部位被消隐次数过多的情况。
对于实现余辉等级的情况,只需要调制m_wAttenuation的大小就可以方便地调节余辉等级。如果需要提高转速,只需增大每次更新的扫描线数目即可,且基本不会影响程序运行速度。
通过CPU+GPU组合的方式模拟不同等级余辉效果如图1、图2所示,此时对应的CPU占用率几乎为零,如图3所示。该方法得到的余辉效果逼真、画面流畅、扫描速度达到了预定的10转/s的要求,且CPU占用率极低,并不妨碍CPU处理其他数据。