间歇性 GLSL 顶点着色器编译错误

9

我一直在编译顶点着色器时遇到间歇性错误,为新创建的OpenGL上下文做第一次渲染的准备。这是通常能够正常工作的同样的顶点着色器,在同样的硬件上。失败后,glGetShaderInfoLog返回的信息日志通常会显示如下内容:

Vertex shader failed to compile with the following errors:

是的,就是这样。它发生在Windows上,使用ATI和NVidia GPU,尽管我主要在ATI上进行测试。如果我在Visual Studio调试器下运行,则可能会检测到glCompileShader或随后的glGetShaderiv调用中的访问冲突。

我没有在Mac上看到这个错误,但由于不总是容易重现它,我不能确定它不会发生。

我注意到,如果我在创建上下文时不调用wglShareLists,则错误会消失(或者至少我无法轻松地重现它)。我认为这意味着一些错误信息从先前创建(也许是先前销毁)的OpenGL上下文渗透到新的上下文中。然而,我已经修剪了一些东西,以至于几乎没有什么需要共享:没有纹理对象、显示列表、VBO、PBO。据我所知,唯一剩下需要共享的东西是着色器和程序对象。

为窗口创建OpenGL上下文后,我大致按照以下方式初始化着色:

// Program initialization
GLint vertexShader = glCreateShader( GL_VERTEX_SHADER );
glShaderSource( vertexShader, 1, &vertexShaderSource, NULL );
glCompileShader( vertexShader );
GLint   status;
glGetShaderiv( vertexShader, GL_COMPILE_STATUS, &status );
if (status == GL_TRUE)
{
    GLint fragmentShader = glCreateShader( GL_FRAGMENT_SHADER );
    glShaderSource( fragmentShader, 1, &fragShaderSource, NULL );
    glCompileShader( fragmentShader );
    glGetShaderiv( vertexShader, GL_COMPILE_STATUS, &status );
    if (status == GL_TRUE)
    {
        GLint program = glCreateProgram();
        if (program != 0)
        {
            glAttachShader( program, vertexShader );
            glAttachShader( program, fragmentShader );
            glLinkProgram( program );
            glDetachShader( program, fragmentShader );
            glDetachShader( program, vertexShader );
            glDeleteShader( fragmentShader );
            // check for link success...
        }
    }
}
else
{
    char logBuf[1024];
    glGetShaderInfoLog( vertexShader, sizeof(logBuf), NULL, (GLchar*)logBuf );
    LogToFile( (char*)logBuf );
}

然后我渲染多个帧,并在某个时刻进行清理。
glDeleteProgram( program );
glDeleteShader( vertexShader );

销毁OpenGL上下文。稍后,我在同一个窗口中创建了一个新的OpenGL上下文,以相同的方式进行初始化,但顶点着色器无法编译。

如果不渲染任何几何图形,则无法重现错误,但渲染一个点就足够了。

即使使用简化的顶点着色器仍然会发生这种情况:

#version 120
void main()
{
    gl_Position = ftransform();
    gl_FrontColor = gl_Color;
    gl_BackColor = gl_Color;
}

以及一个简化的片段着色器:

#version 120
void main()
{
    gl_FragColor = gl_Color;
}

我尝试了AMD的CodeXL OpenGL调试器,并将其设置为在错误时中断。它只告诉我编译失败了,这已经是我知道的了。
OpenGL上下文失败的窗口先前创建了一个不同的上下文,成功渲染并销毁。像这样重复使用窗口没有问题,对吧?(我知道一旦你调用了SetPixelFormat,就无法更改窗口的像素格式。我确保每次都使用相同的像素格式。)
补充:在缩减渲染代码时,我发现如果注释掉这行代码:
glEnable( GL_VERTEX_PROGRAM_TWO_SIDE );

然后错误消失了。但是在重新引入更多真实世界的渲染(例如纹理)后,它似乎并没有什么区别。
2014年3月7日添加:我最终制作了一个自包含的Visual C++项目,可以在某些情况下重现错误,以便任何感兴趣的人都可以尝试:ShaderBugTest project 为了使错误能够重现,您需要将Microsoft的“应用程序验证器”设置为监视堆错误。附带的Read Me有更详细的重现步骤。

你提到“如果我不渲染任何几何体,我就无法重现这个错误”。是否有可能是渲染代码中的错误导致后来出现了错误? - GuyRT
@GuyRT,如果我说渲染代码没有错误的可能性,那就太愚蠢了。我正在简化渲染代码的过程中,但是还没有准备好在这里发布。我想我是希望有人能给我一些线索指导我该从哪方面入手。 - JWWalker
@JWWalker:访问冲突表示您正在尝试读取或写入不属于您的内存。如果您在调试器中检查vertexShader,它看起来是否正常?还是以某种方式被损坏了? - Goz
@JWWalker:说实话,我不确定它是否存储在只读内存中... 最后,堆破坏表明围绕分配的保护块被覆盖。这可能会导致访问冲突。您已经更新了驱动程序,是吗?你无法想象你会遇到多少驱动程序错误。 - Goz
这个链接可能对你在追踪问题时有所帮助:http://www.informit.com/articles/article.aspx?p=1081496 - Goz
显示剩余4条评论
3个回答

5

你的示例涉及大量的OpenGL对象分配和释放,但几乎没有错误检查。

vertexShader = glCreateShader( GL_VERTEX_SHADER );

需要检查返回值vertexShader,确保其有效。

glGetShaderiv( vertexShader, GL_COMPILE_STATUS, &status );

您获取这个值,但不要显示您正在检查状态。
program = glCreateProgram();

再次提醒,使用前需要先检查返回值。

glAttachShader( program, vertexShader );
fragmentShader = glCreateShader( GL_FRAGMENT_SHADER );
glShaderSource( fragmentShader, 1, &fragShaderSource, NULL );
glCompileShader( fragmentShader );

在将着色器附加到程序之前,您应该先创建片段着色器和顶点着色器。这样,如果出现问题,您需要清理的内容就会更少。

 glGetShaderiv( fragmentShader, GL_COMPILE_STATUS, &status );

在获取之后,请检查编译状态。您的编译失败了,但我们还不知道哪个着色器无法编译。是片段着色器还是顶点着色器?

 glAttachShader( program, fragmentShader );
 glDeleteShader( fragmentShader );

在这里删除您的着色器很奇怪。是的,它应该只被标记为删除,因为它实际上已经附加到程序中,但这仍然似乎是一个危险的方法。此外,您对管理片段着色器采取了与顶点着色器不同的方法,没有明显的原因。

 glLinkProgram( program );
 glDetachShader( program, fragmentShader );
 glDetachShader( program, vertexShader );

再次强调,这是一种对于着色器管理相当令人困惑的方法。同样地,将片元着色器和顶点着色器视为同等重要。

 glDeleteProgram( program );
 glDeleteShader( vertexShader );

为什么要对片段着色器进行不同的处理?

在你诊断问题时,应该使用 glGetError() 并在每次 GL 调用后停止程序,以便隔离第一个错误发生的位置。可能编译失败是由先前发生的某些事情引起的级联效应。


我怀疑你的GL实现由于处理方式不当而存在资源泄漏:依赖删除标记机制在删除程序时进行清理。这个问题是否在第一次出现过? - Jherico
除非您实际上正在加载数百或数千个着色器,否则没有真正的理由在编译它们后释放着色器对象。将它们放入持久存储中,通过从源位置检索它们并将它们附加到创建的程序来创建程序。您还可以使用一对着色器键作为程序键以相同的方式持久化程序。所以:在程序启动时编译所有着色器,然后再也不要碰它们了。 - Jherico
例如,这里可以查看GlUtils::getProgram()的实现:https://github.com/OculusRiftInAction/OculusRiftInAction/blob/master/source/common/GlUtils.cpp#L1043 - Jherico
关于“在这里删除您的着色器很奇怪”的问题:似乎我在某本书中看到过这样的建议。无论如何,我尝试了另一种方法,在链接程序并分离着色器后删除片段着色器,但这并没有解决问题。 - JWWalker
让我们在聊天中继续这个讨论:http://chat.stackoverflow.com/rooms/49101/discussion-between-jwwalker-and-jherico - JWWalker
显示剩余3条评论

2

看起来您没有将任何内容写入gl_Color

gl_Color属性在不同的位置可以表示不同的含义。

在您的顶点着色器中,gl_Color表示由用户使用glColor*glColorPointer调用传递的每个顶点颜色属性。

在您的片段着色器中,gl_Color表示正在渲染的三角形面对的插值颜色。

然而,这是一个固定管线功能,并且在现代OpenGL中已经被弃用。这可能是您出错的原因。您需要使用以下代码获取编译器错误消息:

GLint   SuccessFlag = 0;
GLsizei Length      = 0;
GLsizei MaxLength   = 0;

glGetShaderiv( ShaderID, GL_COMPILE_STATUS,  &SuccessFlag );
glGetShaderiv( ShaderID, GL_INFO_LOG_LENGTH, &MaxLength   );

char* Log = ( char* )alloca( MaxLength );

glGetShaderInfoLog( ShaderID, MaxLength, &Length, Log );

将日志添加到您的问题中将有助于更好地诊断您的问题。

您使用的OpenGL版本是什么?它是核心配置文件还是兼容性配置文件?


我已经获取到着色器信息日志。你可以在我的问题的第一段中找到这段文本,并且结尾处有获取它的代码。GL_VERSION字符串是"3.3.11554 Compatibility Profile Context FireGL"。 - JWWalker
如果您使用这些着色器:void main() { gl_Position = ftransform(); }void main() { gl_FragColor = vec4( 1.0, 0.0, 0.0, 0.5 ); },您是否仍然有错误? - Sergey K.
@JWWalker:这意味着错误代码不在着色器中,而是在其他GL调用中。尝试在每个OpenGL调用之后调用“glGetError()”。 - Sergey K.
涉及的计算机已经有几年了,所以它们可能会说不再支持那些卡的驱动程序。我尝试更新其中一个驱动程序,现在我在ChoosePixelFormat中遇到崩溃,甚至还没有进入任何OpenGL代码。 - JWWalker
@JWWalker:“我试图更新其中一个驱动程序,现在出现了崩溃”,这解释了很多问题。 - Sergey K.
显示剩余3条评论

2

我确实收到了编译错误信息,并且在我的问题中早就告诉了你是什么。 - JWWalker
“Vertex shader failed to compile with the following errors:”是实际的错误,还是你的代码打印的前缀?能否贴出你用来打印错误的代码? - user1726433
这是从驱动程序中检索到的实际信息日志文本,而不是来自我的代码。我将把代码添加到我的问题中。 - JWWalker
顺便说一下,我能理解为什么你可能会认为“顶点着色器编译失败,以下是错误信息:”可能是由我的代码提供的前缀,但我认为这实际上是驱动程序编译错误消息的典型开头。 - JWWalker

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接