OSX 上的 glDrawElements 具有高 CPU 使用率
Posted
技术标签:
【中文标题】OSX 上的 glDrawElements 具有高 CPU 使用率【英文标题】:glDrawElements on OSX has high cpu usage 【发布时间】:2017-02-14 23:28:43 【问题描述】:我主要使用 SDL2 和 OpenGL 3.3 在 Linux (Mint) 和 Windows 上进行开发,在绘制对象方面几乎没有问题。 CPU 使用率从未真正飙升到 40% 以上。
那是,直到我尝试将我拥有的东西移植到 OSX (Sierra)。 使用在 Linux 和 Windows 上运行的完全相同的着色器和代码即可,将 OSX 上的 CPU 使用率持续提高到 ~99%。
起初,我认为这是一个批处理问题,所以我将我的绘图调用批处理在一起以尽量减少对 glDrawElements 的调用次数,但这并没有奏效。
然后,我认为这是一个涉及不使用顶点/片段着色器中的属性的问题(例如:OpenGL core profile incredible slowdown on OS X)
此外,我将帧速率保持在 60 fps。
整理之后,没有运气。尝试记录我能记录的所有内容,glGetError() 和着色器日志都没有。
所以我从我的顶点/片段着色器中删除了一些零碎的东西,看看是什么特别减慢了我的绘制调用。我设法将其简化为:在我的顶点/片段着色器中对 texture() 函数的任何调用都会使 CPU 使用率很高。
纹理加载代码:
// Texture loading
void PCShaderSurface::AddTexturePairing(HashString const &aName)
GLint minFilter = GL_LINEAR;
GLint magFilter = GL_LINEAR;
GLint wrapS = GL_REPEAT;
GLint wrapT = GL_REPEAT;
if(Constants::GetString("OpenGLMinFilter") == "GL_NEAREST")
minFilter = GL_NEAREST;
if(Constants::GetString("OpenGLMagFilter") == "GL_NEAREST")
magFilter = GL_NEAREST;
if(Constants::GetString("OpenGLWrapModeS") == "GL_CLAMP_TO_EDGE")
wrapS = GL_CLAMP_TO_EDGE;
if(Constants::GetString("OpenGLWrapModeT") == "GL_CLAMP_TO_EDGE")
wrapT = GL_CLAMP_TO_EDGE;
glGenTextures(1, &mTextureID);
glBindTexture(GL_TEXTURE_2D, mTextureID);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapT);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mSurface->w, mSurface->h, 0, mTextureFormat, GL_UNSIGNED_BYTE, mSurface->pixels);
GetManager()->AddTexturePairing(aName, TextureData(mTextureID, mSurface->w, mSurface->h));
绘制代码:
// I batch objects that use the same program and texture id to draw in the same call.
glUseProgram(program);
int activeTexture = texture % mMaxTextures;
int vertexPosLocation = glGetAttribLocation(program, "vertexPos");
int texCoordPosLocation = glGetAttribLocation(program, "texCoord");
int objectPosLocation = glGetAttribLocation(program, "objectPos");
int colorPosLocation = glGetAttribLocation(program, "primaryColor");
// Calculate matrices and push vertex, color, position, texCoord data
// ...
// Enable textures and set uniforms.
glBindVertexArray(mVertexArrayObjectID);
glActiveTexture(GL_TEXTURE0 + activeTexture);
glBindTexture(GL_TEXTURE_2D, texture);
glUniform1i(glGetUniformLocation(program, "textureUnit"), activeTexture);
glUniform3f(glGetUniformLocation(program, "cameraDiff"), cameraTranslation.x, cameraTranslation.y, cameraTranslation.z);
glUniform3f(glGetUniformLocation(program, "cameraSize"), cameraSize.x, cameraSize.y, cameraSize.z);
glUniformMatrix3fv(glGetUniformLocation(program, "cameraTransform"), 1, GL_TRUE, cameraMatrix);
// Set shader properties. Due to batching, done on a per surface / shader basis.
// Shader uniforms are reset upon relinking.
SetShaderProperties(surface, true);
// Set VBO and buffer data.
glBindVertexArray(mVertexArrayObjectID);
BindAttributeV3(GL_ARRAY_BUFFER, mVertexBufferID, vertexPosLocation, vertexData);
BindAttributeV3(GL_ARRAY_BUFFER, mTextureBufferID, texCoordPosLocation, textureData);
BindAttributeV3(GL_ARRAY_BUFFER, mPositionBufferID, objectPosLocation, positionData);
BindAttributeV4(GL_ARRAY_BUFFER, mColorBufferID, colorPosLocation, colorData);
// Set index data
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, mIndexBufferID);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(GLuint) * indices.size(), &indices[0], GL_DYNAMIC_DRAW);
// Draw and disable
glDrawElements(GL_TRIANGLES, static_cast<unsigned>(vertexData.size()), GL_UNSIGNED_INT, 0);
DisableVertexAttribArray(vertexPosLocation);
DisableVertexAttribArray(texCoordPosLocation);
DisableVertexAttribArray(objectPosLocation);
DisableVertexAttribArray(colorPosLocation);
// Reset shader property values.
SetShaderProperties(surface, false);
// Reset to default texture
glBindTexture(GL_TEXTURE_2D, 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(0);
示例绑定代码:
void PCShaderScreen::BindAttributeV3(GLenum aTarget, int const aBufferID, int const aAttributeLocation, std::vector<Vector3> &aData)
if(aAttributeLocation != -1)
glEnableVertexAttribArray(aAttributeLocation);
glBindBuffer(aTarget, aBufferID);
glBufferData(aTarget, sizeof(Vector3) * aData.size(), &aData[0], GL_DYNAMIC_DRAW);
glVertexAttribPointer(aAttributeLocation, 3, GL_FLOAT, GL_FALSE, sizeof(Vector3), 0);
glBindBuffer(aTarget, 0);
VS 代码:
#version 330
in vec4 vertexPos;
in vec4 texCoord;
in vec4 objectPos;
in vec4 primaryColor;
uniform vec3 cameraDiff;
uniform vec3 cameraSize;
uniform mat3 cameraTransform;
out vec2 texValues;
out vec4 texColor;
void main()
texColor = primaryColor;
texValues = texCoord.xy;
vec3 vertex = vertexPos.xyz + objectPos.xyz;
vertex = (cameraTransform * vertex) - cameraDiff;
vertex.x /= cameraSize.x;
vertex.y /= -cameraSize.y;
vertex.y += 1.0;
vertex.x -= 1.0;
gl_Position.xyz = vertex;
gl_Position.w = 1.0;
FS 代码:
#version 330
uniform sampler2D textureUnit;
in vec2 texValues;
in vec4 texColor;
out vec4 fragColor;
void main()
// Slow, 99% CPU usage on OSX only
fragColor = texture(textureUnit, texValues) * texColor;
// Fine on everything
fragColor = vec4(1,1,1,1);
我真的没有想法,我什至尽我所能遵循 Apple 的最佳实践 (https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/OpenGL-MacProgGuide/opengl_texturedata/opengl_texturedata.html),但没有运气。
我使用的 Windows 和 Linux 驱动程序是否只是为我提供了某种我不知道的宽恕? OSX驱动真的那么敏感吗?我肯定错过了什么。任何帮助和见解将不胜感激。感谢您阅读我冗长的演讲。
【问题讨论】:
首先,使用 Instruments 和 Time Profiler 模板来分析程序中使用 CPU 时间的因素。即使它不在您的代码中,确切地说,它也可能会提供线索。其次,我的猜测是您的纹理格式并不理想,需要调配。从您链接的 Apple 文档中:“加载数据时,GL_RGBA 和 GL_UNSIGNED_BYTE 的组合需要由许多卡混合,因此不推荐。”删除texture()
调用允许 GL 检测到纹理实际上没有被使用,因此它可以跳过 swizzle。
感谢您的洞察力,遗憾的是,我将 glTexImage 调用更改为 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, mSurface->w, mSurface->h, 0, mTextureFormat, GL_UNSIGNED_INT_8_8_8_8_REV, mSurface->pixels);
,但没有运气。我确保将图像作为 GL_BGRA 加载为良好的衡量标准。 imgur.com/a/GDqba 我通过 Instruments 运行我的代码,似乎我的问题出现在 glDrawElements 调用中。
有趣的是,我看到这个对 SCCompileShader 的调用,但不确定那是什么。
@JimmySpencer 你的activeTexture
计算呢,为什么它不总是0?它是保持不变还是随着不同的平局而变化?我想不断改变纹理槽号可能会迫使驱动程序每次都重新编译着色器。
嗯,在SCCompileShader
中花费的时间表明,您对管道状态所做的某些事情导致 GL 驱动程序为每次绘制重新编译您的着色器。 SetShaderProperties()
是做什么的?
【参考方案1】:
感谢@keltar 找到这个,但我的问题出在 glActiveTexture 调用中。
我将调用从 glActiveTexture(GL_TEXTURE0 + activeTexture) 更改为 glActiveTexture(GL_TEXTURE0)。
套用@keltar:“不断更改纹理槽号可能会强制驱动程序每次重新编译着色器。我认为它的确切值并不重要,只要它不改变(并且在 GL限制)。我想您使用的硬件不能有效(或根本)从统一变量指定的任何插槽中采样纹理 - 但 GL 暗示如此。在某些硬件上,例如获取顶点属性也是着色器内部的一部分。当状态改变时, 驱动程序尝试修补着色器,但如果更改太大(或驱动程序不知道如何修补)- 则需要重新编译。遗憾的是,据我所知,OSX 图形驱动程序并不好。”
【讨论】:
【参考方案2】:您在绘制代码中执行了很多 gl 调用:绑定缓冲区、将数据上传到缓冲区等。其中大多数在准备或上传数据时会做得更好。
我更喜欢只在绘制代码中做:
-
glUseProgram(程序);
通过
glBindVertexArray
启用de VAO
通过制服
glActiveTexture
的活动纹理单元
glDrawXXX 命令
glUseProgram(0);
禁用 de VAO
【讨论】:
使用这么多的调用会导致在片段着色器中调用“texture()”,从而使 CPU 变得如此疯狂吗?我并没有真正看到相关性。 在 fs 中取出对“texture()”的调用使我的 cpu 平稳运行在 8-9% 左右,将其放回:95-99%以上是关于OSX 上的 glDrawElements 具有高 CPU 使用率的主要内容,如果未能解决你的问题,请参考以下文章
GLES2.0 上的 VBO glDrawElements 和 glVertexAttribPointer 不显示任何内容
OpenGL 优化 - 重复顶点流或重复调用 glDrawElements?
使用 osx 终端从外部硬盘驱动器中提取具有特定名称的子文件夹。