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

Re: [Public WebGL] using the same context with multiple canvases

On Tue, 13 Nov 2012, Gregg Tavares (社~T) wrote:
> >
> > There is an output rectangle with dimensions w x h.
> >
> > There is a WebGL... I don't know, scene graph, which uses some 
> > coordinates or other.
> >
> > There has to be some mapping from the coordinate system used in the 
> > WebGL, to the dimensions of the canvas.
> >
> > Where is that mapping set?

This question is still important; IMHO the answer to this question is 
related to where settings that apply to rendering to a specific canvas 
element's bitmap should be.

> The difference is in WebGL's default double buffered-ness. With Canvas 
> 2D you'd always end up with either
> (a) a circle on top of a rectangle    setTimeout->raf
> (b) a rectangle on top of a circle    raf->SetTimeout
> With WebGL, because it's double buffered and cleared on each composite. 
> You'll either get
> (a) a circle on top of a rectangle    setTimeout->raf->composite
> (b) a rectangle on top of a circle    raf->setTimeout->composite
> (c) a circle               raf->composite->setTimeout->composite
> (d) a rectangle            setTimeout->composite->raf->composite
> I'd prefer if the clear was tied to the current event exiting, not to 
> the composite. That would get rid of 2 cases. with 
> "preserveDrawingBuffer" = false. The default you'd get cases (c) and 
> (d). With "preserveDrawingBuffer" = true, which makes WebGL act like 
> canvas2d and is slow you'd get (a) and (b)

My recommendation for WebGL would be to have an explicit method that 
pushes the bits to the screen, and that would also clear the buffer. So 
then (c) and (d) couldn't happen.

> > > > I'm not a fan of a DrawingBuffer object because it's yet more 
> > > > indirection, and we have a pile of indirection here already with 
> > > > all the cross-worker canvas stuff. I could see having the contexts 
> > > > implicitly have their own drawing buffer while they aren't bound 
> > > > to a canvas, and having these buffers be reset (along with state, 
> > > > in the 2D world, though not in GL) when you bind/unbind, but I 
> > > > don't see why we'd want to explicitly have an object for this.
> > >
> > > We need canvas contents separate from contexts for all the reasons 
> > > explained in previous posts.
> >
> > The previous posts explained why a single WebGL context needs to be 
> > able to paint its state in different configurations on different 
> > canvases, as well as needing a way to generate off-screen images.
> >
> > That seems quite compatible with having a drawing buffer per canvas, 
> > plus a drawing buffer for contexts that aren't assigned to a canvas.
> Why waste the object?

There's no waste. In practice, you don't actually need to implement the 
drawing buffer for the context, you can just buffer up all the drawing 
commands and push them at once to the actual screen bitmap. You only need 
to actually instantiate the off-screen one when the author is using the 
context to do an off-screen drawing (i.e. when they call commit() in the 
absence of a canvas, assuming we only commit on explicit calls and not on 
the spinning of the event loop).

> If canvas all have a drawingBuffer then the most common use case is to 
> create a context with no drawingBuffer or a 1x1 and then just draw to 
> the mutliple canvas drawingbuffers. Why the wasted drawingBuffer on the 
> context if it will rarely if ever be used?

It doesn't have to be instantiated if it's not used.

> I'm looking at it as each canvas has a drawingBuffer. You can't have a 
> canvas in a worker but you can have a drawingBuffer.

With the CanvasProxy thing I'm currently writing up, you'll be able to 
have a canvas in a worker.

> > > Why should 2D be different than GL?
> >
> > For consistency with what happens today. GL doesn't blow away state, 
> > 2D does. Changing this just because the contexts are bound differently 
> > would just mean that different codepaths get radically different 
> > results even though they end up doing the same thing.
> ???
> Why does 2D need to blow away state? The solution I was proposing no 2D 
> state is blown away.

2D has always blown away state when you change bitmap dimensions.

> Maybe I'm mis-understanding your proposal. You mentioned that attaching 
> a context to a canvas blows away that canvas "bitmap".

It blows away the context's bitmap, not the canvas'. Apologies for any 

> So this is what I'm imaging your proposal is.
>    <canvas width="32" height="32" id="1"></canvas>
>    <canvas width="64" height="64" id="2"></canvas>
>    <canvas width="96" height="96" id="3"></canvas>
>    c1 = document.getElementById("1");
>    c2 = document.getElementById("2");
>    c3 = document.getElementById("3");

At this stage there's three bitmaps in theory, though in practice none 
are instantiated since nothing has been drawn.

>    ctx = new Canvas2DRenderingContext()

Per my proposal (that I'm still trying to write up), ctx officially has 
two 300x150 bitmaps at this stage, a "scratch" one that draws go to, and 
an "output" one that is what would be used if you called drawImage(). 
However, the "scratch" one is really just a record of what is drawn, so 
doesn't really need to exist as a bitmap, and the "output" bitmap is never 
used, so it doesn't need to be instantiated.

So at this stage there's five virtual bitmaps and none have been 

>    c1.setContext(ctx);

This drops the ctx "output" bitmap, instead using the c1 bitmap for that 
purposes. ctx's scratch bitmap is cleared and resized to 32x32. Since this 
is a 2D context, state is reset to initial values (as if today the canvas 
dimensions were changed).

So at this stage there's four virtual bitmaps, and still none have been 
instantiated. If we were to call fillRect() followed by commit(), then 
there'd be one instantiated bitmap, the c1 bitmap.

>    c2.setContext(ctx);

Now ctx's output bitmap is realiased to c2's bitmap, and ctx's scratch 
bitmap is resized and cleared again. In theory, ctx now has a 64x64 
scratch bitmap and ctx's output bitmap is c2's bitmap.

At this stage, there's one instantiated bitmap, c1's. If we call 
fillRect() followed by commit() again, then there's two, c1's and c2's.

>    c3.setContext(ctx);

Same again.

In practice, if the canvases are always visible and not overlapped, there 
might in fact be only one bitmap total: the bitmap for the whole display. 
The fillRect() calls can all be painted straight into that one bitmap. 
Even though there's three canvases.

The key is that there's a difference between the simplest way to describe 
what's going on (which involves lots of bitmaps) and the most optimal 
implementation (which in some cases can be just one bitmap). The important 
thing to do is not expose the bitmaps that we want to keep in the 
"theoretical" world. So for example, if drawImage() and getImageData() 
only ever get data from the "output bitmap"s here, we can ensure that 
there's never a need to actually instantiate the scratch bitmap.

Ian Hickson               U+1047E                )\._.,--....,'``.    fL
http://ln.hixie.ch/       U+263A                /,   _.. \   _\  ;`._ ,.
Things that are impossible just take longer.   `._.-(,_..'--(,_..'`-.;.'