Monday, September 16, 2013

What's coming in WebGL 2.0

The WebGL working group has just released a public draft of the WebGL 2.0 spec. Hooray! Of course, being a public draft things are still subject to change and there are still plenty of TODOs, so don't be too surprised if things get chopped, tweaked, added or completely reworked before WebGL 2.0 reaches a browser near you.

The nice thing is, however, that since the entire goal of the spec is to bring OpenGL ES 3.0 capabilities to the browser the chances of things deviating too much from that spec are pretty minimal, and it's probably safe to assume that most ES 3.0 features will make their way into your browser soon. (With a few notable exceptions that I'll discuss in a bit.)

So what is new in 2.0? I realize that reading specs is rarely fun, so allow me to summarize for you. While this won't be a complete rundown of the upcoming features it should provide a good sense of where things are headed.

(Note that links to the OpenGL Wiki may refer to desktop OpenGL features that are not available in ES 3.0.)

[UPDATE: Apparently the words "This won't be a complete rundown" weren't explicit enough, so allow me to point out that the following are not the only features in WebGL 2.0! Array Textures, Depth Textures, NPOT textures, Floating point textures, New compression formats, and all the rest of your favorite ES 3.0 features are coming unless we find serious compatibility issues with them. Don't freak out because something isn't explicitly listed below. (Unless it's listed in the "Not gonna do it" section, then feel free to complain to the mailing list.]

Promoted Extensions

Some of the new features are already available today as extensions, but will be part of the core spec in WebGL 2.0. The big benefit to being part of the core being, of course, that support is guaranteed. No longer will you have to code fallbacks for Multiple Render Targets! If your device supports WebGL 2.0 then you have these features, period!

Multiple Render Targets
Currently exposed through the WEBGL_draw_buffers extension. Allows a single draw call to write out to multiple targets (textures or renderbuffers) with a single draw call. This is "the big one" in a lot of developers eyes because it makes many of the modern deferred rendering techniques that have become such a core part of modern realtime 3D practical for WebGL.

Currently exposed through the ANGLE_instanced_arrays extension. Instancing allows you to render multiple copies of a mesh with a single draw call. This is a great performance booster for certain types of geometry, especially when rendering highly repetitive elements like grass.

Currently exposed through the OES_vertex_array_object extension. VAOs allow you to store all of the vertex/index binding information for a set of buffers in a single, easy to manage object. This is very similar to the IDirect3DVertexDeclaration9/ID3D11InputLayout interfaces in DirectX land.

Fragment Depth
Currently exposed through the EXT_frag_depth extension. Lets you manipulate the depth of a fragment from the fragment shader. This can be expensive because it forces the GPU to bypass a lot of it's normal fragment discard behavior, but can also allow for some interesting effects that would be difficult to accomplish without having incredibly high poly geometry.

New Features

These features are brand new to WebGL, and bring us to near-feature-parity with OpenGL ES 3.0.

Multisampled Renderbuffers
Previously if you wanted your WebGL content to be antialiased you would either have to render it to the default backbuffer or perform your own post-process AA (like FXAA) on content you've rendered to a texture. That's not a terribly happy situation if you're concerned about the quality of your rendered output. With this new functionality you can render to a multisampled renderbuffer and then blit that multisampled content to a texture. It's not as simple or fast as rendering directly to a texture, but it's still a lot better than not having multisampling at all for non-default framebuffers.

3D Textures
This feature is pretty self-explanatory. A 3D texture is essentially just a stack of 2D textures that can be sampled with X, Y, and Z coords in the shader. This is useful for visualizing volumetric data (like medical scans), 3D effects like smoke, storing lookup tables, etc.

Sampler Objects
Currently when you create a texture it contains two distinct types of data. First is the image data: the array of bytes that makes up the actual pixel data at each mipmap level. Then there's the sampling information, which tells the GPU how to read that image data. This is things like filtering and wrap modes. It's often useful to separate those two concepts, however. For example: Today if you want to read from the same texture twice in one shader, one instance with linear filtering and the other with nearest filtering, your only option is to duplicate the texture! Less critical but still annoying: currently you have to set the sampling information on every single texture you create, even if they'll all use the same settings.

Sampler objects allow you to store all the information about how to sample data from a texture separately from the texture itself, which becomes nothing but a container for the image data. During your draw loop you can pair textures and samplers as needed. It's a simple thing, but surprisingly useful!

Uniform Buffer Objects
Setting shader program uniforms is a huge part of almost any WebGL/OpenGL draw loop. This can make your draw calls fairly chatty as they make hundreds or thousands of gl.uniform____ calls. Uniform Buffer Objects attempts to streamline this process by allowing you to store blocks of uniforms in buffers stored on the GPU (like vertex/index buffers). This can make switching between sets of uniforms faster, and can allow more uniform data to be stored. Additionally, uniform buffers can be bound to multiple programs at the same time, so it's possible to update global data (like projection or view matrices) once and all programs that use them will automatically see the changed values.

This functionality is reasonably close to Direct3D 11 Constant Buffers

Sync Objects
With WebGL today the path from Javascript to GPU to Screen fairly opaque to developers. You dispatch draw commands and at some undefined point in the future the results (probably) show up on the screen. Sync objects allow the developer to gain a little more insight into when the GPU has completed it's work. Using gl.fenceSync, you can place a marker at some point in the GPU command stream and then later call gl.clientWaitSync to pause Javascript execution until the GPU has completed all commands up to the fence. Obviously blocking execution isn't desirable for applications that want to render fast, but this can be very beneficial for getting accurate benchmarks. It may also possibly be used in the future for synchronizing between workers.

Query Objects
Query objects give developers another, more explicit way to peek at the inner workings of the GPU. A query wraps a set of GL commands for the GPU to asynchronously report some sort of statistic about. For example, occlusion queries are done this way: Performing a gl.ANY_SAMPLES_PASSED query around a set of draw calls will let you detect if any of the geometry passed the depth test. If not you know the object wasn't visible and may choose not to draw that geometry in future frames until something happens (object moved, camera moved, etc) that indicates the geometry might have become visible again.

It should be noted that these querys are asynchronous, which means a queries results may not be ready for many frames after the query was originally issued! This makes them tricky to use, but it can be worth it in the right circumstances.

Transform Feedback
Transform feedback allows you to run geometry through the vertex shader and write the resulting vertices into a buffer. That buffer could then be used to re-submit the draw calls without going through the full vertex transforms again. This could be used to capture the positions of a GPU-driven particle system, or write out the results of mesh which was skinned on the GPU.

What didn't make the cut

The nature of browsers and Javascript means that WebGL can't simply be a dumb wrapper over the OpenGL C interface. Most of the time this doesn't pose a big problem, and can actually yield nicer interfaces in some cases (like gl.texImage2D accepting img tags), but in some cases the realities of the environment simply don't allow for some functionality to be exposed in a reasonable manner.

Please understand that nobody likes dropping features, but these decisions have been well thought out (and in some cases fought over.) That said, if you feel very strongly that the wrong decision was made now, while the spec is in draft, is the time to voice your opinion

Mapped Buffers
Javascript VMs simply cannot, for many reasons, expose a raw unchecked chunk of memory. This means that any attempt to expose a mapped buffer interface would involve WebGL allocating a managed typed array, copying the desired data into it, and then going through some extreme contortions to detect changes and copy them back to the actual mapped memory. This would completely destroy any of the performance benefits of using this API, and since the whole point of the API is to achieve better performance it would be disingenuous at best to try and expose something that looked like mapped buffers but didn't actually act like it.

In place of the mapping functions, however, WebGL 2.0 will provide a getBufferSubData function to allow you to query back portions of a buffer. It's not a complete replacement, but it will help cover at least one of the use cases.

Program Binaries
This is a feature that sounds really useful when you first hear about it, but the shine wears off quickly when you learn about the realities of how it works. The fact is that even if we exposed this to Javascript is would be extremely difficult for developers to use in a meaningful fashion.

Instead WebGL implementations can attempt to cache program binaries after they are first built and will re-use that cache for subsequent runs. This is behavior that is already in place, and will continue to function with WebGL 2.0.

Similar to mapped buffers, there doesn't appear to be a good way to expose this function and deliver the performance benefits that are assumed to come with it's use. (The WebGL implementation would have to re-validate all it's indices to ensure that they all fall within the range specified.) As such drawRangeElements it probably won't make it into the final spec, but we'd appreciate external feedback on how widely used/necessary this function is in ES 3.0 or desktop GL content.

What does this mean for you?

For the time being, not much. This is a draft spec, not an announcement of implementation. Full implementations of the spec won't be appearing in browsers for a little while, and even when they do there will be a period where it will likely be hidden behind a flag. As such it's going to be a little while before the average user will be able to see your web content.

As I mentioned earlier, however, some of the more exciting pieces of functionality are available right now as WebGL 1.0 extensions. If you'd like to start prepping your content for WebGL 2.0 feel free to start playing with WEBGL_draw_buffers and ANGLE_instanced_arrays now and you can feel confident that those features will "just work" when WebGL 2.0 lands. As for the rest of WebGL, nothing is being removed or deprecated and WebGL 2.0 is fully backwards compatible. This means that all it should take to "upgrade" your existing content is to add a "version: 2" the context attributes. That won't magically yield any performance or quality gains, of course, but it's nice to know that the code you are writing today won't be obsolete a year from now.


  1. Also very cool: Arrays Textures, probably even more useful than 3D textures as such. :)

  2. So is Google working on WebGL2 like Mozilla? can expect some alpha builds this year? is ANGLE ES 3.0 branch stable enough?

    1. I don't have any timelines for you, unfortunately. We're very excited about it, but there's a lot of work to be done and some higher priority items that we need to sort out first. The good news is that you can start playing with some of these features right now in Mozilla's nightly builds!

  3. Mapped buffers were not designed to get performance on readback. They were meant to support appending to the end of a buffer with the NOOVERWRITE flags, similar to what DX7+ has had for millenia. glSubBufferData is known to lock the entire buffer and stall writes if you try to update a partial section, since it has no guarantee of where the buffer is locked. I hope you revisit this ommission for WebGL 2.

    Also, why is it that ES2/3 (and by extension WebGL1/2) have no support for glDrawElementsBaseVertex (and it's instance counterpart)? There is currently no way to draw from a VAO that stores all models in a single VBO with more than 64K vertices. 16-bit vertices don't have to mean that the VB only has 64K vertices. Verifying the IB range is simple too, since the GLint offset can be positive or negative. This converts 16-bit indices to 32-bit nicely without the buffer cost.

    1. Mapped buffers cover a lot of use cases, all of them useful. I'm not happy to see them excluded but the reality of working with Javascript is that any method we used to expose the function would in fact be a clunky wrapper around the actual functionality, which harms it's usefulness. I very strongly encourage you to bring this point up on the mailing list, however! Even if we can't figure out a way to provide mapped buffers directly we may be able to provide a way to cover the use case you mentioned efficiently.

      As for your complaint about drawElementsBaseVertex, I totally agree! It would be very nice to have that function available, and I'm not sure why it wasn't part of the ES3 spec either. There's a couple of points worth mentioning in this regard, however. First: ES3 supports 32 bit indicies as part of the core spec, and WebGL (1) supports them via an extension, so yay! Obviously that uses more memory, but it's better than nothing. :)

      Secondly, you will probably never see the WebGL (1 or 2) spec require functionality that isn't guaranteed on mobile devices with the appropriate hardware. AKA: Core ES2 or ES3 functionality. Mobile is such a large part of the computing environment these days that it's API suicide to not consider mobile as a first class citizen. Plus it would be really crappy if your brand new ES3-compatible tablet couldn't use WebGL2 because it didn't support some extra functionality we shoved into the spec. We do allow for extensions, however, so it's entirely possible that we could add BaseVertex in as an optional API in the future.

    2. I'm just surprised at all of the GPU efficiencies that ES2 missed, and that ES3 misses as well. Is there any way to obtain the internal texture format, a lack of block compressed textures across devices, and no support in WebGL for max vertex or index counts.

      I think WebGL 1 really missed the mark by adopting mobile GPU limitations. Mobile will soon become desktop, and the mobile world really didn't adopt WebGL initially anyway. The GPUs are also just not powerful enough. I'm happy to see that ES3 is closer to desktop, so that bodes well for WebGL 2. Get the desktop functionality, and then pare down for mobile.

  4. Hi Brandon,

    Along with draw_buffer extension, do you know if EXT_multiview_draw_buffers and EXT_multiview_window will soon follow ?
    The purpose would be to create quad buffer active stereoscopic rendering in Chrome... :-)

    Thanks !

    1. There's no plans that I'm aware of of right now for those extensions. We probably won't start looking at extensions for WebGL 2 until after some implementations have been released. That said, if you would like to see them the right place to propose the extensions be considered would be the WebGL Public mailing list.

  5. Is it possible to enable the use of the webgl-draw-buffers extension for either chrome or FF on Windows 8 now? Or do I have to wait for vendors to start supporting webgl 2.0?
    I've tried enabling draft extensions (on both browsers, canary and nightly FF), and I've tried desktop openGL instead of ANGLE, and still according to the draw-buffers conformance test I don't have draw-buffers.

    1. I got the same situation here. Cannot enable webgl-draw-buffer on Win8 with Canary

    2. WEBGL_draw_buffers is only available on Windows when Direct3D 11 is enabled, which is still experimental. (Hopefully not for longer, though.)

      You can enable D3D11 mode at chrome://flags/#enable-d3d11

  6. Do you have an update on WebGL 2 is expected to be go live? I saw there are commits on meta bugs for WebGL 2 on Chrome and Firefox.

    1. I don't have an good estimate for you, unfortunately. We're still taking care of dependencies and gauging what the roadblocks will be. We are hoping that the initial release is available on the order of N months from now, certainly not N years. :)