HelperGles.h#

Parent directory (OpenGLES)

Contains utility functions to facilitate tasks to create API objects form assets.

Includes#

  • PVRAssets/Model.h

  • PVRCore/IAssetProvider.h

  • PVRCore/texture/PVRTDecompress.h

  • PVRCore/texture/TextureLoad.h

  • PVRCore/textureio/TGAWriter.h

  • PVRUtils/OpenGLES/ConvertToGlesTypes.h

  • PVRUtils/OpenGLES/ErrorsGles.h

  • PVRUtils/OpenGLES/PBRUtilsGles.h

  • PVRUtils/OpenGLES/ShaderUtilsGles.h

  • PVRUtils/OpenGLES/TextureUtilsGles.h

  • PVRUtils/PVRUtilsTypes.h

  • iterator

Included By#

Namespaces#

Classes#

Functions#

Source Code#

#pragma once
#include "PVRCore/IAssetProvider.h"
#include "PVRCore/texture/PVRTDecompress.h"
#include "PVRUtils/PVRUtilsTypes.h"
#include "PVRAssets/Model.h"
#include "PVRCore/texture/TextureLoad.h"
#include "PVRUtils/OpenGLES/TextureUtilsGles.h"
#include "PVRUtils/OpenGLES/ShaderUtilsGles.h"
#include "PVRUtils/OpenGLES/ConvertToGlesTypes.h"
#include "PVRUtils/OpenGLES/ErrorsGles.h"
#include "PVRCore/textureio/TGAWriter.h"
#include "PVRUtils/OpenGLES/PBRUtilsGles.h"
#include <iterator>

namespace pvr {

namespace utils {

template<typename container, typename val, typename cmp>
size_t insertSorted(container& cont, typename container::iterator begin, typename container::iterator end, const val& item, const cmp& compare)
{
    typename container::iterator it = std::upper_bound(begin, end, item, compare);
    int64_t offset = static_cast<int64_t>(it - begin);
    cont.insert(it, item);
    return static_cast<size_t>(offset);
}

template<typename container, typename val>
size_t insertSorted(container& cont, typename container::iterator begin, typename container::iterator end, const val& item)
{
    return insertSorted(cont, begin, end, item, std::less<val>());
}

template<typename container, typename val>
size_t insertSorted(container& cont, const val& item)
{
    return insertSorted(cont, cont.begin(), cont.end(), item);
}

template<typename container, typename val, typename cmp>
size_t insertSorted(container& cont, const val& item, const cmp& compare)
{
    return insertSorted(cont, cont.begin(), cont.end(), item, compare);
}

template<typename container, typename val, typename cmp>
size_t insertSorted_overwrite(container& cont, typename container::iterator begin, typename container::iterator end, const val& item, const cmp& compare)
{
    typename container::iterator it = std::lower_bound(begin, end, item, compare);
    int64_t offset = static_cast<int64_t>(it - begin);
    if (it != end && !(compare(*it, item) || compare(item, *it))) { *it = item; }
    else
    {
        cont.insert(it, item);
    }
    return static_cast<size_t>(offset);
}

template<typename container, typename val>
size_t insertSorted_overwrite(container& cont, typename container::iterator begin, typename container::iterator end, const val& item)
{
    return insertSorted_overwrite(cont, begin, end, item, std::less<val>());
}

template<typename container, typename val>
size_t insertSorted_overwrite(container& cont, const val& item)
{
    return insertSorted_overwrite(cont, cont.begin(), cont.end(), item);
}

template<typename container, typename val, typename cmp>
size_t insertSorted_overwrite(container& cont, const val& item, const cmp& compare)
{
    return insertSorted_overwrite(cont, cont.begin(), cont.end(), item, compare);
}

inline Api getCurrentGlesVersion()
{
    const char* apistring = (const char*)gl::GetString(GL_VERSION);
    int major, minor;
    int s = sscanf(apistring, "OpenGL ES %d.%d", &major, &minor);
    (void)s;

    if (major == 2) return Api::OpenGLES2;
    if (major == 3)
    {
        if (minor == 0) return Api::OpenGLES3;
        return Api::OpenGLES31;
    }
    throw "";
}

inline bool checkFboStatus()
{
    // check status
    GLenum fboStatus = gl::CheckFramebufferStatus(
#if SC_ENABLED
        GL_FRAMEBUFFER
#else
        GL_DRAW_FRAMEBUFFER
#endif
    );
    switch (fboStatus)
    {
#ifdef GL_FRAMEBUFFER_UNDEFINED
    case GL_FRAMEBUFFER_UNDEFINED:
        Log(LogLevel::Error, "Fbo_::checkFboStatus GL_FRAMEBUFFER_UNDEFINED");
        assertion(0);
        break;
#endif
    case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
        Log(LogLevel::Error, "Fbo_::checkFboStatus GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
        assertion(0);
        break;
    case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
        Log(LogLevel::Error, "Fbo_::checkFboStatus GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
        assertion(0);
        break;
    case GL_FRAMEBUFFER_UNSUPPORTED:
        Log(LogLevel::Error, "Fbo_::checkFboStatus GL_FRAMEBUFFER_UNSUPPORTED");
        assertion(0);
        break;
#ifdef GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE
    case GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
        Log(LogLevel::Error, "Fbo_::checkFboStatus GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE");
        assertion(0);
        break;
#endif
    case GL_FRAMEBUFFER_COMPLETE: return true;
    default:
        Log(LogLevel::Error, "Fbo_::checkFboStatus UNKNOWN ERROR");
        assertion(0);
        break;
    }
    return false;
}

inline void takeScreenshot(const std::string& screenshotFileName, const uint32_t width, const uint32_t height, const uint32_t screenshotScale = 1)
{
    std::vector<unsigned char> pBuffer(width * height * 4);
    gl::ReadPixels(0, 0, static_cast<GLsizei>(width), static_cast<GLsizei>(height), GL_RGBA, GL_UNSIGNED_BYTE, pBuffer.data());

    GLenum err = gl::GetError();

    if (err != GL_NO_ERROR) { Log(LogLevel::Information, "Screenshot was not taken successfully, filename %s.", screenshotFileName.c_str()); }
    else
    {
        uint32_t size = width * height * 4;

        // Switch the red and blue channels to convert to BGRA
        for (uint32_t i = 0; i < size; i += 4)
        {
            const unsigned char tmp = pBuffer[i];
            pBuffer[i] = pBuffer[i + 2];
            pBuffer[i + 2] = tmp;
        }

        Log(LogLevel::Information, "Writing TGA screenshot, filename %s.", screenshotFileName.c_str());
        writeTGA(screenshotFileName.c_str(), width, height, pBuffer.data(), 4, screenshotScale);
    }

    err = gl::GetError();

    if (err != GL_NO_ERROR) { Log(LogLevel::Information, "Screenshot was not taken successfully, filename %s.", screenshotFileName.c_str()); }
}

inline GLuint textureUpload(const IAssetProvider& app, const char* file, pvr::Texture& outTexture, bool isEs2 = false)
{
    outTexture = pvr::textureLoad(*app.getAssetStream(file), pvr::getTextureFormatFromFilename(file));

    auto res = pvr::utils::textureUpload(outTexture, isEs2, true);

    return res.image;
}

inline GLuint textureUpload(const IAssetProvider& app, const std::string& file, pvr::Texture& outTexture, bool isEs2 = false)
{
    return textureUpload(app, file.c_str(), outTexture, isEs2);
}

inline GLuint textureUpload(const IAssetProvider& app, const char* file, bool isEs2 = false)
{
    pvr::Texture tex;
    return textureUpload(app, file, tex, isEs2);
}

inline GLuint textureUpload(const IAssetProvider& app, const std::string& file, bool isEs2 = false) { return textureUpload(app, file.c_str(), isEs2); }

inline TextureUploadResults textureUploadWithResults(const IAssetProvider& app, const char* file, pvr::Texture& outTexture, bool isEs2 = false)
{
    outTexture = pvr::textureLoad(*app.getAssetStream(file), pvr::getTextureFormatFromFilename(file));

    return pvr::utils::textureUpload(outTexture, isEs2, true);
}
inline TextureUploadResults textureUploadWithResults(const IAssetProvider& app, const char* file, bool isEs2 = false)
{
    return pvr::utils::textureUpload(pvr::textureLoad(*app.getAssetStream(file), pvr::getTextureFormatFromFilename(file)), isEs2, true);
}

inline pvr::Texture getTextureData(const IAssetProvider& app, const char* file)
{
    Texture outTexture = pvr::textureLoad(*app.getAssetStream(file), pvr::getTextureFormatFromFilename(file));

    // Is the texture compressed? RGB9E5 is treated as an uncompressed texture in OpenGL/ES so is a special case.
    bool isCompressedFormat =
        (outTexture.getPixelFormat().getPart().High == 0) && (outTexture.getPixelFormat().getPixelTypeId() != static_cast<uint64_t>(CompressedPixelFormat::SharedExponentR9G9B9E5));

    if (isCompressedFormat)
    {
        // Get the texture format for the API
        GLenum glInternalFormat = 0;
        GLenum glFormat = 0;
        GLenum glType = 0;
        GLenum glTypeSize = 0;
        bool unused;

        // Check that the format is a valid format for this API - Doesn't check specifically between OpenGL/ES,
        // it simply gets the values that would be set for a KTX file.
        utils::getOpenGLFormat(outTexture.getPixelFormat(), outTexture.getColorSpace(), outTexture.getChannelType(), glInternalFormat, glFormat, glType, glTypeSize, unused);

        // Handles software decompression of PVRTC textures
        Texture cDecompressedTexture;

        // Check for formats only supported by extensions.
        switch (glInternalFormat)
        {
#ifdef GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG
        case GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG:
#endif
#ifdef GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG
        case GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG:
#endif
#ifdef GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG
        case GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG:
#endif
#ifdef GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG
        case GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG:
#endif
#if defined(GL_COMPRESSED_RGB_PVRTC_2BPPV1_IMG) || defined(GL_COMPRESSED_RGBA_PVRTC_2BPPV1_IMG) || defined(GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG) || \
    defined(GL_COMPRESSED_RGBA_PVRTC_4BPPV1_IMG)

            // Set up the new texture and header.
            TextureHeader cDecompressedHeader(outTexture);

            cDecompressedHeader.setPixelFormat(GeneratePixelType4<'r', 'g', 'b', 'a', 8, 8, 8, 8>::ID);

            cDecompressedHeader.setChannelType(VariableType::UnsignedByteNorm);
            cDecompressedTexture = Texture(cDecompressedHeader);

            // Do decompression, one surface at a time.
            for (uint32_t uiMIPLevel = 0; uiMIPLevel < outTexture.getNumMipMapLevels(); ++uiMIPLevel)
            {
                for (uint32_t uiArray = 0; uiArray < outTexture.getNumArrayMembers(); ++uiArray)
                {
                    for (uint32_t uiFace = 0; uiFace < outTexture.getNumFaces(); ++uiFace)
                    {
                        pvr::PVRTDecompressPVRTC(outTexture.getDataPointer(uiMIPLevel, uiArray, uiFace), (outTexture.getBitsPerPixel() == 2u ? 1u : 0u),
                            outTexture.getWidth(uiMIPLevel), outTexture.getHeight(uiMIPLevel), cDecompressedTexture.getDataPointer(uiMIPLevel, uiArray, uiFace));
                    }
                }
            }
            // Use the decompressed texture instead
            outTexture = cDecompressedTexture;
            break;
#endif
        }
    }
    return outTexture;
}

inline void generateTextureAtlas(
    const IAssetProvider& app, const StringHash* fileNames, Rectanglef* outUVs, uint32_t numTextures, GLuint* outTexture, TextureHeader* outDescriptor, bool isEs2 = false)
{
    struct SortedImage
    {
        uint32_t id;
        pvr::Texture texture;
        uint16_t width;
        uint16_t height;
        uint16_t srcX;
        uint16_t srcY;
        bool hasAlpha;
    };
    std::vector<SortedImage> sortedImage(numTextures);
    struct SortCompare
    {
        bool operator()(const SortedImage& a, const SortedImage& b)
        {
            uint32_t aSize = a.width * a.height;
            uint32_t bSize = b.width * b.height;
            return (aSize > bSize);
        }
    };

    struct Area
    {
        uint32_t x;
        uint32_t y;
        uint32_t w;
        uint32_t h;
        uint32_t size;
        bool isFilled;

        Area* right;
        Area* left;

    private:
        void setSize(uint32_t width, uint32_t height)
        {
            w = width;
            h = height;
            size = width * height;
        }

    public:
        Area(uint32_t width, uint32_t height) : x(0), y(0), isFilled(false), right(NULL), left(NULL) { setSize(width, height); }
        Area() : x(0), y(0), isFilled(false), right(NULL), left(NULL) { setSize(0, 0); }

        Area* insert(uint32_t width, uint32_t height)
        {
            // If this area has branches below it (i.e. is not a leaf) then traverse those.
            // Check the left branch first.
            if (left)
            {
                Area* tempPtr = NULL;
                tempPtr = left->insert(width, height);
                if (tempPtr != NULL) { return tempPtr; }
            }
            // Now check right
            if (right) { return right->insert(width, height); }
            // Already filled!
            if (isFilled) { return NULL; }

            // Too small
            if (size < width * height || w < width || h < height) { return NULL; }

            // Just right!
            if (size == width * height && w == width && h == height)
            {
                isFilled = true;
                return this;
            }
            // Too big. Split up.
            if (size > width * height && w >= width && h >= height)
            {
                // Initializes the children, and sets the left child's coordinates as these don't change.
                left = new Area;
                right = new Area;
                left->x = x;
                left->y = y;

                // --- Splits the current area depending on the size and position of the placed texture.
                // Splits vertically if larger free distance across the texture.
                if ((w - width) > (h - height))
                {
                    left->w = width;
                    left->h = h;

                    right->x = x + width;
                    right->y = y;
                    right->w = w - width;
                    right->h = h;
                }
                // Splits horizontally if larger or equal free distance downwards.
                else
                {
                    left->w = w;
                    left->h = height;

                    right->x = x;
                    right->y = y + height;
                    right->w = w;
                    right->h = h - height;
                }

                // Initializes the child members' size attributes.
                left->size = left->h * left->w;
                right->size = right->h * right->w;

                // Inserts the texture into the left child member.
                return left->insert(width, height);
            }
            // Catch all error return.
            return NULL;
        }

        bool deleteArea()
        {
            if (left != NULL)
            {
                if (left->left != NULL)
                {
                    if (!left->deleteArea()) { return false; }
                    if (!right->deleteArea()) { return false; }
                }
            }
            if (right != NULL)
            {
                if (right->left != NULL)
                {
                    if (!left->deleteArea()) { return false; }
                    if (!right->deleteArea()) { return false; }
                }
            }
            delete right;
            right = NULL;
            delete left;
            left = NULL;
            return true;
        }
    };

    // load the textures
    for (uint32_t i = 0; i < numTextures; ++i)
    {
        sortedImage[i].texture = pvr::utils::getTextureData(app, fileNames[i].c_str());

        sortedImage[i].id = i;
        sortedImage[i].width = static_cast<uint16_t>(sortedImage[i].texture.getWidth());
        sortedImage[i].height = static_cast<uint16_t>(sortedImage[i].texture.getHeight());
        const unsigned char* pixelString = sortedImage[i].texture.getPixelFormat().getPixelTypeChar();
        if (sortedImage[i].texture.getPixelFormat().getPixelTypeId() == static_cast<uint64_t>(pvr::CompressedPixelFormat::PVRTCI_2bpp_RGBA) ||
            sortedImage[i].texture.getPixelFormat().getPixelTypeId() == static_cast<uint64_t>(pvr::CompressedPixelFormat::PVRTCI_4bpp_RGBA) || pixelString[0] == 'a' ||
            pixelString[1] == 'a' || pixelString[2] == 'a' || pixelString[3] == 'a')
        {
            sortedImage[i].hasAlpha = true;
        }
        else
        {
            sortedImage[i].hasAlpha = false;
        }
    }

    pvr::utils::throwOnGlError("generateTextureAtlas Begin");

    std::sort(sortedImage.begin(), sortedImage.end(), SortCompare());
    // find the best width and height
    uint32_t width = 0, height = 0, area = 0;
    uint32_t preferredDim[] = { 8, 16, 32, 64, 128, 256, 512, 1024 };
    const uint32_t atlasPixelBorder = 1;
    const uint32_t totalBorder = atlasPixelBorder * 2;
    uint32_t i = 0;
    // calculate the total area
    for (; i < sortedImage.size(); ++i) { area += (sortedImage[i].width + totalBorder) * (sortedImage[i].height + totalBorder); }
    i = 0;
    while ((preferredDim[i] * preferredDim[i]) < area && i < sizeof(preferredDim) / sizeof(preferredDim[0])) { ++i; }
    if (i >= sizeof(preferredDim) / sizeof(preferredDim[0])) { throw InvalidDataError("Cannot find a best size for texture atlas"); }
    width = height = preferredDim[i];
    float oneOverWidth = 1.f / width;
    float oneOverHeight = 1.f / height;

    Area* head = new Area(width, height);
    Area* pRtrn = NULL;
    Offset3D dstOffset[2];

    // create the out texture store
    ImageStorageFormat outFmt(PixelFormat::RGBA_32323232(), 1, ColorSpace::lRGB, VariableType::Float);
    gl::GenTextures(1, outTexture);
    gl::BindTexture(GL_TEXTURE_2D, *outTexture);

    gl::PixelStorei(GL_UNPACK_ALIGNMENT, 1);

    bool useTexStorage = !isEs2;

    // Get the texture format for the API.
    GLenum glInternalFormat = 0;
    GLenum glFormat = 0;
    GLenum glType = 0;
    GLenum glTypeSize = 0;
    bool unused;

    // Check that the format is a valid format for this API - Doesn't check specifically between OpenGL/ES
    utils::getOpenGLFormat(sortedImage[0].texture.getPixelFormat(), sortedImage[0].texture.getColorSpace(), sortedImage[0].texture.getChannelType(), glInternalFormat, glFormat,
        glType, glTypeSize, unused);

    if (useTexStorage) { gl::TexStorage2D(GL_TEXTURE_2D, 1, glInternalFormat, static_cast<GLsizei>(width), static_cast<GLsizei>(height)); }
    else
    {
        if (isEs2) glInternalFormat = glFormat;
        gl::TexImage2D(GL_TEXTURE_2D, 0, glInternalFormat, static_cast<GLsizei>(width), static_cast<GLsizei>(height), 0, glFormat, glType, NULL);
    }

    pvr::utils::throwOnGlError("generateTextureAtlas Generate output texture");

    for (uint32_t textureIndex = 0; textureIndex < numTextures; ++textureIndex)
    {
        const SortedImage& image = sortedImage[textureIndex];
        pRtrn = head->insert(sortedImage[textureIndex].width + totalBorder, sortedImage[textureIndex].height + totalBorder);
        if (!pRtrn)
        {
            head->deleteArea();
            delete head;
            throw InvalidDataError("ERROR: Not enough room in texture atlas!");
        }
        dstOffset[0].x = static_cast<uint16_t>(pRtrn->x + atlasPixelBorder);
        dstOffset[0].y = static_cast<uint16_t>(pRtrn->y + atlasPixelBorder);
        dstOffset[0].z = 0;

        dstOffset[1].x = static_cast<uint16_t>(dstOffset[0].x + sortedImage[textureIndex].width);
        dstOffset[1].y = static_cast<uint16_t>(dstOffset[0].y + sortedImage[textureIndex].height);
        dstOffset[1].z = 1;

        outUVs[image.id].x = dstOffset[0].x * oneOverWidth;
        outUVs[image.id].y = dstOffset[0].y * oneOverHeight;
        outUVs[image.id].width = sortedImage[textureIndex].width * oneOverWidth;
        outUVs[image.id].height = sortedImage[textureIndex].height * oneOverHeight;

        gl::TexSubImage2D(GL_TEXTURE_2D, 0, static_cast<GLint>(dstOffset[0].x), static_cast<GLint>(dstOffset[0].y), static_cast<GLsizei>(sortedImage[textureIndex].width),
            static_cast<GLsizei>(sortedImage[textureIndex].height), glFormat, glType, image.texture.getDataPointer());
    }
    if (outDescriptor)
    {
        outDescriptor->setWidth(width);
        outDescriptor->setHeight(height);
        outDescriptor->setChannelType(outFmt.dataType);
        outDescriptor->setColorSpace(outFmt.colorSpace);
        outDescriptor->setDepth(1);
        outDescriptor->setPixelFormat(outFmt.format);
    }
#if SC_ENABLED
    gl::Finish();
#else
    if (isEs2) { gl::Finish(); }
    else
    {
        gl::FenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0x00000000);
    }
#endif
    head->deleteArea();
    delete head;

    pvr::utils::throwOnGlError("generateTextureAtlas End");
}

inline void deleteTexturesAndZero(GLuint& texture)
{
    if (texture != 0)
    {
        gl::DeleteTextures(1, &texture);
        texture = 0;
    }
}
template<typename... Args>
inline void deleteTexturesAndZero(GLuint& first, Args&... rest)
{
    deleteTexturesAndZero(first);
    deleteTexturesAndZero(rest...);
}

struct VertexBindings
{
    std::string semanticName; //< effect semantic
    uint16_t binding; //< binding id
};

struct VertexBindings_Name
{
    StringHash semantic; //< effect semantic
    StringHash variableName; //< shader attribute name
};

namespace {
struct VertexAttributeInfoCmp_BindingLess_IndexLess
{
    bool operator()(const VertexAttributeInfoWithBinding& lhs, const VertexAttributeInfoWithBinding& rhs) const
    {
        return lhs.binding < rhs.binding || (lhs.binding == rhs.binding && lhs.index < rhs.index);
    }
};
struct VertexBindingInfoCmp_BindingLess
{
    bool operator()(const VertexInputBindingInfo& lhs, const VertexInputBindingInfo& rhs) const { return lhs.bindingId < rhs.bindingId; }
};

struct VertexBindingInfoPred_BindingLess
{
    bool operator()(uint16_t lhs, const VertexInputBindingInfo& rhs) const { return lhs < rhs.bindingId; }
};
} // namespace

struct VertexConfiguration
{
    PrimitiveTopology topology;
    std::vector<VertexAttributeInfoWithBinding> attributes;
    std::vector<VertexInputBindingInfo> bindings;

    VertexConfiguration& addVertexAttribute(uint16_t bufferBinding, const VertexAttributeInfo& attrib)
    {
        pvr::utils::insertSorted_overwrite(attributes, VertexAttributeInfoWithBinding(attrib, bufferBinding), VertexAttributeInfoCmp_BindingLess_IndexLess());
        return *this;
    }

    VertexConfiguration& addVertexAttributes(uint16_t bufferBinding, const VertexAttributeInfo* attrib, uint32_t numAttributes)
    {
        for (uint32_t i = 0; i < numAttributes; ++i)
        {
            pvr::utils::insertSorted_overwrite(attributes, VertexAttributeInfoWithBinding(attrib[i], bufferBinding), VertexAttributeInfoCmp_BindingLess_IndexLess());
        }
        return *this;
    }

    VertexConfiguration& addVertexAttribute(uint16_t index, uint16_t bufferBinding, const VertexAttributeLayout& layout, const char* attributeName = "")
    {
        pvr::utils::insertSorted_overwrite(attributes, VertexAttributeInfoWithBinding(index, layout.dataType, layout.width, layout.offset, bufferBinding, attributeName),
            VertexAttributeInfoCmp_BindingLess_IndexLess());
        return *this;
    }

    VertexConfiguration& setInputBinding(uint16_t bufferBinding, uint16_t strideInBytes = 0, StepRate stepRate = StepRate::Vertex)
    {
        pvr::utils::insertSorted_overwrite(bindings, VertexInputBindingInfo(bufferBinding, strideInBytes, stepRate), VertexBindingInfoCmp_BindingLess());
        return *this;
    }
};

struct VertexAttributeInfoGles
{
    GLuint index;
    GLuint vboIndex;
    GLuint stride;

    GLenum format;
    GLint size;
    void* offset;
    VertexAttributeInfoGles() : index(0), vboIndex(0), stride(0), format(0), size(0), offset(0) {}
    VertexAttributeInfoGles(const VertexAttributeInfoWithBinding& attr, const VertexInputBindingInfo& bind)
        : index(attr.index), vboIndex(attr.binding), stride(bind.strideInBytes), format(utils::convertToGles(attr.format)), size(attr.width),
          offset(reinterpret_cast<void*>(static_cast<size_t>(attr.offsetInBytes)))
    {}

    void callVertexAttribPtr() { gl::VertexAttribPointer(index, size, format, false, static_cast<GLsizei>(stride), offset); }
};

struct VertexBindingInfoGles
{
    GLuint bindingId; //< buffer binding index
    GLuint stride; //< buffer stride in bytes
    GLenum stepRate;

    VertexBindingInfoGles() : bindingId(0), stride(0), stepRate(0) {}
};

inline VertexConfiguration createInputAssemblyFromMesh(const assets::Mesh& mesh, const VertexBindings* bindingMap, uint16_t numBindings, uint16_t* outNumBuffers = NULL)
{
    VertexConfiguration retval;
    if (outNumBuffers) { *outNumBuffers = 0; }
    int16_t current = 0;
    while (current < numBindings)
    {
        auto attr = mesh.getVertexAttributeByName(bindingMap[current].semanticName.c_str());
        if (attr)
        {
            VertexAttributeLayout layout = attr->getVertexLayout();
            uint32_t stride = mesh.getStride(attr->getDataIndex());
            if (outNumBuffers) { *outNumBuffers = (uint16_t)std::max<uint32_t>(attr->getDataIndex() + 1u, *outNumBuffers); }
            retval.addVertexAttribute(bindingMap[current].binding, static_cast<uint16_t>(attr->getDataIndex()), layout)
                .setInputBinding(static_cast<uint16_t>(attr->getDataIndex()), static_cast<uint16_t>(stride), StepRate::Vertex);
        }
        else
        {
            Log("Could not find Attribute with Semantic %s in the supplied mesh. Will render without binding it, erroneously.", bindingMap[current].semanticName.c_str());
        }
        ++current;
    }
    retval.topology = mesh.getMeshInfo().primitiveType;
    return retval;
}

inline VertexConfiguration createInputAssemblyFromMesh(const assets::Mesh& mesh, const VertexBindings_Name* bindingMap, uint16_t numBindings, uint16_t* outNumBuffers = NULL)
{
    VertexConfiguration retval;
    if (outNumBuffers) { *outNumBuffers = 0; }
    uint16_t current = 0;
    // In this scenario, we will be using our own indices instead of user provided ones, correlating them by names.
    while (current < numBindings)
    {
        auto attr = mesh.getVertexAttributeByName(bindingMap[current].semantic);
        if (attr)
        {
            VertexAttributeLayout layout = attr->getVertexLayout();
            uint32_t stride = mesh.getStride(attr->getDataIndex());

            if (outNumBuffers) { *outNumBuffers = (uint16_t)std::max<uint32_t>(attr->getDataIndex() + 1u, *outNumBuffers); }
            retval.addVertexAttribute(current, (uint16_t)attr->getDataIndex(), layout, bindingMap[current].variableName.c_str())
                .setInputBinding((uint16_t)attr->getDataIndex(), (uint16_t)stride, StepRate::Vertex);
            retval.topology = mesh.getMeshInfo().primitiveType;
        }
        else
        {
            Log("Could not find Attribute with Semantic %s in the supplied mesh. Will render without binding it, erroneously.", bindingMap[current].semantic.c_str());
        }
        ++current;
    }
    return retval;
}

inline void createSingleBuffersFromMesh(const assets::Mesh& mesh, GLuint& outVbo, GLuint& outIbo)
{
    size_t total = 0;
    for (uint32_t i = 0; i < mesh.getNumDataElements(); ++i) { total += mesh.getDataSize(i); }

    gl::GenBuffers(1, &outVbo);
    gl::BindBuffer(GL_ARRAY_BUFFER, outVbo);
    gl::BufferData(GL_ARRAY_BUFFER, static_cast<GLsizeiptr>(total), NULL, GL_STATIC_DRAW);

    size_t current = 0;
    for (uint32_t i = 0; i < mesh.getNumDataElements(); ++i)
    {
        gl::BufferSubData(GL_ARRAY_BUFFER, static_cast<uint32_t>(current), static_cast<uint32_t>(mesh.getDataSize(i)), static_cast<const void*>(mesh.getData(i)));
        current += mesh.getDataSize(i);
    }
    gl::BindBuffer(GL_ARRAY_BUFFER, 0);
    if (mesh.getNumFaces())
    {
        gl::GenBuffers(1, &outIbo);
        gl::BindBuffer(GL_ELEMENT_ARRAY_BUFFER, outIbo);
        gl::BufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast<uint32_t>(mesh.getFaces().getDataSize()), static_cast<const void*>(mesh.getFaces().getData()), GL_STATIC_DRAW);
    }
    else
    {
        outIbo = 0;
    }
}

inline void createMultipleBuffersFromMesh(const assets::Mesh& mesh, std::vector<GLuint>& outVbos, GLuint& outIbo)
{
    outVbos.resize(mesh.getNumDataElements());
    for (uint32_t i = 0; i < mesh.getNumDataElements(); ++i)
    {
        gl::GenBuffers(1, &outVbos[i]);
        gl::BindBuffer(GL_ARRAY_BUFFER, outVbos[i]);
        gl::BufferData(GL_ARRAY_BUFFER, static_cast<uint32_t>(mesh.getDataSize(i)), static_cast<const void*>(mesh.getData(i)), GL_STATIC_DRAW);
    }
    if (mesh.getNumFaces())
    {
        gl::GenBuffers(1, &outIbo);
        gl::BindBuffer(GL_ELEMENT_ARRAY_BUFFER, outIbo);
        gl::BufferData(GL_ELEMENT_ARRAY_BUFFER, static_cast<uint32_t>(mesh.getFaces().getDataSize()), static_cast<const void*>(mesh.getFaces().getData()), GL_STATIC_DRAW);
    }
}

template<typename MeshIterator_, typename VboInsertIterator_, typename IboInsertIterator_>
inline void createSingleBuffersFromMeshes(MeshIterator_ meshIter, MeshIterator_ meshIterEnd, VboInsertIterator_ outVbos, IboInsertIterator_ outIbos)
{
    while (meshIter != meshIterEnd)
    {
        size_t total = 0;
        for (uint32_t ii = 0; ii < meshIter->getNumDataElements(); ++ii) { total += meshIter->getDataSize(ii); }

        GLuint vbo;
        gl::GenBuffers(1, &vbo);
        gl::BindBuffer(GL_ARRAY_BUFFER, vbo);
        gl::BufferData(GL_ARRAY_BUFFER, total, NULL, GL_STATIC_DRAW);

        size_t current = 0;
        for (size_t ii = 0; ii < meshIter->getNumDataElements(); ++ii)
        {
            gl::BufferSubData(GL_ARRAY_BUFFER, static_cast<uint32_t>(current), static_cast<uint32_t>(meshIter->getDataSize(static_cast<uint32_t>(ii))),
                (const void*)meshIter->getData(static_cast<uint32_t>(ii)));
            current += meshIter->getDataSize(static_cast<uint32_t>(ii));
        }

        *outVbos = vbo;
        if (meshIter->getNumFaces())
        {
            GLuint ibo;
            gl::GenBuffers(1, &ibo);
            gl::BindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);
            gl::BufferData(
                GL_ELEMENT_ARRAY_BUFFER, static_cast<uint32_t>(meshIter->getFaces().getDataSize()), reinterpret_cast<const void*>(meshIter->getFaces().getData()), GL_STATIC_DRAW);
            *outIbos = ibo;
        }
        else
        {
            *outIbos = 0;
        }
        ++outVbos;
        ++outIbos;
        ++meshIter;
    }
}

template<typename MeshIterator_, typename VboContainer_, typename IboContainer_>
inline void createSingleBuffersFromMeshes(MeshIterator_ meshIter, MeshIterator_ meshIterEnd, VboContainer_& outVbos, typename VboContainer_::iterator vbos_where,
    IboContainer_& outIbos, typename IboContainer_::iterator ibos_where)
{
    createSingleBuffersFromMeshes(meshIter, meshIterEnd, std::inserter(outVbos, vbos_where), std::inserter(outIbos, ibos_where));
}

template<typename VboInsertIterator_, typename IboInsertIterator_>
inline void createSingleBuffersFromModel(const assets::Model& model, VboInsertIterator_ vbos, IboInsertIterator_ ibos)
{
    createSingleBuffersFromMeshes(model.beginMeshes(), model.endMeshes(), vbos, ibos);
}

template<typename VboContainer_, typename IboContainer_>
inline void appendSingleBuffersFromModel(const assets::Model& model, VboContainer_& vbos, IboContainer_& ibos)
{
    createSingleBuffersFromMeshes(model.beginMeshes(), model.endMeshes(), std::inserter(vbos, vbos.end()), std::inserter(ibos, ibos.end()));
}

inline void create3dPlaneMesh(uint32_t width, uint32_t length, bool vertexAttribTex, bool vertexAttribNormal, assets::Mesh& outMesh)
{
    const float halfWidth = width * .5f;
    const float halfLength = length * .5f;

    glm::vec3 normal[4] = { glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f) };

    glm::vec2 texCoord[4] = {
        glm::vec2(0.0f, 1.0f),
        glm::vec2(0.0f, 0.0f),
        glm::vec2(1.0f, 0.0f),
        glm::vec2(1.0f, 1.0f),
    };

    glm::vec3 pos[4] = { glm::vec3(-halfWidth, 0.0f, -halfLength), glm::vec3(-halfWidth, 0.0f, halfLength), glm::vec3(halfWidth, 0.0f, halfLength),
        glm::vec3(halfWidth, 0.0f, -halfLength) };

    uint32_t indexData[] = { 0, 1, 2, 0, 2, 3 };

    float vertData[32];
    uint32_t offset = 0;

    for (uint32_t i = 0; i < 4; ++i)
    {
        memcpy(&vertData[offset], &pos[i], sizeof(pos[i]));
        offset += 3;
        if (vertexAttribNormal)
        {
            memcpy(&vertData[offset], &normal[i], sizeof(normal[i]));
            offset += 3;
        }
        if (vertexAttribTex)
        {
            memcpy(&vertData[offset], &texCoord[i], sizeof(texCoord[i]));
            offset += 2;
        }
    }

    uint32_t stride = sizeof(glm::vec3) + (vertexAttribNormal ? sizeof(glm::vec3) : 0) + (vertexAttribTex ? sizeof(glm::vec2) : 0);

    outMesh.addData((const uint8_t*)vertData, sizeof(vertData), stride, 0);
    outMesh.addFaces((const uint8_t*)indexData, sizeof(indexData), IndexType::IndexType32Bit);
    offset = 0;
    outMesh.addVertexAttribute("POSITION", DataType::Float32, 3, offset, 0);
    offset += sizeof(float) * 3;
    if (vertexAttribNormal)
    {
        outMesh.addVertexAttribute("NORMAL", DataType::Float32, 3, offset, 0);
        offset += sizeof(float) * 2;
    }
    if (vertexAttribTex) { outMesh.addVertexAttribute("UV0", DataType::Float32, 2, offset, 0); }
    outMesh.setPrimitiveType(PrimitiveTopology::TriangleList);
    outMesh.setStride(0, stride);
    outMesh.setNumFaces(ARRAY_SIZE(indexData) / 3);
    outMesh.setNumVertices(ARRAY_SIZE(pos));
}

class VertexStreamDescription
{
public:
    enum DataSemantic
    {
        POSITION,
        NORMAL,
        TANGENT,
        COLOR,
        UV0,
        UV1,
        BONE_WEIGHTS,
        BONE_INDICES,
    };

    VertexStreamDescription();
    void Add(uint16_t bufferBinding, pvr::DataType dataType, uint8_t width, const std::string& name, DataSemantic semantic);
    const pvr::utils::VertexConfiguration& GetVertexConfig() const;
    bool HasChannel(DataSemantic semantic) const;
    bool RetrieveChannelDescription(DataSemantic semantic, pvr::utils::VertexAttributeInfoWithBinding& out) const;
    uint16_t GetBindingCount() const;
    uint32_t GetBindingVertexStride(uint16_t binding) const;
    uint16_t GetSemanticBinding(DataSemantic semantic) const;

private:
    pvr::utils::VertexConfiguration vertexConfig;
    std::vector<DataSemantic> semantics;
    std::map<uint16_t, uint32_t> currentDataOffset;
    std::map<DataSemantic, uint16_t> semanticBindingLut;
};

// void WriteVertexAttributes(uint8_t* srcData, uint8_t* destData, uint32_t nbVertices, uint32_t vertexStride, uint32_t attributeOffset, uint32_t attributeSize);

void writeVertices(uint8_t* input, uint8_t* output, const VertexStreamDescription& description, VertexStreamDescription::DataSemantic semantic, uint32_t nbVertices);

bool RetrieveTexcoords(const pvr::assets::Model::Mesh& mesh, uint32_t texcoordLayer, std::vector<glm::vec2>& uv);

bool RetrieveColours(const pvr::assets::Model::Mesh& mesh, std::vector<glm::u16vec4>& colours);

bool RetrieveTangents(const pvr::assets::Model::Mesh& mesh, std::vector<glm::vec4>& tangents, bool forceNormalization);

bool RetrieveNormals(const pvr::assets::Model::Mesh& mesh, std::vector<glm::vec3>& normals, bool forceNormalization);

bool RetrievePositions(const pvr::assets::Model::Mesh& mesh, std::vector<glm::vec3>& positions);

bool RetrieveBoneIndicesAndWeights(
    const pvr::assets::Model::Mesh& mesh, uint32_t bonesPerVertex, pvr::DataType indexType, std::vector<uint8_t>& boneIndices, std::vector<float>& boneWeights);

void convertMeshesData(const VertexStreamDescription& description, std::vector<pvr::assets::Model::Mesh>::iterator meshIter, std::vector<pvr::assets::Model::Mesh>::iterator meshIterEnd);

} // namespace utils
} // namespace pvr