浅谈OpenGL之DSA
今天准备写一篇文章简单介绍一下OpenGL4.5引入的一个新的扩展ARB_direct_state_access,这个扩展为OpenGL引入了一个新的特性就是Direct State Acess,下文统称为DSA。
那么什么是DSA,又为什么要引入DSA呢?
了解OpenGL的都知道,它的设计是一个基于状态机的API,你的每一次资源绑定,纹理绑定,修改渲染状态等等都会改变一个全局的状态机。这种设计的最大问题就是容易造成状态泄露,你可能只是想为纹理设置一个参数,但是却要改变状态机的纹理绑定,而这个状态你如果不修改,会一直泄露到其他的DrawCall中。这就给基于OpenGL的渲染引擎设计带来很多的问题,大多数引擎都会通过自己的方式来确保一个DrawCall的状态不会泄露到另一个DrawCall,从而导致一些非常难以调试的BUG。
那么为了从一定程度上缓解这个问题,OpenGL就引入了DSA。DSA可以让你直接访问一个object的状态而不需要绑定到全局状态机上 。比如没有DSA时,你需要先绑定一个buffer的object,才能为它分配存储空间,上传数据等,而有了DSA,你就可以直接访问object,为它分配存储,上传数据,避免将其绑定到状态机上。
但是我为什么说是一定程度上缓解呢,因为DSA不能完全避免绑定,当你将buffer或者texture真正用到渲染进程上时,你还是要将它们绑定到上下文里。比如前面你通过DSA的方式分配了一个buffer object,也为它上传了数据,现在你想将其用到某次DrawCall中作为uniform数据,你就需要调用glBindBufferRange(GL_UNIFORM_BUFFER,object,...)。将object绑定到指定Target上。
虽然如此,有了DSA还是可以为程序设计带来很多的方便。你仅仅需要在真正绘制的时候绑定object,而不是在各种初始化时就要绑定它,从一定程度上减少了状态机切换的次数。
下面我就简单列举几个DSA引入的新的API和旧的API之间的对比
Program
Without DSA:
glUseProgram(progId);
glUniform1f(loc, x);
With DSA:
glProgramUniform1fEXT(progId, loc, x);
Texture
Without DSA:
glGenTextures(1, &name); glBindTexture(GL_TEXTURE_2D, name); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE ); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, width, height); glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
With DSA:
glCreateTextures(GL_TEXTURE_2D, 1, &name);
glTextureParameteri(name, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE );
glTextureParameteri(name, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE );
glTextureParameteri(name, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTextureParameteri(name, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTextureStorage2D(name, 1, GL_RGBA8, width, height);
glTextureSubImage2D(name, 0, 0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
Framebuffer
Without DSA:
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tid, 0);
With DSA:
glCreateFramebuffers(1, &fbo);
glNamedFramebufferTexture(fbo, GL_COLOR_ATTACHMENT0, tex, 0);
glNamedFramebufferTexture(fbo, GL_DEPTH_ATTACHMENT, depthTex, 0);
Buffer
Without DSA
struct vertex_t { vec3 pos, nrm; vec2 tex; }; glBindVertexArray(vao);
glBindVertexBuffer(0, vbo, 0, sizeof(vertex_t));
glEnableVertexAttribArray(0);
glEnableVertexAttribArray(1);
glEnableVertexAttribArray(2);
glVertexAttribFormat(0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex_t, pos));
glVertexAttribFormat(1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex_t, nrm));
glVertexAttribFormat(2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex_t, tex));
glVertexAttribBinding(0, 0);
glVertexAttribBinding(1, 0);
glVertexAttribBinding(2, 0);
With DSA
glVertexArrayVertexBuffer(vao, 0, data->vbo, 0, sizeof(vertex_t));
glEnableVertexArrayAttrib(vao, 0);
glEnableVertexArrayAttrib(vao, 1);
glEnableVertexArrayAttrib(vao, 2);
glVertexArrayAttribFormat(vao, 0, 3, GL_FLOAT, GL_FALSE, offsetof(vertex_t, pos));
glVertexArrayAttribFormat(vao, 1, 3, GL_FLOAT, GL_FALSE, offsetof(vertex_t, nrm));
glVertexArrayAttribFormat(vao, 2, 2, GL_FLOAT, GL_FALSE, offsetof(vertex_t, tex));
glVertexArrayAttribBinding(vao, 0, 0);
glVertexArrayAttribBinding(vao, 1, 0);
glVertexArrayAttribBinding(vao, 2, 0);