From 73436a909e9ca3312ad506e2b765a4f51858b05e Mon Sep 17 00:00:00 2001 From: Brian Paul Date: Mon, 11 Mar 2013 18:31:21 -0600 Subject: [PATCH] st/osmesa: new OSMesa gallium state tracker MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit Reviewed-by: José Fonseca --- src/gallium/state_trackers/osmesa/osmesa.c | 793 +++++++++++++++++++++ 1 file changed, 793 insertions(+) create mode 100644 src/gallium/state_trackers/osmesa/osmesa.c diff --git a/src/gallium/state_trackers/osmesa/osmesa.c b/src/gallium/state_trackers/osmesa/osmesa.c new file mode 100644 index 00000000000..35fa3381d04 --- /dev/null +++ b/src/gallium/state_trackers/osmesa/osmesa.c @@ -0,0 +1,793 @@ +/* + * Copyright (c) 2013 Brian Paul All Rights Reserved. + * + * 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 + * THE AUTHORS 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. + */ + + +/* + * Off-Screen rendering into client memory. + * State tracker for gallium (for softpipe and llvmpipe) + * + * Notes: + * + * If Gallium is built with LLVM support we use the llvmpipe driver. + * Otherwise we use softpipe. The GALLIUM_DRIVER environment variable + * may be set to "softpipe" or "llvmpipe" to override. + * + * With softpipe we could render directly into the user's buffer by using a + * display target resource. However, softpipe doesn't suport "upside-down" + * rendering which would be needed for the OSMESA_Y_UP=TRUE case. + * + * With llvmpipe we could only render directly into the user's buffer when its + * width and height is a multiple of the tile size (64 pixels). + * + * Because of these constraints we always render into ordinary resources then + * copy the results to the user's buffer in the flush_front() function which + * is called when the app calls glFlush/Finish. + * + * In general, the OSMesa interface is pretty ugly and not a good match + * for Gallium. But we're interested in doing the best we can to preserve + * application portability. With a little work we could come up with a + * much nicer, new off-screen Gallium interface... + */ + + +#include "GL/osmesa.h" + +#include "glapi/glapi.h" /* for OSMesaGetProcAddress below */ + +#include "pipe/p_context.h" +#include "pipe/p_screen.h" +#include "pipe/p_state.h" + +#include "util/u_atomic.h" +#include "util/u_box.h" +#include "util/u_format.h" +#include "util/u_memory.h" + +#include "state_tracker/st_api.h" +#include "state_tracker/st_gl_api.h" + + + +extern struct pipe_screen * +osmesa_create_screen(void); + + + +struct osmesa_buffer +{ + struct st_framebuffer_iface *stfb; + struct st_visual visual; + unsigned width, height; + + struct pipe_resource *textures[ST_ATTACHMENT_COUNT]; + + void *map; +}; + + +struct osmesa_context +{ + struct st_context_iface *stctx; + + struct osmesa_buffer *current_buffer; + + enum pipe_format depth_stencil_format, accum_format; + + GLenum format; /*< User-specified context format */ + GLenum type; /*< Buffer's data type */ + GLint user_row_length; /*< user-specified number of pixels per row */ + GLboolean y_up; /*< TRUE -> Y increases upward */ + /*< FALSE -> Y increases downward */ +}; + + + +/** + * Called from the ST manager. + */ +static int +osmesa_st_get_param(struct st_manager *smapi, enum st_manager_param param) +{ + /* no-op */ + return 0; +} + + +/** + * Create/return singleton st_api object. + */ +static struct st_api * +get_st_api(void) +{ + static struct st_api *stapi = NULL; + if (!stapi) { + stapi = st_gl_api_create(); + } + return stapi; +} + + +/** + * Create/return a singleton st_manager object. + */ +static struct st_manager * +get_st_manager(void) +{ + static struct st_manager *stmgr = NULL; + if (!stmgr) { + stmgr = CALLOC_STRUCT(st_manager); + if (stmgr) { + stmgr->screen = osmesa_create_screen(); + stmgr->get_param = osmesa_st_get_param; + stmgr->get_egl_image = NULL; + } + } + return stmgr; +} + + +static INLINE boolean +little_endian(void) +{ + const unsigned ui = 1; + return *((const char *) &ui); +} + + +/** + * Given an OSMESA_x format and a GL_y type, return the best + * matching PIPE_FORMAT_z. + * Note that we can't exactly match all user format/type combinations + * with gallium formats. If we find this to be a problem, we can + * implement more elaborate format/type conversion in the flush_front() + * function. + */ +static enum pipe_format +osmesa_choose_format(GLenum format, GLenum type) +{ + switch (format) { + case OSMESA_RGBA: + if (type == GL_UNSIGNED_BYTE) { + if (little_endian()) + return PIPE_FORMAT_R8G8B8A8_UNORM; + else + return PIPE_FORMAT_A8B8G8R8_UNORM; + } + else if (type == GL_UNSIGNED_SHORT) { + return PIPE_FORMAT_R16G16B16A16_UNORM; + } + else if (type == GL_FLOAT) { + return PIPE_FORMAT_R32G32B32A32_FLOAT; + } + else { + return PIPE_FORMAT_NONE; + } + break; + case OSMESA_BGRA: + if (type == GL_UNSIGNED_BYTE) { + if (little_endian()) + return PIPE_FORMAT_B8G8R8A8_UNORM; + else + return PIPE_FORMAT_A8R8G8B8_UNORM; + } + else if (type == GL_UNSIGNED_SHORT) { + return PIPE_FORMAT_R16G16B16A16_UNORM; + } + else if (type == GL_FLOAT) { + return PIPE_FORMAT_R32G32B32A32_FLOAT; + } + else { + return PIPE_FORMAT_NONE; + } + break; + case OSMESA_ARGB: + if (type == GL_UNSIGNED_BYTE) { + if (little_endian()) + return PIPE_FORMAT_A8R8G8B8_UNORM; + else + return PIPE_FORMAT_B8G8R8A8_UNORM; + } + else if (type == GL_UNSIGNED_SHORT) { + return PIPE_FORMAT_R16G16B16A16_UNORM; + } + else if (type == GL_FLOAT) { + return PIPE_FORMAT_R32G32B32A32_FLOAT; + } + else { + return PIPE_FORMAT_NONE; + } + break; + case OSMESA_RGB: + if (type == GL_UNSIGNED_BYTE) { + return PIPE_FORMAT_R8G8B8_UNORM; + } + else if (type == GL_UNSIGNED_SHORT) { + return PIPE_FORMAT_R16G16B16_UNORM; + } + else if (type == GL_FLOAT) { + return PIPE_FORMAT_R32G32B32_FLOAT; + } + else { + return PIPE_FORMAT_NONE; + } + break; + case OSMESA_BGR: + /* No gallium format for this one */ + return PIPE_FORMAT_NONE; + case OSMESA_RGB_565: + return PIPE_FORMAT_B5G6R5_UNORM; + default: + ; /* fall-through */ + } + return PIPE_FORMAT_NONE; +} + + +/** + * Initialize an st_visual object. + */ +static void +osmesa_init_st_visual(struct st_visual *vis, + enum pipe_format color_format, + enum pipe_format ds_format, + enum pipe_format accum_format) +{ + vis->buffer_mask = ST_ATTACHMENT_FRONT_LEFT_MASK; + vis->color_format = color_format; + vis->depth_stencil_format = ds_format; + vis->accum_format = accum_format; + vis->samples = 1; + vis->render_buffer = ST_ATTACHMENT_FRONT_LEFT; +} + + +/** + * Return the osmesa_buffer that corresponds to an st_framebuffer_iface. + */ +static INLINE struct osmesa_buffer * +stfbi_to_osbuffer(struct st_framebuffer_iface *stfbi) +{ + return (struct osmesa_buffer *) stfbi->st_manager_private; +} + + +/** + * Called via glFlush/glFinish. This is where we copy the contents + * of the driver's color buffer into the user-specified buffer. + */ +static boolean +osmesa_st_framebuffer_flush_front(struct st_context_iface *stctx, + struct st_framebuffer_iface *stfbi, + enum st_attachment_type statt) +{ + OSMesaContext osmesa = OSMesaGetCurrentContext(); + struct osmesa_buffer *osbuffer = stfbi_to_osbuffer(stfbi); + struct pipe_context *pipe = stctx->pipe; + struct pipe_resource *res = osbuffer->textures[statt]; + struct pipe_transfer *transfer = NULL; + struct pipe_box box; + void *map; + ubyte *src, *dst; + unsigned y, bytes, bpp; + int dst_stride; + + u_box_2d(0, 0, res->width0, res->height0, &box); + + map = pipe->transfer_map(pipe, res, 0, PIPE_TRANSFER_READ, &box, + &transfer); + + /* + * Copy the color buffer from the resource to the user's buffer. + */ + bpp = util_format_get_blocksize(osbuffer->visual.color_format); + src = map; + dst = osbuffer->map; + if (osmesa->user_row_length) + dst_stride = bpp * osmesa->user_row_length; + else + dst_stride = bpp * osbuffer->width; + bytes = bpp * res->width0; + + if (osmesa->y_up) { + /* need to flip image upside down */ + dst = dst + (res->height0 - 1) * dst_stride; + dst_stride = -dst_stride; + } + + for (y = 0; y < res->height0; y++) { + memcpy(dst, src, bytes); + dst += dst_stride; + src += transfer->stride; + } + + pipe->transfer_unmap(pipe, transfer); + + return TRUE; +} + + +/** + * Called by the st manager to validate the framebuffer (allocate + * its resources). + */ +static boolean +osmesa_st_framebuffer_validate(struct st_framebuffer_iface *stfbi, + const enum st_attachment_type *statts, + unsigned count, + struct pipe_resource **out) +{ + struct pipe_screen *screen = get_st_manager()->screen; + enum st_attachment_type i; + struct osmesa_buffer *osbuffer = stfbi_to_osbuffer(stfbi); + struct pipe_resource templat; + + memset(&templat, 0, sizeof(templat)); + templat.target = PIPE_TEXTURE_RECT; + templat.format = 0; /* setup below */ + templat.last_level = 0; + templat.width0 = osbuffer->width; + templat.height0 = osbuffer->height; + templat.depth0 = 1; + templat.array_size = 1; + templat.usage = PIPE_USAGE_DEFAULT; + templat.bind = 0; /* setup below */ + templat.flags = 0; + + for (i = 0; i < count; i++) { + enum pipe_format format; + unsigned bind; + + /* + * At this time, we really only need to handle the front-left color + * attachment, since that's all we specified for the visual in + * osmesa_init_st_visual(). + */ + if (statts[i] == ST_ATTACHMENT_FRONT_LEFT) { + format = osbuffer->visual.color_format; + bind = PIPE_BIND_RENDER_TARGET; + } + else if (statts[i] == ST_ATTACHMENT_DEPTH_STENCIL) { + format = osbuffer->visual.depth_stencil_format; + bind = PIPE_BIND_DEPTH_STENCIL; + } + else if (statts[i] == ST_ATTACHMENT_ACCUM) { + format = osbuffer->visual.accum_format; + bind = PIPE_BIND_RENDER_TARGET; + } + + templat.format = format; + templat.bind = bind; + out[i] = osbuffer->textures[i] = + screen->resource_create(screen, &templat); + } + + return TRUE; +} + + +static struct st_framebuffer_iface * +osmesa_create_st_framebuffer(void) +{ + struct st_framebuffer_iface *stfbi = CALLOC_STRUCT(st_framebuffer_iface); + if (stfbi) { + stfbi->flush_front = osmesa_st_framebuffer_flush_front; + stfbi->validate = osmesa_st_framebuffer_validate; + p_atomic_set(&stfbi->stamp, 1); + } + return stfbi; +} + + +static struct osmesa_buffer * +osmesa_create_buffer(enum pipe_format color_format, + enum pipe_format ds_format, + enum pipe_format accum_format) +{ + struct osmesa_buffer *osbuffer = CALLOC_STRUCT(osmesa_buffer); + if (osbuffer) { + osbuffer->stfb = osmesa_create_st_framebuffer(); + + osbuffer->stfb->st_manager_private = osbuffer; + osbuffer->stfb->visual = &osbuffer->visual; + + osmesa_init_st_visual(&osbuffer->visual, color_format, + ds_format, accum_format); + } + return osbuffer; +} + + +static void +osmesa_destroy_buffer(struct osmesa_buffer *osbuffer) +{ + FREE(osbuffer->stfb); + FREE(osbuffer); +} + + + +/**********************************************************************/ +/***** Public Functions *****/ +/**********************************************************************/ + + +/** + * Create an Off-Screen Mesa rendering context. The only attribute needed is + * an RGBA vs Color-Index mode flag. + * + * Input: format - Must be GL_RGBA + * sharelist - specifies another OSMesaContext with which to share + * display lists. NULL indicates no sharing. + * Return: an OSMesaContext or 0 if error + */ +GLAPI OSMesaContext GLAPIENTRY +OSMesaCreateContext(GLenum format, OSMesaContext sharelist) +{ + return OSMesaCreateContextExt(format, 24, 8, 0, sharelist); +} + + +/** + * New in Mesa 3.5 + * + * Create context and specify size of ancillary buffers. + */ +GLAPI OSMesaContext GLAPIENTRY +OSMesaCreateContextExt(GLenum format, GLint depthBits, GLint stencilBits, + GLint accumBits, OSMesaContext sharelist) +{ + OSMesaContext osmesa; + struct st_context_iface *st_shared; + enum st_context_error st_error = 0; + struct st_context_attribs attribs; + struct st_api *stapi = get_st_api(); + + if (sharelist) { + st_shared = sharelist->stctx; + } + else { + st_shared = NULL; + } + + osmesa = (OSMesaContext) CALLOC_STRUCT(osmesa_context); + if (!osmesa) + return NULL; + + /* Choose depth/stencil/accum buffer formats */ + if (accumBits > 0) { + osmesa->accum_format = PIPE_FORMAT_R16G16B16A16_SNORM; + } + if (depthBits > 0 && stencilBits > 0) { + osmesa->depth_stencil_format = PIPE_FORMAT_Z24_UNORM_S8_UINT; + } + else if (stencilBits > 0) { + osmesa->depth_stencil_format = PIPE_FORMAT_S8_UINT; + } + else if (depthBits >= 24) { + osmesa->depth_stencil_format = PIPE_FORMAT_Z24X8_UNORM; + } + else if (depthBits >= 16) { + osmesa->depth_stencil_format = PIPE_FORMAT_Z16_UNORM; + } + + /* + * Create the rendering context + */ + attribs.profile = ST_PROFILE_DEFAULT; + attribs.major = 2; + attribs.minor = 1; + attribs.flags = 0; /* ST_CONTEXT_FLAG_x */ + attribs.options.force_glsl_extensions_warn = FALSE; + + osmesa_init_st_visual(&attribs.visual, + PIPE_FORMAT_R8G8B8A8_UNORM, + osmesa->depth_stencil_format, + osmesa->accum_format); + + osmesa->stctx = stapi->create_context(stapi, get_st_manager(), + &attribs, &st_error, st_shared); + if (!osmesa->stctx) { + FREE(osmesa); + return NULL; + } + + osmesa->stctx->st_manager_private = osmesa; + + osmesa->format = format; + osmesa->user_row_length = 0; + osmesa->y_up = GL_TRUE; + + return osmesa; +} + + +/** + * Destroy an Off-Screen Mesa rendering context. + * + * \param osmesa the context to destroy + */ +GLAPI void GLAPIENTRY +OSMesaDestroyContext(OSMesaContext osmesa) +{ + if (osmesa) { + osmesa->stctx->destroy(osmesa->stctx); + FREE(osmesa); + } +} + + +/** + * Bind an OSMesaContext to an image buffer. The image buffer is just a + * block of memory which the client provides. Its size must be at least + * as large as width*height*pixelSize. Its address should be a multiple + * of 4 if using RGBA mode. + * + * By default, image data is stored in the order of glDrawPixels: row-major + * order with the lower-left image pixel stored in the first array position + * (ie. bottom-to-top). + * + * If the context's viewport hasn't been initialized yet, it will now be + * initialized to (0,0,width,height). + * + * Input: osmesa - the rendering context + * buffer - the image buffer memory + * type - data type for pixel components + * GL_UNSIGNED_BYTE, GL_UNSIGNED_SHORT_5_6_5, GL_UNSIGNED_SHORT + * or GL_FLOAT. + * width, height - size of image buffer in pixels, at least 1 + * Return: GL_TRUE if success, GL_FALSE if error because of invalid osmesa, + * invalid type, invalid size, etc. + */ +GLAPI GLboolean GLAPIENTRY +OSMesaMakeCurrent(OSMesaContext osmesa, void *buffer, GLenum type, + GLsizei width, GLsizei height) +{ + struct st_api *stapi = get_st_api(); + struct osmesa_buffer *osbuffer; + enum pipe_format color_format; + + if (osmesa->format == OSMESA_RGB_565 && type != GL_UNSIGNED_SHORT_5_6_5) { + return GL_FALSE; + } + if (width < 1 || height < 1) { + return GL_FALSE; + } + + color_format = osmesa_choose_format(osmesa->format, type); + if (color_format == PIPE_FORMAT_NONE) { + fprintf(stderr, "OSMesaMakeCurrent(unsupported format/type)\n"); + return GL_FALSE; + } + + osbuffer = osmesa_create_buffer(color_format, + osmesa->depth_stencil_format, + osmesa->accum_format); + + osbuffer->width = width; + osbuffer->height = height; + osbuffer->map = buffer; + + if (osmesa->current_buffer) { + /* free old buffer */ + osmesa_destroy_buffer(osmesa->current_buffer); + } + + osmesa->current_buffer = osbuffer; + osmesa->type = type; + + stapi->make_current(stapi, osmesa->stctx, osbuffer->stfb, osbuffer->stfb); + + return GL_TRUE; +} + + + +GLAPI OSMesaContext GLAPIENTRY +OSMesaGetCurrentContext(void) +{ + struct st_api *stapi = get_st_api(); + struct st_context_iface *st = stapi->get_current(stapi); + return st ? (OSMesaContext) st->st_manager_private : NULL; +} + + + +GLAPI void GLAPIENTRY +OSMesaPixelStore(GLint pname, GLint value) +{ + OSMesaContext osmesa = OSMesaGetCurrentContext(); + + switch (pname) { + case OSMESA_ROW_LENGTH: + osmesa->user_row_length = value; + break; + case OSMESA_Y_UP: + osmesa->y_up = value ? GL_TRUE : GL_FALSE; + break; + default: + fprintf(stderr, "Invalid pname in OSMesaPixelStore()\n"); + return; + } +} + + +GLAPI void GLAPIENTRY +OSMesaGetIntegerv(GLint pname, GLint *value) +{ + OSMesaContext osmesa = OSMesaGetCurrentContext(); + struct osmesa_buffer *osbuffer = osmesa ? osmesa->current_buffer : NULL; + + switch (pname) { + case OSMESA_WIDTH: + *value = osbuffer ? osbuffer->width : 0; + return; + case OSMESA_HEIGHT: + *value = osbuffer ? osbuffer->height : 0; + return; + case OSMESA_FORMAT: + *value = osmesa->format; + return; + case OSMESA_TYPE: + /* current color buffer's data type */ + *value = osmesa->type; + return; + case OSMESA_ROW_LENGTH: + *value = osmesa->user_row_length; + return; + case OSMESA_Y_UP: + *value = osmesa->y_up; + return; + case OSMESA_MAX_WIDTH: + /* fall-through */ + case OSMESA_MAX_HEIGHT: + { + struct pipe_screen *screen = get_st_manager()->screen; + int maxLevels = screen->get_param(screen, + PIPE_CAP_MAX_TEXTURE_2D_LEVELS); + *value = 1 << (maxLevels - 1); + *value = 8 * 1024; + } + return; + default: + fprintf(stderr, "Invalid pname in OSMesaGetIntegerv()\n"); + return; + } +} + + +/** + * Return information about the depth buffer associated with an OSMesa context. + * Input: c - the OSMesa context + * Output: width, height - size of buffer in pixels + * bytesPerValue - bytes per depth value (2 or 4) + * buffer - pointer to depth buffer values + * Return: GL_TRUE or GL_FALSE to indicate success or failure. + */ +GLAPI GLboolean GLAPIENTRY +OSMesaGetDepthBuffer(OSMesaContext c, GLint *width, GLint *height, + GLint *bytesPerValue, void **buffer) +{ + struct osmesa_buffer *osbuffer = c->current_buffer; + struct pipe_context *pipe = c->stctx->pipe; + struct pipe_resource *res = osbuffer->textures[ST_ATTACHMENT_DEPTH_STENCIL]; + struct pipe_transfer *transfer = NULL; + struct pipe_box box; + + /* + * Note: we can't really implement this function with gallium as + * we did for swrast. We can't just map the resource and leave it + * mapped (and there's no OSMesaUnmapDepthBuffer() function) so + * we unmap the buffer here and return a 'stale' pointer. This should + * actually be OK in most cases where the caller of this function + * immediately uses the pointer. + */ + + u_box_2d(0, 0, res->width0, res->height0, &box); + + *buffer = pipe->transfer_map(pipe, res, 0, PIPE_TRANSFER_READ, &box, + &transfer); + if (!*buffer) { + return GL_FALSE; + } + + *width = res->width0; + *height = res->height0; + *bytesPerValue = util_format_get_blocksize(res->format); + + pipe->transfer_unmap(pipe, transfer); + + return GL_TRUE; +} + + +/** + * Return the color buffer associated with an OSMesa context. + * Input: c - the OSMesa context + * Output: width, height - size of buffer in pixels + * format - the pixel format (OSMESA_FORMAT) + * buffer - pointer to color buffer values + * Return: GL_TRUE or GL_FALSE to indicate success or failure. + */ +GLAPI GLboolean GLAPIENTRY +OSMesaGetColorBuffer(OSMesaContext osmesa, GLint *width, + GLint *height, GLint *format, void **buffer) +{ + struct osmesa_buffer *osbuffer = osmesa->current_buffer; + + if (osbuffer) { + *width = osbuffer->width; + *height = osbuffer->height; + *format = osmesa->format; + *buffer = osbuffer->map; + return GL_TRUE; + } + else { + *width = 0; + *height = 0; + *format = 0; + *buffer = 0; + return GL_FALSE; + } +} + + +struct name_function +{ + const char *Name; + OSMESAproc Function; +}; + +static struct name_function functions[] = { + { "OSMesaCreateContext", (OSMESAproc) OSMesaCreateContext }, + { "OSMesaCreateContextExt", (OSMESAproc) OSMesaCreateContextExt }, + { "OSMesaDestroyContext", (OSMESAproc) OSMesaDestroyContext }, + { "OSMesaMakeCurrent", (OSMESAproc) OSMesaMakeCurrent }, + { "OSMesaGetCurrentContext", (OSMESAproc) OSMesaGetCurrentContext }, + { "OSMesaPixelsStore", (OSMESAproc) OSMesaPixelStore }, + { "OSMesaGetIntegerv", (OSMESAproc) OSMesaGetIntegerv }, + { "OSMesaGetDepthBuffer", (OSMESAproc) OSMesaGetDepthBuffer }, + { "OSMesaGetColorBuffer", (OSMESAproc) OSMesaGetColorBuffer }, + { "OSMesaGetProcAddress", (OSMESAproc) OSMesaGetProcAddress }, + { "OSMesaColorClamp", (OSMESAproc) OSMesaColorClamp }, + { NULL, NULL } +}; + + +GLAPI OSMESAproc GLAPIENTRY +OSMesaGetProcAddress(const char *funcName) +{ + int i; + for (i = 0; functions[i].Name; i++) { + if (strcmp(functions[i].Name, funcName) == 0) + return functions[i].Function; + } + return _glapi_get_proc_address(funcName); +} + + +GLAPI void GLAPIENTRY +OSMesaColorClamp(GLboolean enable) +{ + extern void GLAPIENTRY _mesa_ClampColor(GLenum target, GLenum clamp); + + _mesa_ClampColor(GL_CLAMP_FRAGMENT_COLOR_ARB, + enable ? GL_TRUE : GL_FIXED_ONLY_ARB); +} -- 2.30.2