Skip to content

Commit 3a287fb

Browse files
committed
Try to emulate WebGL1 on WebGL2
Add some testing so we can see it works.
1 parent 75ca5d2 commit 3a287fb

16 files changed

+31524
-241
lines changed

.vscode/settings.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"cSpell.words": [
3+
"mediump"
4+
]
5+
}

README.md

Lines changed: 99 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33
Virtualizes a single WebGL context into multiple contexts
44

55
[A demo of some WebGL apps running with only 1 shared WebGL context](https://greggman.github.io/virtual-webgl/example/example.html)
6-
and using `alpha: false`, `premultipliedAlpha: false`, `preserveDrawingBuffer: true` and some other things. [A similar demo for WebGL2](https://greggman.github.io/virtual-webgl/example/example2.html).
6+
and using `alpha: false`, `premultipliedAlpha: false`, `preserveDrawingBuffer: true`
7+
and some other things. [A similar demo for WebGL2](https://greggman.github.io/virtual-webgl/example/example2.html).
8+
And, [one for a mix of WebGL1 and WebGL2](https://greggman.github.io/virtual-webgl/example/example1and2.html).
79

810
[A demo of creating and disposing of webgl contexts](https://greggman.github.io/virtual-webgl/example/dispose.html).
911
WebGL implementations often delete the oldest context when too many are created. Using virtual-webgl you can
@@ -20,9 +22,10 @@ Compare to [the original without post processing](https://greggman.github.io/vir
2022

2123
Browsers usually limit WebGL to 8 to 16 contexts max. This is one idea to overcome that limit.
2224

23-
I don't actually recommend this at all. If you're in control of your code then there are
24-
much better solutions [like this for raw webgl](http://twgljs.org/examples/itemlist.html)
25-
and [this for three.js](https://threejs.org/examples/webgl_multiple_elements.html) (both if which I wrote BTW 😛)
25+
If you're in control of your code then there are arguably better solutions
26+
[like this for raw webgl](http://twgljs.org/examples/itemlist.html)
27+
and [this for three.js](https://threejs.org/examples/webgl_multiple_elements.html)
28+
(both if which I wrote BTW 😛)
2629

2730
I mostly wrote this for a fun short technical challenge. I have no plans to actually use it
2831
or maintain it. If you find a problem feel free to file an issue but I can't promise I
@@ -45,6 +48,11 @@ you can do things like
4548
With normal WebGL contexts you can't use resources from one context in another
4649
context but with virtual contexts you can.
4750

51+
This is actually probably the best use-case. You can write 2 different libraries
52+
independently of each other using WebGL and have them share resources and they
53+
won't have to worry about stepping on each other's WebGL state. An example might
54+
be, you have a video conferencing library and you want to add an effects library.
55+
4856
### Use the output of one WebGL app inside another
4957

5058
For example use a [mapbox-gl-js](https://www.mapbox.com/mapbox-gl-js/api/) map
@@ -73,49 +81,77 @@ or for WebGL2 use
7381

7482
## Writing your own compositor
7583

76-
A full solution would probably require some other method but ... If you look in
77-
[unity-example/index.html](https://greggman.github.io/virtual-webgl/unity-example/index.html)
78-
you'll see code that (a) disables WebGL2 so that Unity falls
79-
back to WebGL1 (since this virtual-webgl currently only supports WebGL1), and (b) creates a custom
80-
compositor that draws a different result than the default compositor.
81-
82-
The idea for the `createCompositor` function is that you probably need different compositors
83-
for each canvas on the page so it's up to you how to do that. Either check the `canvas` passed
84-
in and it's ID or keep a count of compositors created and do different things for different ones
85-
or whatever. If you return nothing/undefined the default compositor will be created for that canvas.
84+
The compositor is the part of virtual-webgl that's responsible for updating the
85+
individual canvas. The default compositor draws the framebuffer representing the
86+
drawingbuffer of that canvas, into an offscreen canvas and then calls canvas
87+
2D's drawImage to get the WebGL results into the canvas. You can change this to
88+
do something else by providing your own compositor.
8689

87-
As another example if you wanted to draw a MapGL texture inside THREE.js then you'd probably
88-
make the one compositor do nothing except record the texture needed to use inside three.
89-
For three's canvas you'd use the default compositor. [see this](https://greggman.github.io/virtual-webgl/mapbox-gl/index.html).
90-
Not sure how to use an external WebGL texture in THREE.js so the example uses twgl.
90+
A full solution would probably require some other method but ... If you look in
91+
[unity-example/index2.html](https://greggman.github.io/virtual-webgl/unity-example/index2.html)
92+
you'll see code that creates a custom compositor that draws a different result
93+
than the default compositor.
94+
95+
The idea for the `createCompositor` function is that you probably need different
96+
compositors for each canvas on the page so it's up to you how to do that. For
97+
example you could check the `canvas` passed in and its ID or some `data`
98+
attribute and do create different compositors for different canvases. If you
99+
return nothing/undefined the default compositor will be created for that canvas.
100+
101+
As another example, if you wanted to draw a MapGL texture inside THREE.js then
102+
you'd probably make the one compositor do nothing except record the texture
103+
needed to use inside three.js. For three's canvas you'd use the default compositor.
104+
[see this](https://greggman.github.io/virtual-webgl/mapbox-gl/index.html). I was not
105+
sure how to use an external WebGL texture in THREE.js so the example uses twgl.
91106

92107
Note: If a compositor has a `dispose` method it will be called if `context.dispose` is called
93108
to give your custom compositor a chance to clean up.
94109

95110
## Limits and Issues
96111

97-
* In WebGL2 you must end queries and transformFeedback before exiting
98-
the current event. The good things is, AFAIK, pretty much all WebGL
99-
apps already do this so it should't be a problem but not finishing
100-
those is not technically against the spec.
112+
* In WebGL2 you must end queries and transformFeedback before exiting the
113+
current event. The good things is, AFAIK, pretty much all WebGL2 apps that use
114+
queries and/or transformFeedback already do this so it should't be a problem.
115+
But, not finishing those before exiting the event is not technically against
116+
the spec.
117+
118+
* WebGL1 is emulated on WebGL2 in virtual-webgl2.js
119+
120+
When using virtual-webgl2.js WebGL2 functions are not available on virtual
121+
WebGL1 contexts but WebGL2 constants can be passed to WebGL1 contexts.
122+
123+
In other words:
124+
125+
```js
126+
webgl1Ctx.texImage3D(...); // error! no such function
127+
webgl1Ctx.bindTexture(webgl1ctx.TEXTURE_3D, tex); // error! TEXTURE_3D is not defined
128+
webgl1Ctx.bindTexture(webgl2ctx.TEXTURE_3D, tex); // ok
129+
```
101130

102-
* You can't mix WebGL1 and WebGL2
131+
This should arguably not a come up. A WebGL1 context would be using
132+
WebGL1 constants. I only point this out to say virtual-webgl doesn't
133+
force WebGL1 compliance.
103134

104-
WebGL1 and WebGL2 have a few incompatibilities meaning that emulating
105-
WebGL1 on top of WebGL2 is more work. It might not be that much work.
106-
I have not bothered to think about it. Off the top of my head you'd
107-
have to emulate all the various WebGL1 only extensions like
108-
OES_vertex_array_object, WEBGL_draw_buffers, different floating
109-
point texture support, differences in how depth-stencil renderbuffers
110-
work, and maybe a few other things.
135+
* WebGL1 on WebGL2 support is limited
136+
137+
I took a quick stab at trying to emulate WebGL1 on WebGL2 so that
138+
you could mix WebGL1 and WebGL2 in the same WebGL2 context.
139+
That includes extensions like `OES_vertex_array_object`, `OES_texture_float`,
140+
and `ANGLE_instanced_arrays`.
141+
142+
Unfortunately, when I got to `WEBGL_draw_buffers` I realized that
143+
emulating that would require a full GLSL parser and re-writer to
144+
change GLSL ES 1.0 to GLSL ES 3.0 and several complex transformations.
145+
146+
That's the long way of saying WebGL1 emulation on WebGL2 is incomplete.
111147

112148
* There are no checks for errors.
113149

114150
WebGL (and OpenGL) use a asynchronous command buffer error system
115151
which means checking for errors really slows things down so
116152
this Virtual WebGL also doesn't check for errors. Your code
117153
should not be generating errors in the first place so if it is
118-
fix your code.
154+
fix your code!
119155

120156
Where this might come up? I forget the details of the spec but,
121157
lets say you make an invalid program. When you call `gl.useProgram`
@@ -136,40 +172,47 @@ to give your custom compositor a chance to clean up.
136172

137173
## Perf
138174

139-
The WebGL2 wrapper is newer and saves state so it's pretty fast.
140-
The WebGL1 wrapper is older and queries state so it's pretty slow.
175+
The WebGL2 wrapper (virtual-webgl2.js) is newer and saves state so it's pretty fast.
176+
The WebGL1 wrapper (virtual-webgl.js) is older and queries state so it's pretty slow.
141177

142178
Another perf issue is you can't render directly to different canvases so I have
143-
to make each of the canvases use a `Canvas2DRenderingContext` and call `drawImage`.
144-
That could be solved maybe with `OffscreenCanvas` and `ImageBitmapRenderingContext`
145-
but those features haven't shipped without a flag as of 2018-06-05.
146-
147-
It could also be solved using the techniques used in [this sample](http://twgljs.org/examples/itemlist.html)
148-
149-
Basically put the canvas of the shared GL context full window size in the background and instead
150-
of compositing by copying to a 2D canvas, composite by setting the viewport/scissor and render to
151-
the shared GL context's canvas. The limitation of course is that the result won't appear in front
152-
of other elements but usually that's ok.
153-
154-
That should be trivial to implement using a custom compositor. The first time you get a compositor
155-
put the canvas of the shared context (the one that gets passed to `composite`) in the page and then
156-
render the texture being composited using `gl.viewport` and `gl.scissor`
157-
158-
If your canvases are not all on screen you could try using [an augmented requestAnimationFrame](https://github.com/greggman/requestanimationframe-fix.js)
179+
to make each of the canvases use a `Canvas2DRenderingContext` and call
180+
`drawImage`. That could be solved maybe with `OffscreenCanvas` and
181+
`ImageBitmapRenderingContext` but those features haven't shipped without a flag
182+
as of 2018-06-05.
183+
184+
It could also be solved using the techniques used in
185+
[this sample](http://twgljs.org/examples/itemlist.html)
186+
187+
Basically, put the canvas of the shared GL context full window size in the
188+
background and instead of compositing by copying to a 2D canvas, composite by
189+
setting the viewport/scissor and render to the shared GL context's canvas. The
190+
limitation of course is that the result won't appear in front of other elements
191+
but usually that's ok.
192+
193+
That should be trivial to implement using a custom compositor. The first time
194+
you get a compositor put the canvas of the shared context (the one that gets
195+
passed to `composite`) in the page and then render the texture being composited
196+
using `gl.viewport` and `gl.scissor`
197+
198+
If your canvases are not all on screen you could try using
199+
[an augmented requestAnimationFrame](https://github.com/greggman/requestanimationframe-fix.js)
159200
that only calls the requestAnimationFrame callback to draw the canvases that are on screen.
160201

161202
## Future Enhancements
162203

163-
virtual-webgl adds a `dispose` method to the virtual contexts letting you free the virtual context.
164-
As it is it leaves it up to the app to free all of its own GPU resources. `dispose` only disposes of
165-
internal resources.
204+
virtual-webgl adds a `dispose` method to the virtual contexts letting you free
205+
the virtual context. As it is it leaves it up to the app to free all of its own
206+
GPU resources. `dispose` only disposes of internal resources.
166207

167-
It probably would not be that hard to track resources by context and free them on dispose. It's not
168-
100% clear that's the right thing to do always. For example since virtual-webgl lets you share
169-
resources across contexts it would be a perfectly valid use-case to create a temporary context just to create some
208+
It probably would not be that hard to track resources by context and free them
209+
on dispose. It's not 100% clear that's the right thing to do always. For example
210+
since virtual-webgl lets you share resources across contexts it would be a
211+
perfectly valid use-case to create a temporary context just to create some
170212
resources and the dispose of that context but keep the created resources around.
171213

172-
It's perfectly reasonable to do this yourself 100% outside virtual-webgl. You just *either* augment the context.
214+
It's perfectly reasonable to do this yourself 100% outside virtual-webgl. You
215+
just *either* augment the context.
173216

174217
Example
175218

0 commit comments

Comments
 (0)