[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: [Public WebGL] Asynchronous calls





On Wed, May 2, 2012 at 4:37 PM, Glenn Maynard <glenn@zewt.org> wrote:
Currently, most WebGL calls, like OpenGL, are allowed (though not required) to be asynchronous.  finish() or getLastError() will flush the queue, synchronously waiting for the previous commands to finish.  This is good for rendering, where you normally avoid calling those functions at all, but it means some operations that should be asynchronous--shader compilation, most recently--are effectively synchronous, since you always want to call getLastError to make sure your shaders compiled and your program linked.

A generic solution for this is an asynchronous getLastError.  For example,

Compile errors are returned with gl.getShaderParameter(shader, gl.COMPILE_STATUS), Link errors are returned with gl.getProgramParameter(program, gl.LINK_STATUS)

Neither of those are actually an error.

Also, you can compile X shaders and link Y programs all without checking the status of any of them. Which effectively means you can async compile multiple things at once. If you wanted to check them you'd need to able to check them individually, not with something like getLastError. Which one would be get getting the error for?

The fastest way to compile/link right now is something like

function makeShader(type, src) {
  s = gl.createShader(type);
  gl.shaderSoruce(s, src);
  gl.compileShader();
  return s;
}

var shaders = [
 { vsrc: "some v shader", fsrc: "some f shader" },
 { vsrc: "some v shader", fsrc: "some f shader" },
 { vsrc: "some v shader", fsrc: "some f shader" },
 { vsrc: "some v shader", fsrc: "some f shader" },
 { vsrc: "some v shader", fsrc: "some f shader" }
];
var programs = [];
for (var i = 0; i < shaders.length; ++i) {
  var shaderPair =shaders[i];
  vs = makeShader(gl.VERTEX_SHADER, shaderPair.vsrc);
  fs = makeShader(gl.FRAGMENT_SHADER, shaderPair.fsrc);
  p = gl.createProgram();
  gl.attachShader(p, vs);
  gl.attachShader(p, fs);
  gl.linkProgram(p);
  programs.push(p)
}

// Optionally do something else here that takes some time. Upload textures, geometry, contact servers, ....

// Then... check if any of them failed.

for (var i = 0; i < programs.length; ++i) {
   if (!gl.getProgramParameter(programs[i], gl.LINK_STATUS)) {
      // maybe you want to print the program log and check shader status at this point


Whether or not you do something between the compile+link and checking for failure it will be much faster then the typical compile, check, compile, check, link, check, compile, check, compile, check, link, check which most GL/WebGL apps do which stalls the system on each check.

AFAIK both of these suggestions will currently only help Chrome. AFAIK all other browsers are calling GL on the same thread as _javascript_ and I know of no GL drivers that compile or link on another thread. I don't know what Firefox or Safari's plans are with regards to WebGL but I suspect if this truly is a priority issue it will either require major re-architecting so that those browsers actually issue GL calls on other threads or it will require an API like asyncCompileShader(s, callback) and asyncLinkProgram(p, callback) that is easier to implement given their current implementations.

But, back to the original point. I'm still not sure this is a priority.
 

var shader = ctx.createShader();
ctx.shaderSource(shader, source);
ctx.compileShader(shader);
ctx.getLastErrorAsync(function(e) {
    // All commands up to this point are flushed, and e is the error value.  Check the result and start compiling the next shader.
});
return;

The callback is called when the result is available.

There are other details (eg. what to do if more WebGL calls are made after getLastErrorAsync but before returning, and if multiple getLastErrorAsync calls are made after that), which I'll put off talking about; for now I'll just put the basic idea out there for people to think about.  This could allow making much better use of the asynchronous nature of WebGL/OpenGL, especially at load time (where most getLastError calls are made).

It could also allow checking for errors during rendering, which is currently prohibitively expensive, because calling this function wouldn't cause a stall.

It wouldn't help with other functions that return values (eg. getParameter), but this is the one that usually matters most.

This is probably fairly complex to implement.  It may require running the underlying OpenGL/GLES context in its own thread (maybe they already do this; it seems like a good idea on its own).  I'm not sure about D3D. 

--
Glenn Maynard