CSDN日报20170331 ——《一个屌丝程序猿的人生》      程序员你的职场该这么规划!      Python数据分析与机器学习      博客搬家,有礼相送
 

GPU深度发掘(一)::GPGPU数学基础教程

标签: gpgpufloat扩展语言texturesshader
 993人阅读 评论(0) 收藏 举报

目录(?)[+]

导读: 

GPU深度发掘(一)::GPGPU数学基础教程


作者:Dominik Göddeke                 译者:华文广


Contents




  1. 介绍 

    1. 准备条件 
    2. 硬件设备要求 
    3. 软件设备要求 
    4. 两者选择

  2. 初始化OpenGL 

    1. GLUT 
    2. OpenGL 扩展 
    3. OpenGL 离屏渲染

  3. GPGPU 概念1: 数组 = 纹理 

    1. 在CPU上建立数组 
    2. 在 GPU上生成浮点纹理 
    3. 数组索引与纹理坐标一一对应 
    4. 使用纹理作渲染对像 
    5. 把数据从CPU数组传输到GPU的纹理 
    6. 把数据从GPU的纹理传输到CPU数组 
    7. 一个简单的示例

  4. GPGPU 概念 2: 内核 = 着色器 

    1. 面向循环的CPU运算 vs.面向内核的数据并行运算 
    2. 用Cg着色语言生成一个着色器 
    3. 建立Cg运行环境 
    4. 用OpenGL语言建立一个高级着色环境

  5. GPGPU 概念 3: 运算 = 渲染 

    1. 准备运算的内核 
    2. 设定用于输入的数组/纹理 
    3. 设定用于输出的数组/纹理 
    4. 开始运算

  6. GPGPU 概念 4: 返馈 

    1. 多次渲染传递 
    2. 使用乒乓技术

  7. 归纳总结 

    1. 一个简但的代码 
    2. 程序的变量 
    3. 命令行参数 
    4. 测试模式 
    5. 标准模式

  8. 附言 

    1. 对比 Windows 和 Linux, ATI 和 NVIDIA 
    2. 问题 
    3. OpenGL错误检查 
    4. FBOs错误检查 
    5. Cg错误检查 
    6. GLSL错误检查

  9. 相关知识 
  10. 版权声明

下载源代码


These zip files contain a MS VC 2003.NET solution file, a Linux Makefile and a set of batch files with preconfigured test environments. You might want to read this section about the differences between Windows and Linux, NVIDIA and ATI first.




  • Cg version 
  • GLSL version

引用


对本教程的引用, please use this BibTex citation.






Back to top






介绍


本教程的目的是为了介绍GPU编程的背景及在GPU上运算所需要的步骤,这里通过实现在GPU上运算一个线性代数的简单例子,来阐述我们的观点。saxpy() 是BLAS库上的一个函数,它实现的功能主要是这样的:已知两个长度为N的数组 x 和 y ,一个标量alpha,要求我们计算缩放比例数组之和:y = y + alpha * x。这个函数很简单。我们的目的只是在于向大家阐明一些GPGPU编程入门的必备知识和概念。本教程所介绍的一些编程实现技术,只要稍作修改和扩充,便能运用到复杂的GPU运算应用上。


 



必备条件


 


本文不打算深入到在每一个细节,而是给对OpenGL编程有一定技术基础的朋友看的,你最好还要对图形显卡的组成及管道渲染有一定的了解。对于OpenGL刚入门的朋友,推荐大家看一下以下这些知识:Programming Guide (红宝书)PDF and HTML, 橙宝书 ("OpenGL Shading Language"), 以及 NeHe's OpenGL教程


本教程是基于OpenGL写,目的主要是为不被MS Windows平台的限制。但是这里所阐述的大多数概念但能直接运用到DirectX上。


更多的预备知识,请到 GPGPU.org 上看一下。其中该网站上以下三篇文章,是作者极力推荐大家去看一下的:《Where can I learn about OpenGL and Direct3D?》《How does the GPU pipeline work?》'《n what ways is GPU programming similar to CPU programming?》


译者注:在国内的GPGPU论坛可以到http://www.physdev.com物理开发网上讨论。该网站主要是交流PhysX物理引擎,GPU物理运算等计算机编程的前沿技术



硬件需求.


你需要有NVIDIA GeForce FX 或者 ATI RADEON 9500 以上的显卡, 一些老的显卡可能不支持我们所需要的功能(主要是单精度浮点数据的存取及运算) 。



软件需求


首先,你需要一个C/C++编译器。你有很多可以选择,如:Visual Studio .NET 2003, Eclipse 3.1 plus CDT/MinGW, the Intel C++ Compiler 9.0 及 GCC 3.4+等等。然后更新你的显卡驱动让它可以支持一些最新特性。


本文所附带的源代码,用到了两个扩展库,GLUT 和 GLEW 。对于windows系统,GLUT可以在 这里下载到,而Linux 的freeglut和freeglut-devel大多的版本都集成了。GLEW可以在 SourceForge 上下载到,对于着色语言,大家可以选择GLSL或者CG,GLSL在你安装驱动的时候便一起装好了。如果你想用CG,那就得下载 Cg Toolkit 。



二者择其一


大家如果要找DirectX版本的例子的话,请看一下Jens Krügers的《 Implicit Water Surface》 demo(该例子好像也有OpenGL 版本的)。当然,这只是一个获得高度评价的示例源代码,而不是教程的。


有一些从图形着色编程完全抽象出来的GPU的元程序语言,把底层着色语言作了封装,让你不用学习着色语言,便能使用显卡的高级特性,其中BrookGPU 和 Sh 就是比较出名的两个项目。






Back to top






初始化OpenGL



GLUT


GLUT(OpenGLUtility Toolkit)该开发包主要是提供了一组窗口函数,可以用来处理窗口事件,生成简单的菜单。我们使用它可以用尽可能少的代码来快速生成一个OpenGL 开发环境,另外呢,该开发包具有很好的平台独立性,可以在当前所有主流的操作系统上运行 (MS-Windows or Xfree/Xorg on Linux / Unix and Mac)。


// include the GLUT header file#include // call this and pass the command line arguments from main()void initGLUT(int argc, char **argv) {glutInit ( &argc, argv );glutCreateWindow("SAXPY TESTS");  }

OpenGL 扩展


许多高级特性,如那些要在GPU上进行普通浮点运算的功能,都不是OpenGL内核的一部份。因此,OpenGL Extensions通过对OpenGL API的扩展, 为我们提供了一种可以访问及使用硬件高级特性的机制。OpenGL扩展的特点:不是每一种显卡都支持该扩展,即便是该显卡在硬件上支持该扩展,但不同版本的显卡驱动,也会对该扩展的运算能力造成影响,因为OpenGL扩展设计出来的目的,就是为了最大限度地挖掘显卡运算的能力,提供给那些在该方面有特别需求的程序员来使用。在实际编程的过程中,我们必须小心检测当前系统是否支持该扩展,如果不支持的话,应该及时把错误信息返回给软件进行处理。当然,为了降低问题的复杂性,本教程的代码跳过了这些检测步骤。


OpenGL Extension Registry OpenGL扩展注册列表中,列出了几乎所有的OpenGL可用扩展,有需要的朋友可能的查看一下。


当我们要在程序中使用某些高级扩展功能的时候,我们必须在程序中正确引入这些扩展的扩展函数名。有一些小工具可以用来帮助我们检测一下某个给出的扩展函数是否被当前的硬件及驱动所支持,如:glewinfo, OpenGL extension viewer等等,甚至OpenGL本身就可以(在上面的连接中,就有一个相关的例子)。


如何获取这些扩展函数的入口指针,是一个比较高级的问题。下面这个例子,我们使用GLEW来作为扩展载入函数库,该函数库把许多复杂的问题进行了底层的封装,给我们使用高级扩展提供了一组简洁方便的访问函数。


void initGLEW (void) {// init GLEW, obtain function pointersint err = glewInit();// Warning: This does not check if all extensions used // in a given implementation are actually supported. // Function entry points created by glewInit() will be // NULL in that case!if (GLEW_OK != err) {printf((char*)glewGetErrorString(err));exit(ERROR_GLEW);}  }	

OpenGL离屏渲染的准备工作


在传统的GPU渲染流水线中,每次渲染运算的最终结束点就是帧缓冲区。所谓帧缓冲区,其实是显卡内存中的一块,它特别这处在于,保存在该内存区块中的图像数据,会实时地在显示器上显示出来。根据显示器设置的不同,帧缓冲区最大可以取得32位的颜色深度,也就是说红、绿、蓝、alpha四个颜色通道共享这32位的数据,每个通道占8位。当然用32位来记录颜色,如果加起来的话,可以表示160万种不同的颜色,这对于显示器来说可能是足够了,但是如果我们要在浮点数字下工作,用8位来记录一个浮点数,其数学精度是远远不够的。另外还有一个问题就是,帧缓存中的数据最大最小值会被限定在一个范围内,也就是 [0/255; 255/255]


如何解决以上的一些问题呢?一种比较苯拙的做法就是用有符号指数记数法,把一个标准的IEEE 32位浮点数映射保存到8位的数据中。不过幸运的是,我们不需要这样做。首先,通过使用一些OpenGL的扩展函数,我们可以给GPU提供32位精度的浮点数。另外有一个叫 EXT_framebuffer_object 的OpenGL的扩展, 该扩展允许我们把一个离屏缓冲区作为我们渲染运算的目标,这个离屏缓冲区中的RGBA四个通道,每个都是32位浮点的,这样一来, 要想GPU上实现四分量的向量运算就比较方便了,而且得到的是一个全精度的浮点数,同时也消除了限定数值范围的问题。我们通常把这一技术叫FBO,也就是Frame Buffer Object的缩写。


要使用该扩展,或者说要把传统的帧缓冲区关闭,使用一个离屏缓冲区作我们的渲染运算区,只要以下很少的几行代码便可以实现了。有一点值得注意的是:当我用使用数字0,来绑定一个FBO的时候,无论何时,它都会还原window系统的特殊帧缓冲区,这一特性在一些高级应用中会很有用,但不是本教程的范围,有兴趣的朋友可能自已研究一下。


GLuint fb;void initFBO(void) {// create FBO (off-screen framebuffer)glGenFramebuffersEXT(1, &fb); // bind offscreen buffer glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, fb);}





Back to top






GPGPU 概念 1: 数组 = 纹理


一维数组是本地CPU最基本的数据排列方式,多维的数组则是通过对一个很大的一维数组的基准入口进行坐标偏移来访问的(至少目前大多数的编译器都是这样做的)。一个小例子可以很好说明这一点,那就是一个MxN维的数组 a[i][j] = a[i*M+j];我们可能把一个多维数组,映射到一个一维数组中去。这些数组我开始索引都被假定为0;


而对于GPU,最基本的数据排列方式,是二维数组。一维和三维的数组也是被支持的,但本教程的技术不能直接使用。数组在GPU内存中我们把它叫做纹理或者是纹理样本。纹理的最大尺寸在GPU中是有限定的。每个维度的允许最大值,通过以下一小段代码便可能查询得到,这些代码能正确运行,前提是OpenGL的渲染上下文必须被正确初始化。


int maxtexsize;glGetIntegerv(GL_MAX_TEXTURE_SIZE,&maxtexsize);printf("GL_MAX_TEXTURE_SIZE, %d/n",maxtexsize);      

就目前主流的显卡来说,这个值一般是2048或者4096每个维度,值得提醒大家的就是:一块显卡,虽然理论上讲它可以支持4096*4096*4096的三维浮点纹理,但实际中受到显卡内存大小的限制,一般来说,它达不到这个数字。


在CPU中,我们常会讨论到数组的索引,而在GPU中,我们需要的是纹理坐标,有了纹理坐标才可以访问纹理中每个数据的值。而要得到纹理坐标,我们又必须先得到纹理中心的地址。


传统上讲,GPU是可以四个分量的数据同时运算的,这四个分量也就是指红、绿、蓝、alpha(RGBA)四个颜色通道。稍后的章节中,我将会介绍如何使用显卡这一并行运算的特性,来实现我们想要的硬件加速运算。



在CPU上生成数组


让我们来回顾一下前面所要实现的运算:也就是给定两个长度为N的数组,现在要求两数组的加权和y=y+alpha*x,我们现在需要两个数组来保存每个浮点数的值,及一个记录alpha值的浮点数。


float* dataY = (float*)malloc(N*sizeof(float));float* dataX = (float*)malloc(N*sizeof(float));float alpha;	

虽然我们的实际运算是在GPU上运行,但我们仍然要在CPU上分配这些数组空间,并对数组中的每个元素进行初始化赋值。



在GPU上生成浮点纹理


这个话题需要比较多的解释才行,让我们首先回忆一下在CPU上是如何实现的,其实简单点来说,我们就是要在GPU上建立两个浮点数组,我们将使用浮点纹理来保存数据。


有许多因素的影响,从而使问题变得复杂起来。其中一个重要的因素就是,我们有许多不同的纹理对像可供我们选择。即使我们排除掉一些非本地的目标,以及限定只能使用2维的纹理对像。我们依然还有两个选择,GL_TEXTURE_2D是传统的OpenGL二维纹理对像,而ARB_texture_rectangle则是一个OpenGL扩展,这个扩展就是用来提供所谓的texture rectangles的。对于那些没有图形学背景的程序员来说,选择后者可能会比较容易上手。texture2Ds 和 texture rectangles 在概念上有两大不同之处。我们可以从下面这个列表来对比一下,稍后我还会列举一些例子。



















 texture2Dtexture rectangle
texture targetGL_TEXTURE_2DGL_TEXTURE_RECTANGLE_ARB
纹理坐标

坐标必须被单位化,范围被限定在0到1之间,其它范围不在0到1之间的纹理坐标不会被支持。


纹理坐标不要求单位化

纹理大小

纹理大小必须是2的n次方,如1024,512等。当然如果你的显卡驱动支持ARB_non_power_of_two或者OpenGL2.0的话,则不会受到此限制。

纹理尺寸的大小是任意的,如 ( 513 x1025)

 


另外一个重要的影响因素就是纹理格式,我们必须谨慎选择。在GPU中可能同时处理标量及一到四分量的向量。本教程主要关注标量及四分量向量的使用。比较简单的情况下我们可以在中纹理中为每个像素只分配一个单精度浮点数的储存空间,在OpenGL中,GL_LUMNANCE就是这样的一种纹理格式。但是如果我们要想使用四个通道来作运算的话,我们就可以采用GL_RGBA这种纹理格式。使用这种纹理格式,意味着我们会使用一个像素数据来保存四个浮点数,也就是说红、绿、蓝、alpha四个通道各占一个32位的空间,对于LUMINANCE格式的纹理,每个纹理像素只占有32位4个字节的显存空间,而对于RGBA格式,保存一个纹理像素需要的空间是4*32=128位,共16个字节。


接下来的选择,我们就要更加小心了。在OpenGL中,有三个扩展是真正接受单精度浮点数作为内部格式的纹理的。分别是:NV_float_bufferATI_texture_float 和 ARB_texture_float.每个扩展都就定义了一组自已的列举参数及其标识,如:(GL_FLOAT_R32_NV) ,( 0x8880),在程序中使用不同的参数,可以生成不同格式的纹理对像,下面会作详细描述。


在这里,我们只对其中两个列举参数感兴趣,分别是GL_FLOAT_R32_NVGL_FLOAT_RGBA32_NV . 前者是把每个像素保存在一个浮点值中,后者则是每个像素中的四个分量分别各占一个浮点空间。这两个列举参数,在另外两个扩展(ATI_texture_float and ARB_texture_float )中也分别有其对应的名称:GL_LUMINANCE_FLOAT32_ATIGL_RGBA_FLOAT32_ATI 和 GL_LUMINANCE32F_ARB,GL_RGBA32F_ARB 。在我看来,他们名称不同,但作用都是一样的,我想应该是多个不同的参数名称对应着一个相同的参数标识。至于选择哪一个参数名,这只是看个人的喜好,因为它们全部都既支持NV显卡也支持ATI的显卡。


最后还有一个要解决的问题就是,我们如何把CPU中的数组元素与GPU中的纹理元素一一对应起来。这里,我们采用一个比较容易想到的方法:如果纹理是LUMINANCE格式,我们就把长度为N的数组,映射到一张大小为sqrt(N) x sqrt(N)和纹理中去(这里规定N是刚好能被开方的)。如果采用RGBA的纹理格式,那么N个长度的数组,对应的纹理大小就是sqrt(N/4) x sqrt(N/4),举例说吧,如果N=1024^2,那么纹理的大小就是512*512 。


以下的表格总结了我们上面所讨论的问题,作了一下分类,对应的GPU分别是: NVIDIA GeForce FX (NV3x), GeForce 6 and 7 (NV4x, G7x) 和 ATI.
























 NV3xNV4x, G7x (RECT)NV4x, G7x (2D)ATI
targettexture rectangletexture rectangletexture2Dtexture2D and texture rectangle
formatLUMINANCE and RGBA (and RG and RGB)*
internal
format
NV_float_bufferNV_float_bufferATI_texture_float
ARB_texture_float
ATI_texture_float
ARB_texture_float

(*) Warning: 这些格式作为纹理是被支持的,但是如果作为渲染对像,就不一定全部都能够得到良好的支持(see below).


讲完上面的一大堆基础理论这后,是时候回来看看代码是如何实现的。比较幸运的是,当我们弄清楚了要用那些纹理对像、纹理格式、及内部格式之后,要生成一个纹理是很容易的。


// create a new texture nameGLuint texID;glGenTextures (1, &texID);// bind the texture name to a texture targetglBindTexture(texture_target,texID);// turn off filtering and set proper wrap mode // (obligatory for float textures atm)glTexParameteri(texture_target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(texture_target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(texture_target, GL_TEXTURE_WRAP_S, GL_CLAMP);glTexParameteri(texture_target, GL_TEXTURE_WRAP_T, GL_CLAMP);// set texenv to replace instead of the default modulateglTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);// and allocate graphics memoryglTexImage2D(texture_target, 0, internal_format, texSize, texSize, 0, texture_format, GL_FLOAT, 0);

让我们来消化一下上面这段代码的最后那个OpenGL函数,我来逐一介绍一下它每个参数:第一个参数是纹理对像,上面已经说过了;第二个参数是0,是告诉GL不要使用多重映像纹理。接下来是内部格式及纹理大小,上面也说过了,应该清楚了吧。第六个参数是也是0,这是用来关闭纹理边界的,这里不需要边界。接下来是指定纹理格式,选择一种你想要的格式就可以了。对于参数GL_FLOAT,我们不要被它表面的意思迷惑,它并不会影响我们所保存在纹理中的浮点数的精度。其实它只与CPU方面有关系,目的就是要告诉GL稍后将要传递过去的数据是浮点型的。最后一个参数还是0,意思是生成一个纹理,但现在不给它指定任何数据,也就是空的纹理。该函数的调用必须按上面所说的来做,才能正确地生成一个合适的纹理。上面这段代码,和CPU里分配内存空间的函数malloc(),功能上是很相像的,我们可能用来对比一下。


最后还有一点要提醒注意的:要选择一个适当的数据排列映射方式。这里指的就是纹理格式、纹理大小要与你的CPU数据相匹配,这是一个非常因地制宜的问题,根据解决的问题不同,其相应的处理问题方式也不同。从经验上看,一些情况下,定义这样一个映射方式是很容易的,但某些情况下,却要花费你大量的时间,一个不理想的映射方式,甚至会严重影响你的系统运行。



数组索引与纹理坐标的一一对应关系


在后面的章节中,我们会讲到如何通过一个渲染操作,来更新我们保存在纹理中的那些数据。在我们对纹理进行运算或存取的时候,为了能够正确地控制每一个数据元素,我们得选择一个比较特殊的投影方式,把3D世界映射到2D屏幕上(从世界坐标空间到屏幕设备坐标空间),另外屏幕像素与纹理元素也要一一对应。这种关系要成功,关键是要采用正交投影及合适的视口。这样便能做到几何坐标(用于渲染)、纹理坐标(用作数据输入)、像素坐标(用作数据输出)三者一一对应。有一个要提醒大家的地方:如果使用texture2D,我们则须要对纹理坐标进行适当比例的缩放,让坐标的值在0到1之间,前面有相关的说明。


为了建立一个一一对应的映射,我们把世界坐标中的Z坐标设为0,把下面这段代码加入到initFBO()这个函数中


// viewport for 1:1 pixel=texel=geometry mappingglMatrixMode(GL_PROJECTION);glLoadIdentity();gluOrtho2D(0.0, texSize, 0.0, texSize);glMatrixMode(GL_MODELVIEW);glLoadIdentity();glViewport(0, 0, texSize, texSize);

使用纹理作为渲染对像


其实一个纹理,它不仅可以用来作数据输入对像,也还可以用作数据输出对像。这也是提高GPU运算效率和关键所在。通过使用 framebuffer_object 这个扩展,我们可以把数据直接渲染输出到一个纹理上。但是有一个缺点:一个纹理对像不能同时被读写,也就是说,一个纹理,要么是只读的,要么就是只写的。显卡设计的人提供这样一个解释:GPU在同一时间段内会把渲染任务分派到几个通道并行运行, 它们之间都是相互独立的(稍后的章节会对这个问题作详细的讨论)。如果我们允许对一个纹理同时进行读写操作的话,那我们需要一个相当复杂的逻辑算法来解决读写冲突的问题, 即使在芯片逻辑上可以做到,但是对于GPU这种没有数据安全性约束的处理单元来说,也是没办法把它实现的,因为GPU并不是基von Neumann的指令流结构,而是基于数据流的结构。因此在我们的程序中,我们要用到3个纹理,两个只读纹理分别用来保存输入数组x,y。一个只写纹理用来保存运算结果。用这种方法意味着要把先前的运算公式:y = y + alpha * x 改写为:y_new = y_old + alpha * x.


FBO 扩展提供了一个简单的函数来实现把数据渲染到纹理。为了能够使用一个纹理作为渲染对像,我们必须先把这个纹理与FBO绑定,这里假设离屏帧缓冲已经被指定好了。


glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, texture_target, texID, 0);

 


第一个参数的意思是很明显的。第二个参数是定义一个绑定点(每个FBO最大可以支持四个不同的绑定点,当然,不同的显卡对这个最大绑定数的支持不一样,可以用GL_MAX_COLOR_ATTACHMENTS_EXT来查询一下)。第三和第四个参数应该清楚了吧,它们是实际纹理的标识。最后一个参数指的是使用多重映像纹理,这里没有用到,因此设为0。


为了能成功绑定一纹理,在这之前必须先用glTexImage2D()来对它定义和分配空间。但不须要包含任何数据。我们可以把FBO想像为一个数据结构的指针,为了能够对一个指定的纹理直接进行渲染操作,我们须要做的就调用OpenGL来给这些指针赋以特定的含义。


不幸的是,在FBO的规格中,只有GL_RGB和GL_RGBA两种格式的纹理是可以被绑定为渲染对像的(后来更新这方面得到了改进),LUMINANCE这种格式的绑定有希望在后继的扩展中被正式定义使用。在我定本教程的时候,NVIDIA的硬件及驱动已经对这个全面支持,但是只能结会对应的列举参数NV_float_buffer一起来使用才行。换句话说,纹理中的浮点数的格式与渲染对像中的浮点数格式有着本质上的区别。



下面这个表格对目前不同的显卡平台总结了一下,指的是有哪些纹理格式及纹理对像是可能用来作为渲染对像的,(可能还会有更多被支持的格式,这里只关心是浮点数的纹理格式):
















































 NV3xNV4x, G7xATI
texture 2D, ATI/ARB_texture_float, LUMINANCE
nonono
texture 2D, ATI/ARB_texture_float, RGB, RGBA
noyesyes
texture 2D, NV_float_buffer, LUMINANCE
nonono
texture 2D, NV_float_buffer, RGB, RGBA
nonono
texture RECT, ATI/ARB_texture_float, LUMINANCE
nonono
texture RECT, ATI/ARB_texture_float, RGB, RGBA
noyesyes
texture RECT, NV_float_buffer, LUMINANCE
yesyesno
texture RECT, NV_float_buffer, RGB, RGBA
yesyesno

列表中最后一行所列出来的格式在目前来说,不能被所有的GPU移植使用。如果你想采用LUMINANCE格式,你必须使用ractangles纹理,并且只能在NVIDIA的显卡上运行。想要写出兼容NVIDIA及ATI两大类显卡的代是可能的,但只支持NV4x以上。幸运的是要修改的代码比较少,只在一个switch开关,便能实现代码的可移植性了。相信随着ARB新版本扩展的发布,各平台之间的兼容性将会得到进一步的提高,到时候各种不同的格式也可能相互调用了。



把数据从CPU的数组传输到GPU的纹理


为了把数据传输到纹理中去,我们必须绑定一个纹理作为纹理目标,并通过一个GL函数来发送要传输的数据。实际上就是把数据的首地址作为一个参数传递给该涵数,并指定适当的纹理大小就可以了。如果用LUMINANCE格式,则意味着数组中必须有texSize x texSize个元数。而RGBA格式,则是这个数字的4倍。注意的是,在把数据从内存传到显卡的过程中,是全完不需要人为来干预的,由驱动来自动完成。一但传输完成了,我们便可能对CPU上的数据作任意修改,这不会影响到显卡中的纹理数据。 而且我们下次再访问该纹理的时候,它依然是可用的。在NVIDIA的显卡中,以下的代码是得到硬件加速的。


glBindTexture(texture_target, texID);glTexSubImage2D(texture_target,0,0,0,texSize,texSize,texture_format,GL_FLOAT,data);

这里三个值是0的参数,是用来定义多重映像纹理的,由于我们这里要求一次把整个数组传输一个纹理中,不会用到多重映像纹理,因此把它们都关闭掉。


以上是NVIDIA显卡的实现方法,但对于ATI的显卡,以下的代码作为首选的技术。在ATI显卡中,要想把数据传送到一个已和FBO绑定的纹理中的话,只需要把OpenGL的渲染目标改为该绑定的FBO对像就可以了。


glDrawBuffer(GL_COLOR_ATTACHMENT0_EXT);glRasterPos2i(0,0);glDrawPixels(texSize,texSize,texture_format,GL_FLOAT,data);

 


第一个函数是改变输出的方向,第二个函数中我们使用了起点作为参与点,因为我们在第三个函数中要把整个数据块都传到纹理中去。


两种情况下,CPU中的数据都是以行排列的方式映射到纹理中去的。更详细地说,就是:对于RGBA格式,数组中的前四个数据,被传送到纹理的第一个元素的四个分量中,分别与R,G,B,A分量一一对应,其它类推。而对于LUMINANCE 格式的纹理,纹理中第一行的第一个元素,就对应数组中的第一个数据。其它纹理元素,也是与数组中的数据一一对应的。



把数据从GPU纹理,传输到CPU的数组


这是一个反方向的操作,那就是把数据从GPU传输回来,存放在CPU的数组上。同样,有两种不同的方法可供我们选择。传统上,我们是使用OpenGL获取纹理的方法,也就是绑定一个纹理目标,然后调用glGetTexImage()这个函数。这些函数的参数,我们在前面都有见过。


glBindTexture(texture_target,texID);glGetTexImage(texture_target,0,texture_format,GL_FLOAT,data);

但是这个我们将要读取的纹理,已经和一个FBO对像绑定的话,我们可以采用改变渲染指针方向的技术来实现。


glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);glReadPixels(0,0,texSize,texSize,texture_format,GL_FLOAT,data);

由于我们要读取GPU的整个纹理,因此这里前面两个参数是0,0。表示从0起始点开始读取。该方法是被推荐使用的。


一个忠告:比起在GPU内部的传输来说,数据在主机内存与GPU内存之间相互传输,其花费的时间是巨大的,因此要谨慎使用。由其是从CPU到GPU的逆向传输。


在前面“ 当前显卡设备运行的问题” 中 提及到该方面的问题。



一个简单的例子


现在是时候让我们回头来看一下前面要解决的问题,我强烈建议在开始一个新的更高级的话题之前,让我们先弄一个显浅的例子来实践一下。下面通过一个小的程序,尝试着使用各种不同的纹理格式,纹理对像以及内部格式,来把数据发送到GPU,然后再把数据从GPU取回来,保存在CPU的另一个数组中。在这里,两个过程都没有对数据作任何运算修该,目的只是看一下数据GPU和CPU之间相互传输,所需要使用到的技术及要注意的细节。也就是把前面提及到的几个有迷惑性的问题放在同一个程序中来运行一下。在稍后的章节中将会详细讨论如何来解决这些可能会出现的问题。


由于赶着要完成整个教程,这里就只写了一个最为简单的小程序,采用rectangle纹理、ARB_texture_float作纹理对像并且只能在NVIDIA的显卡上运行。


#include #include #include #include int main(int argc, char **argv) {// 这里声明纹理的大小为:teSize;而数组的大小就必须是texSize*texSize*4int texSize = 2;int i;// 生成测试数组的数据float* data = (float*)malloc(4*texSize*texSize*sizeof(float));float* result = (float*)malloc(4*texSize*texSize*sizeof(float));for (i=0; i<texsize*texsize*4; i++)="" data[i] = (i+1.0)*0.01F;// 初始化OpenGL的环境glutInit (&argc, argv);glutCreateWindow("TEST1");glewInit();// 视口的比例是 1:1 pixel=texel=data 使得三者一一对应glMatrixMode(GL_PROJECTION);glLoadIdentity();gluOrtho2D(0.0,texSize,0.0,texSize);glMatrixMode(GL_MODELVIEW);glLoadIdentity();glViewport(0,0,texSize,texSize);// 生成并绑定一个FBO,也就是生成一个离屏渲染对像GLuint fb;glGenFramebuffersEXT(1,&fb); glBindFramebufferEXT(GL_FRAMEBUFFER_EXT,fb);// 生成两个纹理,一个是用来保存数据的纹理,一个是用作渲染对像的纹理GLuint tex,fboTex;glGenTextures (1, &tex);glGenTextures (1, &fboTex);glBindTexture(GL_TEXTURE_RECTANGLE_ARB,fboTex);// 设定纹理参数glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);// 这里在显卡上分配FBO纹理的贮存空间,每个元素的初始值是0;glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,texSize,texSize,0,GL_RGBA,GL_FLOAT,0);// 分配数据纹理的显存空间glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MIN_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_MAG_FILTER, GL_NEAREST);glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_S, GL_CLAMP);glTexParameteri(GL_TEXTURE_RECTANGLE_ARB, GL_TEXTURE_WRAP_T, GL_CLAMP);glTexEnvf(GL_TEXTURE_ENV,GL_TEXTURE_ENV_COLOR,GL_DECAL);glTexImage2D(GL_TEXTURE_RECTANGLE_ARB,0,GL_RGBA32F_ARB,texSize,texSize,0,GL_RGBA,GL_FLOAT,0);//把当前的FBO对像,与FBO纹理绑定在一起glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, GL_TEXTURE_RECTANGLE_ARB,fboTex,0);// 把本地数据传输到显卡的纹理上。glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);glTexSubImage2D(GL_TEXTURE_RECTANGLE_ARB,0,0,0,texSize,texSize,GL_RGBA,GL_FLOAT,data);//--------------------begin-------------------------//以下代码是渲染一个大小为texSize * texSize矩形,//其作用就是把纹理中的数据,经过处理后,保存到帧缓冲中去,//由于用到了离屏渲染,这里的帧缓冲区指的就是FBO纹理。//在这里,只是简单地把数据从纹理直接传送到帧缓冲中,//没有对这些流过GPU的数据作任何处理,但是如果我们会用CG、//GLSL等高级着色语言,对显卡进行编程,便可以在GPU中//截获这些数据,并对它们进行任何我们所想要的复杂运算。//这就是GPGPU技术的精髓所在。问题讨论:www.physdev.comglColor4f(1.00f,1.00f,1.00f,1.0f);glBindTexture(GL_TEXTURE_RECTANGLE_ARB,tex);glEnable(GL_TEXTURE_RECTANGLE_ARB);glBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0);glTexCoord2f(texSize, 0.0); glVertex2f(texSize, 0.0);glTexCoord2f(texSize, texSize); glVertex2f(texSize, texSize);glTexCoord2f(0.0, texSize); glVertex2f(0.0, texSize);glEnd();//--------------------end------------------------// 从帧缓冲中读取数据,并把数据保存到result数组中。glReadBuffer(GL_COLOR_ATTACHMENT0_EXT);glReadPixels(0, 0, texSize, texSize,GL_RGBA,GL_FLOAT,result);// 显示最终的结果printf("Data before roundtrip:/n");for (i=0; i<texsize*texsize*4; i++)="" printf("%f/n",data[i]);printf("Data after roundtrip:/n");for (i=0; i<texsize*texsize*4; i++)="" printf("%f/n",result[i]);// 释放本地内存free(data);free(result);// 释放显卡内存glDeleteFramebuffersEXT (1,&fb);glDeleteTextures (1,&tex);glDeleteTextures(1,&fboTex);return 0;}

你可以在这里下载到为ATI显卡写的另一个版本。


        --------------CPU----------------      -------------GPU------------|                               |      |                          ||   data arr:                   |      |  texture:                ||    [][][][][][][][][]     --------------> [][][]                ||                               |      |    [][][]                ||                               |      |    [][][]                ||                               |      |           //             ||   result:                     |      |              FBO:        ||    [][][][][][][][][]         |      |              [][][]      ||                              <-----------------     [][][]      ||                               |      |              [][][]      ||-------------------------------|      |--------------------------|

以上代码是理解GPU编程的基础,如果你完全看得懂,并且能对这代码作简单的修改运用的话,那恭喜你,你已经向成功迈进了一大步,并可以继续往下看,走向更深入的学习了。但如看不懂,那回头再看一编吧。






Back to top






GPGPU 概念 2:内核(Kernels) = 着色器(shaders)


在这一章节中,我们来讨论GPU和CPU两大运算模块最基本的区别,以及理清一些算法和思想。一但我们弄清楚了GPU是如何进行数据并行运算的,那我们要编写一个自已的着色程序,还是比较容易的。



面向循环的CPU运算 vs. 面向内核的GPU数据并行运算


让我们来回忆一下我们所想要解决的问题:y = y + alpha* x; 在CPU上,通常我们会使用一个循环来遍历数组中的每个元素。如下:


for (int i=0; i<n; i++)="" dataY[i] = dataY[i] + alpha * dataX[i];

每一次的循环,都会有两个层次的运算在同时运作:在循环这外,有一个循环计数器在不断递增,并与我们的数组的长度值作比较。而在循环的内部,我们利用循环计数器来确定数组的一个固定位置,并对数组该位置的数据进行访问,在分别得到两个数组该位置的值之后,我们便可以实现我们所想要的运算:两个数组的每个元素相加了。这个运算有一个非常重要的特点:那就是我们所要访问和计算的每个数组元数,它们之间是相互独立的。这句话的意思是:不管是输入的数组,还是输出结果的数组,对于同一个数组内的各个元素是都是相互独立的,我们可以不按顺序从第一个算到最后一个,可先算最后一个,再算第一个,或在中间任意位置选一个先算,它得到的最终结果是不变的。如果我们有一个数组运算器,或者我们有N个CPU的话,我们便可以同一时间把整个数组给算出来,这样就根本不需要一个外部的循环。我们把这样的示例叫做SIMD(single instruction multiple data)。现在有一种技术叫做“partial loop unrolling”就是让允许编译器对代码进行优化,让程序在一些支持最新特性(如:SSE , SSE2)的CPU上能得到更高效的并行运行。


在我们这个例子中,输入数数组的索引与输出数组的索引是一样,更准确地说,是所有输入数组下标,都与输出数组的下标是相同的,另外,在对于两个数组,也没有下标的错位访问或一对多的访问现像,如:y[i] = -x[i-1] + 2*x[[i] - x[i+1] 。这个公式可以用一句不太专业的语言来描术:“组数Y中每个元素的值等于数组X中对应下标元素的值的两倍,再减去该下标位置左右两边元素的值。”


在这里,我们打算使用来实现我们所要的运算的GPU可编程模块,叫做片段管线(fragment pipeline),它是由多个并行处理单元组成的,在GeFore7800GTX中,并行处理单元的个数多达24个。在硬件和驱动逻辑中,每个数据项会被自动分配到不同的渲染线管线中去处理,到底是如何分配,则是没法编程控制的。从概念观点上看,所有对每个数据顶的运算工作都是相互独立的,也就是说不同片段在通过管线被处理的过程中,是不相互影响的。在前面的章节中我们曾讨论过,如何实现用一个纹理来作为渲染目标,以及如何把我们的数组保存到一个纹理上。因此这里我们分析一下这种运算方式:片段管线就像是一个数组处理器,它有能力一次处理一张纹理大小的数据。虽然在内部运算过程中,数据会被分割开来然后分配到不同的片段处理器中去,但是我们没办法控制片段被处理的先后顺序,我们所能知道的就是“地址”,也就是保存运算最终结果的那张纹理的纹理坐标。我们可能想像为所有工作都是并行的,没有任何的数据相互依赖性。这就是我们通常所说的数据并行运算(data-paralel computing)。


现在,我们已经知道了解决问题的核心算法,我们可以开始讨论如何用可编程片段管线来编程实现了。内核,在GPU中被叫做着色器。所以,我们要做的就是写一个可能解决问题的着色器,然后把它包含在我们的程序中。在本教程程中,我们会分别讨论如何用CG着色语言及GLSL着色语言来实现,接下来两个小节就是对两种语言实现方法的讨论,我们只要学会其中一种方法就可以了,两种语言各有它自已的优缺点,至于哪个更好一点,则不是本教程所要讨论的范围。



用CG着色语言来编写一个着色器


为了用CG语言来着色渲染,我们首先要来区分一下CG着色语言和CG运行时函数,前者是一门新的编程语言,所写的程序经编译后可以在GPU上运行,后者是C语言所写的一系列函数,在CPU上运算,主要是用来初始化环境,把数据传送给GPU等。在GPU中,有两种不同的着色,对应显卡渲染流水线的两个不同的阶段,也就是顶点着色和片段着色。本教程中,顶点着色阶段,我们采用固定渲染管线。只在片段着色阶段进行编程。在这里,使用片段管线能更容易解决我们的问题,当然,顶点着色也会有它的高级用途,但本文不作介绍。另外,从传统上讲,片段着色管线提供更强大的运算能力。


让我们从一段写好了的CG着色代码开始。回忆一下CPU内核中包含的一些算法:在两个包含有浮点数据的数组中查找对应的值。我们知道在GPU中纹理就等同于CPU的数组,因此在这里我们使用纹理查找到代替数组查找。在图形运算中,我们通过给定的纹理坐标来对纹理进行采样。这里有一个问题,就是如何利用硬件自动计算生成正确的纹理坐标。我们把这个问题压后到下面的章节来讨论。为了处理一些浮点的常量,我们有两种处理的方法可选:我们可以把这些常量包含在着色代码代中,但是如果要该变这些常量的值的话,我们就得把着色代码重新编译一次。另一种方法更高效一点,就是把常量的值作为一个uniform参数传递给GPU。uniform参数的意思就是:在整个渲染过程中值不会被改变的。以下代码就是采用较高较的方法写的。


                              float saxpy (float2 coords : TEXCOORD0,uniform sampler2D textureY,uniform sampler2D textureX,uniform float alpha ) : COLOR {float result;float yval=y_old[i];              float y = tex2D(textureY,coords);float xval=x[i];                  float x = tex2D(textureX,coords);y_new[i]=yval+alpha*xval;         result = y + alpha * x;return result;}

从概念上讲,一个片段着色器,就是像上像这样的一段小程序,这段代码在显卡上会对每个片段运行一编。在我们的代码中,程序被命名为saxpy。它会接收几个输入参数,并返回一个浮点值。用作变量复制的语法叫做语义绑定(semantics binding):输入输出参数名称是各种不同的片段静态变量的标识,在前面的章节中我们把这个叫“地址”。片段着色器的输出参数必须绑定为COLOR语义,虽然这个语义不是很直观,因为我们的输出参数并不是传统作用上颜色,但是我们还是必须这样做。绑定一个二分量的浮点元组(tuple ,float2)到TEXCOORD0语义上,这样便可以在运行时为每个像素指定一对纹理坐标。对于如何在参数中定义一个纹理样本以及采用哪一个纹理采样函数,这就要看我们种用了哪一种纹理对像,参考下表:















 texture2Dtexture rectangle
样本定义uniform sampler2Duniform samplerRECT
纹理查找函数tex2D(name, coords)texRECT(name, coords)

如果我们使用的是四通道的纹理而不是LUMINANCE格式的纹理,那们只须把上面代码中的用来保存纹理查询结果的浮点型变量改为四分量的浮点变量(float4 )就可以了。由于GPU具有并行运算四分量数的能力,因此对于使用了rectangle为对像的RGBA格式纹理,我们可以采用以下代码:


float4 saxpy (float2 coords : TEXCOORD0,uniform samplerRECT textureY,uniform samplerRECT textureX,uniform float alpha ) : COLOR {float4 result;float4 y = texRECT(textureY,coords);float4 x = texRECT(textureX,coords);result = y + alpha*x;  // equivalent: result.rgba=y.rgba+alpha*x.rgba //         or: result.r=y.r+alpha*x.y; result.g=...return result;}

我们可以把着色代码保存在字符数组或文本文件中,然后通过OpenGL的CG运行时函数来访问它们。



建立CG运行环境


在这一小节,中描术了如何在OpenGL应用程序中建立Cg运行环境。首先,我们要包含CG的头文件(#include ),并且把CG的库函数指定到编译连接选项中,然后声明一些变量。


// Cg varsCGcontext cgContext;CGprofile fragmentProfile;CGprogram fragmentProgram;CGparameter yParam, xParam, alphaParam;char* program_source = "float saxpy( [....] return result; } ";

CGcontext  是一个指向CG运行时组件的入口指针,由于我们打算对片段管线进行编程,因此我们要一个fragment profile,以及一个程序Container。为了简单起见,我们还声明了三个句柄,分别对应了着色程序中的三个没有语义的入口参数。我们用一个全局的字符串变量来保存前面所写好的着色代码。现在就把所有的CG初始化工作放在一个函数中完成。这里只作了最简单的介绍,详细的内容可以查看CG手册,或者到Cg Toolkit page.网页上学习一下。


译注:对于CG入门,可以看一下《CG编程入门》这篇文章:http://www.physdev.com/phpbb/cms_view_article.PHP?aid=7


void initCG(void) {// set up CgcgContext = cgCreateContext();fragmentProfile = cgGLGetLatestProfile(CG_GL_FRAGMENT);cgGLSetOptimalOptions(fragmentProfile);// create fragment programfragmentProgram = cgCreateProgram (cgContext,CG_SOURCE,program_source, fragmentProfile,"saxpy",NULL);// load programcgGLLoadProgram (fragmentProgram);// and get parameter handles by nameyParam = cgGetNamedParameter (fragmentProgram,"textureY");xParam = cgGetNamedParameter (fragmentProgram,"textureX");alphaParam = cgGetNamedParameter (fragmentProgram,"alpha");}

用OpenGL着色语言来编写一个着色器


使用OpenGL的高级着色语言,我们不需要另外引入任何的头文件或库文件,因因它们在安装驱动程序的时候就一起被建立好了。三个OpenGL的扩展:(ARB_shader_objectsARB_vertex_shader 和 ARB_fragment_shader)定义了相关的接口函数。它的说明书(specification )中对语言本身作了定义。两者,API和GLSL语言,现在都是OpenGL2.0内核的一个重要组成部份。但是如果我们用的是OpenGL的老版本,就要用到扩展。


我们为程序对像定义了一系列的全局变量,包括着色器对像及数据变量的句柄,通过使用这些句柄,我们可以访问着色程序中的变量。前面两个对像是简单的数据容器,由OpenGL进行管理。一个完整的着色程序是由顶点着色和片段着色两大部份组成的,每部分又可以由多个着色程序组成。


// GLSL varsGLhandleARB programObject;GLhandleARB shaderObject;GLint yParam, xParam, alphaParam;

编写着色程序和使用Cg语言是相似的,下面提供了两个GLSL的例子,两个主程序的不同之处在于我们所采用的纹理格式。变量的类型入关键字与CG有很大的不同,一定要按照OpenGL的定义来写。


// shader for luminance data          |   // shader for RGBA data // and texture rectangles             |   // and texture2D|uniform samplerRect textureY;         |   uniform sampler2D textureY;uniform samplerRect textureX;         |   uniform sampler2D textureX;uniform float alpha;                  |   uniform float alpha;|void main(void) {                     |    void main(void) {float y = textureRect(            |       vec4 y = texture2D(textureY,                  |              textureY, gl_TexCoord[0].st).x;      |             gl_TexCoord[0].st);float x = textureRect(            |       vec4 x = texture2D(textureX,                  |              textureXgl_TexCoord[0].st).x;      |             gl_TexCoord[0].st);gl_FragColor.x =                  |       gl_FragColor = y + alpha*x;              |              y + alpha*x;}                                     |   }


下面代码就是把所有对GLSL的初始化工作放在一个函数中实现,GLSL API是被设计成可以模拟传统的编译及连接过程,更多的细节,请参考橙皮书(Orange Book),或者查找一些GLSL的教程来学习一下,推荐到Lighthouse 3D's GLSL tutorial 网站上看一下


void initGLSL(void) {// create program objectprogramObject = glCreateProgramObjectARB();// create shader object (fragment shader) and attach to programshaderObject = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB);glAttachObjectARB (programObject, shaderObject);// set source to shader objectglShaderSourceARB(shaderObject, 1, &program_source, NULL);// compileglCompileShaderARB(shaderObject);// link program object togetherglLinkProgramARB(programObject);// Get location of the texture samplers for future useyParam = glGetUniformLocationARB(programObject, "textureY");xParam = glGetUniformLocationARB(programObject, "textureX");alphaParam = glGetUniformLocationARB(programObject, "alpha");}





Back to top






GPGPU 概念3:运算 = 绘图


在这一章节里,我们来讨论一下如何把本教程前面所学到的知识拼凑起来,以及如何使用这些知识来解决前面所提出的加权数组相加问题:y_new = y_old + alpha * x 。关于执行运算的部份,我们把所有运算都放在performComputation()这个函数中实现。一共有四个步骤:首先是激活内核,然后用着色函数来分配输入输出数组的空间,接着是通过渲染一个适当的几何图形来触发GPU的运算,最后一步是简单验证一下我们前面所列出的所有的基本理论。



准备好运算内核


使用CG运行时函数来激活运算内核就是显卡着色程序。首先用enable函数来激活一个片段profile,然后把前面所写的着色代码传送到显卡上并绑定好。按规定,在同一时间内只能有一个着色器是活动的,更准确的说,是同一时间内,只能分别激活一个顶点着色程序和一个片段着色程序。由于本教程中采用了固定的顶点渲染管线,所以我们只关注片段着色就行了,只需要下面两行代码便可以了。


// enable fragment profilecgGLEnableProfile(fragmentProfile);// bind saxpy programcgGLBindProgram(fragmentProgram);

如果使用的是GLSL着色语言,这一步就更容易实现了,如果我们的着色代码已以被成功地编译连接,那么剩下我们所需要做的就只是把程序作为渲染管线的一部分安装好,代码如下:


glUseProgramObjectARB(programObject);	

建立用于输入的数组和纹理


在CG环境中,我们先要把纹理的标识与对应的一个uniform样本值关联起来,然后激活该样本。这样该纹理样本便可以在CG中被直接使用了。


// enable texture y_old (read-only)cgGLSetTextureParameter(yParam, y_oldTexID);cgGLEnableTextureParameter(yParam);// enable texture x (read-only)cgGLSetTextureParameter(xParam, xTexID);cgGLEnableTextureParameter(xParam);// enable scalar alphacgSetParameter1f(alphaParam, alpha);

但在GLSL中,我们必须把纹理与不同的纹理单元绑定在一起(在CG中,这部分由程序自动完成),然后把这些纹理单元传递给我们的uniform参数。


// enable texture y_old (read-only)glActiveTexture(GL_TEXTURE0);glBindTexture(textureParameters.texTarget,yTexID[readTex]);glUniform1iARB(yParam,0); // texunit 0// enable texture x (read-only)glActiveTexture(GL_TEXTURE1);	glBindTexture(textureParameters.texTarget,xTexID);glUniform1iARB(xParam, 1); // texunit 1// enable scalar alphaglUniform1fARB(alphaParam,alpha);

建立用于输出的纹理及数组


定义用于输出的纹理,从本质上讲,这和把数据传输到一个FBO纹理上的操作是一样的,我们只需要指定OpenGL函数参数的特定意义就可以了。这里我们只是简单地改变输出的方向,也就是,把目标纹理与我们的FBO绑定在一起,然后使用标准的GL扩展函数来把该FBO指为渲染的输出目标。


// attach target texture to first attachment pointglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT, texture_target, y_newTexID, 0);// set the texture as render targetglDrawBuffer (GL_COLOR_ATTACHMENT0_EXT);

准备运算


让们暂时先来回顾一下到目前为止,我们所做过了的工作:我们实现了目标像素、纹理坐标、要绘制的图形三者元素一一对应的关系。我们还写好了一个片段着色器,用来让每个片段渲染的时候都可以运行一次。现在剩下来还要做的工作就是:绘制一个“合适的几何图形” ,这个合适的几何图形,必须保证保存在目标纹理中的数据每个元素就会去执行一次我们的片段着色程序。换句话来说,我们必须保证纹理中的每个数据顶在片段着色中只会被访一次。只要指定好我们的投影及视口的设置,其它的工作就非常容易:我们所需要的就只是一个刚好能覆盖整个视口的填充四边形。我们定义一个这样的四边形,并调用标准的OpenGL函数来对其进行渲染。这就意味着我们要直接指定四边形四个角的顶点坐标,同样地我们还要为每个顶点指定好正确的纹理坐标。由于我们没有对顶点着色进行编程,程序会把四个顶点通过固定的渲染管线传输到屏幕空间中去。光册处理器(一个位于顶点着色与片段着色之间的固定图形处理单元)会在四个顶点之间进行插值处理,生成新的顶点来把整个四边形填满。插值操作除了生成每个插值点的位置之外,还会自动计算出每个新顶点的纹理坐标。它会为四边形中每个像素生成一个片段。由于我们在写片段着色器中绑定了相关的语义,因此插值后的片段会被自动发送到我们的片段着色程序中去进行处理。换句话说,我们渲染的这个简单的四边形,就可以看作是片段着色程序的数据流生成器。由于目标像素、纹理坐标、要绘制的图形三者元素都是一一对应的,从而我们便可以实现:为数组每个输出位置触发一次片段着色程序的运行。也就是说通过渲染一个带有纹理的四边形,我们便可以触发着色内核的运算行,着色内核会为纹理或数组中的每个数据项运行一次。


使用  texture rectangles 纹理坐标是与像素坐标相同的,我样使用下面一小段代码便可以实现了。


// make quad filled to hit every pixel/texelglPolygonMode(GL_FRONT,GL_FILL);// and render quadglBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0);glTexCoord2f(texSize, 0.0); glVertex2f(texSize, 0.0);glTexCoord2f(texSize, texSize); glVertex2f(texSize, texSize);glTexCoord2f(0.0, texSize); glVertex2f(0.0, texSize);glEnd();

如果使用 texture2D ,就必须单位化所有的纹理坐标,等价的代码如下:


// make quad filled to hit every pixel/texelglPolygonMode(GL_FRONT,GL_FILL);// and render quadglBegin(GL_QUADS);glTexCoord2f(0.0, 0.0); glVertex2f(0.0, 0.0);glTexCoord2f(1.0, 0.0); glVertex2f(texSize, 0.0);glTexCoord2f(1.0, 1.0);glVertex2f(texSize, texSize);glTexCoord2f(0.0, 1.0); glVertex2f(0.0, texSize);glEnd();

这里提示一下那些做高级应用的程序员:在我们的着色程序中,只用到了一组纹理坐标,但是我们也可以为每个顶点定义多组不同的纹理坐标,相关的更多细节,可以查看一下glMultiTexCoord()函数的使用






Back to top






GPGPU 概念 4: 反馈


当运算全部完成之后,的、得到的结果会被保存在目标纹理y_new中。



多次渲染传递.


在一些通用运算中,我们会希望把前一次运算结果传递给下一个运算用来作为后继运算的输入变量。但是在GPU中,一个纹理不能同时被读写,这就意味着我们要创建另外一个渲染通道,并给它绑定不同的输入输出纹理,甚至要生成一个不同的运算内核。有一种非常重要的技术可以用来解决这种多次渲染传递的问题,让运算效率得到非常好的提高,这就是“乒乓”技术。



关于乒乓技术


乒乓技术,是一个用来把渲染输出转换成为下一次运算的输入的技术。在本文中(y_new = y_old + alpha * x) ,这就意味我们要切换两个纹理的角色,y_new 和 y_old 。有三种可能的方法来实现这种技术(看一下以下这篇论文Simon Green's FBO slides ,这是最经典的资料了):




  • 为每个将要被用作渲染输出的纹理指定一个绑定点,并使用函数glBindFramebufferEXT()来为每个渲染通道绑定一个不同的FBO. 
  • 只使用一个FBO,但每次通道渲染的时候,使用函数glBindFramebufferEXT()来重新绑定渲染的目标纹理。 
  • 使用一个FBO和多个绑定点,使用函数glDrawBuffer()来交换它们。

由于每个FBO最多有4个绑定点可以被使用,而且,最后一种方法的运算是最快的,我们在这里将详细解释一下,看看我们是如何在两个不同的绑定点之间实现“乒乓” 的。


要实现这个,我们首先需要一组用于管理控制的变量。


// two textures identifiers referencing y_old and y_newGLuint yTexID[2];// ping pong management varsint writeTex = 0;int readTex = 1;GLenum attachmentpoints[] = { GL_COLOR_ATTACHMENT0_EXT, GL_COLOR_ATTACHMENT1_EXT };

在运算其间,我们只需要做的就是给内核传递正确的参数值,并且每次运算都要交换一次组组的索引值: 


// attach two textures to FBOglFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachmentpoints[writeTex], texture_Target, yTexID[writeTex], 0);glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, attachmentpoints[readTex], texture_Target, yTexID[readTex], 0);// enable fragment profile, bind program [...]// enable texture x (read-only) and uniform parameter [...]// iterate computation several timesfor (int i=0; i<numiterations; i++)="" {="" // set render destinationglDrawBuffer (attachmentpoints[writeTex]);// enable texture y_old (read-only)cgGLSetTextureParameter(yParam, yTexID[readTex]);cgGLEnableTextureParameter(yParam);// and render multitextured viewport-sized quad// swap role of the two textures (read-only source becomes // write-only target and the other way round):swap();}





Back to top






把所有东西放在一起



对本文附带源代码的一个简要说明


在附带的代码例子中,使用到了本文所有阐述过的所有概念,主要实现了以下几个运算: 




  • 为每个数组生成一个浮点的纹理。 
  • 把初始化的数据传输到纹理中去 。 
  • 使用CG或者GLSL来生成一个片段着色器。 
  • 一个多次重复运算的模块,主要是用来演试“乒乓”技术。 
  • 把最终的运算结果返回到主内存中。 
  • 把结果与CPU的参考结果进行比较。

执行过行中的可变化部份


在代码中,我们使用了一系列的结构体来保存各种可能的参数,主要是为了方便OpenGL的调用,例如:不同类型的浮点纹理扩展,不同的纹理格式,不同的着色器之间的细微差别,等等。下面这段代码就是这样一个结构体的示例,采用LUMINANCE格式,RECTANGLES纹理,及NV_float_buffer的扩展。


rect_nv_r_32.name              = "TEXRECT - float_NV - R - 32";rect_nv_r_32.texTarget         = GL_TEXTURE_RECTANGLE_ARB;rect_nv_r_32.texInternalFormat = GL_FLOAT_R32_NV;rect_nv_r_32.texFormat         = GL_LUMINANCE;rect_nv_r_32.shader_source     = "float saxpy ("/"in float2 coords : TEXCOORD0,"/"uniform samplerRECT textureY,"/"uniform samplerRECT textureX,"/"uniform float alpha )  : COLOR {"/"float y = texRECT (textureY, coords);"/"float x = texRECT (textureX, coords);"/"return y+alpha*x; }";

为了给不同的情况取得一个合适的工作版本,我们只须要查找和替换就可以了。或者使用第二个命令行参数如:rect_nv_r_32。在应用程序中,一个全局变量textureParameters 指向我们实现要使用的结构体。



命令行参数


在程序中,使用命令行参数来对程序进行配置。如果你运行该程序而没带任何参数的话,程序会输出一个对各种不同参数的解释。提醒大家注意的是:本程序对命令行参数的解释是不稳定的,一个不正确的参数有可能会造成程序的崩溃。因此我强烈建义大家使用输出级的参数来显示运算的结果,这样可以降低出现问题的可能性,尤其是当你不相信某些运算错误的时候。请查看包含在示例中的批处理文件。



测试模式


本程序可以用来对一个给定的GPU及其驱动的 结合进行测试,主要是测试一下,看看哪种内部格式及纹理排列是可以在FBO扩展中被组合在一起使用的。示例中有一个批处理文件叫做:run_test_*.bat,是使用各种不同的命令行参数来运行程序,并会生成一个报告文件。如果是在LINUX下,这个文件也可能当作一个shell脚本来使用,只需要稍作修改就可以了。这ZIP文档中包含有对一些显卡测试后的结果。



基准模式


这种模式被写进程序中,完全是为了好玩。它可以对不同的问题产成一个运算时序,并在屏幕上生成MFLOP/s速率图,和其它的一些性能测试软件一样。它并不代表GPU运算能力的最高值,只是接近最高值的一种基准性能测试。想知道如何运行它的话,请查看命令行参数。






Back to top






附言



简单对比一下Windows 和 Linux,NVIDIA 和 ATI 之间的差别


对于NVIDIA的显卡,不管是Windows还是Linux,它们都提供了相同的函数来实现本教程中的例子。但如果是ATI的显卡,它对LINUX的支持就不是很好。因此如果是ATI显卡,目前还是建义在Windows下使用。


看一看这片相关的文章  table summarizing renderable texture formats on various hardware.


本文中提供下载的源代码,是在NV4X以上的显卡上编译通过的。对于ATI的用户,则要作以下的修改才行:在transferToTexture() 函数中,把NVIDIA相应部份的代码注释掉,然使用ATI版本的代码,如这里所描述的。


Cg 1.5 combined with the precompiled freeglut that ships with certain Linus distributions somehow breaks "true offscreen rendering" since a totally meaningless empty window pops up. There are three workarounds: Live with it. Use "real GLUT" instead of freeglut. Use plain X as described in the OpenGL.org wiki (just leave out the mapping of the created window to avoid it being displayed).



问题及局限性




  • 对于ATI显卡,当我们把数据传送到纹理中去时,如果使用glTexSubImage2D(),会产生一个非常奇怪的问题:就是原本是RGBA排列的数据,会被改变为BGRA格式。这是一个已得到确认的BUG,希望在以后的版本中能得到修正,目前只能用glDrawPixels() 来代替。 
  • 而对于NV3X系列显卡,如果想用glDrawPixels() ,则要求一定要在GPU中绑定一个着色程序。因此这里用glTexSubImage()函数代替(其实对于所有的NVIDIA 的显卡,都推荐使用该函数)。 
  • ATI显卡,在GLSL中不支持rectangles纹理采样,甚至这样的着色代码没法被编译通过。samplerRect 或sampler2DRect 被指定为保留的关键字,ARB_texture_rextangle的扩展说明书中得到定义,但驱动没有实现对它们的支持。可以用CG来代替。 
  • 在ATI中,当我们使用glDrawPixels() 下载一个纹理的时候,如果纹理是被enable的,则会导致下载失败,这不是一个BUG,但是也是一个有争议性的问题,因为这样会使程序难以调试。 
  • 对于NVIDIA的显卡,我们不能把纹理渲染到纹理最大值的最后一行中去。也就是说,尽管我们用函数glGetIntegerv(GL_MAX_TEXTURE_SIZE,&maxtexsize); 得到的值是4096,但是你也只能渲染一张4095 x 4095 纹理。这是一个已知的BUG,同样也希望以后能得到修正。

检查OpenGL的错误


高度推荐大家在代码中经常使用以下函数来检测OpenGL运行过程中产生的错误。


void checkGLErrors(const char *label) {GLenum errCode;const GLubyte *errStr;if ((errCode = glGetError()) != GL_NO_ERROR) {errStr = gluErrorString(errCode);printf("OpenGL ERROR: ");printf((char*)errStr);printf("(Label: ");printf(label);printf(")/n.");}}

检查FBO中的错误


EXT_framebuffer_object 扩展,定义了一个很好用的运行时Debug函数。这里只列出了它的一些常见的反回值作参考,要详细解释这些返回信息,请查看规格说明书的framebuffer completeness 部分。


bool checkFramebufferStatus() {GLenum status;status=(GLenum)glCheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);switch(status) {case GL_FRAMEBUFFER_COMPLETE_EXT:return true;case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT_EXT:printf("Framebuffer incomplete,incomplete attachment/n");return false;case GL_FRAMEBUFFER_UNSUPPORTED_EXT:printf("Unsupported framebuffer format/n");return false;case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT_EXT:printf("Framebuffer incomplete,missing attachment/n");return false;case GL_FRAMEBUFFER_INCOMPLETE_DIMENSIONS_EXT:printf("Framebuffer incomplete,attached images must have same dimensions/n");return false;case GL_FRAMEBUFFER_INCOMPLETE_FORMATS_EXT:printf("Framebuffer incomplete,attached images must have same format/n");return false;case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER_EXT:printf("Framebuffer incomplete,missing draw buffer/n");return false;case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER_EXT:printf("Framebuffer incomplete,missing read buffer/n");return false;}return false;}

检查CG的错误


在CG中检查错误有一些细微的不同,一个自写入的错误处理句柄被传递给CG的错误处理回调函数。


// register the error callback once the context has been createdcgSetErrorCallback(cgErrorCallback);// callback functionvoid cgErrorCallback(void) {CGerror lastError = cgGetError();if(lastError) {printf(cgGetErrorString(lastError));printf(cgGetLastListing(cgContext));}}

检查GLSL的错误


使用以下的函数来查看编译的结果:


/*** copied from * http://www.lighthouse3d.com/opengl/glsl/index.php?oglinfo*/void printInfoLog(GLhandleARB obj) {int infologLength = 0;int charsWritten  = 0;char *infoLog;glGetObjectParameterivARB(obj, GL_OBJECT_INFO_LOG_LENGTH_ARB, &infologLength);if (infologLength > 1) {infoLog = (char *)malloc(infologLength);glGetInfoLogARB(obj, infologLength, &charsWritten, infoLog);printf(infoLog);printf("/n");free(infoLog);}}

大多数情况下,你可以使用以上查询函数,详细内容可以查看一下GLSL的规格说明书。还有另一个非常重要的查询函数,是用来检查程序是否可以被连接:


GLint success;glGetObjectParameterivARB(programObject, GL_OBJECT_LINK_STATUS_ARB, &success);if (!success) {printf("Shader could not be linked!/n");}





Back to top






感谢


Writing this tutorial would have been impossible without all contributors at the GPGPU.org forums. They answered all my questions patiently, and without them, starting to work in the GPGPU field (and consequently, writing this tutorial) would have been impossible. I owe you one, guys!


如果没有GPGPU.org论坛所作出的贡献,可能也就没有这篇论文的产生。他们非常耐心地回答了我所有的问题,在大家的帮助下,我才踏入GPGPU的大门,也因此才有了这篇文章,感谢多位朋友:


Andrew Corrigan, Wojciech Jaskowski, Matthias Miemczyk, Stephan Wagner and especially Thomas Rohkämper were invaluably helpful in proof-reading the tutorial and beta-testing the implementation. Thanks a lot!






版权及声明


本译文可以自由转载,要求保留原作者信息并注明文章出自物理开发网:www.physdev.com


中文原文: http://www.physdev.com/articles/GPGPU_math_Tutorial.htm


英文原文: http://www.mathematik.uni-dortmund.de/~goeddeke/gpgpu/tutorial.html


 


(c) 2005,2006 Dominik Göddeke, University of Dortmund, Germany.


The example code for this tutorial is released under a weakened version of the zlib/libPNG licence which basically says: Feel free to use the code in any way you want, but do not blame me if it does not work.


This software is provided 'as-is', without any express or implied warranty. In no event will the author be held liable for any damages arising from the use of this software. 
Permission is granted to anyone to use this software for any purpose, including commercial applications, and to alter it and redistribute it freely.

Feedback (preferably by e-mail) is appreciated!



本文转自 
http://www.physdev.com/articles/GPGPU_math_Tutorial.htm#setupgl4
0
0

参考知识库

img

PHP知识库

img

.NET知识库

img

Linux知识库

img

C语言知识库

img

操作系统知识库

img

软件测试知识库

img

Docker知识库

img

算法与数据结构知识库

猜你在找
3D游戏引擎之GPU渲染(DX篇)
OpenGL视频教程
OpenGL实现RGB到YUV的转化
OpenGL-实现视频播放(FFMpeg)
Windows系统内核-保护模式
3D数学基础 图形与游戏开发的学习 六多坐标系
3D数学基础及图形与游戏开发的学习 三
OpenGL学习脚印 坐标和变换的数学基础math-coordinates and transformations
AES加密算法的数学基础
RSA的数学基础欧拉函数与欧拉定理
查看评论

  暂无评论

发表评论
  • 用 户 名:
  • yz2010
  • 评论内容:
  • 插入代码
  •   
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
核心技术类目
全部主题 Hadoop AWS 移动游戏 Java Android iOS Swift 智能硬件 Docker OpenStackVPN Spark ERP IE10 Eclipse CRM JavaScript 数据库 Ubuntu NFC WAP jQueryBI HTML5 Spring Apache .NET API HTML SDK IIS Fedora XML LBS UnitySplashtop UML components Windows Mobile Rails QEMU KDE Cassandra CloudStackFTC coremail OPhone CouchBase 云计算 iOS6 Rackspace Web App SpringSide MaemoCompuware 大数据 aptech Perl Tornado Ruby Hibernate ThinkPHP HBase Pure SolrAngular Cloud Foundry Redis Scala Django Bootstrap
  • 个人资料
  •  
    i53nd
     
    • 访问:70527次
    • 积分:1097
    • 等级: 
    • 排名:千里之外
    • 原创:23篇
    • 转载:45篇
    • 译文:0篇
    • 评论:3条
  • 文章分类
  • C#(1)
  • c++(15)
  • linux(1)
  • OpenGL(9)
  • windows相关(5)
  • 图形学(11)
  • 文章存档
    • 2009年03月(19)
    • 2009年02月(1)
    • 2009年01月(20)
    • 2008年11月(1)
    • 2008年08月(6)
      展开
  • 阅读排行
  • Rate This Topic(5691)
  • ccproxy 6.61 注册机(纯绿/傻瓜/真正去除线程限制)(4811)
  • LINK : fatal error LNK1104: cannot open file 'LIBCD.lib'(3630)
  • Error 1327 : Invalid drive G: error(2769)
  • 用glRotatef函数转动对象(2483)
  • 代理服务器软件CCProxy支持Permeo Security Driver代理(1938)
  • 修改windows远程桌面端口(1462)
  • 修复“无法切换输入法”(1354)
  • Framebuffer Object, FBO(1079)
  • 在Windows下使用OpenGL 2.0的API(包括GLSL)(1064)
  • 评论排行
  • What is svchost.exe And Why Is It Running? :: the How-To Geek(1)
  • 使用模板来实现多态(1)
  • Error 1327 : Invalid drive G: error(1)
  • Rate This Topic(0)
  • Framebuffer objects(0)
  • OpenGL Frame Buffer Object 101(0)
  • FBO question(0)
  • programming blend function(0)
  • 修改windows远程桌面端口(0)
  • OpenGL ES系列 之 基本-3:Hello,EGL(0)
  • 推荐文章
    • * 一个想法照进现实-《IT连》创业项目:关于团队组建
    • * CSDN日报20170326——《谈谈程序员解决问题的能力》
    • * 最全面总结 Android WebView与 JS 的交互方式
    • * 蓝牙DA14580开发:固件格式、二次引导和烧写
    • * 你不知道的 Android WebView 使用漏洞
  • 最新评论
  • 使用模板来实现多态

    myworld20100406:

  • Error 1327 : Invalid drive G: error

    i53nd: 解决方法(以G:为例) 在注册表里搜索"G:",并一一修正表项

  • What is svchost.exe And Why Is It Running? :: the How-To Geek

    i53nd: 原来svchost.exe就是用来加载各种dll的进程

查看全文
如若内容造成侵权/违法违规/事实不符,请联系编程学习网邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!

相关文章

  1. 关于linux的最新问题合集

    关于linux的最新问题合集 关于linux的最新问题合集&#xff0c;有技术问题&#xff0c;上 bug200.com 如何实现malloc函数[保留] 任何人都能帮助我使用系统调用&#xff08;如brk&#xff08;&#xff09;和sbrk&#xff08;&#xff09;&#xff09;实现malloc&#xff08;&a…...

    2024/4/20 15:22:05
  2. led HAL简单案例分析

    mr_raptor的专栏 专注Android系统&#xff0c;移动平台研究&#xff0c;ARM BSP开发&#xff0c;著有《深入浅出嵌入式底层软件开发》北航出版社 目录视图 摘要视图 订阅 CSDN Android客户端 下载就送50C币 又见人月神话 最流行的语言想学就学 写博文&#xff0c;传…...

    2024/4/23 0:03:49
  3. 当你不知道学啥的时候就去面试吧

    最近职业上出现一些瓶颈&#xff0c;公司事情不多&#xff0c;而且做的行业由于运营商各种政策的变化也不太看好&#xff0c;一直在考虑自己想要做的方向可是也没有考虑出个所以然。 公司没什么事情做&#xff0c;想着自己学点东西&#xff0c;可以没有目标的学习实在不长久&am…...

    2024/4/20 7:46:59
  4. 埋线双眼皮可以喝酒吗

    ...

    2024/4/28 18:54:00
  5. 埋线双眼皮可以出门吗

    ...

    2024/4/28 2:29:11
  6. Vue入门---常用指令详解

    Vue入门 Vue是一个MVVM&#xff08;Model / View / ViewModel&#xff09;的前端框架&#xff0c;相对于Angular来说简单、易学上手快&#xff0c;近两年也也别流行&#xff0c;发展速度较快&#xff0c;已经超越Angular了。比较适用于移动端&#xff0c;轻量级的框架&#xf…...

    2024/4/28 7:25:00
  7. InstallShield 教程

    InstallShield多语言安装包中如何获取用户选择的安装语言初次安装:Not Installed 修改,修复或删除:Installed 删除(卸载):REMOVE 维护或修复:Installed AND Not REMOVE 小版本升级:IS_MINOR_UPGRADE 大版本升级:IS_MAJOR_UPGRADE上述适用于InstallShield的Basic MSI工…...

    2024/4/27 23:00:39
  8. Vue常用指令详解分析

    2019独角兽企业重金招聘Python工程师标准>>> Vue入门 Vue是一个MVVM&#xff08;Model / View / ViewModel&#xff09;的前端框架&#xff0c;相对于Angular来说简单、易学上手快&#xff0c;近两年也也别流行&#xff0c;发展速度较快&#xff0c;已经超越Angular…...

    2024/4/28 20:21:33
  9. bootstrap模态框导致Y轴下拉框消失解决方法

    使用angular 的ng-include在页面中跳转&#xff0c;一个子页面中有一个模态框&#xff0c;当操作了模态框从页面跳转回来时右侧滚动条会消失&#xff0c;添加以下CSS解决 .modal {overflow-y: auto; }.modal-open {overflow: auto; } 使用bootstrap3,原先网页有Y轴滚动条,打开m…...

    2024/4/28 20:34:59
  10. angularjs 的一个图片列表指令

    这是一个基于angulsrjs 和bootstrap样式的图片列表指令 js部分代码如下 var appangular.module("test",["bane/image/showgrid.html"]).directive("baneImggrid",function(){return {restrict:EA,transclude: true,replace: false,//templateUrl…...

    2024/4/28 1:38:15
  11. 埋线双眼皮可以抽线么

    ...

    2024/4/28 1:55:59
  12. [转]How to Add Bootstrap to an Angular CLI project

    本文转自&#xff1a;https://loiane.com/2017/08/how-to-add-bootstrap-to-an-angular-cli-project/ In this article we will learn how to setup an Angular project with Bootstrap 3 or Bootstrap 4. Update May 2018: code updated to Angular v6. Stackblitz link also …...

    2024/4/29 22:44:17
  13. 埋线双眼皮可以拆了吗

    ...

    2024/4/28 12:04:30
  14. Android聊天软件开发(基于网易云IM即时通讯)——添加好友(三)

    这里先搭建viewpage+BottomNavigationView+Fragment的底部导航栏activity_tabhost.xml<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http:/…...

    2024/4/28 15:10:31
  15. 埋线双眼皮看的到线条

    ...

    2024/4/28 7:04:13
  16. 埋线埋线双眼皮看到有线头

    ...

    2024/4/28 18:38:54
  17. 埋线双眼皮开眼角恢复

    ...

    2024/4/28 4:57:59
  18. 埋线双眼皮开线的感觉

    ...

    2024/4/28 9:44:24
  19. 埋线双眼皮经常摸线

    ...

    2024/4/27 22:24:41
  20. Web应用的组件化开发(一)

    基本思路 1. 为什么要做组件化&#xff1f; 无论前端也好&#xff0c;后端也好&#xff0c;都是整个软件体系的一部分。软件产品也是产品&#xff0c;它的研发过程也必然是有其目的。绝大多数软件产品是追逐利润的&#xff0c;在产品目标确定的情况下&#xff0c;成本有两个途径…...

    2024/4/28 19:21:37

最新文章

  1. 日期类的实现,const成员

    目录 一&#xff1a;日期类实现 二&#xff1a;const成员 三&#xff1a;取地址及const取地址操作符重载 一&#xff1a;日期类实现 //头文件#include <iostream> using namespace std;class Date {friend ostream& operator<<(ostream& out, const Dat…...

    2024/5/2 0:01:29
  2. 梯度消失和梯度爆炸的一些处理方法

    在这里是记录一下梯度消失或梯度爆炸的一些处理技巧。全当学习总结了如有错误还请留言&#xff0c;在此感激不尽。 权重和梯度的更新公式如下&#xff1a; w w − η ⋅ ∇ w w w - \eta \cdot \nabla w ww−η⋅∇w 个人通俗的理解梯度消失就是网络模型在反向求导的时候出…...

    2024/3/20 10:50:27
  3. 推荐学习什么编程语言?

    选择编程语言学习时&#xff0c;除了就业因素外&#xff0c;还可以考虑以下几个方面来决定学习哪些编程语言&#xff1a; 个人兴趣与目标&#xff1a;如果你对某个特定领域感兴趣&#xff0c;比如游戏开发、数据分析、人工智能等&#xff0c;可以选择与该领域紧密相关的编程语言…...

    2024/5/1 18:09:40
  4. 整理的微信小程序日历(单选/多选/筛选)

    一、日历横向多选&#xff0c;支持单日、双日、三日、工作日等选择 效果图 wxml文件 <view class"calendar"><view class"section"><view class"title flex-box"><button bindtap"past">上一页</button&…...

    2024/5/1 13:16:44
  5. 系列学习前端之第 7 章:一文掌握 AJAX

    1、AJAX 简介 AJAX 全称为 Asynchronous JavaScript And XML&#xff08;中文名&#xff1a;阿贾克斯&#xff09;&#xff0c;就是异步的 JS 和 XML。AJAX 不是新的编程语言&#xff0c;而是一种将现有的标准组合在一起使用的新方式。AJAX 可以在浏览器中向服务器发送异步请求…...

    2024/4/30 22:50:29
  6. 【外汇早评】美通胀数据走低,美元调整

    原标题:【外汇早评】美通胀数据走低,美元调整昨日美国方面公布了新一期的核心PCE物价指数数据,同比增长1.6%,低于前值和预期值的1.7%,距离美联储的通胀目标2%继续走低,通胀压力较低,且此前美国一季度GDP初值中的消费部分下滑明显,因此市场对美联储后续更可能降息的政策…...

    2024/5/1 17:30:59
  7. 【原油贵金属周评】原油多头拥挤,价格调整

    原标题:【原油贵金属周评】原油多头拥挤,价格调整本周国际劳动节,我们喜迎四天假期,但是整个金融市场确实流动性充沛,大事频发,各个商品波动剧烈。美国方面,在本周四凌晨公布5月份的利率决议和新闻发布会,维持联邦基金利率在2.25%-2.50%不变,符合市场预期。同时美联储…...

    2024/4/30 18:14:14
  8. 【外汇周评】靓丽非农不及疲软通胀影响

    原标题:【外汇周评】靓丽非农不及疲软通胀影响在刚结束的周五,美国方面公布了新一期的非农就业数据,大幅好于前值和预期,新增就业重新回到20万以上。具体数据: 美国4月非农就业人口变动 26.3万人,预期 19万人,前值 19.6万人。 美国4月失业率 3.6%,预期 3.8%,前值 3…...

    2024/4/29 2:29:43
  9. 【原油贵金属早评】库存继续增加,油价收跌

    原标题:【原油贵金属早评】库存继续增加,油价收跌周三清晨公布美国当周API原油库存数据,上周原油库存增加281万桶至4.692亿桶,增幅超过预期的74.4万桶。且有消息人士称,沙特阿美据悉将于6月向亚洲炼油厂额外出售更多原油,印度炼油商预计将每日获得至多20万桶的额外原油供…...

    2024/4/30 18:21:48
  10. 【外汇早评】日本央行会议纪要不改日元强势

    原标题:【外汇早评】日本央行会议纪要不改日元强势近两日日元大幅走强与近期市场风险情绪上升,避险资金回流日元有关,也与前一段时间的美日贸易谈判给日本缓冲期,日本方面对汇率问题也避免继续贬值有关。虽然今日早间日本央行公布的利率会议纪要仍然是支持宽松政策,但这符…...

    2024/4/27 17:58:04
  11. 【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响

    原标题:【原油贵金属早评】欧佩克稳定市场,填补伊朗问题的影响近日伊朗局势升温,导致市场担忧影响原油供给,油价试图反弹。此时OPEC表态稳定市场。据消息人士透露,沙特6月石油出口料将低于700万桶/日,沙特已经收到石油消费国提出的6月份扩大出口的“适度要求”,沙特将满…...

    2024/4/27 14:22:49
  12. 【外汇早评】美欲与伊朗重谈协议

    原标题:【外汇早评】美欲与伊朗重谈协议美国对伊朗的制裁遭到伊朗的抗议,昨日伊朗方面提出将部分退出伊核协议。而此行为又遭到欧洲方面对伊朗的谴责和警告,伊朗外长昨日回应称,欧洲国家履行它们的义务,伊核协议就能保证存续。据传闻伊朗的导弹已经对准了以色列和美国的航…...

    2024/4/28 1:28:33
  13. 【原油贵金属早评】波动率飙升,市场情绪动荡

    原标题:【原油贵金属早评】波动率飙升,市场情绪动荡因中美贸易谈判不安情绪影响,金融市场各资产品种出现明显的波动。随着美国与中方开启第十一轮谈判之际,美国按照既定计划向中国2000亿商品征收25%的关税,市场情绪有所平复,已经开始接受这一事实。虽然波动率-恐慌指数VI…...

    2024/4/30 9:43:09
  14. 【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试

    原标题:【原油贵金属周评】伊朗局势升温,黄金多头跃跃欲试美国和伊朗的局势继续升温,市场风险情绪上升,避险黄金有向上突破阻力的迹象。原油方面稍显平稳,近期美国和OPEC加大供给及市场需求回落的影响,伊朗局势并未推升油价走强。近期中美贸易谈判摩擦再度升级,美国对中…...

    2024/4/27 17:59:30
  15. 【原油贵金属早评】市场情绪继续恶化,黄金上破

    原标题:【原油贵金属早评】市场情绪继续恶化,黄金上破周初中国针对于美国加征关税的进行的反制措施引发市场情绪的大幅波动,人民币汇率出现大幅的贬值动能,金融市场受到非常明显的冲击。尤其是波动率起来之后,对于股市的表现尤其不安。隔夜美国股市出现明显的下行走势,这…...

    2024/4/25 18:39:16
  16. 【外汇早评】美伊僵持,风险情绪继续升温

    原标题:【外汇早评】美伊僵持,风险情绪继续升温昨日沙特两艘油轮再次发生爆炸事件,导致波斯湾局势进一步恶化,市场担忧美伊可能会出现摩擦生火,避险品种获得支撑,黄金和日元大幅走强。美指受中美贸易问题影响而在低位震荡。继5月12日,四艘商船在阿联酋领海附近的阿曼湾、…...

    2024/4/28 1:34:08
  17. 【原油贵金属早评】贸易冲突导致需求低迷,油价弱势

    原标题:【原油贵金属早评】贸易冲突导致需求低迷,油价弱势近日虽然伊朗局势升温,中东地区几起油船被袭击事件影响,但油价并未走高,而是出于调整结构中。由于市场预期局势失控的可能性较低,而中美贸易问题导致的全球经济衰退风险更大,需求会持续低迷,因此油价调整压力较…...

    2024/4/26 19:03:37
  18. 氧生福地 玩美北湖(上)——为时光守候两千年

    原标题:氧生福地 玩美北湖(上)——为时光守候两千年一次说走就走的旅行,只有一张高铁票的距离~ 所以,湖南郴州,我来了~ 从广州南站出发,一个半小时就到达郴州西站了。在动车上,同时改票的南风兄和我居然被分到了一个车厢,所以一路非常愉快地聊了过来。 挺好,最起…...

    2024/4/29 20:46:55
  19. 氧生福地 玩美北湖(中)——永春梯田里的美与鲜

    原标题:氧生福地 玩美北湖(中)——永春梯田里的美与鲜一觉醒来,因为大家太爱“美”照,在柳毅山庄去寻找龙女而错过了早餐时间。近十点,向导坏坏还是带着饥肠辘辘的我们去吃郴州最富有盛名的“鱼头粉”。说这是“十二分推荐”,到郴州必吃的美食之一。 哇塞!那个味美香甜…...

    2024/4/30 22:21:04
  20. 氧生福地 玩美北湖(下)——奔跑吧骚年!

    原标题:氧生福地 玩美北湖(下)——奔跑吧骚年!让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 让我们红尘做伴 活得潇潇洒洒 策马奔腾共享人世繁华 对酒当歌唱出心中喜悦 轰轰烈烈把握青春年华 啊……啊……啊 两…...

    2024/5/1 4:32:01
  21. 扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!

    原标题:扒开伪装医用面膜,翻六倍价格宰客,小姐姐注意了!扒开伪装医用面膜,翻六倍价格宰客!当行业里的某一品项火爆了,就会有很多商家蹭热度,装逼忽悠,最近火爆朋友圈的医用面膜,被沾上了污点,到底怎么回事呢? “比普通面膜安全、效果好!痘痘、痘印、敏感肌都能用…...

    2024/4/27 23:24:42
  22. 「发现」铁皮石斛仙草之神奇功效用于医用面膜

    原标题:「发现」铁皮石斛仙草之神奇功效用于医用面膜丽彦妆铁皮石斛医用面膜|石斛多糖无菌修护补水贴19大优势: 1、铁皮石斛:自唐宋以来,一直被列为皇室贡品,铁皮石斛生于海拔1600米的悬崖峭壁之上,繁殖力差,产量极低,所以古代仅供皇室、贵族享用 2、铁皮石斛自古民间…...

    2024/4/28 5:48:52
  23. 丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者

    原标题:丽彦妆\医用面膜\冷敷贴轻奢医学护肤引导者【公司简介】 广州华彬企业隶属香港华彬集团有限公司,专注美业21年,其旗下品牌: 「圣茵美」私密荷尔蒙抗衰,产后修复 「圣仪轩」私密荷尔蒙抗衰,产后修复 「花茵莳」私密荷尔蒙抗衰,产后修复 「丽彦妆」专注医学护…...

    2024/4/30 9:42:22
  24. 广州械字号面膜生产厂家OEM/ODM4项须知!

    原标题:广州械字号面膜生产厂家OEM/ODM4项须知!广州械字号面膜生产厂家OEM/ODM流程及注意事项解读: 械字号医用面膜,其实在我国并没有严格的定义,通常我们说的医美面膜指的应该是一种「医用敷料」,也就是说,医用面膜其实算作「医疗器械」的一种,又称「医用冷敷贴」。 …...

    2024/4/30 9:43:22
  25. 械字号医用眼膜缓解用眼过度到底有无作用?

    原标题:械字号医用眼膜缓解用眼过度到底有无作用?医用眼膜/械字号眼膜/医用冷敷眼贴 凝胶层为亲水高分子材料,含70%以上的水分。体表皮肤温度传导到本产品的凝胶层,热量被凝胶内水分子吸收,通过水分的蒸发带走大量的热量,可迅速地降低体表皮肤局部温度,减轻局部皮肤的灼…...

    2024/4/30 9:42:49
  26. 配置失败还原请勿关闭计算机,电脑开机屏幕上面显示,配置失败还原更改 请勿关闭计算机 开不了机 这个问题怎么办...

    解析如下&#xff1a;1、长按电脑电源键直至关机&#xff0c;然后再按一次电源健重启电脑&#xff0c;按F8健进入安全模式2、安全模式下进入Windows系统桌面后&#xff0c;按住“winR”打开运行窗口&#xff0c;输入“services.msc”打开服务设置3、在服务界面&#xff0c;选中…...

    2022/11/19 21:17:18
  27. 错误使用 reshape要执行 RESHAPE,请勿更改元素数目。

    %读入6幅图像&#xff08;每一幅图像的大小是564*564&#xff09; f1 imread(WashingtonDC_Band1_564.tif); subplot(3,2,1),imshow(f1); f2 imread(WashingtonDC_Band2_564.tif); subplot(3,2,2),imshow(f2); f3 imread(WashingtonDC_Band3_564.tif); subplot(3,2,3),imsho…...

    2022/11/19 21:17:16
  28. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机...

    win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”问题的解决方法在win7系统关机时如果有升级系统的或者其他需要会直接进入一个 等待界面&#xff0c;在等待界面中我们需要等待操作结束才能关机&#xff0c;虽然这比较麻烦&#xff0c;但是对系统进行配置和升级…...

    2022/11/19 21:17:15
  29. 台式电脑显示配置100%请勿关闭计算机,“准备配置windows 请勿关闭计算机”的解决方法...

    有不少用户在重装Win7系统或更新系统后会遇到“准备配置windows&#xff0c;请勿关闭计算机”的提示&#xff0c;要过很久才能进入系统&#xff0c;有的用户甚至几个小时也无法进入&#xff0c;下面就教大家这个问题的解决方法。第一种方法&#xff1a;我们首先在左下角的“开始…...

    2022/11/19 21:17:14
  30. win7 正在配置 请勿关闭计算机,怎么办Win7开机显示正在配置Windows Update请勿关机...

    置信有很多用户都跟小编一样遇到过这样的问题&#xff0c;电脑时发现开机屏幕显现“正在配置Windows Update&#xff0c;请勿关机”(如下图所示)&#xff0c;而且还需求等大约5分钟才干进入系统。这是怎样回事呢&#xff1f;一切都是正常操作的&#xff0c;为什么开时机呈现“正…...

    2022/11/19 21:17:13
  31. 准备配置windows 请勿关闭计算机 蓝屏,Win7开机总是出现提示“配置Windows请勿关机”...

    Win7系统开机启动时总是出现“配置Windows请勿关机”的提示&#xff0c;没过几秒后电脑自动重启&#xff0c;每次开机都这样无法进入系统&#xff0c;此时碰到这种现象的用户就可以使用以下5种方法解决问题。方法一&#xff1a;开机按下F8&#xff0c;在出现的Windows高级启动选…...

    2022/11/19 21:17:12
  32. 准备windows请勿关闭计算机要多久,windows10系统提示正在准备windows请勿关闭计算机怎么办...

    有不少windows10系统用户反映说碰到这样一个情况&#xff0c;就是电脑提示正在准备windows请勿关闭计算机&#xff0c;碰到这样的问题该怎么解决呢&#xff0c;现在小编就给大家分享一下windows10系统提示正在准备windows请勿关闭计算机的具体第一种方法&#xff1a;1、2、依次…...

    2022/11/19 21:17:11
  33. 配置 已完成 请勿关闭计算机,win7系统关机提示“配置Windows Update已完成30%请勿关闭计算机”的解决方法...

    今天和大家分享一下win7系统重装了Win7旗舰版系统后&#xff0c;每次关机的时候桌面上都会显示一个“配置Windows Update的界面&#xff0c;提示请勿关闭计算机”&#xff0c;每次停留好几分钟才能正常关机&#xff0c;导致什么情况引起的呢&#xff1f;出现配置Windows Update…...

    2022/11/19 21:17:10
  34. 电脑桌面一直是清理请关闭计算机,windows7一直卡在清理 请勿关闭计算机-win7清理请勿关机,win7配置更新35%不动...

    只能是等着&#xff0c;别无他法。说是卡着如果你看硬盘灯应该在读写。如果从 Win 10 无法正常回滚&#xff0c;只能是考虑备份数据后重装系统了。解决来方案一&#xff1a;管理员运行cmd&#xff1a;net stop WuAuServcd %windir%ren SoftwareDistribution SDoldnet start WuA…...

    2022/11/19 21:17:09
  35. 计算机配置更新不起,电脑提示“配置Windows Update请勿关闭计算机”怎么办?

    原标题&#xff1a;电脑提示“配置Windows Update请勿关闭计算机”怎么办&#xff1f;win7系统中在开机与关闭的时候总是显示“配置windows update请勿关闭计算机”相信有不少朋友都曾遇到过一次两次还能忍但经常遇到就叫人感到心烦了遇到这种问题怎么办呢&#xff1f;一般的方…...

    2022/11/19 21:17:08
  36. 计算机正在配置无法关机,关机提示 windows7 正在配置windows 请勿关闭计算机 ,然后等了一晚上也没有关掉。现在电脑无法正常关机...

    关机提示 windows7 正在配置windows 请勿关闭计算机 &#xff0c;然后等了一晚上也没有关掉。现在电脑无法正常关机以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;关机提示 windows7 正在配…...

    2022/11/19 21:17:05
  37. 钉钉提示请勿通过开发者调试模式_钉钉请勿通过开发者调试模式是真的吗好不好用...

    钉钉请勿通过开发者调试模式是真的吗好不好用 更新时间:2020-04-20 22:24:19 浏览次数:729次 区域: 南阳 > 卧龙 列举网提醒您:为保障您的权益,请不要提前支付任何费用! 虚拟位置外设器!!轨迹模拟&虚拟位置外设神器 专业用于:钉钉,外勤365,红圈通,企业微信和…...

    2022/11/19 21:17:05
  38. 配置失败还原请勿关闭计算机怎么办,win7系统出现“配置windows update失败 还原更改 请勿关闭计算机”,长时间没反应,无法进入系统的解决方案...

    前几天班里有位学生电脑(windows 7系统)出问题了&#xff0c;具体表现是开机时一直停留在“配置windows update失败 还原更改 请勿关闭计算机”这个界面&#xff0c;长时间没反应&#xff0c;无法进入系统。这个问题原来帮其他同学也解决过&#xff0c;网上搜了不少资料&#x…...

    2022/11/19 21:17:04
  39. 一个电脑无法关闭计算机你应该怎么办,电脑显示“清理请勿关闭计算机”怎么办?...

    本文为你提供了3个有效解决电脑显示“清理请勿关闭计算机”问题的方法&#xff0c;并在最后教给你1种保护系统安全的好方法&#xff0c;一起来看看&#xff01;电脑出现“清理请勿关闭计算机”在Windows 7(SP1)和Windows Server 2008 R2 SP1中&#xff0c;添加了1个新功能在“磁…...

    2022/11/19 21:17:03
  40. 请勿关闭计算机还原更改要多久,电脑显示:配置windows更新失败,正在还原更改,请勿关闭计算机怎么办...

    许多用户在长期不使用电脑的时候&#xff0c;开启电脑发现电脑显示&#xff1a;配置windows更新失败&#xff0c;正在还原更改&#xff0c;请勿关闭计算机。。.这要怎么办呢&#xff1f;下面小编就带着大家一起看看吧&#xff01;如果能够正常进入系统&#xff0c;建议您暂时移…...

    2022/11/19 21:17:02
  41. 还原更改请勿关闭计算机 要多久,配置windows update失败 还原更改 请勿关闭计算机,电脑开机后一直显示以...

    配置windows update失败 还原更改 请勿关闭计算机&#xff0c;电脑开机后一直显示以以下文字资料是由(历史新知网www.lishixinzhi.com)小编为大家搜集整理后发布的内容&#xff0c;让我们赶快一起来看一下吧&#xff01;配置windows update失败 还原更改 请勿关闭计算机&#x…...

    2022/11/19 21:17:01
  42. 电脑配置中请勿关闭计算机怎么办,准备配置windows请勿关闭计算机一直显示怎么办【图解】...

    不知道大家有没有遇到过这样的一个问题&#xff0c;就是我们的win7系统在关机的时候&#xff0c;总是喜欢显示“准备配置windows&#xff0c;请勿关机”这样的一个页面&#xff0c;没有什么大碍&#xff0c;但是如果一直等着的话就要两个小时甚至更久都关不了机&#xff0c;非常…...

    2022/11/19 21:17:00
  43. 正在准备配置请勿关闭计算机,正在准备配置windows请勿关闭计算机时间长了解决教程...

    当电脑出现正在准备配置windows请勿关闭计算机时&#xff0c;一般是您正对windows进行升级&#xff0c;但是这个要是长时间没有反应&#xff0c;我们不能再傻等下去了。可能是电脑出了别的问题了&#xff0c;来看看教程的说法。正在准备配置windows请勿关闭计算机时间长了方法一…...

    2022/11/19 21:16:59
  44. 配置失败还原请勿关闭计算机,配置Windows Update失败,还原更改请勿关闭计算机...

    我们使用电脑的过程中有时会遇到这种情况&#xff0c;当我们打开电脑之后&#xff0c;发现一直停留在一个界面&#xff1a;“配置Windows Update失败&#xff0c;还原更改请勿关闭计算机”&#xff0c;等了许久还是无法进入系统。如果我们遇到此类问题应该如何解决呢&#xff0…...

    2022/11/19 21:16:58
  45. 如何在iPhone上关闭“请勿打扰”

    Apple’s “Do Not Disturb While Driving” is a potentially lifesaving iPhone feature, but it doesn’t always turn on automatically at the appropriate time. For example, you might be a passenger in a moving car, but your iPhone may think you’re the one dri…...

    2022/11/19 21:16:57