Michael> We will probably never agree on this, it seems, so I'm going
Michael> to stop debating it now.  There has been very little input
Michael> from other implementors, and I would be interested in hearing
Michael> more of their opinions and less of mine.  :)

O.K. I've been lurking on this issue, but I'll jump in, trying to
summerize, elicidate the goals, and unify the proposals.

* Allen, Michael, Bret, and Bob each propose viable implementation approaches.

* The assertion that function pointers returned by a GetProcAddress
  call are context specfic and the application needs to manage these
  on a per-context basis is entirely correct, BUT I believe a bit
  pedantic, is not what application developers want/need and contrary
  to the overarching design of OpenGL. Unlike many other graphic API's
  that pass contexts in the parameter list of calls OpenGL eschewed
  this approach in favor of "hidden current per-thread context". By
  demanding applications introduce a per-context state in order to
  make calls into the graphic API you have effectively re-introduced
  the "context parameter" that is otherwise absent in OpenGL. Yes,
  applications will need be aware of constraints a specfic context may
  have (e.g. absent extensions), but why demand they also modify how
  they call the grahics API for a minor subset of entry points? If an
  application calls a function which does not exist in a particular
  context we should follow the existing OpenGL error model, the
  function is a no-op, sets the error flag, and returns.

* Proceeding on the assumption we want to allow a single function
  pointer to work across all contexts just as the core entry points
  do, then this is an issue only of implementation and performance
  considerations. Lets summerize the implmenttaion issues:

The goals are:

1) Desire for a single indirection (specfically a single function call,
   not a single pointer dereference).

2) Desire for drivers to modify the jump target efficently.

3) Desire for dissimilar contexts to co-exist without knowledge of any
   other context.

4) Simplicity.

Michael> Through an API?  I kind of like this api:
Michael> 
Michael>    gc->currentDispatchState->MultiTexCoordPointer2fARB =
Michael> __glim_MultiTexCoordPointer2fARB;
Michael> 
Michael> Why should it be more complex?

But goal 3 is not addressed by this and this is the crux of many of
the implementation proposals. Allen suggests solving goal 3 with a
combination of pre-assigned well-known table entries augmented with a
minor set of dynamic allocations, but I don't believe Allen's approach
fully satisfies goal 3. Bob suggests a hidden library
implementation. Michael does not believe goal 3 is worthy of the
costs. Both Allen's and Bob's suggestions could achieve goals 1 and 2.

All OpenGL functions, whether they be core functions which have
external linkage or extension routines which are callable only
through function pointers are effectively implemented the same way
to provide for dispatching. The simplified process is:

JSR (either though linkage or indirection) to code that jumps to
the current function implementation. Identifying the jump target is
most easily/efficiently done via a table lookup and this allows for
target to be easily modified on the fly. In my view it makes little
difference whether the index into the dispatch table is computed by
the compiler (e.g. table->foo) and is static or whether the index is
contained in a variable (e.g. table[fooIndex]) and is dynamic. In
the former case its a "move immediate" into the offset register, in
the latter case its a "move memory" into the offset register. The
cycle count difference for this one instruction hardly seems worth
worrying about.

Allowing for a dynamic table index is a huge advantage because it
goes a long way to addressing goal 3. The reason is dissimlar
contexts can share the same index. As I will illustrate below the
dispatch for dynamic indices can still be done with a static index.

Allen's proposal of well known indexes (which effectively reduces
to the static index case (table->foo). But as he points out static
indexes are not sufficient for allowing future expansion and an
additional mechanism is needed for "new" functions. But if you have
to support dynamic index allocation anyway why even have the well
known static indexes? Aren't you only saving a move memory vs. a
move immediate? Is that worth it? Also Allen's proposal is not
friendly to apps who call a dynamic indexed function out of
context. An app is clearly not going to understand the hidden
distinction between the two function types, this is surprising
behavor and not good API design.

I suggest, like others have, that drivers must "register" their
extension entry points with the library at run time. This is not
very burdensome. The primary benefit is ALL extension entry points
across all drivers share the same dynamically allocated index. When
a context is created all function pointers in the extension part of
the table are initialized to point to a routine that sets the error
flag and returns. Then the driver is free to set at will the table
entries its supports using the index returned when it registered its
extension with the libary. This achieves all the goals, its
efficient at run time with a single indirection, it allows the
driver to simply manage its own table, disimilar contexts always map
their functions to the same place and there is a clean well defined
behavior for contexts that don't implement a function, and its simple.

Allen's idea of having two separate pointers in the TIB, one for the
dispatch table and one for the context is good because it does not
demand contexts between drivers share any common structure (knowing
where to find the dispatch table in the context), this is more
flexible.

The table should logically be partitioned into two parts, well known
indices at the bottom (e.g. core functions and shared extensions), and
a dynamic part of the table up at the top.

There is library function which when passed the string name of a
routine returns the table index. If its a static well known index that
is simply returned. If its an unknown name an index is reserved, all
future calls to map that name return the same index. The registering
of names only has to be done when a driver initializes, this should
not be a burden.

The library would be built with a set of functions
(e.g. __glExtFuncNNN) that map to index NNN in the table. When someone
calls GetProcAddress the libary calls the exact same name mapping
function the drivers call to get index NNN and returns the function
pointer to __glExtFuncNNN. The implementation of __glExtFuncNNN would
look like something like this:

__glExtFuncNNN()
{
    TIB tib = GetTIB();

    JMP tib->dispatch.funcNNN;
}

Note: Core functions would be identical. Also note that even though
the index to this function might have been dynamically allocated the
dispatch remains a static index.

The driver at start up would do something like this:

extFuncFoobarIndex = __glGetDispatchIndex("glFoobar");

When the driver wants to modify a table entry in the dispatch it would
do this for well know names:

tib->dispatch.WellKnownName = myFunc;

OR:

tib->dispatch[WELL_KNOWN_NAME_INDEX] = myFunc;

And for extensions:

tib->dispatch[extFuncFoobarIndex] = myFunc;

I believe these concepts borrow heavily on concepts presented earlier
by others and credit is due to them. I'm just trying to bring together
the concepts in a unified fashion that solves all the perceived
goals. There is some overhead going on here, but I believe its as
close to minimal as one can get and hit all the goals.

John Dennis (Sharp Eye, Inc.)
Contract programming services specializing in 3D graphics
http://www.sharpeye.com
[EMAIL PROTECTED] 

Reply via email to