[Mesa-dev] Proposal for an Updated Linux OpenGL ABI

Andy Ritger aritger at nvidia.com
Wed Sep 12 14:09:32 PDT 2012


There was some recent Khronos discussion about updating the OpenGL
Implementer's Guide for Linux, to which Ian Romanick noted that this
topic will be discussed at XDC as part of the broad "Discuss the future
of EGL, GLX, and OpenGL ES on Linux" agenda item.

To help facilitate the XDC discussion, I've put together a straw-man
proposal for an updated Linux OpenGL ABI.  I'm sending this out now to
start generating feedback and ideas, to help make the XDC discussion as
productive as possible.

Thanks,
- Andy Ritger

________________________________________________________________________

2012 Linux OpenGL ABI Proposal

Background

    The current Linux OpenGL ABI [1] (the "2000 Linux OpenGL ABI") is
    twelve years old, and the industry has advanced significantly in
    the past decade:

    * EGL has emerged as a compelling alternative window system binding,
      for use both within and without the X Window System.

    * The OpenGL API has evolved, reducing pressure, in modern usage, on
      OpenGL API call overhead.

    * One of the major short comings of the current ABI is that it does
      not allow multiple vendors' implementations to coexist on the file
      system.  Each vendor provides libGL.so.1; this can lead to library
      collisions and installation fights.  Each Linux distributor has
      been forced to invent its own library search path mechanism to
      resolve these conflicts.

    * In OpenGL 3.1, portions of the OpenGL API were removed (with some
      vendors continuing to provide the removed portions, via the
      ARB_compatibility extension).  A new Linux OpenGL ABI should
      potentially give vendors the option to not provide those entry
      points.

Requirements

    * This document proposes a new Linux ABI for OpenGL, OpenGL ES, GLX,
      and EGL on desktop Linux systems.  While this is hopefully
      useful for other UNIX and UNIX-like platforms, none are called
      out explicitly.  Further, this document does not try to standardize
      with Android or embedded Linux sorts of scenarios.

    * This document defines two ABIs:

        * The ABI between applications and a vendor-independent layer.

        * The ABI between a vendor-independent layer and a vendor
          implementation.

    * The existing ABI to applications must be preserved, though perhaps
      marked deprecated.  I.e., /usr/lib/libGL.so.1 must continue to
      exist for the foreseeable future, and continue to provide the
      entry points and semantics guaranteed by the 2000 Linux OpenGL ABI.
      How libGL.so.1 is implemented, and how vendor implementations fit
      into libGL.so.1, could change relative to the 2000 Linux OpenGL ABI.

    * Whatever minimum versions of OpenGL, OpenGL ES, EGL, and GLX are
      established by this standardization document, vendors must be allowed
      to provide extensions and newer API versions, beyond what is defined
      in this standard.

    * Multiple vendors' implementations must be able to coexist on the
      filesystem without distribution-specific "alternatives"-style selection.

    * It would be nice, from a design standpoint, to allow multiple vendor
      implementations to co-exist within the same process, and for the API
      library to dispatch to the current vendor library with per-context
      granularity.

Glossary

    * API Library: The vendor-neutral library that contains the API entry
      points.  In most cases, this should be fairly thin and simply dispatch
      to the vendor implementation.

    * Vendor Library: The vendor-provided library that provides the actual
      implementation.  The vendor libraries get enumerated, selected, and loaded
      by the API libraries.

    * Window system API: one of EGL or GLX.  Conceivably other window system
      APIs could be added in the future.

    * Client API: any rendering API that is used with a window system
      API.  OpenGL, OpenGL ES, OpenVG, etc.

Libraries

    The libraries to be provided are:

    * libGL.so.1
        * Provides symbols for all OpenGL 1.2 entry points (as per [1]).
        * Provides symbols for all GLX 1.3 entry points (as per [1]).
        * This would be a vendor-neutral API library.
        * Hopefully would just pass through to libGLX.so.1 and libOpenGL.so.1
          (maybe using ELF DT_FILTER; see [2] and the GNU ld(1) man page).

    * libOpenGL.so.1
        * Provides symbols for all entry points in a TBD version of OpenGL.
        * Vendors can provide additional OpenGL entry points that can be
          retrieved via {egl,glX}GetProcAddress.
        * No EGL or GLX entry points are provided by this library; it
          is expected that libOpenGL.so.1 would be used in conjunction
          with one of the Window system libraries (libGLX.so.1 or
          libEGL.so.1).
        * Provides a function for vendor libraries to install a dispatch table.
        * Provides a function for vendor libraries to report their
          GetProcAddress-able functions.

    * libGLESv1_CM.so.1:
        * Provides symbols for all OpenGL ES 1 common profile entry points.

    * libGLESv2.so.1:
        * Provides symbols for all OpenGL ES 2 and 3 entry points.

    * libEGL.so.1
        * Provides symbols for all EGL 1.4 entry points.
        * Loads and dispatches to one or more vendor libraries.

    * libGLX.so.1
        * Provides symbols for all GLX 1.4 entry points.
        * Provides symbols for the GLX_ARB_create_context_profile extension.
        * Loads and dispatches to one or more vendor libraries.

    * libEGL_${VENDOR}.so.1
        * Provides a function that libEGL.so.1 can call to initialize and
          get the EGL dispatch table.
        * Expected to pull in the vendor's implementation of all the client
          APIs it supports, and register with the appropriate API library at
          MakeCurrent time.
        * Must not export symbols with names that collide with the namespace
          of EGL (^egl.*) or OpenGL (^gl.*).

    * libGLX_${VENDOR}.so.1
        * Provides a function that libGLX.so.1 can call to initialize and
          get the GLX dispatch table.
        * Expected to pull in the vendor's implementation of all the client
          APIs it supports, and register with the appropriate API library at
          MakeCurrent time.
        * Must not export symbols with names that collide with the namespace
          of GLX (^glX.*) or OpenGL (^gl.*).

    For more background, see [3].

    API libraries (i.e., everything other than the vendor libraries)
    are expected to change infrequently.  For maintenance reasons, they
    should be as simple/thin as reasonable and dispatch to vendors for as
    much as possible.  While Khronos members would author and maintain
    the API libraries, the source to them would presumably be hosted by
    Khronos, available to the general public.  We would recommend to Linux
    distributions to package the API libraries separately from vendor
    libraries (i.e., in separate packages from Mesa, NVIDIA, AMD, etc).

GLX

    The GLX API library should allow for a different vendor library per
    X screen, and dispatch to the correct vendor as early as possible
    (though, see Issue (9)).

    GLX 1.4 entry points fall into one of several categories:

    (1) Entry points that take an X Display pointer and an X screen number
        (or, an object, such as a GLXContect or GLXDrawable, that implies
        an X screen).  E.g.,

            Bool glXMakeCurrent(Display *dpy,
                                GLXDrawable drawable,
                                GLXContext ctx);

        Such functions could be implemented by dispatching to the appropriate
        vendor library based on Display and screen.

    (2) Entry points that operate on the current context.  E.g.,

            void glXWaitGL(void);

        Such functions could be implemented by dispatching to the appropriate
        vendor library based on the context of the current thread.

    (3) Entry points that are vendor-independent and return current state.
        E.g.,

            GLXContext glXGetCurrentContext(void);

        Such functions could be implemented entirely within the GLX
        API library.

    (4) "Special functions":

            void *glXGetProcAddress(const GLubyte *procName);
            const char *glXGetClientString(Display *dpy, int name);

        glXGetClientString() is addressed in the Issues section, and
        glXGetProcAddress is addressed in the Dispatching section.

    There would be an X protocol-based mechanism to map an X Display
    pointer and screen number to GLX vendor library, presumably using
    something like the existing X_DRI2Connect/DRI2DriverVDPAU request.
    This would be used similarly to how VDPAU determines the vendor
    library to load (see [4]).

    At MakeCurrent time, the vendor library would call a function in
    libOpenGL.so.1 to register its dispatch table.

EGL

    Similar to GLX, EGL API entry points fall into one of several categories:

    (1) Entry points that take an EGLDisplay.  E.g.,

            EGLSurface eglCreateWindowSurface(EGLDisplay dpy,
                                              EGLConfig config,
                                              EGLNativeWindowType win,
                                              const EGLint *attrib_list);

        Such functions could be implemented by dispatching to the appropriate
        vendor library based on EGLDisplay.

    (2) Entry points that operate on the current context.  E.g.,

            EGLBoolean eglWaitGL(void);

        Such functions could be implemented by dispatching to the appropriate
        vendor library based on the context of the current thread.

    (3) Entry points that are vendor-independent and return current state.  E.g.,

            EGLContext eglGetCurrentContext(void);

        Such functions could be implemented entirely within the EGL API library.

    (4) "Special functions":

            EGLDisplay eglGetDisplay(EGLNativeDisplayType display_id);
            EGLBoolean eglInitialize(EGLDisplay dpy, EGLint *major, EGLint *minor);
            void *eglGetProcAddress(const char *procname);

    The general dispatching approach in EGL would be the same as described
    above for GLX.  The vendor selection, done during eglInitialize(),
    would be more sophisticated that the GLX equivalent.

    The Mesa EGL implementation appears to be almost exactly this.

Dispatching

    All client API library (OpenGL, OpenGL ES) entry points are
    context-dependent.  All the entry points provided by these libraries
    would retrieve the current dispatch table from TLS and jump to the
    vendor implementation.

    There are two categories of client API library entry points:

    (1) A fixed list of entry points that all vendors must provide per
        this document.

    (2) A dynamic list of entry points (for extensions, newer versions
        of OpenGL, etc) which would only be accessible to applications via
        {glX,egl}GetProcAddress.  This list will vary by vendor.

    For category (1), the vendor would just provide an array of function
    pointers as the dispatch table.  The ABI standard would define a
    dispatch table index to each entry point in this category.  The ABI
    library would need to provide a function to the vendor library that
    allowed the vendor to change the dispatch table pointer.

    For category (2), since GetProcAddress is context-independent, the
    API library would need to provide the union of entry points for all
    vendor implementations.  It seems like this could be handled in
    several different ways:

    Option (a): Define the possible list, or at least the indices,
    statically: make the dispatch table for category (2) fixed size,
    and have some sort of centralized registry to manage indices into
    the dispatch table.  Allocation into this table could be handled in
    a way similar to OpenGL enum assignment (see [5]).

        Pros:
            * Easier for vendors to define this dispatch table statically
              at compile time.
        Cons:
            * Need to coordinate for dispatch table index assignment.
            * As more entry points are added, a fixed size would
              eventually be exhausted.
            * Potentially large and sparse dispatch tables would be
              inefficient for memory usage.

    Option (b): At run time, the API library could:
        * Get the list of GetProcAddress-able functions from each
          vendor library.
        * Build the union of GetProcAddress-able functions.
        * Assign dispatch table indices for each function.
        * Tell the vendor libraries what dispatch table index was
          assigned for each of their functions.

        Pros:
            * Flexible to handle an arbitrary number of functions.
        Cons:
            * Vendors have to build their dispatch tables at run time
              to accommodate the dynamically assigned dispatch table indices.
            * Need to query all vendor libraries upfront; this seems a
              little unfortunate, since there potentially could be many
              vendor implementations on the file system, and only a few
              are likely to be used within the context of any one process.

    Option (c): Return entry points for all GetProcAddress'ed strings.  In this
        model, the API library would use logic like this:

        * Does a dispatch function already exist for the string passed
          into GetProcAddress?  If so, return that.
        * Else, online generate a dispatch function for the given string.
          Default the dispatch table to a noop function.
        * At MakeCurrent time, the vendor library would provide all
          the strings/function pointers that it supports and the API
          library would need to plug those vendor functions into the
          online-generated entry points.  I.e., effectively "lazily
          resolve" at MakeCurrent time.

        Pros:
            * Does not require loading vendor libraries before the vendor
              library would be otherwise needed.
        Cons:
            * Some complexity at MakeCurrent time to map the
              GetProcAddress'ed entry points to the vendor functions.
            * This would break apps who determine if a feature is
              available via GetProcAddress (though such apps are broken:
              they are supposed to check the extension string, per [6]).

Example Control Flow

    Scenario 1: Application links against libGLX.so.1 and libOpenGL.so.1:

        * libOpenGL.so.1's entry points default to a dispatch table filled
          with noop functions.
        * Application calls any GLX entry point; libGLX.so.1 queries
          the X server for the vendor name to use with each X screen.
        * When application calls entry point that should be dispatched
          to vendor, libGLX.so.1 searches defined search paths to find
          the correct vendor library.
        * libGLX.so.1 loads vendor library and gets its table of GLX
          dispatch functions.
        * libGLX.so.1 dispatches to vendor library for appropriate GLX
          entry points.
        * Application calls MakeCurrent, which gets dispatched to
          vendor library.
        * Vendor library's MakeCurrent calls libOpenGL.so.1 to plug
          dispatch table(s) into TLS.
        * Application calls OpenGL entry points in libOpenGL.so.1;
          these dispatch to vendor library.

    Scenario 2: Application links against libEGL.so.1 and libOpenGL.so.1:

        * libOpenGL.so.1's entry points default to a dispatch table filled
          with noop functions.
        * Application calls eglInitialize(); EGL API library uses
          configuration magic to select appropriate vendor driver.
        * libEGL.so.1 loads vendor library and gets its table of EGL
          dispatch functions.
        * libEGL.so.1 dispatches to vendor library for appropriate EGL
          entry points.
        * Application calls MakeCurrent, which gets dispatched to
          vendor library.
        * Vendor library's MakeCurrent calls libOpenGL.so.1 to plug 
          dispatch table(s) into TLS.
        * Application calls OpenGL entry points in libOpenGL.so.1; 
          these dispatch to vendor library.

    Scenario 3: Application links against libGL.so.1

        * Should be same as the libGLX.so.1 + libOpenGL.so.1 scenario.

Issues

    (1) Should this document mandate particular filesystem paths for
        any of the libraries?

        As long as the API libraries are in the link-time and load-time
        search paths, leaving API library path up to distributions seems
        acceptable.  But we ought to standardize where vendor libraries
        should get installed (since they might be installed by the vendor,
        rather than the distribution).

    (2) glXGetClientString() is vendor-specific, but does not take a
        screen argument (though it does take a Display pointer).  How should
        it be implemented?

        PROPOSED: The best libGLX.so.1 can do is probably the following:
        * Enumerate all of the X screens on the X server.
        * Query the vendor for each X screen.
        * Get the client string for each vendor.
        * Take the union of client strings across all vendors.

    (3) How should Window System (GLX,EGL) API functions work with
        {glX,egl}GetProcAddress?  GetProcAddress is supposed to return
        context-independent functions.  For client-API functions,
        dispatching can always be done based on the current context,
        but window system layer functions must be dispatched differently
        depending on the arguments they take.  It isn't clear how to
        dispatch a window system layer function without knowledge of
        its parameters.

    (4) What dispatching model should be used for GetProcAddress-able
        entry points?

        PROPOSED: Option (c) from the Dispatching section: online generate
        entry points for every string passed to GetProcAddress, and map
        to the actual vendor's functions at MakeCurrent time.

    (5) Even though it is 2012, some important OpenGL applications still
        use immediate mode OpenGL in very API-heavy ways.  In such cases,
        even just minimal dispatching overhead has a significant impact
        on performance.  How can we mitigate the performance impact in
        such scenarios?

        PROPOSED: The performant solution is to online-generate code
        directly into the top-level function of the API library.  The API
        library should provide a function that vendors can call, when
        they deem it thread-safe to do so, that replaces the code in an
        API library function with vendor-provided code.

    (6) How should libEGL.so select the vendor?

        Mesa's EGL implementation seems like at least a good starting
        point for these heuristics.

    (7) Multiple client API libraries (libOpenGL.so.1, libGLESv2.so.1,
        etc) provide symbols with the same name.  It is conceivable that
        a single process may have multiple libraries loaded (either
        explicitly loaded by an application, or chains of library
        dependencies cause multiple of these libraries to be loaded).
        How should symbol collisions be resolved?

        As I understand it, Mesa has resolved this problem by making it
        not matter which conflicting symbol gets loaded: the dispatch
        table contains the union of entry points exposed by GLESv1,
        GLESv2, and GL, and the dispatch function in each client API
        library jumps through the same dispatch table.

        Is the Mesa solution a reasonable requirement to make of all
        vendors?  It would be fine for NVIDIA.

        Alternatively, ELF symbol versioning could be used to distinguish
        between symbols of the same name in each of the client API
        libraries.  See "Maintaining APIs and ABIs" in Ulrich Drepper's
        DSO Howto [7].  I've coded up a demonstration of how that might
        work here: [8].

    (8) How should OpenGL deprecation impact the Linux OpenGL ABI?

        NVIDIA intends to support the OpenGL ARB_compatibility context
        indefinitely, but other vendors may want to omit that.  In that
        case, perhaps it would make sense to split libOpenGL.so.1 into
        separate libraries; e.g.,

            * libOpenGLv31.so.1 provides all entry points available in
              the core profile of OpenGL 3.1.

            * libOpenGLv10.so.1 provides entry points that were removed
              in OpenGL 3.1.

        The two digits identify the major.minor number of the first GL
        version the library supports.

        If an application wants to use a compatibility profile, utilizing
        entry points in OpenGL 3.1 and any that were removed in OpenGL
        3.1, then link against both libraries:

           -lOpenGLv31 -lOpenGLv10

        It would be intended that there are no symbol collisions between
        libOpenGLv31.so.1 and libOpenGLv10.so.1.

        But what happens if a future OpenGL version (M.N) removes another
        set of entry points?  It would sort of make sense to organize
        the libraries like this:

        * libOpenGLvMN.so.1 provides all entry points available in the
          core profile of OpenGL M.N.
        * libOpenGLv31.so.1 provides all entry points that were removed
          in OpenGL M.N.
        * libOpenGLv10.so.1 provides all entry points that were removed
          in OpenGL 3.1.

        But, we should not remove entry points from libOpenGLv31.so.1.

        Perhaps multiple libraries are more complicated than they are
        worth, and we should use a single libOpenGL.so.1?

    (9) How should server-side GLX be handled with multiple vendors?
        X extensions are registered with server, not screen, scope.
        Thus, there is not a good way for different vendors to provide
        their server-side GLX implementation per-screen.

        EGL, because it does not define any X server extension, may be
        an easier way to allow multiple vendors to execute simultaneously
        on different X screens.

        It seems conceivable that a vendor neutral GLX server module
        could be defined, which would dispatch to vendors' server-side
        GLX implementations with X screen granularity.

        However, that standardization effort is deferred for now: this
        proposal is already large enough.  Plus, with the growing interest
        in EGL, there may not be sufficient motivation to standardize
        server-side GLX multi-vendor support.

        It still seems prudent to design the client-side libGLX.so.1
        API library to allow multiple simultaneous vendors.

    (10) How should any of the vendor selection mechanisms in libEGL.so.1
        or libGLX.so.1 interact with Dave Airlie's Prime work?

References

    [1] http://www.opengl.org/registry/ABI/
    [2] http://docs.oracle.com/cd/E23824_01/html/819-0690/chapter4-4.html
    [3] http://www.khronos.org/registry/implementers_guide.html
    [4] http://cgit.freedesktop.org/~aplattner/libvdpau/
    [5] http://www.opengl.org/registry/doc/enums.html
    [6] http://www.opengl.org/registry/doc/rules.html#using
    [7] http://www.akkadia.org/drepper/dsohowto.pdf
    [8] http://github.com/aritger/libgl-elf-tricks-demo

Revision History

    #1 September 12, 2012: aritger
        - initial version



More information about the mesa-dev mailing list