[持续更新]先进OpenGL编程注意事项
来源请注明http://www.cnblogs.com/vertexshader/articles/3022981.html,复制不可耻,复制不加原文地址最可耻。
GLSL
1.Compatibility Build-in Variable:在AMD的驱动中和Nvidia的驱动中对Compatibility Build-in Variables的态度是不同的,在AMD的驱动上使用Comatibility Build-in Variables将不会产生任何的问题,而在Nvidia的驱动中编译带有Compatibility Build-in Variables的GLSL代码段会导致Warning的出现。抱歉一个Warning也不许有!是时候抛弃任何Compatibility Build-in Variables and functions了。[GLSL][08/01/2014]
2.Type Safe:在AMD的驱动中对类型安全似乎没有那么严格的要求,而在Nvidia中对类型安全有严格的要求,同样的代码段在两者的设备上会产生不同的编译结果:
float factor; if(factor) { //... }
在AMD上这段代码将毫无问题的编译通过,很靠近C/C++的风格和习惯,而在Nvidia上将抛异常“error C7011: implicit cast from float to bool”,很明显地反应了对于类型安全要求的不同。而根据规范《OpenGL Shading Language 4.4》的描述:
The OpenGL Shading Language is type safe. There are some implicit conversions between types. Exactly how and when this can occur is described in section 4.1.10 “Implicit Conversions” and as referenced by other sections in this specification.
也就是说GLSL语言是类型安全的,但是存在部分类型之间可以进行隐式转换,具体的类型转换由下表表示:
由此可以见得AMD的驱动是不符合规范的,Nvidia的驱动符合这一规范的。[GLSL][08/01/2014]
3.Bindless Texture:Bindless Texture会使得OpenGL 4.2提供的Opaque-Uniform Layout Qualifiers失效,类似layout(binding = integer-constant)中的<integer-constant>指的是Texture Unit,而并非是Bindless handle,但是可以通过explicit uniform location指定uniform location的值,然后外部可以通过这个location的值,通过glUniformHandle*上传相关纹理的handle。出现这个扩展后,纹理分为了bindless和bound类型,而在没有启用bindless之前,所有的纹理默认就是bound类型的,也就是过去通过Texture Unit然后通过glUniform1i上传的。[OpenGL 4.4][12/11/2013]
4.Vertex Attrib: Vertex Program的输出限定在gl_MaxOutputComponent的四分之一,超过这个数值,是实现定义的,可能产生错误。
The number of input locations available to a shader is limited. For vertex shaders, the limit is the advertised number of vertex attributes. For all other shaders, the limit is implementation-dependent and must be no less than one fourth of the advertised maximum input component count.A program will fail to link if any attached shader uses a location greater than or equal to the number of supported locations, unless device-dependent optimizations are able to make the program fit within available hardware resources.
5.Array: GLSL允许首先声明数组,然后在使用之前的时候声明其长度,如果不声明其长度,索引其的时候肯定会产生错误:
#version 440 uniform vec4 pos[]; const int length = 5; uniform vec4 pos[length];
OpenGL 4
1.Bindless Texture:过去OpenGL进行采样需要将Texture Object绑定到Texture Unit,然后通过glUniform1i上传Texture Unit的值;现在有了Bindless Texture,Shader可以直接通过Handle访问Texture Object,也没有了Texture Unit数量的限制,可以更快访问纹理和绑定更多的纹理(over 1 million unique textures!)。[OpenGL 4.4][09/11/2013]
2.Immutable Storage:OpenGL通过GL_ARB_texture_storage和GL_ARB_buffer_storage来支持新的存储就是Immutable Storage,和字面的意思一样是不可改变大小的存储。Texture Object增加了GL_TEXTURE_IMMUTABLE_FORMAT来标识是否是Immutable storage;Buffer Object增加了GL_BUFFER_IMMUTABLE_STORAGE来标识是否是Immutable Storage——其实这些也非常有好处,在实时渲染中需要动态地改变资源的大小的情况很少。glTexStorage*提供的好处就是优化了创建纹理的过程,可以通过这个API直接创建Mipmap层而不需要调用glGenerateMipmap函数,而且能避免设置Texture Level所产生的隐藏错误;也不需要拆分internalformat为format和type,直接提供internalformat就可以。glBufferStorage和glBufferData的区别就是提供了更多的精细控制:GL_DYNAMIC_STORAGE_BIT提供了开启/关闭Client端更新Server端的功能;GL_MAP_READ_BIT和GL_MAP_WRITE_BIT提供了CPU读取/写入数据的功能;GL_MAP_PERSISTENT_BIT提供了当数据仍然在Mapped状态时也能够被读取/写入数据的功能;GL_MAP_COHERENT_BIT提供了服务端数据和客户端数据的一致性(这个有待探讨);而GL_CLIENT_STORAGE_BIT提供了显示指定数据存储在客户端的功能,如果这个bit没设置,那么数据默认存储在服务端。这些API与D3D的API基本是类似的,例如D3D中通过D3D11Device::CreateBuffer创建的Buffer Object也是Immutable Storage,想要Resize Buffer则需要重新调用这个函数创建一个新的Buffer Object然后获取新的句柄,这个和OpenGL的函数是一致的。
这些函数和TexImage*的区别就是,TexStorage*声明后创建的Storage的Format和大小都是不可变的,故在之后调用TexImage*、CopyTexImage*、CompressTexImage*和TexStorage*都是会报错且无效的;并且创建的Storage总是Mipmap Complete(对于TextureCube还有Cubemap Complete)的,这是Immutable Texture Storage主要解决的问题。总之,Immutable Texture Storage是可以替代Mutable Texture Storage的,并且更加高效。[OpenGL 4.4][01/16/2014]
3.Separate Program Object:扩展GL_ARB_separate_shader_object带来了新的性能提升,过去通过编译Shader Object,然后绑定Shader Object到Shader Program上然后Link完成Shader的载入,体现出来的问题就是许多相同的shader需要重复地Link,降低了程序的效率。这个扩展则基本上弱化了Shader Object的功能,使用Shader Program作为Shader Stage,可以把文本的Shader通过glCreateShaderProgramv来直接创建(其实等价于创建一个Shader Object,编译后绑定到Shader Program上,然后再Link),或者先glCreateProgram创建好Shader Program,然后通过glProgramBinary上传编译好的Shader(这个过程也会Link程序)。这个扩展还提供了一个新的Container Object就是Program Pipeline Object,相当于VAO一样是用来记录使用了哪些Shader Stage,之前通过glUseProgramStages设定好需要使用的Program Stage,然后绑定它到Context就激活了使用的Program;同时当Program Pipeline Object未绑定的时候,也可以使用原来的Program Object来开启可编程功能,不过显而易见没理由不全部使用Program Pipeline Object了。Shader Stage现在和D3D是类似的了,Program Pipeline Object在D3D中需要自己弄一个,其实底层也就是设置D3D11context:*SetShader,功能上基本一致,抽象在一起很简单。
这是一个OpenGL 4带来的重大改变之一。过去使用Shader必须先创建Shader Object,编译好之后绑定在一个Shader Program上面,链接完毕之后使用glUseProgram激活相应的Shader Program,这么做导致了几个问题:①同一个Shader Object可能绑定到多个Shader Program上,不同的Shader Program之间都可能会有相同的Shader 代码,导致重复链接;②更换Shader Program,可能会导致Vertex Array Object也需要更换。GL_EXT_separate_shader_objects扩展的升级版本GL_ARB_separate_shader_objects带来了Separate Shader Objects,允许OpenGL像Direct3D一样将Program单独分成好几个Stage,更改其中的一个Stage并不影响其他的Stage,这样可以有更好的灵活性。
这个扩展也带来繁琐的ProgramUniform*函数系列(其实这些函数来自DSA扩展),用来上传Uniform数据,和调用ActiveShaderProgram之后然后调用Uniform*函数系列一样,这些函数对于不同类型都有不同的函数,频繁调用会造成很大的性能问题,而且最后这些函数上传的值,最后也会放入Default Uniform Buffer Object里,不推荐使用,应该用更加高效的Uniform Buffer Object替代,用更加MapBuffer来更新Uniform Buffer Object。另外谈到的一点就是,在启用Separate Shader Objects的时候,每个Stage的Shader Program都带有独立的namespace,而不是绑定到Program Pipeline Object的所有Shader Program都共享一个namespace,那么这样不同的Shader Program里的Uniform变量可以拥有相同的名字。[OpenGL 4.1][01/16/2014]
4.BPTC:OpenGL现在也完全支持了D3D中的所有的压缩纹理,加上自己的ETC,压缩的格式挺多的,不过还是推荐使用BC,用的比较多。[OpenGL 4.2][01/16/2014]
5.Shader Binary:扩展GL_ARB_get_program_binary提供了不需要在运行态编译shader的方法——之前编译好并保存为二进制文件,通过glGetProgramBinary可以下载编译好的二进制数据,通过glProgramBinary可以上传编译好的二进制数据,这样可以大大提升程序的性能。这个和D3D也是一样的,可以事先把Shader编译进一个Blob中,序列化进入二进制文件,使用的时候直接加载即可。[OpenGL 4.1][01/16/2014]
6.Texture View:除了创建的时候简洁的代码和能避免一般错误之外,Immutable Texture Storage还有一项好处就是Immutable Storage可以被其他的Texture Object共享,而Mutable Storage只能绑定在单独的Texture Object上。扩展GL_ARB_texture_view实现了共享Immutable Texture Storage的函数TextureView,在功能上有点类似Texture Copy,但是本质上是为了减少内存拷贝,使不同的Texture Object能访问一块内存区域。[OpenGL 4.3][01/16/2014]
7.一维压缩纹理:很奇怪的是,s3tc定义的内容是,如果想要创建一个一维的压缩纹理,则会产生GL错误,结果到了glTexStorage这里则不发生错误,成功开辟了空间,也许是一个BUG。[OpenGL 4.3][01/27/2014]
8.Cube Map & Cube Map Array:这个用法非常的Tricky,对于GL_TEXTURE_CUBE_MAP使用的是glTexStorage2D,GL会创建六个面的数据;而对于GL_TEXTURE_CUBE_MAP_ARRAY,却要用glTexStorage3D来创建,并且depth的值要设置为6N,而不是N。一个是自己创建六个面,一个是让你指定六个面,这接口设计的~ [OpenGL 4.3][03/04/2014]
9.Tricky的指针:在使用Buffer Object之后,OpenGL中很多的函数比如glDrawElements或者glGetTexImage最后的一个参数再也不是一个内存指针,而是检测当前绑定点绑定的buffer object然后直接使用null,或者当成一个byte offset来使用,有很多人尝试使用100* 10这样或者是GLvoid*(100)来强制转换,这明显是错误的!既然是一个byte offset,传入的自然也是一个指针:
GLbyte *offset = 0; offst += 100; // Has bind a PBO to GL_PIXEL_PACK_BUFFER glGetTexImage(target, level, format, type, offset);
是不是一个很Tricky的设计![OpenGL 4.0][03/04/2014]
OpenGL 3
OpenGL Operations
1.Transform feedback: glTransformFeedbackVaryings只是一次有效的,多次调用glTransformFeedbackVaryings只有最后一次是有效的。故GL_INTERLEAVED_ATTRIB与GL_SEPARATE_ATTRIB只能存在一个而不能同时存在,这比Direct3D中Streamout可以通过绑定slot和SOTarget的办法要tricky的多,但是D3D中RenderLayout也只是设置一次。[OpenGL 3.3][04/15/2013]
2.Shader Program: 当glUseProgram的参数为0时,那么当前的shader program是无效的,并且其输出结果是未定义的。虽然如此,但是OpenGL不会认为其是一个错误。[OpenGL 3.3][04/16/2013]
3.Binding Buffer Objects to Indexed Targets: glBindBufferOffset存在于诸多Registry中,但并未收录在Spec和Reference中,不过其等价于glBindBufferRange(target, index, buffer, offset, buffer_size - offset),和D3D中SOSetTarget相同。[OpenGL 3.3][04/20/2013]
4.Interleaved & Separate: Transform Feedback支持2种缓冲区模式,但是其实本质是一样的,GL_MAX_TRANSFORM_FEEDBACK_ATTRIB限制了输出变量的数量,变量最大的为matrix4x4,也就是16 components,GL_MAX_TRANSFORM_FEEDBACK_ATTRIB * 16 = GL_MAX_TRANSFORM_FEEDBACK_INTERLEAVED_COMPONENTS,本质上只是将输出变量路由到不同的Stream Out Target(buffer)而已。所以最好的方法就是只绑定一个Stream Out Target(buffer),这样可以让Direct3D的SOSetTarget和OpenGL的BindBufferOffset行为一致,更易于封装。[OpenGL 3.3][04/20/2013]
5.Vertex Shader Input: 虽然GLSL内部支持float,int,bool三个类型的变量,但是实质上允许输入的变量和glVertexAttribPointer支持的类型有关。[OpenGL 3.3][04/24/2013]
6.Shader Program: Direct3D并没有Shader Program Object,要激活相同功能,必须通过Set函数来一一设置Shader Object对象。[OpenGL 3.3][05/01/2013]
7.Vertex Attrib Pointer: 你不得不使用Vertex Array Object!如果单独设置glVertexAttribPointer,每次Dall Call的时候这个状态就要重新设置一遍,否则就会出现隐含的错误。[OpenGL 3.3][05/05/2013]
8.Shader Object: 如果一个Shader Object没有Attach到任何Shader Program,那么Delete它的操作是立即的;但是当它已经Attach到Shader Program上的时候,Delete操作不是立即的,而是先做一个flag,当它不再被Attach的的时候Delete。一个好的建议是,可以利用智能指针自动析构Shader Object对象,减少内存而不抛出异常。[OpenGL 3.3][05/06/2013]
Rasterization
1.Point Size: 可以通过Shader内建变量gl_PointSize来设置(但是要启用GL_PROGRAM_POINT_SIZE),而不通过光栅化状态glPointSize,这和Direct3D中Semantic设置为PSIZE的Buffer相同。[OpenGL 3.3][04/16/2013]
2.Texture Completeness: 一个导致纹理不起作用的行为是,当你创建了一个纹理而未显式指定GL_TEXTURE_MIN_FILTER的时候,默认的值是GL_NEAST_MIPMAP_LINEAR,而默认的GL_TEXTURE_MAX_LEVEL是1000。[OpenGL 3.3][05/01/2013]
3.SMOOTH: 值得注意的是,在旧有的Rasterizer state中,GL_XXX_SMOOTH这类光栅化状态已经无明显作用,想要启用AA应该使用Multisample,或者其他的方式。[OpenGL 3.3][05/01/2013]
4.Pixel Format: 适用于RenderBuffer和Texture的格式:
– RGBA32F, RGBA32I, RGBA32UI, RGBA16, RGBA16F, RGBA16I, RGBA16UI, RGBA8, RGBA8I, RGBA8UI, SRGB8_ALPHA8, RGB10_A2, and RGB10_A2UI.
– R11F_G11F_B10F.
– RG32F, RG32I, RG32UI, RG16, RG16F, RG16I, RG16UI, RG8, RG8I, and RG8UI.
– R32F, R32I, R32UI, R16F, R16I, R16UI, R16, R8, R8I, and R8UI.
只适用于Texture的格式:
– RGBA16_SNORM and RGBA8_SNORM.
– RGB32F, RGB32I, and RGB32UI.
– RGB16_SNORM, RGB16F, RGB16I, RGB16UI, and RGB16.
– RGB8_SNORM, RGB8, RGB8I, RGB8UI, and SRGB8.
– RGB9_E5.
– RG16_SNORM, RG8_SNORM, COMPRESSED_RG_RGTC2 and COMPRESSED_SIGNED_RG_RGTC2.
– R16_SNORM, R8_SNORM, COMPRESSED_RED_RGTC1 and COMPRESSED_SIGNED_RED_RGTC1.[OpenGL 3.3][05/03/2013]
5.Mipmap: 虽然OpenGL可以使用glGenerateMipMap自动生成MipMap层,但是生成MipMap的过程比预先加载做好的MipMap要慢,会产生可能存在的性能问题。[OpenGL 3.3][05/23/2013]
6. Texture map operation: OpenGL的Texture不支持map操作,需要借助pixel buffer object来完成。一个值得注意的问题是,当你需要从Texture中download数据的时候,glGetTexImage这个函数将会返回一个level的所有数据,这里需要调整。
Per-fragment Operation
1.Depth Buffer Test: 在OpenGL中,Depth Buffer Test是默认关闭的,需要自行开启glEnable(GL_DEPTH_TEST),比较Tricky。[OpenGL3.3][11/18/2013]
Whole Framebuffer Operation
1.Render Buffer Storage: 重复对一个RBO对象调用glRenderbufferStorage或者glRenderbufferStorageMultisample,将会销毁原有的storage创建新的storage——但是“极其”不推荐这样做,如果需要一个新的RBO,删除它然后再次创建。重复创建RBO可能导致完整性问题,尤其是当它正绑定在其他对象上的时候。
"Similar to glTexImage2D, calling this function on a renderbuffer that has already had this function called on it will cause it to deallocate any resources associated with the previous call and allocate new storage. Note: You are strongly advised not to do this. If you need a new renderbuffer, just delete the old object and create a new one. Recreating a renderbuffer with the same object name can cause completeness problems, particularly if it is attached to another object at the time." [OpenGL 3.3][04/20/2013]
2.Render Target: Render Target在OpenGL中可以有两种,即Render Buffer Object和Texture,操作都比较相同;同时都支持反走样,即使用glRenderbufferStorageMultisample和glTexImage{2,3}DMultisample来开启;将其attach到Frame Buffer Object的方法类似,使用glFramebufferRenderbuffer,glFramebuffer,glFramebufferTexture{1,2,3}D还有glFramebufferLayer来完成。
Several types of framebuffer-attachable images: ① The image of a renderbuffer object, which is always two-dimentional; ② A single level of a one-dimentional texture, which is treated as a two dimensional image with a height of one; ③ A single level of a two-demensional or rectangle texture; ④ A single face of a cube map texture level, which is treated as two-dimensional image; ⑤ A single layer of a one-or two-dimentional array texture or three-dimensional texture, which is treaded as a two-dimensional image.
这些类型与Direct3D的D3D10_RENDER_TARGET_VIEW_DESC和D3D11_RENDER_TARGET_VIEW_DESC中允许的Buffer, Texture1D, Texture1DArray, Texture2D, Texture2DArray, Texture2DMS, Texture2DMSArray, Texture3D一致,也完全可以封装上层类执行相同的过程。另外同样这些类型都支持Pixel Transfer Operation,Render Buffer Object需要通过glReadBuffer指定,然后通过glReadPixel完成Pixel Transfer。[OpenGL 3.3][04/20/2013]
3.Clear: OpenGL的Clear操作分为glClear,glClearColor,glClearDepth,glClearStencil和glClearBuffer*,与IDirect3DDevice9::Clear类似;到了Direct3D10和Direct3D11, Clear操作被细分为ClearRenderTargetView和ClearDepthStencilView。OpenGL的函数可以轻松匹配这些,使用glClearBuffer{i,f,ui}和glClearBufferfi即可,而glClearColor、glClearDepth与glClearStencil可以做次要考虑。[OpenGL 3.3][04/22/2013]
4.Render State: OpenGL本身没有Render State,Direct3D中的Render State很多都是对Whole Framebuffer Operation中的Fine Control of Buffer Update的简单封装,完全可以自己封装一个Render State用于OpenGL中。[OpenGL 3.3][04/23/2013]
5.Framebuffer Object: Framebuffer不占用多少内存,它只是一个 状态向量对象(State Vector Object)。当你绑定Framebuffer的时候,驱动需要消耗CPU time来验证Framebuffer的状态,这就产生了性能上的问题,所以最好还是只有一个Framebuffer Object用来做Render to Texture。Direct3D没有Framebuffer Object,Render Target足以完成工作,事实上OMSetRenderTarget已经完成了FBO的工作。Framebuffer在一定程度上方便了Pixel Transfer操作,但是增加了性能问题。
"Is it better to make 1 FBO and bind your texture to it each time you need to render to the texture?"
"An FBO itself doesn't use much memory. It is a state vector object. In terms of performance, each time you bind, the driver needs to validate the state which costs CPU time. Logically, it would be better to have 1 FBO per Render_To_Texture (RTT).However, it has been found that you get a speed boost if your textures is the same size and you use 1 FBO for them.If you have 10 textures that are 64x64 and 10 textures that are 512x64, make 2 FBOs. One FBO for each group."[OpenGL 3.3][04/23/2013]
6.Fragment Output Route: GLSL中Fragment Output Route通过对out变量指定layout绑定slot,通过glDrawBuffer路由到Render Targets(不推荐使用compatibility的gl_FragData[]),而Depth/Stencil通过gl_FragDepth输出;同样,HLSL通过SV_Target[]直接输出到对应的RenderTargetView,而Depth/Stencil通过SV_Depth输出到DepthStencilView。一个较好的方法是,牺牲OpenGL的灵活性,glDrawBuffers的buf参数从COLOR_ATTACHMENT0~COLOR_ATTACHMENTi按照数量遍历一遍,这样可以使行为一致更易于封装。[OpenGL 3.3][04/24/2013]
7.RenderBuffer: Renderbuffer的用途是无格式的buffer用来存储fragement shader输出,例如可以存储depth和stencil,其本质是一个2D的Image,其大小GL_MAX_RENDERBUFFER_SIZE和GL_MAX_TEXTURE_SIZE相同证明了这点。但是其灵活性较差,不能直接被PBO通过pack op完成pixel transfer。[OpenGL 3.3][05/04/2013]
8.Default Framebuffer: 只要glFramebuffer绑定到0上时,default framebuffer就启用了,可以对其的image进行复制操作。Direct3D并没有framebuffer的概念!通过 DXGI初始化Direct3D10的时候,已经创建了相应的back buffer,这个back buffer才能完成和OpenGL一致的工作。[OpenGL 3.3][04/24/2013]
9.Framebuffer Completeness: Depth Stencil Target的数据类型必须和绑定的类型一致,如果Depth Stencil Target的数据类型只有Depth分量而没有Stencil分量,那么不可以把它绑定到GL_DEPTH_STENCIL_ATTACHMENT上去,只能绑定到GL_DEPTH_ATTACHMENT上去,否则会导致Framebuffer Incompleteness错误而导致不能使用;同样的如果Depth Stencil Target有Depth分量也有Stencil分量,那么必须将其绑定到GL_DEPTH_STENCIL_ATTACHMENT上去。[OpenGL 3.3][07/01/2013]
Others
1.Vertical Synchronization: 在AMD上OpenGL默认是关闭垂直同步的,但是在Nvidia上同样的程序确实默认开启的,两者的行为不一致;使用WGL的扩展wglSwapIntervalEXT来开启和关闭垂直同步,关闭或者开启帧率的限制,使其程序的行为在不同的设备上保持一致。[OpenGL WGL][10/02/2013]
2.Video Memory Check: 在有些情况下需要检查剩余的显存大小是否够用,AMD的显卡提供了GL_ATI_mem_info扩展,而Nvidia提供了GL_NVX_gpu_memory_info扩展,相应的扩展应该都被主流的显卡驱动所支持了(我没有非常多的硬件供我自己调查,但是我自己的HD 4300支持了GL_ATI_mem_info扩展,而GTX 650支持了GL_NVX_gpu_memory扩展)。[OpenGL][01/24/2014]
3.GPU Capability:Direct3D10开始没有Max Capability,只有“Direct3D 10 Compatible”。
Unlike prior versions of the API, Direct3D 10 no longer uses "capability bits" (or "caps") to indicate which features are supported on a given graphics device. Instead, it defines a minimum standard of hardware capabilities which must be supported for a display system to be "Direct3D 10 compatible". This is a significant departure, with the goal of streamlining application code by removing capability-checking code and special cases based on the presence or absence of specific capabilities. Because Direct3D 10 hardware was comparatively rare after the initial release of Windows Vista and because of the massive installed base of non-Direct3D 10 compatible graphics cards, the first Direct3D 10-compatible games still provide Direct3D 9 render paths. Examples of such titles are games originally written for Direct3D 9 and ported to Direct3D 10 after their release, such as Company of Heroes, or games originally developed for Direct3D 9 with a Direct3D 10 path retrofitted later in development, such as Hellgate: London or Crysis.[OpenGL 3.3][04/30、2013]
4.Shader Model: OpenGL中并没有Shader Model的概念,取代之的是GL_SHADING_LANGUAGE_VERSION。从#version 330开始对应的是Shader Model 4.0;#version 430对应的是Shader Model 5.0。[OpenGL 3.3][05/05/2013]