/*
* 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 == INT_MAX) ? UINTPTR_MAX : 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;
/* If the size of the image is zero then no pixels are accessed so we
* don't need to check anything else.
*/
if (width == 0 || height == 0 || depth == 0)
return GL_TRUE;
/* 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,
MAP_INTERNAL);
if (!buf)
return NULL;
buf = ADD_POINTERS(buf, src);
}
else {
/* unpack from normal memory */
buf = src;
}
return buf;
}
/**
* Perform PBO validation for read operations with uncompressed textures.
* If any GL errors are detected, false is returned, otherwise returns true.
* \sa _mesa_validate_pbo_access
*/
bool
_mesa_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 false;
}
if (!_mesa_is_bufferobj(unpack->BufferObj)) {
/* non-PBO access: no further validation to be done */
return true;
}
if (_mesa_check_disallowed_mapping(unpack->BufferObj)) {
/* buffer is already mapped - that's an error */
_mesa_error(ctx, GL_INVALID_OPERATION, "%s(PBO is mapped)",
where);
return false;
}
return true;
}
/**
* Perform PBO validation for read operations with compressed textures.
* If any GL errors are detected, false is returned, otherwise returns true.
*/
bool
_mesa_validate_pbo_source_compressed(struct gl_context *ctx, GLuint dimensions,
const struct gl_pixelstore_attrib *unpack,
GLsizei imageSize, const GLvoid *pixels,
const char *where)
{
if (!_mesa_is_bufferobj(unpack->BufferObj)) {
/* not using a PBO */
return true;
}
if ((const GLubyte *) pixels + imageSize >
((const GLubyte *) 0) + unpack->BufferObj->Size) {
/* out of bounds read! */
_mesa_error(ctx, GL_INVALID_OPERATION, "%s(invalid PBO access)",
where);
return false;
}
if (_mesa_check_disallowed_mapping(unpack->BufferObj)) {
/* buffer is already mapped - that's an error */
_mesa_error(ctx, GL_INVALID_OPERATION, "%s(PBO is mapped)",
where);
return false;
}
return true;
}
/**
* Perform PBO-read mapping.
* If any GL errors are detected, they'll be recorded and NULL returned.
* \sa _mesa_validate_pbo_source
* \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)
{
if (!_mesa_validate_pbo_source(ctx, dimensions, unpack,
width, height, depth, format, type,
clientMemSize, ptr, 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, MAP_INTERNAL);
}
}
/**
* 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,
MAP_INTERNAL);
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_check_disallowed_mapping(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, MAP_INTERNAL);
}
}
/**
* 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,
MAP_INTERNAL);
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_validate_pbo_source_compressed(ctx, dimensions, packing,
imageSize, pixels, funcName)) {
/* error is already set during validation */
return NULL;
}
if (!_mesa_is_bufferobj(packing->BufferObj)) {
/* not using a PBO - return pointer unchanged */
return pixels;
}
buf = (GLubyte*) ctx->Driver.MapBufferRange(ctx, 0,
packing->BufferObj->Size,
GL_MAP_READ_BIT,
packing->BufferObj,
MAP_INTERNAL);
/* Validation above already checked that PBO is not mapped, so buffer
* should not be null.
*/
assert(buf);
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, MAP_INTERNAL);
}
}