void PGlState::setViewport(puint32 x, puint32 y, puint32 width, puint32 height)
{
    // FIXME: We are still at 4K age.
    PASSERT(x < 4096 &&
            y < 4096 &&
            x + width <= 4096 &&
            y + height <= 4096);
    if (x >= 4096 || y >= 4096 || x + width > 4096 || y + height > 4096)
    {
        PLOG_ERRORX(P_LOG_CHANNEL_OPENGLEGL, "invalid viewport values: (%d, %d, %d, %d)", x, y, width, height);

        // FIXME: it is good to let application crash at OpenGL so we don't return here.
    }

    // Cache the viewport value.
    if (m_viewport[0] == x && m_viewport[1] == y && 
        m_viewport[2] == width && m_viewport[3] == height)  
    {
        return ;
    }

    m_viewport[0] = x;
    m_viewport[1] = y;
    m_viewport[2] = width;
    m_viewport[3] = height;

    glViewport(x, y, width, height);
}
void PGlTexture::interpretFormat(PGlTextureFormatEnum format, 
    puint32 &internalFormat,
    puint32 &dataFormat)
{
    switch (format)
    {
        case P_GLTEXTURE_FORMAT_RGBA8888: 
            internalFormat = GL_RGBA;
            dataFormat = GL_RGBA;
            break;
        case P_GLTEXTURE_FORMAT_RGB888: 
            internalFormat = GL_RGB;
            dataFormat = GL_RGB;
            break;
        case P_GLTEXTURE_FORMAT_R8:
            internalFormat = GL_LUMINANCE;
            dataFormat = GL_LUMINANCE;
            break;
        case P_GLTEXTURE_FORMAT_RA88:
            internalFormat = GL_LUMINANCE_ALPHA;
            dataFormat = GL_LUMINANCE_ALPHA;
            break;
        default:
            PASSERT(!"Unsupported texture format");
            PLOG_ERRORX(P_LOG_CHANNEL_OPENGLEGL, "Unsupported texture format");
            break;
    }
}
PSpriteKeyframe::PSpriteKeyframe(pfloat32 time, PAnimationResource *animation)
    : PAbstractKeyframe(time, animation)
{
    PASSERT(animation->animationType() == P_ANIMATION_SPRITE);
    if (animation->animationType() != P_ANIMATION_SPRITE)
    {
        PLOG_ERRORX(P_LOG_CHANNEL_ANIMATION, "PSpriteKeyframe can be only bound to a sprite animation clip");
    }
}
PValueKeyframe::PValueKeyframe(pfloat32 time, PAnimationResource *animation)
    : PAbstractKeyframe(time, animation)
{
    PASSERT(m_animation->animationType() == P_ANIMATION_DATAVALUE);
    if (m_animation->animationType() != P_ANIMATION_DATAVALUE)
    {
        PLOG_ERRORX(P_LOG_CHANNEL_ANIMATION, "PValueKeyframe can be only bound to a datavalue animation clip");
    }
}
PMorphingKeyframe::PMorphingKeyframe(pfloat32 time, PAnimationResource *animation)
    : PAbstractKeyframe(time, animation)
{
    PASSERT(m_animation->animationType() == P_ANIMATION_MORPHING);
    if (m_animation->animationType() != P_ANIMATION_MORPHING)
    {
        PLOG_ERRORX(P_LOG_CHANNEL_ANIMATION, "PMorphingKeyframe can be only bound to a morphing animation clip");
    }
}
PSkeletonKeyframe::PSkeletonKeyframe(pfloat32 time, PAnimationResource *animation)
    : PAbstractKeyframe(time, animation)
{
    PASSERT(m_animation->animationType() == P_ANIMATION_SKELETON);
    if (m_animation->animationType() != P_ANIMATION_SKELETON)
    {
        PLOG_ERRORX(P_LOG_CHANNEL_ANIMATION, "PSkeletonKeyframe can be only bound to a skeleton animation clip");
    }
}
pbool PGlTexture::create(puint8* data, puint32 width, puint32 height, 
    PGlTextureFormatEnum format, pbool mipmap)
{
    // We can't overwrite an existing texture.
    if (m_texture != 0)
    {
        PLOG_WARNINGX(P_LOG_CHANNEL_OPENGLEGL, "Texture can't be overwritten");
        return false;
    }

    glGenTextures(1, &m_texture);

    m_target = GL_TEXTURE_2D;
    m_compressed = false;
    m_mipmap = mipmap;

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(m_target, m_texture);

    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

    // Use the same texture format for internalFormat and format in OpenGL ES 2.0.
    puint32 internalFormat;
    puint32 dataFormat;
    interpretFormat(format, internalFormat, dataFormat);

    // Compute the size of the texture
    switch (format)
    {
        case P_GLTEXTURE_FORMAT_R8: m_bytes = width * height; break;
        case P_GLTEXTURE_FORMAT_RA88: m_bytes = width * height * 2; break;
        case P_GLTEXTURE_FORMAT_RGB888: m_bytes = width * height * 3; break;
        case P_GLTEXTURE_FORMAT_RGBA8888: m_bytes = width * height * 4; break;
        default:
            PASSERT(!"Unsupported OpenGL texture format");
            PLOG_ERRORX(P_LOG_CHANNEL_OPENGLEGL, "Unsupported OpenGL texture format");
            glDeleteTextures(1, &m_texture);
            return false;
    }

    // Create the OpenGL texture object.
    glTexImage2D(GL_TEXTURE_2D, 
                 0, 
                 internalFormat, 
                 width, 
                 height, 
                 0, 
                 dataFormat, 
                 GL_UNSIGNED_BYTE, 
                 data);
    
    setMipmapInternal(m_mipmap);
    
    setFilteringInternal(m_minFiltering, m_magFiltering);

    setWrapModeInternal(m_wrapModeS, m_wrapModeT);

    glBindTexture(m_target, 0);

    pGlErrorCheckAbort();

    return true;
}
pbool PGlTexture::create(puint8 **data, puint32 *width, puint32 *height, PGlTextureFormatEnum format, 
        pbool mipmap)
{
    // We can't overwrite an existing texture.
    if (m_texture != 0)
    {
        PLOG_WARNINGX(P_LOG_CHANNEL_OPENGLEGL, "Texture can't be overwritten");
        return false;
    }

    glGenTextures(1, &m_texture);

    m_target = GL_TEXTURE_CUBE_MAP;
    m_compressed = false;
    m_mipmap = mipmap;

    glActiveTexture(GL_TEXTURE0);
    glBindTexture(m_target, m_texture);

    // Use the same texture format for internalFormat and format in OpenGL ES 2.0.
    puint32 internalFormat;
    puint32 dataFormat;
    interpretFormat(format, internalFormat, dataFormat);

    // Compute the size of the texture
    m_bytes = 0;
    for (pint32 i = 0; i < 6; i++)
    {
        switch (format)
        {
            case P_GLTEXTURE_FORMAT_R8: m_bytes += width[i] * height[i]; break;
            case P_GLTEXTURE_FORMAT_RA88: m_bytes += width[i] * height[i] * 2; break;
            case P_GLTEXTURE_FORMAT_RGB888: m_bytes += width[i] * height[i] * 3; break;
            case P_GLTEXTURE_FORMAT_RGBA8888: m_bytes += width[i] * height[i] * 4; break;
            default:
                PASSERT(!"Unsupported OpenGL texture format");
                PLOG_ERRORX(P_LOG_CHANNEL_OPENGLEGL, "Unsupported OpenGL texture format");
                glDeleteTextures(1, &m_texture);
                return false;
        }
    }

    // Create the OpenGL texture object.
    for (pint32 i = 0; i < 6; i++)
    {
        glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 
                     0, 
                     internalFormat, 
                     width[i], 
                     height[i], 
                     0, 
                     dataFormat, 
                     GL_UNSIGNED_BYTE, 
                     data[i]);
    }
    
    setMipmapInternal(m_mipmap);
    
    setFilteringInternal(m_minFiltering, m_magFiltering);

    if (m_wrapModeS != P_GLTEXTURE_WRAPMODE_CLAMP_TO_EDGE ||
        m_wrapModeT != P_GLTEXTURE_WRAPMODE_CLAMP_TO_EDGE)
    {
        PLOG_WARNINGX(P_LOG_CHANNEL_OPENGLEGL, "Cubemap only supports wrapping of clamping to edge");
        m_wrapModeS = P_GLTEXTURE_WRAPMODE_CLAMP_TO_EDGE;
        m_wrapModeT = P_GLTEXTURE_WRAPMODE_CLAMP_TO_EDGE;
    }
    setWrapModeInternal(m_wrapModeS, m_wrapModeT);

    glBindTexture(m_target, 0);

    // FIXME: how to ensure the completeness of the cubemap.

    pGlErrorCheckAbort();

    return true;
}