Michael Gold wrote:
| 
| In a context-independent implementation, we'll have to synthesize a wrapper
| function in device independent code...

I think that's true.

|                                  ... and devise a mechanism whereby the
| driver informs the higher level code every time it wishes to change the
| dispatch table, and/or update it directly through an additional level of
| indirection after registering the entry point with the device-independent
| code.  ...

I'm not sure either of those is true.

I think it's particularly important to have the driver own the
dispatch table.  Validating the table after a state change needs to be
efficient, and forcing the driver to request higher level code to
perform the update seems unnecessarily slow to me.  I suspect we agree
on this.  :-)

Unless I've missed something, an additional level of indirection is
also unnecessary, provided that all drivers use the same dispatch
table entry for a given function.  The registration mechanism that we
use for extension enumerants is a good example of a way to guarantee
dispatch table consistency.

My original proposal didn't solve the problem that John Zulauf
mentioned, that is, GetProcAddress needs to return an appropriate
pointer even when the current context doesn't support the extension in
question.  Solving this in the most app-friendly way does take a
little more effort.  One approach could be as follows.

        Recall that each dynamic dispatch routine looks something
        like this:

                offsetNNN:      word    0
                dispNNN:        load    r1,dispatchTableBase
                                add     r1,offsetNNN
                                jump    (r1)

        We don't know what the correct value for the offset will be
        until we make a call to a driver that implements the extension
        function.  So we have to force either an explicit test, or a
        jump to a thunk that performs the test and can later patch up
        the jump.  The latter is faster after the resolution has been
        performed, but implementation is more likely to be
        machine-dependent, so I'll just show the straightforward test
        code here.

        We need one more thing:  Each driver must implement a
        GetProcOffsetEXT that returns the dispatch-table offset for
        a given function name, if it implements that function.

                nameNNN:        byte    "filled in by GetProcAddress"
                offsetNNN:      word    0
                dispNNN:        load    r1,offsetNNN
                                jzero   r1,queryNNN
                jumpNNN:        add     r1,dispatchTableBase
                                jump    (r1)
                queryNNN:       load    rParam,&nameNNN
                                load    r1,dispatchTableBase
                                add     r1,offsetOfGetProcOffsetEXT
                                jsr     (r1)
                                store   rResult,offsetNNN
                                jnzero  rResult,dispNNN
                                ret

        Hopefully that pseudo-assembly code is clear.  Basically we're
        just testing for a null pointer, querying the driver for the
        given function name if the pointer is null, and saving the
        offset returned in a result register by the driver.  If the
        driver returns a nonzero result, then we know we can use it
        for all subsequent dispatches of that function, no matter what
        the context, because offsets are consistent across all
        implementations.

        Obviously this can be tuned, and as I mentioned, I think you
        can use a thunk-based implementation if you prefer.  The main
        thing I haven't addressed is how to ensure that calling a
        function when the current context doesn't support it is
        handled politely.  Personally I don't think that's too
        important, but if anyone wants to propose a solution, please
        feel free.

| Whether or not the overhead and complexity of the latter case is
| manageable, my primary objection is that it simply doesn't solve a problem
| worth solving, since the application must already manage per-context state
| (e.g. is the extension supported?) and the function pointers are one more
| piece of state to be stored in some per-context application-level data
| structure.  ...

Application-level data structures aren't accessible to black-box
libraries.

But let's talk about design principles for a moment.  I think the
principle underlying a desire for context-dependent pointers is the
notion that interface and implementation are separated at the binary
level (as opposed to the source level), and because of the limitations
of current programming languages, this separation must be exposed all
the way up to code at the application level.

This is a defensible point of view, but to iron out *all* the problems
that arise from radical separation of binary interface and
implementation, you need industrial-strength mechanisms like COM. 
That's what they're designed for.

I would argue that one of the reasons people prefer OpenGL to D3D is
the simplicity of the interface model.  I'd like to maintain that
simplicity if I can.  I suspect that one of the costs of doing so is
the requirement for implementations to support context-independent
function pointers.  Otherwise you are, in effect, taking a step down
the path of requiring an explicit query for each driver interface, and
then accessing all functions indirectly through a driver-specific
interface structure in the app.

It also just occurred to me that by forcing the indirection to happen
at the application level, you not only add the indirection you were
trying to avoid by using context-dependent pointers, you also move the
indirection out of the library so that the library implementor can't
employ a more-efficient mechanism if he knows of one.

Allen

Reply via email to