我正在Android上尝试一些OpenGL,而且我之前没有任何3D编程经验。显然我在程序中犯了很多错误。
当我遇到问题并发现glGetError
产生了错误代码时,我在我的绘图代码中每次调用OpenGL命令后都添加了对glGetError
的调用。虽然这很有效,但我发现了我的错误,我的绘图代码现在是我认为的两倍大,难以阅读。
有没有办法摆脱所有这些显式调用glGetError
并自动调用它?优选地,如果发生OpenGL错误,我的app应该只是中止错误,指示哪个命令负责。
从版本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
在日志中。在这种情况下,我将错误的枚举/ int传递给glEnableClientState()
以触发错误。请注意,启用此选项将“消耗”该错误,并且进一步的glGetError()
检查将不再报告此错误。相反,您现在可以节省将glGetError()
调用放入代码中的时间,并将grep日志输出为“glGetError:”。
在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的日志记录类中,除非在前一帧中发现错误。
桌面OpenGL 4.3+具有扩展的调试和回调功能(虽然我没有任何经验)。但在ES中并没有什么比这更好的了。可悲的是,最好的解决方案仍然是不写任何glGetError
s(或者可能只在某些选定的重要点,如每帧的结尾或某些东西),并且只在“不起作用”的东西时才引入它们。
除此之外你还可以做一些包装
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
s,关于可读性和简洁性。
有一种更好的方法称为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) {
...
完成所有这些后,只需在Android Studio中重新运行该项目即可。您将获得以下输出:
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类的构造函数。
在你玩这个并获得那些烦人的“无错误”消息之后你可以做一些改进:0)仅打印错误1)监视所有匹配gl *的方法(所有OpenGl方法)。
请享用!
检查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 );