Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dart WebGL Debugging

I have started to use Dart with WebGL to write some tech demos and tests, however I can't seem to figure out how to debug WebGL in Dart.

Markus Notch (the creator of Minecraft) had a livestream on twitch where he coded his game with Dart and WebGL too, however every time he messed up something he got an error log in his console from WebGL with the description of the error. For example check out 1:13:30. Sadly I couldn't find the part that does the debugging in his code.

Of course I can check for errors in my shaders with getShaderParameter() and getProgramParameter() and then get the error log with the appropriate getInfoLog() method, however for regular debugging (e.g. check for INVALID_ENUM errors) I can get the error as an integer with getError() but can't turn it into an enum, error message or any humanly readable debug information.

I've tried to look into web_gl.dart's source code but couldn't find anything in it that would allow for debugging.

Any help would be very much appreciated.

Edit: Based on Notch's answer Chromium (probably) automatically logs these errors. Not for me though, gl.getError() reports error 1280 while the Chromium console is empty. :(

Edit2: Found the solution, check out my answer.

like image 513
Krisztián Szabó Avatar asked Jan 03 '14 12:01

Krisztián Szabó


3 Answers

Edit: I totally misunderstood the question. Ignore this answer.

For the shaders (vertex and fragment):

gl.compileShader(shader);
boolean isCompiled = gl.getShaderParameter(shader, WebGL.COMPILE_STATUS);
if (isCompiled == false) {
  String compileLog = gl.getShaderInfoLog(shader);
  // Display the compile log
}

For the linking the program:

gl.linkProgram(program);
bool isCompiled = gl.getProgramParameter(program, WebGL.LINK_STATUS);
if (isCompiled == false) {
  String linkLog = gl.getProgramInfoLog(program);
  // Display the link log
}

On some hardware, the shader info log and program info log will be an empty string if there was no errors, but you can't rely on this behavior.

like image 110
Notch Avatar answered Nov 20 '22 11:11

Notch


Okay, so I actually found the answer, and it was really obvious.

Notch was right: Chromium automatically reports the WebGL errors. However, it stops logging them after a bit of a time ("WebGL: too many errors, no more errors will be reported to the console for this context.") and since I was logging stuff every frame my console quickly got filled up with my own information, pushing Chromium's logs to the very top, or even deleting them (not sure how many lines the debug console can store at max, but I remember scrolling up to the top a few times and I've never seen Chromium's report).

A quick launch and stop of the application revealed the information.
Gotta love this kind of errors. Sigh

like image 41
Krisztián Szabó Avatar answered Nov 20 '22 13:11

Krisztián Szabó


Little late to the party here but there is a robust way to handle errors in WebGL in Dart and that is through creating a proxy object to the WebGLRenderingContext. This solution uses dart:mirrors so this should only be used during development and should be turned off on shipping code. The other reason to turn it off is because calling getError all the time can be a real drain on your performance, but this provides a relatively painless way to hunt errors during development.

class DebugRenderingContext implements WebGL.RenderingContext {
  final StreamController<RenderingErrorEvent> _onErrorController;
  final WebGL.RenderingContext _gl;

  DebugRenderingContext(WebGL.RenderingContext gl)
      : _gl = gl
      , _onErrorController = new StreamController<RenderingErrorEvent>();

  Stream<RenderingErrorEvent> get onError => _onErrorController.stream;

  dynamic noSuchMethod(Invocation invocation) {
    // Invoke the method and get the result
    var mirror = reflect(_gl);
    var result = mirror.delegate(invocation);

    // See if there was an error
    var errorCode = _gl.getError();

    // Multiple errors can occur with a single call to WebGL so continue to
    // loop until WebGL doesn't return an error
    while (errorCode != WebGL.NO_ERROR) {
      if (!_onErrorController.isPaused) {
        // Query the symbol name
        var methodName = MirrorSystem.getName(invocation.memberName);

        // Put the error in the stream
        _onErrorController.add(new RenderingErrorEvent._internal(errorCode, methodName));
      }

      errorCode = _gl.getError();
    }

    return result;
  }
}

So this code wraps a WebGL.RenderingContext and invokes methods on the actual context through the noSuchMethod method on the class. The noSuchMethod is passed in a mirror of the invocation which provides the method being called and the parameters. This is delegated to the actual WebGL.RenderingContext.

From there the getError value is checked and if an error is found it is put into a stream. This is just a class that gives you a nice output similar to what Khronos provides.

class RenderingErrorEvent {
  /// The [WebGL] error code.
  final int error;
  /// The name of the method whose call resulted in the [error].
  final String methodName;

  RenderingErrorEvent(this.error, this.methodName);

  /// Retrieves a human readable error message.
  String get message {
    var errorMessage;

    switch (error) {
      case WebGL.INVALID_ENUM:
        errorMessage = 'An unacceptable value is specified for an enumerated argument. The offending command is ignored and has no other side effect than to set the error flag.';
        break;
      case WebGL.INVALID_VALUE:
        errorMessage = 'A numeric argument is out of range. The offending command is ignored and has no other side effect than to set the error flag.';
        break;
      case WebGL.INVALID_OPERATION:
        errorMessage = 'The specified operation is not allowed in the current state. The offending command is ignored and has no other side effect than to set the error flag.';
        break;
      case WebGL.INVALID_FRAMEBUFFER_OPERATION:
        errorMessage = 'The framebuffer object is not complete. The offending command is ignored and has no other side effect than to set the error flag.';
        break;
      case WebGL.OUT_OF_MEMORY:
        errorMessage = 'There is not enough memory left to execute the command. The state of the GL is undefined, except for the state of the error flags, after this error is recorded.';
        break;
      default:
        errorMessage = 'An unknown error occurred';
        break;
    }

    return '${methodName}: ${errorMessage}';
  }
}

This code doesn't handle extensions but could be extended to do so. You would just need to provide a getExtension method and create the same sort of wrappers around the extensions you're concerned about.

like image 2
Don Olmstead Avatar answered Nov 20 '22 13:11

Don Olmstead