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

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






On Tue, Nov 13, 2012 at 3:14 PM, Ian Hickson <ian@hixie.ch> wrote:
On Tue, 13 Nov 2012, Gregg Tavares (社ç~T¨) wrote:
> On Tue, Nov 13, 2012 at 8:35 AM, Ian Hickson <ian@hixie.ch> wrote:
> > On Sun, 11 Nov 2012, Brandon Jones wrote:
> > > On Nov 11, 2012 10:47 PM, "Ian Hickson" <ian@hixie.ch> wrote:
> > > >
> > > > When would you want one canvas done one way and another a
> > > > different way, for the same rendering context?
> > >
> > > There's several reasonable explanations for having differing
> > > properties per-canvas. Consider Gregg's example of a Maya like tool:
> > > it would be sensible to request an antialiased canvas for the main
> > > editing view, but for performance reasons request a non-antialiased
> > > canvas for a texture preview widget.
> >
> > The viewport dimensions are on the context currently, right? Why not
> > put these on the context as well, and just have the author update them
> > when they switch canvas?
>
> There's some clarification needed here about what you are referring to
> when you say "viewport". In WebGL there is a function. gl.viewport that
> specifies the affine transformation of x and y from normalized device
> coordinates to window coordinates and happens to clip vertices (but not
> pixels).
>
> The system can not set that automatically because it doesn't know what
> the user is using it for. The user could have set it explicitly to
> various values including for rendering to framebuffer objects. Except
> for initialization the browser can't know when it's appropriate to set.
> Only the app knows.

Sorry if my terminology was wrong.

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?


> > On Mon, 12 Nov 2012, Gregg Tavares (社ç~T¨) wrote:
> > >
> > > How about
> > >
> > >    db = new DrawingBuffer(creatationAttrubites);
> > >
> > > for gl
> > >
> > >    gl.bindDrawingBuffer(db);
> > >
> > > for canvas 2d???
> > >
> > >    ctx.setDrawingBuffer(db);
> >
> > Why have two different APIs?
>
> We already have 2 apis that don't share a single function name or
> signature. Each one should do what feels natural for its api.

I prefer to look at it as one API, the Web Platform, which happens to
support both 2D and 3D graphics.

As a general rule, I don't think it makes sense to have arbitrary
differences between parts of this platform.

But in any case, I don't think this DrawingBuffer primitive needs to be
explicit, so the point here is moot.


> > > > Today, when does a WebGLRenderingContext "commit" or "draw" to the
> > > > currently assigned canvas? Is it just when the event loop spins,
> > > > or is there an explicit "paint a frame now" method?
> > >
> > > It's currently supposed to be when the current event exits IMO but
> > > the spec is ambiguous saying something about when it's compositied
> > > which means it's browser specific if other events come by before
> > > compositing happens.
> >
> > So you if drawImage() a GL canvas half-way through a script, it draws
> > the previous image state (as it stood the last time the event loop
> > span), not the mid-way state like the 2D canvas?
>
> In the drawImage case you get the latest results including the latest
> draw commands.

Oh! Then it's exactly like 2D, no?


> The part that's undefined is this
>
> requestAnimationFrame(renderRectangle);
> setTimeout(renderCircle, 1);
>
> Whether you see a circle, a rectangle, or a circle and a rectangle is
> undefined

Sure but that has nothing to do with WebGL or even canvas in general,
right? That's just a matter of it not being defined which of those two
callbacks will be invoked first. You could show the same problem using
"alert()".

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)




> > 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? 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?

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. Breaking canvas into 2 parts
solves that problem. That seems like a clean solution to me. context always
draw to drawingbuffers. They either draw to one from a canvas or one created
explicitly.

You seem to be saying sometimes a context has a drawingbuffer,
sometimes it doesn't. That seems inconsistent.

 


> > If GL doesn't need in-between state (i.e. if drawImage() for GL always
> > draws the last committed image, not the current state), and if we're
> > ok with using this model for 2D as well when in a worker, I think the
> > simplest solution is:
> >
> > - have two ways to get a context: via getContext(), and via a
> > constructor
> >
> > - the getContext() method is the current method, it's always fixed to
> > the same canvas it was created from, and can't be moved; drawImage()
> > in the 2D world gets the current state
> >
> > - the constructor gives you back a context with its own implicit
> > backing store; you can set its dimensions dynamically using
> > constructor arguments and can change the dimensions later on the
> > context itself; this object can be bound to a canvas (or CanvasProxy
> > for workers) later
> >
> > - when you bind a context to a canvas, it blows away its bitmap (and
> > state, for 2D, though not for GL), and drawing commands start going to
> > the canvas. The bitmap is now just the canvas bitmap; drawImage() and,
> > in 2D, getImageData(), take the last committed image; you can commit
> > the drawing commands to that bitmap using commit() or spinning the
> > event loop
>
> 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.

 


> Again, this is not a solution for us. If we want to make apps like those
> linked to above we can't have canvases having their contents blown away
> when we move the context between them and it makes no sense for us to
> have a backstore specific to a context.

This seems to directly contradict what you've described before. I'm
confused.

If you have a context that describes the user's 3D designs, and you want
to draw it once from above in one box, once from the side in another,
and once without AA at an angle in a third, then what I'm describing seems
perfect: you create your model in the context, then you bind to canvas 1,
set your angle and dimensions, paint, bind to canvas 2, repeat, bind to
canvas 3, repeat. Then when you want to do the next frame, you just do
that cycle again, and the graphics get nicely replaced.

Where's the problem?

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

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");

   ctx = new Canvas2DRenderingContext(???)
   c1.setContext(ctx);  // c1's bitmap has been blown away. It's no longer 32x32
   c2.setContext(ctx);  // c2's bitmap has been blown away. It's no longer 64x64
   c3.setContext(ctx);  // c3's bitmap has been blown away. It's no longer 96x96

Was I mis-understanding?
 


> There's also the problem that blowing away the canvas backstore each
> time you move the context and having to reset the size of the context's
> backstore to match the size of the canvas you just attached to means
> reallocating a very heavy object every time you change canvases.

This kind of thing can be trivially optimised. You don't even really need
to create the context's backing store until someone does anything with it.
For the canvases, you never need to recreate the bitmaps, you just use the
canvas bitmaps that were created with the canvas.


> Yet another reason to keep the storage separate from the context.

Keeping them separate is what would result in more (unnecessary) bitmaps,
as far as I can tell.


> [no implicit commits for the new contexts]

Sounds good.

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