/* * Mesa 3-D graphics library * * Copyright (C) 1999-2008 Brian Paul All Rights Reserved. * Copyright (C) 2009-2011 VMware, Inc. 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 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 SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ /** * \file pbo.c * \brief Functions related to Pixel Buffer Objects. */ #include "glheader.h" #include "bufferobj.h" #include "glformats.h" #include "image.h" #include "imports.h" #include "mtypes.h" #include "pbo.h" /** * When we're about to read pixel data out of a PBO (via glDrawPixels, * glTexImage, etc) or write data into a PBO (via glReadPixels, * glGetTexImage, etc) we call this function to check that we're not * going to read/write out of bounds. * * XXX This would also be a convenient time to check that the PBO isn't * currently mapped. Whoever calls this function should check for that. * Remember, we can't use a PBO when it's mapped! * * If we're not using a PBO, this is a no-op. * * \param width width of image to read/write * \param height height of image to read/write * \param depth depth of image to read/write * \param format format of image to read/write * \param type datatype of image to read/write * \param clientMemSize the maximum number of bytes to read/write * \param ptr the user-provided pointer/offset * \return GL_TRUE if the buffer access is OK, GL_FALSE if the access would * go out of bounds. */ GLboolean _mesa_validate_pbo_access(GLuint dimensions, const struct gl_pixelstore_attrib *pack, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei clientMemSize, const GLvoid *ptr) { /* unsigned, to detect overflow/wrap-around */ uintptr_t start, end, offset, size; /* If no PBO is bound, 'ptr' is a pointer to client memory containing 'clientMemSize' bytes. If a PBO is bound, 'ptr' is an offset into the bound PBO. In that case 'clientMemSize' is ignored: we just use the PBO's size. */ if (!_mesa_is_bufferobj(pack->BufferObj)) { offset = 0; size = clientMemSize; } else { offset = (uintptr_t)ptr; size = pack->BufferObj->Size; /* The ARB_pixel_buffer_object spec says: * "INVALID_OPERATION is generated by ColorTable, ColorSubTable, * ConvolutionFilter2D, ConvolutionFilter1D, SeparableFilter2D, * TexImage1D, TexImage2D, TexImage3D, TexSubImage1D, * TexSubImage2D, TexSubImage3D, and DrawPixels if the current * PIXEL_UNPACK_BUFFER_BINDING_ARB value is non-zero and the data * parameter is not evenly divisible into the number of basic machine * units needed to store in memory a datum indicated by the type * parameter." */ if (type != GL_BITMAP && (offset % _mesa_sizeof_packed_type(type))) return GL_FALSE; } if (size == 0) /* no buffer! */ return GL_FALSE; /* get the offset to the first pixel we'll read/write */ start = _mesa_image_offset(dimensions, pack, width, height, format, type, 0, 0, 0); /* get the offset to just past the last pixel we'll read/write */ end = _mesa_image_offset(dimensions, pack, width, height, format, type, depth-1, height-1, width); start += offset; end += offset; if (start > size) { /* This will catch negative values / wrap-around */ return GL_FALSE; } if (end > size) { /* Image read/write goes beyond end of buffer */ return GL_FALSE; } /* OK! */ return GL_TRUE; } /** * For commands that read from a PBO (glDrawPixels, glTexImage, * glPolygonStipple, etc), if we're reading from a PBO, map it read-only * and return the pointer into the PBO. If we're not reading from a * PBO, return \p src as-is. * If non-null return, must call _mesa_unmap_pbo_source() when done. * * \return NULL if error, else pointer to start of data */ const GLvoid * _mesa_map_pbo_source(struct gl_context *ctx, const struct gl_pixelstore_attrib *unpack, const GLvoid *src) { const GLubyte *buf; if (_mesa_is_bufferobj(unpack->BufferObj)) { /* unpack from PBO */ buf = (GLubyte *) ctx->Driver.MapBufferRange(ctx, 0, unpack->BufferObj->Size, GL_MAP_READ_BIT, unpack->BufferObj); if (!buf) return NULL; buf = ADD_POINTERS(buf, src); } else { /* unpack from normal memory */ buf = src; } return buf; } /** * Combine PBO-read validation and mapping. * If any GL errors are detected, they'll be recorded and NULL returned. * \sa _mesa_validate_pbo_access * \sa _mesa_map_pbo_source * A call to this function should have a matching call to * _mesa_unmap_pbo_source(). */ const GLvoid * _mesa_map_validate_pbo_source(struct gl_context *ctx, GLuint dimensions, const struct gl_pixelstore_attrib *unpack, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei clientMemSize, const GLvoid *ptr, const char *where) { ASSERT(dimensions == 1 || dimensions == 2 || dimensions == 3); if (!_mesa_validate_pbo_access(dimensions, unpack, width, height, depth, format, type, clientMemSize, ptr)) { if (_mesa_is_bufferobj(unpack->BufferObj)) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s(out of bounds PBO access)", where); } else { _mesa_error(ctx, GL_INVALID_OPERATION, "%s(out of bounds access: bufSize (%d) is too small)", where, clientMemSize); } return NULL; } if (!_mesa_is_bufferobj(unpack->BufferObj)) { /* non-PBO access: no further validation to be done */ return ptr; } if (_mesa_bufferobj_mapped(unpack->BufferObj)) { /* buffer is already mapped - that's an error */ _mesa_error(ctx, GL_INVALID_OPERATION, "%s(PBO is mapped)", where); return NULL; } ptr = _mesa_map_pbo_source(ctx, unpack, ptr); return ptr; } /** * Counterpart to _mesa_map_pbo_source() */ void _mesa_unmap_pbo_source(struct gl_context *ctx, const struct gl_pixelstore_attrib *unpack) { ASSERT(unpack != &ctx->Pack); /* catch pack/unpack mismatch */ if (_mesa_is_bufferobj(unpack->BufferObj)) { ctx->Driver.UnmapBuffer(ctx, unpack->BufferObj); } } /** * For commands that write to a PBO (glReadPixels, glGetColorTable, etc), * if we're writing to a PBO, map it write-only and return the pointer * into the PBO. If we're not writing to a PBO, return \p dst as-is. * If non-null return, must call _mesa_unmap_pbo_dest() when done. * * \return NULL if error, else pointer to start of data */ void * _mesa_map_pbo_dest(struct gl_context *ctx, const struct gl_pixelstore_attrib *pack, GLvoid *dest) { void *buf; if (_mesa_is_bufferobj(pack->BufferObj)) { /* pack into PBO */ buf = (GLubyte *) ctx->Driver.MapBufferRange(ctx, 0, pack->BufferObj->Size, GL_MAP_WRITE_BIT, pack->BufferObj); if (!buf) return NULL; buf = ADD_POINTERS(buf, dest); } else { /* pack to normal memory */ buf = dest; } return buf; } /** * Combine PBO-write validation and mapping. * If any GL errors are detected, they'll be recorded and NULL returned. * \sa _mesa_validate_pbo_access * \sa _mesa_map_pbo_dest * A call to this function should have a matching call to * _mesa_unmap_pbo_dest(). */ GLvoid * _mesa_map_validate_pbo_dest(struct gl_context *ctx, GLuint dimensions, const struct gl_pixelstore_attrib *unpack, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, GLsizei clientMemSize, GLvoid *ptr, const char *where) { ASSERT(dimensions == 1 || dimensions == 2 || dimensions == 3); if (!_mesa_validate_pbo_access(dimensions, unpack, width, height, depth, format, type, clientMemSize, ptr)) { if (_mesa_is_bufferobj(unpack->BufferObj)) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s(out of bounds PBO access)", where); } else { _mesa_error(ctx, GL_INVALID_OPERATION, "%s(out of bounds access: bufSize (%d) is too small)", where, clientMemSize); } return NULL; } if (!_mesa_is_bufferobj(unpack->BufferObj)) { /* non-PBO access: no further validation to be done */ return ptr; } if (_mesa_bufferobj_mapped(unpack->BufferObj)) { /* buffer is already mapped - that's an error */ _mesa_error(ctx, GL_INVALID_OPERATION, "%s(PBO is mapped)", where); return NULL; } ptr = _mesa_map_pbo_dest(ctx, unpack, ptr); return ptr; } /** * Counterpart to _mesa_map_pbo_dest() */ void _mesa_unmap_pbo_dest(struct gl_context *ctx, const struct gl_pixelstore_attrib *pack) { ASSERT(pack != &ctx->Unpack); /* catch pack/unpack mismatch */ if (_mesa_is_bufferobj(pack->BufferObj)) { ctx->Driver.UnmapBuffer(ctx, pack->BufferObj); } } /** * Check if an unpack PBO is active prior to fetching a texture image. * If so, do bounds checking and map the buffer into main memory. * Any errors detected will be recorded. * The caller _must_ call _mesa_unmap_teximage_pbo() too! */ const GLvoid * _mesa_validate_pbo_teximage(struct gl_context *ctx, GLuint dimensions, GLsizei width, GLsizei height, GLsizei depth, GLenum format, GLenum type, const GLvoid *pixels, const struct gl_pixelstore_attrib *unpack, const char *funcName) { GLubyte *buf; if (!_mesa_is_bufferobj(unpack->BufferObj)) { /* no PBO */ return pixels; } if (!_mesa_validate_pbo_access(dimensions, unpack, width, height, depth, format, type, INT_MAX, pixels)) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s%uD(invalid PBO access)", funcName, dimensions); return NULL; } buf = (GLubyte *) ctx->Driver.MapBufferRange(ctx, 0, unpack->BufferObj->Size, GL_MAP_READ_BIT, unpack->BufferObj); if (!buf) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s%uD(PBO is mapped)", funcName, dimensions); return NULL; } return ADD_POINTERS(buf, pixels); } /** * Check if an unpack PBO is active prior to fetching a compressed texture * image. * If so, do bounds checking and map the buffer into main memory. * Any errors detected will be recorded. * The caller _must_ call _mesa_unmap_teximage_pbo() too! */ const GLvoid * _mesa_validate_pbo_compressed_teximage(struct gl_context *ctx, GLuint dimensions, GLsizei imageSize, const GLvoid *pixels, const struct gl_pixelstore_attrib *packing, const char *funcName) { GLubyte *buf; if (!_mesa_is_bufferobj(packing->BufferObj)) { /* not using a PBO - return pointer unchanged */ return pixels; } if ((const GLubyte *) pixels + imageSize > ((const GLubyte *) 0) + packing->BufferObj->Size) { /* out of bounds read! */ _mesa_error(ctx, GL_INVALID_OPERATION, "%s%uD(invalid PBO access)", funcName, dimensions); return NULL; } buf = (GLubyte*) ctx->Driver.MapBufferRange(ctx, 0, packing->BufferObj->Size, GL_MAP_READ_BIT, packing->BufferObj); if (!buf) { _mesa_error(ctx, GL_INVALID_OPERATION, "%s%uD(PBO is mapped)", funcName, dimensions); return NULL; } return ADD_POINTERS(buf, pixels); } /** * This function must be called after either of the validate_pbo_*_teximage() * functions. It unmaps the PBO buffer if it was mapped earlier. */ void _mesa_unmap_teximage_pbo(struct gl_context *ctx, const struct gl_pixelstore_attrib *unpack) { if (_mesa_is_bufferobj(unpack->BufferObj)) { ctx->Driver.UnmapBuffer(ctx, unpack->BufferObj); } }