483 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			483 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			C
		
	
	
	
| /*
 | |
|  * Copyright (c) 2016, NVIDIA CORPORATION.
 | |
|  *
 | |
|  * Permission is hereby granted, free of charge, to any person obtaining a
 | |
|  * copy of this software and/or associated documentation files (the
 | |
|  * "Materials"), to deal in the Materials without restriction, including
 | |
|  * without limitation the rights to use, copy, modify, merge, publish,
 | |
|  * distribute, sublicense, and/or sell copies of the Materials, and to
 | |
|  * permit persons to whom the Materials are furnished to do so, subject to
 | |
|  * the following conditions:
 | |
|  *
 | |
|  * The above copyright notice and this permission notice shall be included
 | |
|  * unaltered in all copies or substantial portions of the Materials.
 | |
|  * Any additions, deletions, or changes to the original source files
 | |
|  * must be clearly indicated in accompanying documentation.
 | |
|  *
 | |
|  * If only executable code is distributed, then the accompanying
 | |
|  * documentation must state that "this software is based in part on the
 | |
|  * work of the Khronos Group."
 | |
|  *
 | |
|  * THE MATERIALS ARE PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 | |
|  * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 | |
|  * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 | |
|  * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
 | |
|  * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 | |
|  * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
 | |
|  * MATERIALS OR THE USE OR OTHER DEALINGS IN THE MATERIALS.
 | |
|  */
 | |
| 
 | |
| #include <dix-config.h>
 | |
| 
 | |
| #include "hashtable.h"
 | |
| #include "vndserver.h"
 | |
| #include "vndservervendor.h"
 | |
| 
 | |
| /**
 | |
|  * The length of the dispatchFuncs array. Every opcode above this is a
 | |
|  * X_GLsop_* code, which all can use the same handler.
 | |
|  */
 | |
| #define OPCODE_ARRAY_LEN 100
 | |
| 
 | |
| // This hashtable is used to keep track of the dispatch stubs for
 | |
| // GLXVendorPrivate and GLXVendorPrivateWithReply.
 | |
| typedef struct GlxVendorPrivDispatchRec {
 | |
|     CARD32 vendorCode;
 | |
|     GlxServerDispatchProc proc;
 | |
|     HashTable hh;
 | |
| } GlxVendorPrivDispatch;
 | |
| 
 | |
| static GlxServerDispatchProc dispatchFuncs[OPCODE_ARRAY_LEN] = {};
 | |
| static HashTable vendorPrivHash = NULL;
 | |
| static HtGenericHashSetupRec vendorPrivSetup = {
 | |
|     .keySize = sizeof(CARD32)
 | |
| };
 | |
| 
 | |
| static int DispatchBadRequest(ClientPtr client)
 | |
| {
 | |
|     return BadRequest;
 | |
| }
 | |
| 
 | |
| static GlxVendorPrivDispatch *LookupVendorPrivDispatch(CARD32 vendorCode, Bool create)
 | |
| {
 | |
|     GlxVendorPrivDispatch *disp = NULL;
 | |
| 
 | |
|     disp = ht_find(vendorPrivHash, &vendorCode);
 | |
|     if (disp == NULL && create) {
 | |
|         if ((disp = ht_add(vendorPrivHash, &vendorCode))) {
 | |
|             disp->vendorCode = vendorCode;
 | |
|             disp->proc = NULL;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return disp;
 | |
| }
 | |
| 
 | |
| static GlxServerDispatchProc GetVendorDispatchFunc(CARD8 opcode, CARD32 vendorCode)
 | |
| {
 | |
|     GlxServerVendor *vendor;
 | |
| 
 | |
|     xorg_list_for_each_entry(vendor, &GlxVendorList, entry) {
 | |
|         GlxServerDispatchProc proc = vendor->glxvc.getDispatchAddress(opcode, vendorCode);
 | |
|         if (proc != NULL) {
 | |
|             return proc;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return DispatchBadRequest;
 | |
| }
 | |
| 
 | |
| static void SetReplyHeader(ClientPtr client, void *replyPtr)
 | |
| {
 | |
|     xGenericReply *rep = (xGenericReply *) replyPtr;
 | |
|     rep->type = X_Reply;
 | |
|     rep->sequenceNumber = client->sequence;
 | |
|     rep->length = 0;
 | |
| }
 | |
| 
 | |
| /* Include the trivial dispatch handlers */
 | |
| #include "vnd_dispatch_stubs.c"
 | |
| 
 | |
| static int dispatch_GLXQueryVersion(ClientPtr client)
 | |
| {
 | |
|     xGLXQueryVersionReply reply;
 | |
|     REQUEST_SIZE_MATCH(xGLXQueryVersionReq);
 | |
| 
 | |
|     SetReplyHeader(client, &reply);
 | |
|     reply.majorVersion = GlxCheckSwap(client, 1);
 | |
|     reply.minorVersion = GlxCheckSwap(client, 4);
 | |
| 
 | |
|     WriteToClient(client, sz_xGLXQueryVersionReply, &reply);
 | |
|     return Success;
 | |
| }
 | |
| 
 | |
| /* broken header workaround */
 | |
| #ifndef X_GLXSetClientInfo2ARB
 | |
| #define X_GLXSetClientInfo2ARB X_GLXSetConfigInfo2ARB
 | |
| #endif
 | |
| 
 | |
| /**
 | |
|  * This function is used for X_GLXClientInfo, X_GLXSetClientInfoARB, and
 | |
|  * X_GLXSetClientInfo2ARB.
 | |
|  */
 | |
| static int dispatch_GLXClientInfo(ClientPtr client)
 | |
| {
 | |
|     GlxServerVendor *vendor;
 | |
|     void *requestCopy = NULL;
 | |
|     size_t requestSize = client->req_len * 4;
 | |
| 
 | |
|     if (client->minorOp == X_GLXClientInfo) {
 | |
|         REQUEST_AT_LEAST_SIZE(xGLXClientInfoReq);
 | |
|     } else if (client->minorOp == X_GLXSetClientInfoARB) {
 | |
|         REQUEST_AT_LEAST_SIZE(xGLXSetClientInfoARBReq);
 | |
|     } else if (client->minorOp == X_GLXSetClientInfo2ARB) {
 | |
|         REQUEST_AT_LEAST_SIZE(xGLXSetClientInfo2ARBReq);
 | |
|     } else {
 | |
|         return BadImplementation;
 | |
|     }
 | |
| 
 | |
|     // We'll forward this request to each vendor library. Since a vendor might
 | |
|     // modify the request data in place (e.g., for byte swapping), make a copy
 | |
|     // of the request first.
 | |
|     requestCopy = malloc(requestSize);
 | |
|     if (requestCopy == NULL) {
 | |
|         return BadAlloc;
 | |
|     }
 | |
|     memcpy(requestCopy, client->requestBuffer, requestSize);
 | |
| 
 | |
|     xorg_list_for_each_entry(vendor, &GlxVendorList, entry) {
 | |
|         vendor->glxvc.handleRequest(client);
 | |
|         // Revert the request buffer back to our copy.
 | |
|         memcpy(client->requestBuffer, requestCopy, requestSize);
 | |
|     }
 | |
|     free(requestCopy);
 | |
|     return Success;
 | |
| }
 | |
| 
 | |
| static int CommonLoseCurrent(ClientPtr client, GlxContextTagInfo *tagInfo)
 | |
| {
 | |
|     int ret;
 | |
| 
 | |
|     ret = tagInfo->vendor->glxvc.makeCurrent(client,
 | |
|             tagInfo->tag, // No old context tag,
 | |
|             None, None, None, 0);
 | |
| 
 | |
|     if (ret == Success) {
 | |
|         GlxFreeContextTag(tagInfo);
 | |
|     }
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static int CommonMakeNewCurrent(ClientPtr client,
 | |
|         GlxServerVendor *vendor,
 | |
|         GLXDrawable drawable,
 | |
|         GLXDrawable readdrawable,
 | |
|         GLXContextID context,
 | |
|         GLXContextTag *newContextTag)
 | |
| {
 | |
|     int ret = BadAlloc;
 | |
|     GlxContextTagInfo *tagInfo;
 | |
| 
 | |
|     tagInfo = GlxAllocContextTag(client, vendor);
 | |
| 
 | |
|     if (tagInfo) {
 | |
|         ret = vendor->glxvc.makeCurrent(client,
 | |
|                 0, // No old context tag,
 | |
|                 drawable, readdrawable, context,
 | |
|                 tagInfo->tag);
 | |
| 
 | |
|         if (ret == Success) {
 | |
|             tagInfo->drawable = drawable;
 | |
|             tagInfo->readdrawable = readdrawable;
 | |
|             tagInfo->context = context;
 | |
|             *newContextTag = tagInfo->tag;
 | |
|         } else {
 | |
|             GlxFreeContextTag(tagInfo);
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return ret;
 | |
| }
 | |
| 
 | |
| static int CommonMakeCurrent(ClientPtr client,
 | |
|         GLXContextTag oldContextTag,
 | |
|         GLXDrawable drawable,
 | |
|         GLXDrawable readdrawable,
 | |
|         GLXContextID context)
 | |
| {
 | |
|     xGLXMakeCurrentReply reply = {};
 | |
|     GlxContextTagInfo *oldTag = NULL;
 | |
|     GlxServerVendor *newVendor = NULL;
 | |
| 
 | |
|     oldContextTag = GlxCheckSwap(client, oldContextTag);
 | |
|     drawable = GlxCheckSwap(client, drawable);
 | |
|     readdrawable = GlxCheckSwap(client, readdrawable);
 | |
|     context = GlxCheckSwap(client, context);
 | |
| 
 | |
|     SetReplyHeader(client, &reply);
 | |
| 
 | |
|     if (oldContextTag != 0) {
 | |
|         oldTag = GlxLookupContextTag(client, oldContextTag);
 | |
|         if (oldTag == NULL) {
 | |
|             return GlxErrorBase + GLXBadContextTag;
 | |
|         }
 | |
|     }
 | |
|     if (context != 0) {
 | |
|         newVendor = GlxGetXIDMap(context);
 | |
|         if (newVendor == NULL) {
 | |
|             return GlxErrorBase + GLXBadContext;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     if (oldTag == NULL && newVendor == NULL) {
 | |
|         // Nothing to do here. Just send a successful reply.
 | |
|         reply.contextTag = 0;
 | |
|     } else if (oldTag != NULL && newVendor != NULL
 | |
|             && oldTag->context == context
 | |
|             && oldTag->drawable == drawable
 | |
|             && oldTag->readdrawable == readdrawable)
 | |
|     {
 | |
|         // The old and new values are all the same, so send a successful reply.
 | |
|         reply.contextTag = oldTag->tag;
 | |
|     } else {
 | |
|         // TODO: For switching contexts in a single vendor, just make one
 | |
|         // makeCurrent call?
 | |
| 
 | |
|         // TODO: When changing vendors, would it be better to do the
 | |
|         // MakeCurrent(new) first, then the LoseCurrent(old)?
 | |
|         // If the MakeCurrent(new) fails, then the old context will still be current.
 | |
|         // If the LoseCurrent(old) fails, then we can (probably) undo the MakeCurrent(new) with
 | |
|         // a LoseCurrent(old).
 | |
|         // But, if the recovery LoseCurrent(old) fails, then we're really in a bad state.
 | |
| 
 | |
|         // Clear the old context first.
 | |
|         if (oldTag != NULL) {
 | |
|             int ret = CommonLoseCurrent(client, oldTag);
 | |
|             if (ret != Success) {
 | |
|                 return ret;
 | |
|             }
 | |
|             oldTag = NULL;
 | |
|         }
 | |
| 
 | |
|         if (newVendor != NULL) {
 | |
|             int ret = CommonMakeNewCurrent(client, newVendor, drawable, readdrawable, context, &reply.contextTag);
 | |
|             if (ret != Success) {
 | |
|                 return ret;
 | |
|             }
 | |
|         } else {
 | |
|             reply.contextTag = 0;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     reply.contextTag = GlxCheckSwap(client, reply.contextTag);
 | |
|     WriteToClient(client, sz_xGLXMakeCurrentReply, &reply);
 | |
|     return Success;
 | |
| }
 | |
| 
 | |
| static int dispatch_GLXMakeCurrent(ClientPtr client)
 | |
| {
 | |
|     REQUEST(xGLXMakeCurrentReq);
 | |
|     REQUEST_SIZE_MATCH(*stuff);
 | |
| 
 | |
|     return CommonMakeCurrent(client, stuff->oldContextTag,
 | |
|             stuff->drawable, stuff->drawable, stuff->context);
 | |
| }
 | |
| 
 | |
| static int dispatch_GLXMakeContextCurrent(ClientPtr client)
 | |
| {
 | |
|     REQUEST(xGLXMakeContextCurrentReq);
 | |
|     REQUEST_SIZE_MATCH(*stuff);
 | |
| 
 | |
|     return CommonMakeCurrent(client, stuff->oldContextTag,
 | |
|             stuff->drawable, stuff->readdrawable, stuff->context);
 | |
| }
 | |
| 
 | |
| static int dispatch_GLXMakeCurrentReadSGI(ClientPtr client)
 | |
| {
 | |
|     REQUEST(xGLXMakeCurrentReadSGIReq);
 | |
|     REQUEST_SIZE_MATCH(*stuff);
 | |
| 
 | |
|     return CommonMakeCurrent(client, stuff->oldContextTag,
 | |
|             stuff->drawable, stuff->readable, stuff->context);
 | |
| }
 | |
| 
 | |
| static int dispatch_GLXCopyContext(ClientPtr client)
 | |
| {
 | |
|     REQUEST(xGLXCopyContextReq);
 | |
|     GlxServerVendor *vendor;
 | |
|     REQUEST_SIZE_MATCH(*stuff);
 | |
| 
 | |
|     // If we've got a context tag, then we'll use it to select a vendor. If we
 | |
|     // don't have a tag, then we'll look up one of the contexts. In either
 | |
|     // case, it's up to the vendor library to make sure that the context ID's
 | |
|     // are valid.
 | |
|     if (stuff->contextTag != 0) {
 | |
|         GlxContextTagInfo *tagInfo = GlxLookupContextTag(client, GlxCheckSwap(client, stuff->contextTag));
 | |
|         if (tagInfo == NULL) {
 | |
|             return GlxErrorBase + GLXBadContextTag;
 | |
|         }
 | |
|         vendor = tagInfo->vendor;
 | |
|     } else {
 | |
|         vendor = GlxGetXIDMap(GlxCheckSwap(client, stuff->source));
 | |
|         if (vendor == NULL) {
 | |
|             return GlxErrorBase + GLXBadContext;
 | |
|         }
 | |
|     }
 | |
|     return vendor->glxvc.handleRequest(client);
 | |
| }
 | |
| 
 | |
| static int dispatch_GLXSwapBuffers(ClientPtr client)
 | |
| {
 | |
|     GlxServerVendor *vendor = NULL;
 | |
|     REQUEST(xGLXSwapBuffersReq);
 | |
|     REQUEST_SIZE_MATCH(*stuff);
 | |
| 
 | |
|     if (stuff->contextTag != 0) {
 | |
|         // If the request has a context tag, then look up a vendor from that.
 | |
|         // The vendor library is then responsible for validating the drawable.
 | |
|         GlxContextTagInfo *tagInfo = GlxLookupContextTag(client, GlxCheckSwap(client, stuff->contextTag));
 | |
|         if (tagInfo == NULL) {
 | |
|             return GlxErrorBase + GLXBadContextTag;
 | |
|         }
 | |
|         vendor = tagInfo->vendor;
 | |
|     } else {
 | |
|         // We don't have a context tag, so look up the vendor from the
 | |
|         // drawable.
 | |
|         vendor = GlxGetXIDMap(GlxCheckSwap(client, stuff->drawable));
 | |
|         if (vendor == NULL) {
 | |
|             return GlxErrorBase + GLXBadDrawable;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     return vendor->glxvc.handleRequest(client);
 | |
| }
 | |
| 
 | |
| /**
 | |
|  * This is a generic handler for all of the X_GLXsop* requests.
 | |
|  */
 | |
| static int dispatch_GLXSingle(ClientPtr client)
 | |
| {
 | |
|     REQUEST(xGLXSingleReq);
 | |
|     GlxContextTagInfo *tagInfo;
 | |
|     REQUEST_AT_LEAST_SIZE(*stuff);
 | |
| 
 | |
|     tagInfo = GlxLookupContextTag(client, GlxCheckSwap(client, stuff->contextTag));
 | |
|     if (tagInfo != NULL) {
 | |
|         return tagInfo->vendor->glxvc.handleRequest(client);
 | |
|     } else {
 | |
|         return GlxErrorBase + GLXBadContextTag;
 | |
|     }
 | |
| }
 | |
| 
 | |
| static int dispatch_GLXVendorPriv(ClientPtr client)
 | |
| {
 | |
|     GlxVendorPrivDispatch *disp;
 | |
|     REQUEST(xGLXVendorPrivateReq);
 | |
|     REQUEST_AT_LEAST_SIZE(*stuff);
 | |
| 
 | |
|     disp = LookupVendorPrivDispatch(GlxCheckSwap(client, stuff->vendorCode), TRUE);
 | |
|     if (disp == NULL) {
 | |
|         return BadAlloc;
 | |
|     }
 | |
| 
 | |
|     if (disp->proc == NULL) {
 | |
|         // We don't have a dispatch function for this request yet. Check with
 | |
|         // each vendor library to find one.
 | |
|         // Note that even if none of the vendors provides a dispatch stub,
 | |
|         // we'll still add an entry to the dispatch table, so that we don't
 | |
|         // have to look it up again later.
 | |
| 
 | |
|         disp->proc = GetVendorDispatchFunc(stuff->glxCode,
 | |
|                                            GlxCheckSwap(client,
 | |
|                                                         stuff->vendorCode));
 | |
|     }
 | |
|     return disp->proc(client);
 | |
| }
 | |
| 
 | |
| Bool GlxDispatchInit(void)
 | |
| {
 | |
|     GlxVendorPrivDispatch *disp;
 | |
| 
 | |
|     vendorPrivHash = ht_create(sizeof(CARD32), sizeof(GlxVendorPrivDispatch),
 | |
|                                ht_generic_hash, ht_generic_compare,
 | |
|                                (void *) &vendorPrivSetup);
 | |
|     if (!vendorPrivHash) {
 | |
|         return FALSE;
 | |
|     }
 | |
| 
 | |
|     // Assign a custom dispatch stub GLXMakeCurrentReadSGI. This is the only
 | |
|     // vendor private request that we need to deal with in libglvnd itself.
 | |
|     disp = LookupVendorPrivDispatch(X_GLXvop_MakeCurrentReadSGI, TRUE);
 | |
|     if (disp == NULL) {
 | |
|         return FALSE;
 | |
|     }
 | |
|     disp->proc = dispatch_GLXMakeCurrentReadSGI;
 | |
| 
 | |
|     // Assign the dispatch stubs for requests that need special handling.
 | |
|     dispatchFuncs[X_GLXQueryVersion] = dispatch_GLXQueryVersion;
 | |
|     dispatchFuncs[X_GLXMakeCurrent] = dispatch_GLXMakeCurrent;
 | |
|     dispatchFuncs[X_GLXMakeContextCurrent] = dispatch_GLXMakeContextCurrent;
 | |
|     dispatchFuncs[X_GLXCopyContext] = dispatch_GLXCopyContext;
 | |
|     dispatchFuncs[X_GLXSwapBuffers] = dispatch_GLXSwapBuffers;
 | |
| 
 | |
|     dispatchFuncs[X_GLXClientInfo] = dispatch_GLXClientInfo;
 | |
|     dispatchFuncs[X_GLXSetClientInfoARB] = dispatch_GLXClientInfo;
 | |
|     dispatchFuncs[X_GLXSetClientInfo2ARB] = dispatch_GLXClientInfo;
 | |
| 
 | |
|     dispatchFuncs[X_GLXVendorPrivate] = dispatch_GLXVendorPriv;
 | |
|     dispatchFuncs[X_GLXVendorPrivateWithReply] = dispatch_GLXVendorPriv;
 | |
| 
 | |
|     // Assign the trivial stubs
 | |
|     dispatchFuncs[X_GLXRender] = dispatch_Render;
 | |
|     dispatchFuncs[X_GLXRenderLarge] = dispatch_RenderLarge;
 | |
|     dispatchFuncs[X_GLXCreateContext] = dispatch_CreateContext;
 | |
|     dispatchFuncs[X_GLXDestroyContext] = dispatch_DestroyContext;
 | |
|     dispatchFuncs[X_GLXWaitGL] = dispatch_WaitGL;
 | |
|     dispatchFuncs[X_GLXWaitX] = dispatch_WaitX;
 | |
|     dispatchFuncs[X_GLXUseXFont] = dispatch_UseXFont;
 | |
|     dispatchFuncs[X_GLXCreateGLXPixmap] = dispatch_CreateGLXPixmap;
 | |
|     dispatchFuncs[X_GLXGetVisualConfigs] = dispatch_GetVisualConfigs;
 | |
|     dispatchFuncs[X_GLXDestroyGLXPixmap] = dispatch_DestroyGLXPixmap;
 | |
|     dispatchFuncs[X_GLXQueryExtensionsString] = dispatch_QueryExtensionsString;
 | |
|     dispatchFuncs[X_GLXQueryServerString] = dispatch_QueryServerString;
 | |
|     dispatchFuncs[X_GLXChangeDrawableAttributes] = dispatch_ChangeDrawableAttributes;
 | |
|     dispatchFuncs[X_GLXCreateNewContext] = dispatch_CreateNewContext;
 | |
|     dispatchFuncs[X_GLXCreatePbuffer] = dispatch_CreatePbuffer;
 | |
|     dispatchFuncs[X_GLXCreatePixmap] = dispatch_CreatePixmap;
 | |
|     dispatchFuncs[X_GLXCreateWindow] = dispatch_CreateWindow;
 | |
|     dispatchFuncs[X_GLXCreateContextAttribsARB] = dispatch_CreateContextAttribsARB;
 | |
|     dispatchFuncs[X_GLXDestroyPbuffer] = dispatch_DestroyPbuffer;
 | |
|     dispatchFuncs[X_GLXDestroyPixmap] = dispatch_DestroyPixmap;
 | |
|     dispatchFuncs[X_GLXDestroyWindow] = dispatch_DestroyWindow;
 | |
|     dispatchFuncs[X_GLXGetDrawableAttributes] = dispatch_GetDrawableAttributes;
 | |
|     dispatchFuncs[X_GLXGetFBConfigs] = dispatch_GetFBConfigs;
 | |
|     dispatchFuncs[X_GLXQueryContext] = dispatch_QueryContext;
 | |
|     dispatchFuncs[X_GLXIsDirect] = dispatch_IsDirect;
 | |
| 
 | |
|     return TRUE;
 | |
| }
 | |
| 
 | |
| void GlxDispatchReset(void)
 | |
| {
 | |
|     memset(dispatchFuncs, 0, sizeof(dispatchFuncs));
 | |
| 
 | |
|     ht_destroy(vendorPrivHash);
 | |
|     vendorPrivHash = NULL;
 | |
| }
 | |
| 
 | |
| int GlxDispatchRequest(ClientPtr client)
 | |
| {
 | |
|     REQUEST(xReq);
 | |
|     if (GlxExtensionEntry->base == 0)
 | |
|         return BadRequest;
 | |
|     if (stuff->data < OPCODE_ARRAY_LEN) {
 | |
|         if (dispatchFuncs[stuff->data] == NULL) {
 | |
|             // Try to find a dispatch stub.
 | |
|             dispatchFuncs[stuff->data] = GetVendorDispatchFunc(stuff->data, 0);
 | |
|         }
 | |
|         return dispatchFuncs[stuff->data](client);
 | |
|     } else {
 | |
|         return dispatch_GLXSingle(client);
 | |
|     }
 | |
| }
 |