调试OpenGL比在每个命令后调用glGetError有更好的方法吗?

时间:2013-05-16 18:03:59

标签: android opengl-es

我正在Android上试验一些OpenGL,而且我之前没有任何3D编程经验。显然我在程序中犯了很多错误。

当我遇到问题并发现glGetError产生了错误代码时,我在我的绘图代码中每次调用OpenGL命令后都添加了对glGetError的调用。虽然这很有效,但我发现了我的错误,我的绘图代码现在是我认为的两倍大,难以阅读。

有没有办法摆脱对glGetError的所有这些显式调用,只是自动调用它?优选地,我的应用程序应该中止一个错误,指示如果发生OpenGL错误,哪个命令负责。

5 个答案:

答案 0 :(得分:10)

从版本4.2开始,Android在手机的开发者选项中提供了一个名为“启用OpenGL跟踪”的选项。如果将其设置为“在glGetError上调用堆栈”,您将获得类似

的输出
07-15 15:44:43.045: D/libEGL(14251): [glEnableClientState] 0x500
07-15 15:44:43.068: D/CallStack(14251): glGetError:glEnableClientState#00  pc 00019388  /system/lib/libEGL.so
07-15 15:44:43.076: D/CallStack(14251): glGetError:glEnableClientState#01  pc 0001e290  /system/lib/libdvm.so (dvmPlatformInvoke+112)
07-15 15:44:43.076: D/CallStack(14251): glGetError:glEnableClientState#02  pc 0004d410  /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JValue*, Method const*, Thread*)+395)
07-15 15:44:43.076: D/CallStack(14251): glGetError:glEnableClientState#03  pc 000276e4  /system/lib/libdvm.so

在日志中。在这种情况下,我将错误的enum / int传递给glEnableClientState()以触发错误。请注意,启用此选项将“消耗”该错误,并且进一步的glGetError()检查将不再报告此错误。相反,您现在可以节省将glGetError()调用放入代码中的时间,只需将日志输出grep为“glGetError:”。

答案 1 :(得分:2)

在OpenGL ES上你不能做得更好,如果你的目标是OpenGL ES 2.0,你也应该使用一些供应商工具(取决于你的参考/目标设备)来帮助你进行着色器开发和性能调整。

必须在循环中调用glError ,例如在java:

public void checkGLError(String op) {
    int error;
    while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
            Log.e("MyApp", op + ": glError " + error);
    }
}

但是将生产代码留给这些检查是个坏主意,glError很慢。最好的选择是封装在禁用glError的日志记录类中,除非在前一帧中发现错误。

答案 2 :(得分:1)

桌面OpenGL 4.3+具有扩展的调试和回调功能(虽然我没有任何经验)。但在ES中并没有什么比这更好的了。可悲的是,最好的解决方案仍然是不写任何glGetError(或者可能只在某些选定的重要点,如每帧的结尾或某些东西),并且只在“没有的时候引入它们。工作“

除此之外,您还可以制作一些像

这样的包装器
template<typename F> void checked(F fn)
{
    fn();
    auto error = glGetError();
    if(error != GL_NO_ERROR)
        throw std::runtime_error("OpenGL error: "+std::to_string(error));
}

...
checked([&]() { glDrawElements(...); });

(假设C ++ 11,但其他语言应具有类似的功能)

但我认为,就可读性和简洁性而言,这些解决方案仍然不能完全等同于glGetError

答案 3 :(得分:1)

有一种更好的方法称为AOP(面向方面​​编程)。我在过去(大约7年前)使用SpringFramework和PostSharp在C#中有过一些经验。这种方法广泛使用代码注入技术。

因此,当我遇到这个问题(追踪GL错误)时,它似乎是AOP的经典问题。由于代码注入引入了一些性能损失,我假设这种更改(启用GL日志记录)是暂时的,并且将它保存在我想要使用它的情况下的git补丁中。

1)首先,改变你的gradle构建脚本: 在顶级构建脚本中添加以下内容:

buildscript {
    dependencies {
        classpath 'com.uphyca.gradle:gradle-android-aspectj-plugin:0.9.14'

在app-level脚本中添加:

apply plugin: 'com.uphyca.android-aspectj'

这将在gradle中启用aspectj插件。这个项目(在这里托管:https://github.com/uPhyca/gradle-android-aspectj-plugin)现在似乎已被弃用,但它正在运行。您可以在此处查看较新版本:https://github.com/HujiangTechnology/gradle_plugin_android_aspectjx。根据我的需要(基本的java代码编织)旧版本运行良好。 重建以查找是否有任何问题。

2)添加我们以后使用的注释,以标记我们希望应用方面的方法:

package com.example.neutrino.maze;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by Greg Stein on 7/18/2016.
 */

@Retention(RetentionPolicy.CLASS)
@Target({ ElementType.CONSTRUCTOR, ElementType.METHOD })
public @interface GlTrace {
}

3)添加我们的看点:

package com.example.neutrino.maze;

import android.opengl.GLES20;
import android.opengl.GLU;
import android.util.Log;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

/**
 * Created by Greg Stein on 7/18/2016.
 */
@Aspect
public class GlTraceAspect {

    private static final String POINTCUT_METHOD =
            "execution(@com.example.neutrino.maze.GlTrace * *(..))";

    private static final String POINTCUT_CONSTRUCTOR =
            "execution(@com.example.neutrino.maze.GlTrace *.new(..))";

    @Pointcut(POINTCUT_METHOD)
    public void methodAnnotatedWithGlTrace() {}

    @Pointcut(POINTCUT_CONSTRUCTOR)
    public void constructorAnnotatedWithGlTrace() {}

    @Around("methodAnnotatedWithGlTrace() || constructorAnnotatedWithGlTrace()")
    public Object weaveJoinPoint(ProceedingJoinPoint joinPoint) throws Throwable {
        Signature signature = joinPoint.getSignature();
        String className = signature.getDeclaringType().getSimpleName();
        String methodName = signature.getName();

        // Before method execution
        // -- nothing --

        Object result = joinPoint.proceed();

        // After method execution
        Log.d(className, buildLogMessage(methodName));

        return result;
    }

    /**
     * Create a log message.
     *
     * @param methodName A string with the method name.
     * @return A string representing message.
     */
    private static String buildLogMessage(String methodName) {
        StringBuilder message = new StringBuilder();

        int errorCode = GLES20.glGetError();
        message.append("GlState[");
        message.append(methodName);
        message.append("]: ");

        if (GLES20.GL_NO_ERROR != errorCode) {
            message.append("ERROR:");
        }

        message.append(GLU.gluErrorString(errorCode));
        return message.toString();
    }
}

4)使用@GlTrace注释标记执行GL代码的方法或构造函数:

...
    @GlTrace
    public GlEngine(int quadsNum) {
...
    @GlTrace
    public void draw(float[] mvpMatrix) {
...

完成所有这些后,只需在AndroidStudio中重新运行该项目即可。您将获得以下输出:

07-18 12:34:37.715 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[<init>]: no error
07-18 12:34:37.715 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[draw]: no error
07-18 12:34:37.733 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[<init>]: no error
07-18 12:34:37.735 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[draw]: no error
07-18 12:34:37.751 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[<init>]: no error
07-18 12:34:37.751 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[draw]: no error
07-18 12:34:37.771 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[<init>]: no error
07-18 12:34:37.771 19167-19187/com.example.neutrino.maze D/GlEngine: GlState[draw]: no error

在我的项目中,我只有两种GL调用方法:draw方法和GlEngine类的构造函数。

在你玩了一下这个并且让那些烦人的&#34;没有错误&#34;消息你可以做一些改进: 0)仅打印错误 1)监控所有匹配gl *的方法(所有OpenGl方法)。

享受!

答案 4 :(得分:1)

检查glDebugMessageCallback

void GLAPIENTRY
MessageCallback( GLenum source,
                 GLenum type,
                 GLuint id,
                 GLenum severity,
                 GLsizei length,
                 const GLchar* message,
                 const void* userParam )
{
  fprintf( stderr, "GL CALLBACK: %s type = 0x%x, severity = 0x%x, message = %s\n",
           ( type == GL_DEBUG_TYPE_ERROR ? "** GL ERROR **" : "" ),
            type, severity, message );
}


// During init, enable debug output
glEnable ( GL_DEBUG_OUTPUT );
glDebugMessageCallback( MessageCallback, 0 );