/* * * GLX Hardware Device Driver for Sun Creator/Creator3D * Copyright (C) 2000, 2001 David S. Miller * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. * * THE SOFTWARE IS 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 * DAVID MILLER, OR ANY OTHER CONTRIBUTORS 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 SOFTWARE * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * * David S. Miller */ #include "ffb_xmesa.h" #include "context.h" #include "framebuffer.h" #include "matrix.h" #include "renderbuffer.h" #include "simple_list.h" #include "imports.h" #include "utils.h" #include "swrast/swrast.h" #include "swrast_setup/swrast_setup.h" #include "tnl/tnl.h" #include "tnl/t_pipeline.h" #include "vbo/vbo.h" #include "drivers/common/driverfuncs.h" #include "ffb_context.h" #include "ffb_dd.h" #include "ffb_span.h" #include "ffb_depth.h" #include "ffb_stencil.h" #include "ffb_clear.h" #include "ffb_vb.h" #include "ffb_tris.h" #include "ffb_lines.h" #include "ffb_points.h" #include "ffb_state.h" #include "ffb_tex.h" #include "ffb_lock.h" #include "ffb_vtxfmt.h" #include "ffb_bitmap.h" #include "drm_sarea.h" #include "drirenderbuffer.h" static GLboolean ffbInitDriver(__DRIscreenPrivate *sPriv) { ffbScreenPrivate *ffbScreen; FFBDRIPtr gDRIPriv = (FFBDRIPtr) sPriv->pDevPriv; drmAddress map; if (getenv("LIBGL_FORCE_XSERVER")) return GL_FALSE; if (sPriv->devPrivSize != sizeof(FFBDRIRec)) { fprintf(stderr,"\nERROR! sizeof(FFBDRIRec) does not match passed size from device driver\n"); return GL_FALSE; } /* Allocate the private area. */ ffbScreen = (ffbScreenPrivate *) MALLOC(sizeof(ffbScreenPrivate)); if (!ffbScreen) return GL_FALSE; /* Map FBC registers. */ if (drmMap(sPriv->fd, gDRIPriv->hFbcRegs, gDRIPriv->sFbcRegs, &map)) { FREE(ffbScreen); return GL_FALSE; } ffbScreen->regs = (ffb_fbcPtr) map; /* Map ramdac registers. */ if (drmMap(sPriv->fd, gDRIPriv->hDacRegs, gDRIPriv->sDacRegs, &map)) { drmUnmap((drmAddress)ffbScreen->regs, gDRIPriv->sFbcRegs); FREE(ffbScreen); return GL_FALSE; } ffbScreen->dac = (ffb_dacPtr) map; /* Map "Smart" framebuffer views. */ if (drmMap(sPriv->fd, gDRIPriv->hSfb8r, gDRIPriv->sSfb8r, &map)) { drmUnmap((drmAddress)ffbScreen->regs, gDRIPriv->sFbcRegs); drmUnmap((drmAddress)ffbScreen->dac, gDRIPriv->sDacRegs); FREE(ffbScreen); return GL_FALSE; } ffbScreen->sfb8r = (volatile char *) map; if (drmMap(sPriv->fd, gDRIPriv->hSfb32, gDRIPriv->sSfb32, &map)) { drmUnmap((drmAddress)ffbScreen->regs, gDRIPriv->sFbcRegs); drmUnmap((drmAddress)ffbScreen->dac, gDRIPriv->sDacRegs); drmUnmap((drmAddress)ffbScreen->sfb8r, gDRIPriv->sSfb8r); FREE(ffbScreen); return GL_FALSE; } ffbScreen->sfb32 = (volatile char *) map; if (drmMap(sPriv->fd, gDRIPriv->hSfb64, gDRIPriv->sSfb64, &map)) { drmUnmap((drmAddress)ffbScreen->regs, gDRIPriv->sFbcRegs); drmUnmap((drmAddress)ffbScreen->dac, gDRIPriv->sDacRegs); drmUnmap((drmAddress)ffbScreen->sfb8r, gDRIPriv->sSfb8r); drmUnmap((drmAddress)ffbScreen->sfb32, gDRIPriv->sSfb32); FREE(ffbScreen); return GL_FALSE; } ffbScreen->sfb64 = (volatile char *) map; ffbScreen->fifo_cache = 0; ffbScreen->rp_active = 0; ffbScreen->sPriv = sPriv; sPriv->private = (void *) ffbScreen; ffbDDLinefuncInit(); ffbDDPointfuncInit(); return GL_TRUE; } static void ffbDestroyScreen(__DRIscreenPrivate *sPriv) { ffbScreenPrivate *ffbScreen = sPriv->private; FFBDRIPtr gDRIPriv = (FFBDRIPtr) sPriv->pDevPriv; drmUnmap((drmAddress)ffbScreen->regs, gDRIPriv->sFbcRegs); drmUnmap((drmAddress)ffbScreen->dac, gDRIPriv->sDacRegs); drmUnmap((drmAddress)ffbScreen->sfb8r, gDRIPriv->sSfb8r); drmUnmap((drmAddress)ffbScreen->sfb32, gDRIPriv->sSfb32); drmUnmap((drmAddress)ffbScreen->sfb64, gDRIPriv->sSfb64); FREE(ffbScreen); } static const struct tnl_pipeline_stage *ffb_pipeline[] = { &_tnl_vertex_transform_stage, &_tnl_normal_transform_stage, &_tnl_lighting_stage, /* REMOVE: fog coord stage */ &_tnl_texgen_stage, &_tnl_texture_transform_stage, /* REMOVE: point attenuation stage */ &_tnl_render_stage, 0, }; /* Create and initialize the Mesa and driver specific context data */ static GLboolean ffbCreateContext(const __GLcontextModes *mesaVis, __DRIcontextPrivate *driContextPriv, void *sharedContextPrivate) { ffbContextPtr fmesa; GLcontext *ctx, *shareCtx; __DRIscreenPrivate *sPriv; ffbScreenPrivate *ffbScreen; char *debug; struct dd_function_table functions; /* Allocate ffb context */ fmesa = (ffbContextPtr) CALLOC(sizeof(ffbContextRec)); if (!fmesa) return GL_FALSE; _mesa_init_driver_functions(&functions); /* Allocate Mesa context */ if (sharedContextPrivate) shareCtx = ((ffbContextPtr) sharedContextPrivate)->glCtx; else shareCtx = NULL; fmesa->glCtx = _mesa_create_context(mesaVis, shareCtx, &functions, fmesa); if (!fmesa->glCtx) { FREE(fmesa); return GL_FALSE; } driContextPriv->driverPrivate = fmesa; ctx = fmesa->glCtx; sPriv = driContextPriv->driScreenPriv; ffbScreen = (ffbScreenPrivate *) sPriv->private; /* Dri stuff. */ fmesa->hHWContext = driContextPriv->hHWContext; fmesa->driFd = sPriv->fd; fmesa->driHwLock = &sPriv->pSAREA->lock; fmesa->ffbScreen = ffbScreen; fmesa->driScreen = sPriv; fmesa->ffb_sarea = FFB_DRISHARE(sPriv->pSAREA); /* Register and framebuffer hw pointers. */ fmesa->regs = ffbScreen->regs; fmesa->sfb32 = ffbScreen->sfb32; ffbDDInitContextHwState(ctx); /* Default clear and depth colors. */ { GLubyte r = (GLint) (ctx->Color.ClearColor[0] * 255.0F); GLubyte g = (GLint) (ctx->Color.ClearColor[1] * 255.0F); GLubyte b = (GLint) (ctx->Color.ClearColor[2] * 255.0F); fmesa->clear_pixel = ((r << 0) | (g << 8) | (b << 16)); } fmesa->clear_depth = Z_FROM_MESA(ctx->Depth.Clear * 4294967295.0f); fmesa->clear_stencil = ctx->Stencil.Clear & 0xf; /* No wide points. */ ctx->Const.MinPointSize = 1.0; ctx->Const.MinPointSizeAA = 1.0; ctx->Const.MaxPointSize = 1.0; ctx->Const.MaxPointSizeAA = 1.0; /* Disable wide lines as we can't antialias them correctly in * hardware. */ ctx->Const.MinLineWidth = 1.0; ctx->Const.MinLineWidthAA = 1.0; ctx->Const.MaxLineWidth = 1.0; ctx->Const.MaxLineWidthAA = 1.0; ctx->Const.LineWidthGranularity = 1.0; /* Instead of having GCC emit these constants a zillion times * everywhere in the driver, put them here. */ fmesa->ffb_2_30_fixed_scale = __FFB_2_30_FIXED_SCALE; fmesa->ffb_one_over_2_30_fixed_scale = (1.0 / __FFB_2_30_FIXED_SCALE); fmesa->ffb_16_16_fixed_scale = __FFB_16_16_FIXED_SCALE; fmesa->ffb_one_over_16_16_fixed_scale = (1.0 / __FFB_16_16_FIXED_SCALE); fmesa->ffb_ubyte_color_scale = 255.0f; fmesa->ffb_zero = 0.0f; fmesa->debugFallbacks = GL_FALSE; debug = getenv("LIBGL_DEBUG"); if (debug && strstr(debug, "fallbacks")) fmesa->debugFallbacks = GL_TRUE; /* Initialize the software rasterizer and helper modules. */ _swrast_CreateContext( ctx ); _vbo_CreateContext( ctx ); _tnl_CreateContext( ctx ); _swsetup_CreateContext( ctx ); /* All of this need only be done once for a new context. */ /* XXX these should be moved right after the * _mesa_init_driver_functions() call above. */ ffbDDExtensionsInit(ctx); ffbDDInitDriverFuncs(ctx); ffbDDInitStateFuncs(ctx); ffbDDInitRenderFuncs(ctx); /*ffbDDInitTexFuncs(ctx); not needed */ ffbDDInitBitmapFuncs(ctx); ffbInitVB(ctx); #if 0 ffbInitTnlModule(ctx); #endif _tnl_destroy_pipeline(ctx); _tnl_install_pipeline(ctx, ffb_pipeline); return GL_TRUE; } static void ffbDestroyContext(__DRIcontextPrivate *driContextPriv) { ffbContextPtr fmesa = (ffbContextPtr) driContextPriv->driverPrivate; if (fmesa) { ffbFreeVB(fmesa->glCtx); _swsetup_DestroyContext( fmesa->glCtx ); _tnl_DestroyContext( fmesa->glCtx ); _vbo_DestroyContext( fmesa->glCtx ); _swrast_DestroyContext( fmesa->glCtx ); /* free the Mesa context */ fmesa->glCtx->DriverCtx = NULL; _mesa_destroy_context(fmesa->glCtx); FREE(fmesa); } } /* Create and initialize the Mesa and driver specific pixmap buffer data */ static GLboolean ffbCreateBuffer(__DRIscreenPrivate *driScrnPriv, __DRIdrawablePrivate *driDrawPriv, const __GLcontextModes *mesaVis, GLboolean isPixmap ) { /* Mesa checks for pitch > 0, but ffb doesn't use pitches */ int bogusPitch = 1; int bpp = 4; /* we've always got a 32bpp framebuffer */ int offset = 0; /* always at 0 for offset */ if (isPixmap) { return GL_FALSE; /* not implemented */ } else { GLboolean swStencil = (mesaVis->stencilBits > 0 && mesaVis->depthBits != 24); struct gl_framebuffer *fb = _mesa_create_framebuffer(mesaVis); { driRenderbuffer *frontRb = driNewRenderbuffer(GL_RGBA, NULL, bpp, offset, bogusPitch, driDrawPriv); ffbSetSpanFunctions(frontRb, mesaVis); _mesa_add_renderbuffer(fb, BUFFER_FRONT_LEFT, &frontRb->Base); } if (mesaVis->doubleBufferMode) { driRenderbuffer *backRb = driNewRenderbuffer(GL_RGBA, NULL, bpp, offset, bogusPitch, driDrawPriv); ffbSetSpanFunctions(backRb, mesaVis); _mesa_add_renderbuffer(fb, BUFFER_BACK_LEFT, &backRb->Base); } if (mesaVis->depthBits == 16) { driRenderbuffer *depthRb = driNewRenderbuffer(GL_DEPTH_COMPONENT16, NULL, bpp, offset, bogusPitch, driDrawPriv); ffbSetDepthFunctions(depthRb, mesaVis); _mesa_add_renderbuffer(fb, BUFFER_DEPTH, &depthRb->Base); } if (mesaVis->stencilBits > 0 && !swStencil) { driRenderbuffer *stencilRb = driNewRenderbuffer(GL_STENCIL_INDEX8_EXT, NULL, bpp, offset, bogusPitch, driDrawPriv); ffbSetStencilFunctions(stencilRb, mesaVis); _mesa_add_renderbuffer(fb, BUFFER_STENCIL, &stencilRb->Base); } _mesa_add_soft_renderbuffers(fb, GL_FALSE, /* color */ GL_FALSE, /* depth */ swStencil, mesaVis->accumRedBits > 0, GL_FALSE, /* alpha */ GL_FALSE /* aux */); driDrawPriv->driverPrivate = (void *) fb; return (driDrawPriv->driverPrivate != NULL); } } static void ffbDestroyBuffer(__DRIdrawablePrivate *driDrawPriv) { _mesa_unreference_framebuffer((GLframebuffer **)(&(driDrawPriv->driverPrivate))); } #define USE_FAST_SWAP static void ffbSwapBuffers( __DRIdrawablePrivate *dPriv ) { ffbContextPtr fmesa = (ffbContextPtr) dPriv->driContextPriv->driverPrivate; unsigned int fbc, wid, wid_reg_val, dac_db_bit; unsigned int shadow_dac_addr, active_dac_addr; ffb_fbcPtr ffb; ffb_dacPtr dac; if (fmesa == NULL || fmesa->glCtx->Visual.doubleBufferMode == 0) return; /* Flush pending rendering commands */ _mesa_notifySwapBuffers(fmesa->glCtx); ffb = fmesa->regs; dac = fmesa->ffbScreen->dac; fbc = fmesa->fbc; wid = fmesa->wid; /* Swap the buffer we render into and read pixels from. */ fmesa->back_buffer ^= 1; /* If we are writing into both buffers, don't mess with * the WB setting. */ if ((fbc & FFB_FBC_WB_AB) != FFB_FBC_WB_AB) { if ((fbc & FFB_FBC_WB_A) != 0) fbc = (fbc & ~FFB_FBC_WB_A) | FFB_FBC_WB_B; else fbc = (fbc & ~FFB_FBC_WB_B) | FFB_FBC_WB_A; } /* But either way, we must flip the read buffer setting. */ if ((fbc & FFB_FBC_RB_A) != 0) fbc = (fbc & ~FFB_FBC_RB_A) | FFB_FBC_RB_B; else fbc = (fbc & ~FFB_FBC_RB_B) | FFB_FBC_RB_A; LOCK_HARDWARE(fmesa); if (fmesa->fbc != fbc) { FFBFifo(fmesa, 1); ffb->fbc = fmesa->fbc = fbc; fmesa->ffbScreen->rp_active = 1; } /* And swap the buffer displayed in the WID. */ if (fmesa->ffb_sarea->flags & FFB_DRI_PAC1) { shadow_dac_addr = FFBDAC_PAC1_SPWLUT(wid); active_dac_addr = FFBDAC_PAC1_APWLUT(wid); dac_db_bit = FFBDAC_PAC1_WLUT_DB; } else { shadow_dac_addr = FFBDAC_PAC2_SPWLUT(wid); active_dac_addr = FFBDAC_PAC2_APWLUT(wid); dac_db_bit = FFBDAC_PAC2_WLUT_DB; } FFBWait(fmesa, ffb); wid_reg_val = DACCFG_READ(dac, active_dac_addr); if (fmesa->back_buffer == 0) wid_reg_val |= dac_db_bit; else wid_reg_val &= ~dac_db_bit; #ifdef USE_FAST_SWAP DACCFG_WRITE(dac, active_dac_addr, wid_reg_val); #else DACCFG_WRITE(dac, shadow_dac_addr, wid_reg_val); /* Schedule the window transfer. */ DACCFG_WRITE(dac, FFBDAC_CFG_WTCTRL, (FFBDAC_CFG_WTCTRL_TCMD | FFBDAC_CFG_WTCTRL_TE)); { int limit = 1000000; while (limit--) { unsigned int wtctrl = DACCFG_READ(dac, FFBDAC_CFG_WTCTRL); if ((wtctrl & FFBDAC_CFG_WTCTRL_DS) == 0) break; } } #endif UNLOCK_HARDWARE(fmesa); } static void ffb_init_wid(ffbContextPtr fmesa, unsigned int wid) { ffb_dacPtr dac = fmesa->ffbScreen->dac; unsigned int wid_reg_val, dac_db_bit, active_dac_addr; unsigned int shadow_dac_addr; if (fmesa->ffb_sarea->flags & FFB_DRI_PAC1) { shadow_dac_addr = FFBDAC_PAC1_SPWLUT(wid); active_dac_addr = FFBDAC_PAC1_APWLUT(wid); dac_db_bit = FFBDAC_PAC1_WLUT_DB; } else { shadow_dac_addr = FFBDAC_PAC2_SPWLUT(wid); active_dac_addr = FFBDAC_PAC2_APWLUT(wid); dac_db_bit = FFBDAC_PAC2_WLUT_DB; } wid_reg_val = DACCFG_READ(dac, active_dac_addr); wid_reg_val &= ~dac_db_bit; #ifdef USE_FAST_SWAP DACCFG_WRITE(dac, active_dac_addr, wid_reg_val); #else DACCFG_WRITE(dac, shadow_dac_addr, wid_reg_val); /* Schedule the window transfer. */ DACCFG_WRITE(dac, FFBDAC_CFG_WTCTRL, (FFBDAC_CFG_WTCTRL_TCMD | FFBDAC_CFG_WTCTRL_TE)); { int limit = 1000000; while (limit--) { unsigned int wtctrl = DACCFG_READ(dac, FFBDAC_CFG_WTCTRL); if ((wtctrl & FFBDAC_CFG_WTCTRL_DS) == 0) break; } } #endif } /* Force the context `c' to be the current context and associate with it buffer `b' */ static GLboolean ffbMakeCurrent(__DRIcontextPrivate *driContextPriv, __DRIdrawablePrivate *driDrawPriv, __DRIdrawablePrivate *driReadPriv) { if (driContextPriv) { ffbContextPtr fmesa = (ffbContextPtr) driContextPriv->driverPrivate; int first_time; fmesa->driDrawable = driDrawPriv; _mesa_make_current(fmesa->glCtx, (GLframebuffer *) driDrawPriv->driverPrivate, (GLframebuffer *) driReadPriv->driverPrivate); first_time = 0; if (fmesa->wid == ~0) { first_time = 1; if (getenv("LIBGL_SOFTWARE_RENDERING")) FALLBACK( fmesa->glCtx, FFB_BADATTR_SWONLY, GL_TRUE ); } LOCK_HARDWARE(fmesa); if (first_time) { fmesa->wid = fmesa->ffb_sarea->wid_table[driDrawPriv->index]; ffb_init_wid(fmesa, fmesa->wid); } fmesa->state_dirty |= FFB_STATE_ALL; fmesa->state_fifo_ents = fmesa->state_all_fifo_ents; ffbSyncHardware(fmesa); UNLOCK_HARDWARE(fmesa); if (first_time) { /* Also, at the first switch to a new context, * we need to clear all the hw buffers. */ ffbDDClear(fmesa->glCtx, (BUFFER_BIT_FRONT_LEFT | BUFFER_BIT_BACK_LEFT | BUFFER_BIT_DEPTH | BUFFER_BIT_STENCIL)); } } else { _mesa_make_current(NULL, NULL, NULL); } return GL_TRUE; } /* Force the context `c' to be unbound from its buffer */ static GLboolean ffbUnbindContext(__DRIcontextPrivate *driContextPriv) { return GL_TRUE; } void ffbXMesaUpdateState(ffbContextPtr fmesa) { __DRIdrawablePrivate *dPriv = fmesa->driDrawable; __DRIscreenPrivate *sPriv = fmesa->driScreen; int stamp = dPriv->lastStamp; DRI_VALIDATE_DRAWABLE_INFO(sPriv, dPriv); if (dPriv->lastStamp != stamp) { GLcontext *ctx = fmesa->glCtx; ffbCalcViewport(ctx); driUpdateFramebufferSize(ctx, dPriv); if (ctx->Polygon.StippleFlag) { ffbXformAreaPattern(fmesa, (const GLubyte *)ctx->PolygonStipple); } } } static const __DRIconfig ** ffbFillInModes( __DRIscreenPrivate *psp, unsigned pixel_bits, unsigned depth_bits, unsigned stencil_bits, GLboolean have_back_buffer ) { __DRIconfig **configs; __GLcontextModes *m; unsigned depth_buffer_factor; unsigned back_buffer_factor; GLenum fb_format; GLenum fb_type; int i; /* GLX_SWAP_COPY_OML is only supported because the FFB driver doesn't * support pageflipping at all. */ static const GLenum back_buffer_modes[] = { GLX_NONE, GLX_SWAP_UNDEFINED_OML, GLX_SWAP_COPY_OML }; uint8_t depth_bits_array[3]; uint8_t stencil_bits_array[3]; depth_bits_array[0] = 0; depth_bits_array[1] = depth_bits; depth_bits_array[2] = depth_bits; /* Just like with the accumulation buffer, always provide some modes * with a stencil buffer. It will be a sw fallback, but some apps won't * care about that. */ stencil_bits_array[0] = 0; stencil_bits_array[1] = 0; stencil_bits_array[2] = (stencil_bits == 0) ? 8 : stencil_bits; depth_buffer_factor = ((depth_bits != 0) || (stencil_bits != 0)) ? 3 : 1; back_buffer_factor = (have_back_buffer) ? 3 : 1; if ( pixel_bits == 16 ) { fb_format = GL_RGB; fb_type = GL_UNSIGNED_SHORT_5_6_5; } else { fb_format = GL_BGRA; fb_type = GL_UNSIGNED_INT_8_8_8_8_REV; } configs = driCreateConfigs(fb_format, fb_type, depth_bits_array, stencil_bits_array, depth_buffer_factor, back_buffer_modes, back_buffer_factor); if (configs == NULL) { fprintf(stderr, "[%s:%u] Error creating FBConfig!\n", __func__, __LINE__); return NULL; } /* Mark the visual as slow if there are "fake" stencil bits. */ for (i = 0; configs[i]; i++) { m = &configs[i]->modes; if ((m->stencilBits != 0) && (m->stencilBits != stencil_bits)) { m->visualRating = GLX_SLOW_CONFIG; } } return (const __DRIconfig **) configs; } /** * This is the driver specific part of the createNewScreen entry point. * * \todo maybe fold this into intelInitDriver * * \return the __GLcontextModes supported by this driver */ static const __DRIconfig ** ffbInitScreen(__DRIscreen *psp) { static const __DRIversion ddx_expected = { 0, 1, 1 }; static const __DRIversion dri_expected = { 4, 0, 0 }; static const __DRIversion drm_expected = { 0, 0, 1 }; if ( ! driCheckDriDdxDrmVersions2( "ffb", &psp->dri_version, & dri_expected, &psp->ddx_version, & ddx_expected, &psp->drm_version, & drm_expected ) ) return NULL; if (!ffbInitDriver(psp)) return NULL; return ffbFillInModes( psp, 32, 16, 0, GL_TRUE ); } const struct __DriverAPIRec driDriverAPI = { .InitScreen = ffbInitScreen, .DestroyScreen = ffbDestroyScreen, .CreateContext = ffbCreateContext, .DestroyContext = ffbDestroyContext, .CreateBuffer = ffbCreateBuffer, .DestroyBuffer = ffbDestroyBuffer, .SwapBuffers = ffbSwapBuffers, .MakeCurrent = ffbMakeCurrent, .UnbindContext = ffbUnbindContext, .GetSwapInfo = NULL, .GetDrawableMSC = NULL, .WaitForMSC = NULL, .WaitForSBC = NULL, .SwapBuffersMSC = NULL };