/**************************************************************************
 *
 * Copyright 2011 Jose Fonseca
 * Copyright 2010 VMware, Inc.
 * Copyright 2004 IBM Corporation
 * 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, sub license,
 * 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 (including the next
 * paragraph) 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 NON-INFRINGEMENT.  IN NO EVENT SHALL
 * AUTHORS,
 * AND/OR THEIR SUPPLIERS 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.
 *
 **************************************************************************/

/*
 * Auxiliary functions to compute the size of array/blob arguments, depending.
 */

#pragma once


#include <string.h>
#include <assert.h>

#include <algorithm>

#include "os.hpp"
#include "glimports.hpp"


// Vertex/element formats
static inline size_t
_gl_type_size(GLenum type, GLint size = 1)
{
    switch (type) {
    case GL_BOOL:
    case GL_BYTE:
    case GL_UNSIGNED_BYTE:
        return size * 1;
    case GL_SHORT:
    case GL_UNSIGNED_SHORT:
    case GL_2_BYTES:
    case GL_HALF_FLOAT:
        return size * 2;
    case GL_3_BYTES:
        return size * 3;
    case GL_INT:
    case GL_UNSIGNED_INT:
    case GL_FLOAT:
    case GL_4_BYTES:
    case GL_FIXED:
        return size * 4;
    case GL_DOUBLE:
        return size * 8;
    case GL_INT_2_10_10_10_REV:
    case GL_INT_10_10_10_2_OES:
    case GL_UNSIGNED_INT_2_10_10_10_REV:
    case GL_UNSIGNED_INT_10_10_10_2_OES:
    case GL_UNSIGNED_INT_10F_11F_11F_REV:
        // packed
        return 4;
    default:
        os::log("apitrace: warning: %s: unknown GLenum 0x%04X\n", __FUNCTION__, type);
        return 0;
    }
}

static inline void
_gl_uniform_size(GLenum type, GLenum &elemType, GLint &numCols, GLint &numRows) {
    numCols = 1;
    numRows = 1;

    switch (type) {
    case GL_BYTE:
    case GL_UNSIGNED_BYTE:
    case GL_SHORT:
    case GL_UNSIGNED_SHORT:
    case GL_HALF_FLOAT:
        elemType = type;
        break;
    case GL_FLOAT:
        elemType = GL_FLOAT;
        break;
    case GL_FLOAT_VEC2:
        elemType = GL_FLOAT;
        numCols = 2;
        break;
    case GL_FLOAT_VEC3:
        elemType = GL_FLOAT;
        numCols = 3;
        break;
    case GL_FLOAT_VEC4:
        elemType = GL_FLOAT;
        numCols = 4;
        break;
    case GL_DOUBLE:
        elemType = GL_DOUBLE;
        break;
    case GL_DOUBLE_VEC2:
        elemType = GL_DOUBLE;
        numCols = 2;
        break;
    case GL_DOUBLE_VEC3:
        elemType = GL_DOUBLE;
        numCols = 3;
        break;
    case GL_DOUBLE_VEC4:
        elemType = GL_DOUBLE;
        numCols = 4;
        break;
    case GL_INT:
        elemType = GL_INT;
        break;
    case GL_INT_VEC2:
        elemType = GL_INT;
        numCols = 2;
        break;
    case GL_INT_VEC3:
        elemType = GL_INT;
        numCols = 3;
        break;
    case GL_INT_VEC4:
        elemType = GL_INT;
        numCols = 4;
        break;
    case GL_UNSIGNED_INT:
        elemType = GL_UNSIGNED_INT;
        break;
    case GL_UNSIGNED_INT_VEC2:
        elemType = GL_UNSIGNED_INT;
        numCols = 2;
        break;
    case GL_UNSIGNED_INT_VEC3:
        elemType = GL_UNSIGNED_INT;
        numCols = 3;
        break;
    case GL_UNSIGNED_INT_VEC4:
        elemType = GL_UNSIGNED_INT;
        numCols = 4;
        break;
    case GL_INT64_ARB:
        elemType = GL_INT64_ARB;
        break;
    case GL_INT64_VEC2_ARB:
        elemType = GL_INT64_ARB;
        numCols = 2;
        break;
    case GL_INT64_VEC3_ARB:
        elemType = GL_INT64_ARB;
        numCols = 3;
        break;
    case GL_INT64_VEC4_ARB:
        elemType = GL_INT64_ARB;
        numCols = 4;
        break;
    case GL_UNSIGNED_INT64_ARB:
        elemType = GL_UNSIGNED_INT64_ARB;
        break;
    case GL_UNSIGNED_INT64_VEC2_ARB:
        elemType = GL_UNSIGNED_INT64_ARB;
        numCols = 2;
        break;
    case GL_UNSIGNED_INT64_VEC3_ARB:
        elemType = GL_UNSIGNED_INT64_ARB;
        numCols = 3;
        break;
    case GL_UNSIGNED_INT64_VEC4_ARB:
        elemType = GL_UNSIGNED_INT64_ARB;
        numCols = 4;
        break;
    case GL_BOOL:
        elemType = GL_BOOL;
        break;
    case GL_BOOL_VEC2:
        elemType = GL_BOOL;
        numCols = 2;
        break;
    case GL_BOOL_VEC3:
        elemType = GL_BOOL;
        numCols = 3;
        break;
    case GL_BOOL_VEC4:
        elemType = GL_BOOL;
        numCols = 4;
        break;
    case GL_FLOAT_MAT2:
        elemType = GL_FLOAT;
        numCols = 2;
        numRows = 2;
        break;
    case GL_FLOAT_MAT3:
        elemType = GL_FLOAT;
        numCols = 3;
        numRows = 3;
        break;
    case GL_FLOAT_MAT4:
        elemType = GL_FLOAT;
        numCols = 4;
        numRows = 4;
        break;
    case GL_FLOAT_MAT2x3:
        elemType = GL_FLOAT;
        numCols = 2;
        numRows = 3;
        break;
    case GL_FLOAT_MAT2x4:
        elemType = GL_FLOAT;
        numCols = 2;
        numRows = 4;
        break;
    case GL_FLOAT_MAT3x2:
        elemType = GL_FLOAT;
        numCols = 3;
        numRows = 2;
        break;
    case GL_FLOAT_MAT3x4:
        elemType = GL_FLOAT;
        numCols = 3;
        numRows = 4;
        break;
    case GL_FLOAT_MAT4x2:
        elemType = GL_FLOAT;
        numCols = 4;
        numRows = 2;
        break;
    case GL_FLOAT_MAT4x3:
        elemType = GL_FLOAT;
        numCols = 4;
        numRows = 3;
        break;
    case GL_DOUBLE_MAT2:
        elemType = GL_DOUBLE;
        numCols = 2;
        numRows = 2;
        break;
    case GL_DOUBLE_MAT3:
        elemType = GL_DOUBLE;
        numCols = 3;
        numRows = 3;
        break;
    case GL_DOUBLE_MAT4:
        elemType = GL_DOUBLE;
        numCols = 4;
        numRows = 4;
        break;
    case GL_DOUBLE_MAT2x3:
        elemType = GL_DOUBLE;
        numCols = 2;
        numRows = 3;
        break;
    case GL_DOUBLE_MAT2x4:
        elemType = GL_DOUBLE;
        numCols = 2;
        numRows = 4;
        break;
    case GL_DOUBLE_MAT3x2:
        elemType = GL_DOUBLE;
        numCols = 3;
        numRows = 2;
        break;
    case GL_DOUBLE_MAT3x4:
        elemType = GL_DOUBLE;
        numCols = 3;
        numRows = 4;
        break;
    case GL_DOUBLE_MAT4x2:
        elemType = GL_DOUBLE;
        numCols = 4;
        numRows = 2;
        break;
    case GL_DOUBLE_MAT4x3:
        elemType = GL_DOUBLE;
        numCols = 4;
        numRows = 3;
        break;
    case GL_SAMPLER_1D:
    case GL_SAMPLER_2D:
    case GL_SAMPLER_3D:
    case GL_SAMPLER_CUBE:
    case GL_SAMPLER_1D_SHADOW:
    case GL_SAMPLER_2D_SHADOW:
    case GL_SAMPLER_1D_ARRAY:
    case GL_SAMPLER_2D_ARRAY:
    case GL_SAMPLER_CUBE_MAP_ARRAY:
    case GL_SAMPLER_1D_ARRAY_SHADOW:
    case GL_SAMPLER_2D_ARRAY_SHADOW:
    case GL_SAMPLER_2D_MULTISAMPLE:
    case GL_SAMPLER_2D_MULTISAMPLE_ARRAY:
    case GL_SAMPLER_CUBE_SHADOW:
    case GL_SAMPLER_CUBE_MAP_ARRAY_SHADOW:
    case GL_SAMPLER_BUFFER:
    case GL_SAMPLER_2D_RECT:
    case GL_SAMPLER_2D_RECT_SHADOW:
    case GL_INT_SAMPLER_1D:
    case GL_INT_SAMPLER_2D:
    case GL_INT_SAMPLER_3D:
    case GL_INT_SAMPLER_CUBE:
    case GL_INT_SAMPLER_1D_ARRAY:
    case GL_INT_SAMPLER_2D_ARRAY:
    case GL_INT_SAMPLER_CUBE_MAP_ARRAY:
    case GL_INT_SAMPLER_2D_MULTISAMPLE:
    case GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
    case GL_INT_SAMPLER_BUFFER:
    case GL_INT_SAMPLER_2D_RECT:
    case GL_UNSIGNED_INT_SAMPLER_1D:
    case GL_UNSIGNED_INT_SAMPLER_2D:
    case GL_UNSIGNED_INT_SAMPLER_3D:
    case GL_UNSIGNED_INT_SAMPLER_CUBE:
    case GL_UNSIGNED_INT_SAMPLER_1D_ARRAY:
    case GL_UNSIGNED_INT_SAMPLER_2D_ARRAY:
    case GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY:
    case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE:
    case GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY:
    case GL_UNSIGNED_INT_SAMPLER_BUFFER:
    case GL_UNSIGNED_INT_SAMPLER_2D_RECT:
    case GL_IMAGE_1D:
    case GL_IMAGE_2D:
    case GL_IMAGE_3D:
    case GL_IMAGE_2D_RECT:
    case GL_IMAGE_CUBE:
    case GL_IMAGE_BUFFER:
    case GL_IMAGE_1D_ARRAY:
    case GL_IMAGE_2D_ARRAY:
    case GL_IMAGE_CUBE_MAP_ARRAY:
    case GL_IMAGE_2D_MULTISAMPLE:
    case GL_IMAGE_2D_MULTISAMPLE_ARRAY:
    case GL_INT_IMAGE_1D:
    case GL_INT_IMAGE_2D:
    case GL_INT_IMAGE_3D:
    case GL_INT_IMAGE_2D_RECT:
    case GL_INT_IMAGE_CUBE:
    case GL_INT_IMAGE_BUFFER:
    case GL_INT_IMAGE_1D_ARRAY:
    case GL_INT_IMAGE_2D_ARRAY:
    case GL_INT_IMAGE_CUBE_MAP_ARRAY:
    case GL_INT_IMAGE_2D_MULTISAMPLE:
    case GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY:
    case GL_UNSIGNED_INT_IMAGE_1D:
    case GL_UNSIGNED_INT_IMAGE_2D:
    case GL_UNSIGNED_INT_IMAGE_3D:
    case GL_UNSIGNED_INT_IMAGE_2D_RECT:
    case GL_UNSIGNED_INT_IMAGE_CUBE:
    case GL_UNSIGNED_INT_IMAGE_BUFFER:
    case GL_UNSIGNED_INT_IMAGE_1D_ARRAY:
    case GL_UNSIGNED_INT_IMAGE_2D_ARRAY:
    case GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY:
    case GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE:
    case GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY:
        elemType = GL_INT;
        break;
    default:
        os::log("apitrace: warning: %s: unknown GLenum 0x%04X\n", __FUNCTION__, type);
        elemType = GL_NONE;
        numCols = 0;
        numRows = 0;
        return;
    }
}
    
static inline size_t
_glArrayPointer_size(GLint size, GLenum type, GLsizei stride, GLsizei count)
{
    if (!count) {
        return 0;
    }

    if (size == GL_BGRA) {
        size = 4; 
    }

    if (size > 4) {
        os::log("apitrace: warning: %s: unexpected size 0x%04X\n", __FUNCTION__, size);
    }

    size_t elementSize = _gl_type_size(type, size);
    if (!stride) {
        stride = (GLsizei)elementSize;
    }

    return stride*(count - 1) + elementSize;
}

#define _glVertexPointer_size(size, type, stride, count) _glArrayPointer_size(size, type, stride, count)
#define _glNormalPointer_size(type, stride, count) _glArrayPointer_size(3, type, stride, count)
#define _glColorPointer_size(size, type, stride, count) _glArrayPointer_size(size, type, stride, count)
#define _glIndexPointer_size(type, stride, count) _glArrayPointer_size(1, type, stride, count)
#define _glTexCoordPointer_size(size, type, stride, count) _glArrayPointer_size(size, type, stride, count)
#define _glEdgeFlagPointer_size(stride, count) _glArrayPointer_size(1, GL_BOOL, stride, count)
#define _glFogCoordPointer_size(type, stride, count) _glArrayPointer_size(1, type, stride, count)
#define _glSecondaryColorPointer_size(size, type, stride, count) _glArrayPointer_size(size, type, stride, count)
#define _glVertexAttribPointer_size(size, type, normalized, stride, count) _glArrayPointer_size(size, type, stride, count)
#define _glVertexAttribPointerARB_size(size, type, normalized, stride, count) _glArrayPointer_size(size, type, stride, count)
#define _glVertexAttribPointerNV_size(size, type, stride, count) _glArrayPointer_size(size, type, stride, count)

/**
 * Same as glGetIntegerv, but passing the result in the return value.
 */
static inline GLint
_glGetInteger(GLenum pname) {
    GLint param = 0;
    _glGetIntegerv(pname, &param);
    return param;
}

static inline GLint
_element_array_buffer_binding(void) {
    return _glGetInteger(GL_ELEMENT_ARRAY_BUFFER_BINDING);
}

static inline GLint
_draw_indirect_buffer_binding(void) {
    return _glGetInteger(GL_DRAW_INDIRECT_BUFFER_BINDING);
}

/**
 * Same as glGetVertexAttribiv, but passing the result in the return value.
 */
static inline GLint
_glGetVertexAttribi(GLuint index, GLenum pname) {
    GLint param = 0;
    _glGetVertexAttribiv(index, pname, &param);
    return param;
}


static inline size_t
_glCallLists_size(GLsizei n, GLenum type)
{
    return n*_gl_type_size(type);
}

static inline size_t
_glMap1d_size(GLenum target, GLint stride, GLint order)
{
    if (order < 1) {
        return 0;
    }

    GLint channels;
    switch (target) {
    case GL_MAP1_INDEX:
    case GL_MAP1_TEXTURE_COORD_1:
        channels = 1;
        break;
    case GL_MAP1_TEXTURE_COORD_2:
        channels = 2;
        break;
    case GL_MAP1_NORMAL:
    case GL_MAP1_TEXTURE_COORD_3:
    case GL_MAP1_VERTEX_3:
        channels = 3;
        break;
    case GL_MAP1_COLOR_4:
    case GL_MAP1_TEXTURE_COORD_4:
    case GL_MAP1_VERTEX_4:
        channels = 4;
        break;
    default:
        os::log("apitrace: warning: %s: unknown GLenum 0x%04X\n", __FUNCTION__, target);
        return 0;
    }

    if (stride < channels) {
        return 0;
    }

    return channels + stride * (order - 1);
}

#define _glMap1f_size _glMap1d_size

static inline size_t
_glMap2d_size(GLenum target, GLint ustride, GLint uorder, GLint vstride, GLint vorder)
{
    if (uorder < 1 || vorder < 1) {
        return 0;
    }

    GLint channels;
    switch (target) {
    case GL_MAP2_INDEX:
    case GL_MAP2_TEXTURE_COORD_1:
        channels = 1;
        break;
    case GL_MAP2_TEXTURE_COORD_2:
        channels = 2;
        break;
    case GL_MAP2_NORMAL:
    case GL_MAP2_TEXTURE_COORD_3:
    case GL_MAP2_VERTEX_3:
        channels = 3;
        break;
    case GL_MAP2_COLOR_4:
    case GL_MAP2_TEXTURE_COORD_4:
    case GL_MAP2_VERTEX_4:
        channels = 4;
        break;
    default:
        os::log("apitrace: warning: %s: unknown GLenum 0x%04X\n", __FUNCTION__, target);
        return 0;
    }

    if (ustride < channels || vstride < channels) {
        return 0;
    }

    return channels + 
           ustride * (uorder - 1) +
           vstride * (vorder - 1);
}

#define _glMap2f_size _glMap2d_size

/**
 * Number of channels in this format.
 *
 * That is, the number of elements per pixel when this format is passed with a
 * to DrawPixels, ReadPixels, TexImage*, TexSubImage*, GetTexImage, etc.
 */
static inline unsigned
_gl_format_channels(GLenum format) {
    switch (format) {
    case GL_COLOR_INDEX:
    case GL_RED:
    case GL_RED_INTEGER:
    case GL_GREEN:
    case GL_GREEN_INTEGER:
    case GL_BLUE:
    case GL_BLUE_INTEGER:
    case GL_ALPHA:
    case GL_ALPHA_INTEGER:
    case GL_INTENSITY:
    case GL_LUMINANCE:
    case GL_LUMINANCE_INTEGER_EXT:
    case GL_DEPTH_COMPONENT:
    case GL_STENCIL_INDEX:
        return 1;
    case GL_DEPTH_STENCIL:
    case GL_LUMINANCE_ALPHA:
    case GL_LUMINANCE_ALPHA_INTEGER_EXT:
    case GL_RG:
    case GL_RG_INTEGER:
    case GL_422_EXT: // (luminance, chrominance)
    case GL_422_REV_EXT: // (luminance, chrominance)
    case GL_422_AVERAGE_EXT: // (luminance, chrominance)
    case GL_422_REV_AVERAGE_EXT: // (luminance, chrominance)
    case GL_HILO_NV: // (hi, lo)
    case GL_DSDT_NV: // (ds, dt)
    case GL_YCBCR_422_APPLE: // (luminance, chroma)
    case GL_RGB_422_APPLE: // (G, B) on even pixels, (G, R) on odd pixels
    case GL_YCRCB_422_SGIX: // (Y, [Cb,Cr])
        return 2;
    case GL_RGB:
    case GL_RGB_INTEGER:
    case GL_BGR:
    case GL_BGR_INTEGER:
    case GL_DSDT_MAG_NV: // (ds, dt, magnitude)
    case GL_YCRCB_444_SGIX: // (Cb, Y, Cr)
        return 3;
    case GL_RGBA:
    case GL_RGBA_INTEGER:
    case GL_BGRA:
    case GL_BGRA_INTEGER:
    case GL_ABGR_EXT:
    case GL_CMYK_EXT:
    case GL_DSDT_MAG_VIB_NV: // (ds, dt, magnitude, vibrance)
        return 4;
    case GL_CMYKA_EXT:
        return 5;
    case GL_FORMAT_SUBSAMPLE_24_24_OML:
    case GL_FORMAT_SUBSAMPLE_244_244_OML:
        // requires UNSIGNED_INT_10_10_10_2, so this value will be ignored
        return 0;
    default:
        os::log("apitrace: warning: %s: unexpected format GLenum 0x%04X\n", __FUNCTION__, format);
        return 0;
    }
}

template<class X>
static inline bool
_is_pot(X x) {
    return (x & (x - 1)) == 0;
}

template<class X, class Y>
static inline X
_align(X x, Y y) {
    return (x + (y - 1)) & ~(y - 1);
}

static inline unsigned
_gl_format_size(GLenum format, GLenum type)
{
    unsigned num_elements = _gl_format_channels(format);

    switch (type) {
    case GL_BITMAP:
        return 1;
    case GL_BYTE:
    case GL_UNSIGNED_BYTE:
        return 8 * num_elements;
    case GL_SHORT:
    case GL_UNSIGNED_SHORT:
    case GL_HALF_FLOAT:
        return 16 * num_elements;
    case GL_INT:
    case GL_UNSIGNED_INT:
    case GL_FLOAT:
        return 32 * num_elements;
    case GL_UNSIGNED_BYTE_3_3_2:
    case GL_UNSIGNED_BYTE_2_3_3_REV:
        return 8;
    case GL_UNSIGNED_SHORT_4_4_4_4:
    case GL_UNSIGNED_SHORT_4_4_4_4_REV:
    case GL_UNSIGNED_SHORT_5_5_5_1:
    case GL_UNSIGNED_SHORT_1_5_5_5_REV:
    case GL_UNSIGNED_SHORT_5_6_5:
    case GL_UNSIGNED_SHORT_5_6_5_REV:
    case GL_UNSIGNED_SHORT_8_8_MESA:
    case GL_UNSIGNED_SHORT_8_8_REV_MESA:
        return 16;
    case GL_UNSIGNED_INT_8_8_8_8:
    case GL_UNSIGNED_INT_8_8_8_8_REV:
    case GL_UNSIGNED_INT_10_10_10_2:
    case GL_UNSIGNED_INT_2_10_10_10_REV:
    case GL_UNSIGNED_INT_24_8:
    case GL_UNSIGNED_INT_10F_11F_11F_REV:
    case GL_UNSIGNED_INT_5_9_9_9_REV:
    case GL_UNSIGNED_INT_S8_S8_8_8_NV:
    case GL_UNSIGNED_INT_8_8_S8_S8_REV_NV:
        return 32;
    case GL_FLOAT_32_UNSIGNED_INT_24_8_REV:
        assert(num_elements == 2);
        return 32 * 2;
    default:
        os::log("apitrace: warning: %s: unexpected type GLenum 0x%04X\n", __FUNCTION__, type);
        return 0;
    }
}

static inline size_t
_glClearBufferData_size(GLenum format, GLenum type)
{
    unsigned bits_per_pixel = _gl_format_size(format, type);
    return (bits_per_pixel + 7)/8;
}

static inline size_t
_gl_image_size(GLenum format, GLenum type, GLsizei width, GLsizei height, GLsizei depth, GLboolean has_unpack_subimage)
{
    unsigned bits_per_pixel = _gl_format_size(format, type);

    GLint alignment = 4;
    GLint row_length = 0;
    GLint image_height = 0;
    GLint skip_rows = 0;
    GLint skip_pixels = 0;
    GLint skip_images = 0;

    _glGetIntegerv(GL_UNPACK_ALIGNMENT, &alignment);
    if (has_unpack_subimage) {
        _glGetIntegerv(GL_UNPACK_ROW_LENGTH,   &row_length);
        _glGetIntegerv(GL_UNPACK_IMAGE_HEIGHT, &image_height);
        _glGetIntegerv(GL_UNPACK_SKIP_ROWS,    &skip_rows);
        _glGetIntegerv(GL_UNPACK_SKIP_PIXELS,  &skip_pixels);
        _glGetIntegerv(GL_UNPACK_SKIP_IMAGES,  &skip_images);
    }

    if (row_length <= 0) {
        row_length = width;
    }

    size_t row_stride = (row_length*bits_per_pixel + 7)/8;

    /*
     * The OpenGL specification states that the unpack alignment should be
     * ignored if the number of bits per element is not 1, 2, 4, or 8 times the
     * number of bits in a GL ubyte, but the matter of fact is that the number
     * of bits per element is always one of those.
     */
    if (_is_pot(alignment)) {
        row_stride = _align(row_stride, alignment);
    }

    if (image_height <= 0) {
        image_height = height;
    }

    size_t image_stride = image_height*row_stride;

    /*
     * We can't just do
     *
     *   size = depth*image_stride
     *
     * here as that could result in reading beyond the end of the buffer when
     * selecting sub-rectangles via GL_UNPACK_SKIP_*.
     */
    size_t size = (width*bits_per_pixel + 7)/8;
    if (height > 1) {
        size += (height - 1)*row_stride;
    }
    if (depth > 1) {
        size += (depth - 1)*image_stride;
    }

    /* XXX: GL_UNPACK_IMAGE_HEIGHT and GL_UNPACK_SKIP_IMAGES should probably
     * not be considered for pixel rectangles. */

    size += (skip_pixels*bits_per_pixel + 7)/8;
    size += skip_rows*row_stride;
    size += skip_images*image_stride;

    return size;
}

// note that can_unpack_subimage() is generated by gltrace.py
#define _glTexImage3D_size(format, type, width, height, depth) _gl_image_size(format, type, width, height, depth, can_unpack_subimage())
#define _glTexImage2D_size(format, type, width, height)        _gl_image_size(format, type, width, height, 1, can_unpack_subimage())
#define _glTexImage1D_size(format, type, width)                _gl_image_size(format, type, width, 1, 1, can_unpack_subimage())

#define _glDrawPixels_size(format, type, width, height) _glTexImage2D_size(format, type, width, height)
#define _glConvolutionFilter1D_size(format, type, width) _glTexImage1D_size(format, type, width)
#define _glConvolutionFilter2D_size(format, type, width, height) _glTexImage2D_size(format, type, width, height)
#define _glColorTable_size(format, type, width) _glTexImage1D_size(format, type, width)
#define _glColorSubTable_size(format, type, count) _glColorTable_size(format, type, count)

#define _glBitmap_size(width, height) _glTexImage2D_size(GL_COLOR_INDEX, GL_BITMAP, width, height)
#define _glPolygonStipple_size() _glBitmap_size(32, 32)

static inline size_t
_glClearBuffer_size(GLenum buffer)
{
    switch (buffer) {
    case GL_COLOR:
    case GL_FRONT:
    case GL_BACK:
    case GL_LEFT:
    case GL_RIGHT:
    case GL_FRONT_AND_BACK:
        return 4;
    case GL_DEPTH:
    case GL_STENCIL:
        return 1;
    default:
        os::log("apitrace: warning: %s: unexpected buffer GLenum 0x%04X\n", __FUNCTION__, buffer);
        return 0;
    }
}

static inline size_t
_glPath_coords_size(GLsizei numCoords, GLenum coordType)
{
    switch (coordType) {
    case GL_BYTE:
        return numCoords * sizeof(GLbyte);
    case GL_UNSIGNED_BYTE:
        return numCoords * sizeof(GLubyte);
    case GL_SHORT:
        return numCoords * sizeof(GLshort);
    case GL_UNSIGNED_SHORT:
        return numCoords * sizeof(GLushort);
    case GL_FLOAT:
        return numCoords * sizeof(GLfloat);
    default:
        return 0;
    }
}

static inline size_t
_glPath_fontName_size(GLenum fontTarget, const void *fontName)
{
    switch (fontTarget) {
    case GL_STANDARD_FONT_NAME_NV:
    case GL_SYSTEM_FONT_NAME_NV:
    case GL_FILE_NAME_NV:
        {
            // Include +1 to copy nul terminator.
            GLsizei size = GLsizei(strlen(reinterpret_cast<const char*>(fontName))+1);
            return size;
        }
    default:
        return 0;
    }
}

static inline size_t
_glPath_chardcodes_size(GLsizei numGlyphs, GLenum type)
{
    GLsizei bytes_per_glyph;
    switch (type) {
    case GL_FLOAT:
    case GL_INT:
        bytes_per_glyph = 4;
        break;
    case GL_BYTE:
        bytes_per_glyph = 1;
        break;
    case GL_SHORT:
        bytes_per_glyph = 2;
        break;
    default:
        return 0;
    }
    return bytes_per_glyph*numGlyphs;
}

static GLsizei floatPerTransformList(GLenum transformType)
{
    switch (transformType) {
    case GL_NONE:
        return 0;
    case GL_TRANSLATE_X_NV:
    case GL_TRANSLATE_Y_NV:
        return 1;
    case GL_TRANSLATE_2D_NV:
        return 2;
    case GL_TRANSLATE_3D_NV:
        return 3;
    case GL_AFFINE_2D_NV:
    case GL_TRANSPOSE_AFFINE_2D_NV:
        return 6;
    case GL_PROJECTIVE_2D_NV:
    case GL_TRANSPOSE_PROJECTIVE_2D_NV:
        return 9;
    case GL_AFFINE_3D_NV:
    case GL_TRANSPOSE_AFFINE_3D_NV:
        return 12;
    case GL_PROJECTIVE_3D_NV:
    case GL_TRANSPOSE_PROJECTIVE_3D_NV:
        return 16;
    default:
        return 0;
    }
}

static inline size_t
_gl_transformType_size(GLenum transformType)
{
    return floatPerTransformList(transformType)*sizeof(GLfloat);
}

static inline size_t
_gl_transformType_size(GLsizei numPaths, GLenum transformType)
{
    return numPaths*floatPerTransformList(transformType)*sizeof(GLfloat);
}

static size_t valuesPerPathParameter(GLenum pname)
{
    switch (pname) {
    case GL_PATH_FILL_MODE_NV:
    case GL_PATH_FILL_MASK_NV:
    case GL_PATH_FILL_COVER_MODE_NV:
    case GL_PATH_STROKE_WIDTH_NV:
    case GL_PATH_INITIAL_END_CAP_NV:
    case GL_PATH_TERMINAL_END_CAP_NV:
    case GL_PATH_JOIN_STYLE_NV:
    case GL_PATH_MITER_LIMIT_NV:
    case GL_PATH_INITIAL_DASH_CAP_NV:
    case GL_PATH_TERMINAL_DASH_CAP_NV:
    case GL_PATH_DASH_OFFSET_NV:
    case GL_PATH_DASH_OFFSET_RESET_NV:
    case GL_PATH_CLIENT_LENGTH_NV:
    case GL_PATH_STROKE_COVER_MODE_NV:
    case GL_PATH_STROKE_MASK_NV:
    case GL_PATH_STROKE_OVERSAMPLE_COUNT_NV:
    case GL_PATH_SAMPLE_QUALITY_NV:
    case GL_PATH_END_CAPS_NV:  // not valid for glGetPathParameter
    case GL_PATH_DASH_CAPS_NV: // not valid for glGetPathParameter
        return 1;
    default:
        return 0;
    }
}

static inline size_t
_gl_PathParameter_size(GLenum pname)
{
    return valuesPerPathParameter(pname);
}

// See RFC-3629 "UTF-8, a transformation format of ISO 10646"
// http://www.rfc-editor.org/rfc/rfc3629.txt
// http://rfc-ref.org/RFC-TEXTS/3629/index.html
static bool
__glPathGetCodePointUTF8(const void *&utf_string,
                         GLuint &code_point)
{
    const GLubyte *p = reinterpret_cast<const GLubyte*>(utf_string);
    GLubyte c0 = p[0];
    if ((c0 & 0x80) == 0x00) {
        // Zero continuation (0 to 127)
        code_point = c0;
        assert(code_point <= 127);
        p += 1;
    } else {
        GLubyte c1 = p[1];
        if ((c1 & 0xC0) != 0x80) {
            // Stop processing the UTF byte sequence early.
            return false;
        }
        if ((c0 & 0xE0) == 0xC0) {
            // One contination (128 to 2047)
            code_point = ((c0 & 0x1F) << 6) | (c1 & 0x3F);
            if (code_point < 128) {
                return false;
            }
            assert(code_point >= 128 && code_point <= 2047);
            p += 2;
        } else {
            GLubyte c2 = p[2];
            if ((c2 & 0xC0) != 0x80) {
                // Stop processing the UTF byte sequence early.
                return false;
            }
            if ((c0 & 0xF0) == 0xE0) {
                // Two continuation (2048 to 55295 and 57344 to 65535)
                code_point = ((c0 & 0x0F) << 12) |
                             ((c1 & 0x3F) << 6) |
                              (c2 & 0x3F);
                // "The definition of UTF-8 prohibits encoding character numbers between
                // U+D800 and U+DFFF, which are reserved for use with the UTF-16
                // encoding form (as surrogate pairs) and do not directly represent
                // characters."
                // 0xD800 = 55,296
                // 0xDFFF = 57,343
                if ((code_point >= 55296) && (code_point <= 57343)) {
                    // Stop processing the UTF byte sequence early.
                    return false;
                }
                if (code_point < 2048) {
                    return false;
                }
                assert(code_point >= 2048 && code_point <= 65535);
                assert(code_point < 55296 || code_point > 57343);
                p += 3;
            } else {
                GLubyte c3 = p[3];
                if ((c3 & 0xC0) != 0x80) {
                    // Stop processing the UTF byte sequence early.
                    return false;
                }
                if ((c0 & 0xF8) == 0xF0) {
                    // Three continuation (65536 to 1114111)
                    code_point = ((c0 & 0x07) << 18) |
                                 ((c1 & 0x3F) << 12) | 
                                 ((c2 & 0x3F) << 6) |
                                  (c3 & 0x3F);
                    if (code_point < 65536 || code_point > 1114111) {
                        return false;
                    }
                    assert(code_point >= 65536 && code_point <= 1114111);
                    p += 4;
                } else {
                    // Skip invalid or restricted encodings.
                    // Stop processing the UTF byte sequence early.
                    return false;
                }
            }
        }
    }
    utf_string = p;
    return true;
}

// See RFC-2781 "UTF-16, a transformation format of ISO 10646"
// http://rfc-ref.org/RFC-TEXTS/2781/index.html
// http://www.rfc-editor.org/rfc/rfc2781.txt
static bool
__glPathGetCodePointUTF16(const void *&utf_string,
                          GLuint &code_point)
{
    // Section 2.2 (Decoding UTF-16) of http://www.rfc-editor.org/rfc/rfc2781.txt
    // "Decoding of a single character from UTF-16 to an ISO 10646 character
    // value proceeds as follows."
    const GLushort *p = reinterpret_cast<const GLushort*>(utf_string);

    // "Let W1 be the next 16-bit integer in the
    // sequence of integers representing the text."
    GLushort W1 = p[0];
    // "1) If W1 < 0xD800 or W1 > 0xDFFF, the character value U is the value
    // of W1. Terminate."
    if ((W1 < 0xDB00) || (W1 > 0xDFFF)) {
        code_point = W1;
        p += 1;
    } else {
        // "2) Determine if W1 is between 0xD800 and 0xDBFF."
        bool between1 = (W1 >= 0xDB00) && (W1 <= 0xDBFF);
        if (!between1) {
            // "If not, the sequence
            // is in error and no valid character can be obtained using W1.
            // Terminate."
            return false;
        }
        // "Let W2 be the (eventual) next integer following W1."
        GLushort W2 = p[1];
        // DOES NOT APPLY because API provides character (not byte) count.
        // "3) If there is no W2 (that is, the sequence ends with W1), [Terminate]"

        // "... or if W2 is not between 0xDC00 and 0xDFFF, the sequence
        // is in error.  Terminate."
        bool between2 = (W2 >= 0xDC00) && (W2 <= 0xDFFF);
        if (!between2) {
            return false;
        }
        // "4) Construct a 20-bit unsigned integer U', taking the 10 low-order
        // bits of W1 as its 10 high-order bits and the 10 low-order bits of
        // W2 as its 10 low-order bits."
        code_point = ((W1 & 0x3FF) << 10) |
                      (W2 & 0x3FF);
        // "5) Add 0x10000 to U' to obtain the character value U. Terminate."
        code_point += 0x10000;
        p += 2;
    }
    utf_string = p;
    return true;
}

static size_t bytesOfSequence(GLsizei count, GLenum type, const GLvoid *sequence)
{
    GLsizei bytes_per_element;
    switch (type) {
    case GL_BYTE:
        bytes_per_element = sizeof(GLbyte);
        break;
    case GL_UNSIGNED_BYTE:
        bytes_per_element = sizeof(GLubyte);
        break;
    case GL_SHORT:
        bytes_per_element = sizeof(GLshort);
        break;
    case GL_UNSIGNED_SHORT:
        bytes_per_element = sizeof(GLushort);
        break;
    case GL_INT:
        bytes_per_element = sizeof(GLint);
        break;
    case GL_UNSIGNED_INT:
        bytes_per_element = sizeof(GLuint);
        break;
    case GL_FLOAT:
        bytes_per_element = sizeof(GLfloat);
        break;
    case GL_2_BYTES:
        bytes_per_element = 2*sizeof(GLubyte);
        break;
    case GL_3_BYTES:
        bytes_per_element = 3*sizeof(GLubyte);
        break;
    case GL_4_BYTES:
        bytes_per_element = 4*sizeof(GLubyte);
        break;
    case GL_UTF8_NV:
        {
            const void *utf_string = sequence;
            for (GLsizei i=0; i<count; i++) {
                GLuint code_point;  // ignored
                bool ok = __glPathGetCodePointUTF8(utf_string, code_point);
                if (!ok) {
                    break;
                }
            }
            const char *start = reinterpret_cast<const char*>(sequence);
            const char *end = reinterpret_cast<const char*>(utf_string);
            return end - start;
        }
    case GL_UTF16_NV:
        {
            const void *utf_string = sequence;
            for (GLsizei i=0; i<count; i++) {
                GLuint code_point;  // ignored
                bool ok = __glPathGetCodePointUTF16(utf_string, code_point);
                if (!ok) {
                    break;
                }
            }
            const char *start = reinterpret_cast<const char*>(sequence);
            const char *end = reinterpret_cast<const char*>(utf_string);
            return end - start;
        }
    default:  // generate INVALID_ENUM
        return 0;
    }
    if (count > 0) {
        return count * bytes_per_element;
    } else {
        return 0;
    }
}

static inline size_t
_gl_Paths_size(GLsizei numPaths, GLenum pathNameType, const GLvoid *paths)
{
    return bytesOfSequence(numPaths, pathNameType, paths);
}

static inline size_t
_gl_PathColorGen_size(GLenum genMode, GLenum colorFormat)
{
    GLsizei coeffsPerComponent;
    switch (genMode) {
    case GL_NONE:
        coeffsPerComponent = 0;
        break;
    case GL_CONSTANT:
        coeffsPerComponent = 1;
        break;
    case GL_OBJECT_LINEAR:
    case GL_PATH_OBJECT_BOUNDING_BOX_NV:
        coeffsPerComponent = 3;
        break;
    case GL_EYE_LINEAR:
        coeffsPerComponent = 4;
        break;
    default:
        return 0;
    }

    GLsizei components;
    switch (colorFormat) {
    case GL_LUMINANCE:
    case GL_ALPHA:
    case GL_INTENSITY:
        components = 1;
        break;
    case GL_LUMINANCE_ALPHA:
        components = 2;
        break;
    case GL_RGB:
        components = 3;
        break;
    case GL_RGBA:
        components = 4;
        break;
    default:
        return 0;
    }

    GLsizei numCoeffs = components * coeffsPerComponent;
    return numCoeffs*sizeof(GLfloat);
}

static inline size_t
_gl_PathTexGen_size(GLenum genMode, GLsizei components)
{
    GLsizei coeffsPerComponent;
    switch (genMode) {
    case GL_NONE:
        return 0;
    case GL_CONSTANT:
        coeffsPerComponent = 1;
        break;
    case GL_OBJECT_LINEAR:
    case GL_PATH_OBJECT_BOUNDING_BOX_NV:
        coeffsPerComponent = 3;
        break;
    case GL_EYE_LINEAR:
        coeffsPerComponent = 4;
        break;
    default:
        return 0;
    }

    if (components < 1 || components > 4) {
        return 0;
    }

    GLsizei numCoeffs = components * coeffsPerComponent;
    return numCoeffs*sizeof(GLfloat);
}

static size_t valuesPerGetPathParameter(GLenum pname)
{
    switch (pname) {
    case GL_PATH_FILL_MODE_NV:
    case GL_PATH_FILL_MASK_NV:
    case GL_PATH_FILL_COVER_MODE_NV:
    case GL_PATH_STROKE_WIDTH_NV:
    case GL_PATH_INITIAL_END_CAP_NV:
    case GL_PATH_TERMINAL_END_CAP_NV:
    case GL_PATH_JOIN_STYLE_NV:
    case GL_PATH_MITER_LIMIT_NV:
    case GL_PATH_INITIAL_DASH_CAP_NV:
    case GL_PATH_TERMINAL_DASH_CAP_NV:
    case GL_PATH_DASH_OFFSET_NV:
    case GL_PATH_DASH_OFFSET_RESET_NV:
    case GL_PATH_CLIENT_LENGTH_NV:
    case GL_PATH_STROKE_COVER_MODE_NV:
    case GL_PATH_STROKE_MASK_NV:
    case GL_PATH_STROKE_BOUND_NV:
    case GL_PATH_STROKE_OVERSAMPLE_COUNT_NV:
    case GL_PATH_SAMPLE_QUALITY_NV:
        return 1;
    default:
        return 0;
    }
}

static inline size_t
_gl_GetPathParameter_size(GLenum pname)
{
    return valuesPerGetPathParameter(pname);
}

static inline size_t
_gl_GetPathSpacing(GLsizei numPaths, GLenum transformType)
{
    switch (transformType) {
    case GL_TRANSLATE_X_NV:
        return (numPaths-1)*1;
    case GL_TRANSLATE_2D_NV:
        return (numPaths-1)*2;
    default:
        return 0;
    }
}

/**
 * Helper function for determining the string lengths for glShaderSource and
 * glShaderSourceARB, which is a tad too complex to inline in the specs.
 */
template<class Char>
static inline size_t
_glShaderSource_length(const Char * const * string, const GLint *length, GLsizei index)
{
    if (length != NULL && length[index] >= 0) {
        return (size_t)length[index];
    } else {
        return strlen(string[index]);
    }
}

/**
 * Helper function for determining the string lengths for glGetDebugMessageLog*.
 */
template<class Char>
static inline size_t
_glGetDebugMessageLog_length(const Char * string, const GLsizei *lengths, GLuint count)
{
    size_t size = 0;
    GLuint index;
    if (lengths) {
        for (index = 0; index < count; ++index) {
            size += lengths[index];
        }
    } else {
        for (index = 0; index < count; ++index) {
            size += strlen(&string[size]) + 1;
        }
    }
    if (size) {
        // Remove the last null terminator
        --size;
    }
    return size;
}
