Tuesday, April 9, 2013

Design of Mesa 3D Part 2: Contexts

Previously I wrote about how Mesa 3D sets up its Display and Screen objects. Now I'd like to start working with contexts. In particular, there are two relevant commands that I'd like to run: glXCreateNewContext() and glXMakeCurrent(). Let's start with context creation.

glXCreateNewContext() is defined in src/glx/x11glxcmds.c, and immediately delegates to a shared function (shared between glXCreateNewContext() and glXCreateContext()). This function returns a GLXContext object (which is ::NOT:: a GLContext object). This object, defined in src/glx/x11/glxclient.h, has lots of things in it, many of which are not very interesting. Instead, I'll just list a few of the relevant items:
  • A small buffer to pack drawing commands into, so multiple commands can be sent in a single GLX protocol request (to the X11 server)
  • The XID of the rendering context
  • A __GLXscreenConfigs object, as well as the index of the screen that this context is associated with
  • A boolean for if the context is direct or not
  • GLenum error
  • A GLXDrawable for the current drawable and another for the current readable
  • Strings for the vendor, renderer, version, and extensions
  • If GLX_DIRECT_RENDERING is enabled, a __GLXDRIcontext and a __DRIcontext
  • ints for the server major and minor version numbers
The first thing that CreateContext() does is run a helper function, AllocateGLXContext(), which (first runs __glXSetupForCommand()), allocates a context and populates the context with some default values. However, if GLX_DIRECT_RENDERING is enabled, it first calls a helper function, GetGLXScreenConfigs(), which calls __glXInitialize() (to get the __GLXdisplayPrivate that's associated with the Display), then returns the associated __GLXscreenConfigs object with the current screen (passed to glXCreateNewContext()). Then, once we have the relevant screen, we can set the context's driContext member (of type __GLXDRIcontext) to psc->driScreen->createContext() with the __GLXscreenConfigs, the input __GLcontextModes, and the new GLXContext. I'll go into more detail with this call in a second. The last thing that CreateContext() does is send the X11 server a GLXCreateNewContext request (and sets the XID member of the context using a call to XAllocID()).

Alright, let's talk about psc->driScreen->createContext(). The driScreen object actually has a function pointer called createContext which gets set by the dri2CreateScreen() function, in either dri2_glx.c, dri_glx.c, or drisw_glx.c. The DRI2 implementation is in a function called dri2CreateContext(). Because Mesa 3D's DRI2 implementation is driver-based, dri2CreateContext() just calls the createNewContext function in the "dri2" extension. This looks like "(*psc->dri2->createNewContext)(...)". It then sets the GLXContext's __driContext member (of type __DRIcontext) to the newly created context. The __GLXDRIcontext class, defined in src/glx/x11/glxclient.h, is quite simply a vtable, so dri2CreateContext() just fills in the vtable with local functions:

struct __GLXDRIcontextRec
   void (*destroyContext) (__GLXDRIcontext * context,
                           __GLXscreenConfigs * psc, Display * dpy);
     Bool(*bindContext) (__GLXDRIcontext * context, __GLXDRIdrawable * pdraw,
                         __GLXDRIdrawable * pread);
   void (*unbindContext) (__GLXDRIcontext * context);

Alright, let's dig into one driver's implementation of createNewContext. This function is a function pointer inside the __DRIdri2ExtensionRec struct, defined in include/GL/internal/dri_interface.h (It makes sense that it's in an "include" directory since it's the interface to a potentially third-party .so). This struct is also just a vtable:

struct __DRIdri2ExtensionRec {
    __DRIextension base;
    __DRIscreen *(*createNewScreen)(int screen, int fd,
   const __DRIextension **extensions,
   const __DRIconfig ***driver_configs,
   void *loaderPrivate);
    __DRIdrawable *(*createNewDrawable)(__DRIscreen *screen,
const __DRIconfig *config,
void *loaderPrivate);
    __DRIcontext *(*createNewContext)(__DRIscreen *screen,
     const __DRIconfig *config,
     __DRIcontext *shared,
     void *loaderPrivate);

However, rather than each DRI2 driver implement their own version of this function, all the current DRI2 drivers share one implementation, in src/mesa/drivers/dri/common/dri_util.c. This file defines one particular implementation of a __DRIdri2Extension, with a static function, dri2CreateNewContext. This function does some of the boilerplate setup that each DRI2 driver would have to do, such as allocating the output object and setting its __DRIscreen and drm_context_t pointers. Then, however, it delegates to the DriverAPI to populate the newly allocated object. The call looks like this: "(*psp->DriverAPI.CreateContext)(&config->modes, pcp, shareCtx)". The DriverAPI gets set in driCreateNewScreen, which is also defined in dri_util.c. It gets set to the extern symbol "driDriverAPI", which would have to be defined by the author of the driver. This means that the API is simply translated to a slightly different API for the driver author to write, and the translation is done by shared code (in dri_util.c). This means that the interface that a driver implements is actually the driDriverAPI struct, defined in src/mesa/drivers/common/dri_util.h:

struct __DriverAPIRec {
    const __DRIconfig **(*InitScreen) (__DRIscreen * priv);
    void (*DestroyScreen)(__DRIscreen *driScrnPriv);
    GLboolean (*CreateContext)(const __GLcontextModes *glVis,
                               __DRIcontext *driContextPriv,
                               void *sharedContextPrivate);
    void (*DestroyContext)(__DRIcontext *driContextPriv);
    GLboolean (*CreateBuffer)(__DRIscreen *driScrnPriv,
                              __DRIdrawable *driDrawPriv,
                              const __GLcontextModes *glVis,
                              GLboolean pixmapBuffer);
    void (*DestroyBuffer)(__DRIdrawable *driDrawPriv);
    void (*SwapBuffers)(__DRIdrawable *driDrawPriv);
    GLboolean (*MakeCurrent)(__DRIcontext *driContextPriv,
                             __DRIdrawable *driDrawPriv,
                             __DRIdrawable *driReadPriv);
    GLboolean (*UnbindContext)(__DRIcontext *driContextPriv);
    int (*GetSwapInfo)( __DRIdrawable *dPriv, __DRIswapInfo * sInfo );
    int (*WaitForMSC)( __DRIdrawable *priv, int64_t target_msc, 
      int64_t divisor, int64_t remainder,
      int64_t * msc );
    int (*WaitForSBC)( __DRIdrawable *priv, int64_t target_sbc,
      int64_t * msc, int64_t * sbc );
    int64_t (*SwapBuffersMSC)( __DRIdrawable *priv, int64_t target_msc,
      int64_t divisor, int64_t remainder );
    void (*CopySubBuffer)(__DRIdrawable *driDrawPriv,
 int x, int y, int w, int h);
    int (*GetDrawableMSC) ( __DRIscreen * priv,
   __DRIdrawable *drawablePrivate,
   int64_t *count);
    const __DRIconfig **(*InitScreen2) (__DRIscreen * priv);

Notice that the Screen functions are no longer for creation, but are instead for initialization.

Alright, now let's look at Intel's 810 driver (I've found that this one is fairly well-behaved). The __DriverAPIRec struct is defined in src/mesa/drivers/dri/i810/i810screen.c, and sets the CreateContext pointer to a function called i810CreateContext, defined in src/mesa/drivers/dri/i810/i810context.c. This function returns a bool, indicating success or failure. It populates the __DRIcontextPrivate struct that was constructed in driCreateNewContext (in dri_util.c), but it actually does much more than that. Here's the definition of the __DRIcontextPrivate, defined in mesa/drivers/dri/common/dri_util.h:

struct __DRIcontextRec {
    drm_context_t hHWContext;
    void *driverPrivate;
    __DRIcontext *pctx;
    __DRIdrawable *driDrawablePriv;
    __DRIdrawable *driReadablePriv;
    __DRIscreen *driScreenPriv;

After setting up a few members in its driver-specific DRI context, it then runs _mesa_create_context() to create an actual GLcontext (finally!!), defined in src/mesa/main/mtypes.h. This is actually a little surprising, since we're in a device driver: I would not have expected a device driver to be able to call a fairly generic mesa function. However, looking at the Makefile definitions in src/mesa/drivers/dri/Makefile.template, you can see that the library rule links with a variable called $(MESA_MODULES), which is defined at the top of the file to be $(TOP)/src/mesa/libmesa.a. So, drivers have access to the entire mesa source, which allowed them to call functions like _mesa_create_context().

As you can see, one of the members in the __DRIcontextPrivate is an opaque pointer for the driver. The i810 driver creates a driver-specific context (a i810ContextPtr) and sets driContextPriv->driverPrivate equal to it. In addition, the GLcontext has a similar DriverCtx field, which gets set to the driver-specific context.

Then, much of the rest of this function is setting members of the GLcontext struct, in particular ctx->Const members. In addition, many of the pieces of Mesa's context requires their own contexts, so the driver calls _swrast_CreateContext(), _vbo_CreateContext(), _tnl_CreateContext(), _swsetup_CreateContext(), etc. This allows Mesa to be fairly modularlized. In addition, the i810 driver wants to use their own implementation of the TNL pipeline, so they call _tnl_destroy_pipeline(ctx) and _tnl_install_pipeline(ctx, i810_pipeline).

One thing I neglected to mention is that one of the arguments that _mesa_create_context() expects is a dd_function_table struct. This struct contains a function for each of the OpenGL calls, which Mesa will call after doing its own processing. The struct is initialized with a call to _mesa_init_driver_functions(&functions), and then some of the functions in the struct are replaced so the driver can get a handle on calls being made.

The glXMakeCurrent() call delegates to the driver in much the same way that glXCreateContext() works. The relevant driver function, in this case i810MakeCurrent() in src/mesa/drivers/dri/i810/i810context.c, essentially is just a wrapper around _mesa_make_current(). This function, implemented in src/mesa/main/context.c, first calls _glapi_set_context(), which uses preprocessor defines to switch between different implementations. This function is defined in src/mesa/glapi/glapi.c.

In particular, there are three implementations: TLS, threads, and no threads. The threadless implementation is simple: just set a global pointer, named _glapi_Context. The TLS implementation is also quite simple, it just sets a thread-local pointer, named _glapi_tls_Context. This variable is defined in src/mesa/main/glapi/glapi.h, and uses the "__thread" specifier to make it thread local. It uses the __attribute__((tls_model("initial-exec"))), which determines how the variable interacts with shared objects (when they're loaded). The last implementation, "threads," delegates to a generic function, called _glthread_SetTSD(), designed to set a thread-local variable. This function, implemented in src/mesa/glapi/glthread.c, first checks to see if this is the first time dealing with this particular thread-local variable. It does this by determining if a magic number has been filled in properly. If the magic number hasn't been filled in, it calls _thread_InitTSD(), which uses pthread_key_create(), and fills in the proper magic number. Then, _glthread_SetTSD() runs pthread_setspecific() to set the thread-local variable.

Getting the current context works similarly. There is a preprocessor macro, GET_CURRENT_CONTEXT(ctx), which is defined in mesa/glapi/glapi.h, and it also has three different implementations. If the implementation is either using TLS or non-threaded, the macro is just defined as a reference to the relevant variable. Otherwise,  if we're using the "thread" implementation, it calls _glapi_get_context(), which calls _glthread_GetTSD(), which uses the same magic number mechanism, as well as pthread_getspecific(). Almost all API functions call this macro as their first statement.

Alright, at this point I feel that I've got a pretty good handle on the structure of the code surrounding contexts. I'll continue later (hopefully) with my findings about how the software GL pipeline works, and then see how drivers can modify the pipeline.

No comments:

Post a Comment