
/*
 * Copyright 2011 Google Inc.
 *
 * Use of this source code is governed by a BSD-style license that can be
 * found in the LICENSE file.
 */


#include "GrTypes.h"
#include "GrGLInterface.h"
#include "GrGLDefines.h"

#include <stdio.h>

#if GR_GL_PER_GL_FUNC_CALLBACK
namespace {
void GrGLDefaultInterfaceCallback(const GrGLInterface*) {}
}
#endif

GrGLVersion GrGLGetVersionFromString(const char* versionString) {
    if (NULL == versionString) {
        GrAssert(!"NULL GL version string.");
        return 0;
    }

    int major, minor;

    int n = sscanf(versionString, "%d.%d", &major, &minor);
    if (2 == n) {
        return GR_GL_VER(major, minor);
    }

    char profile[2];
    n = sscanf(versionString, "OpenGL ES-%c%c %d.%d", profile, profile+1,
               &major, &minor);
    if (4 == n) {
        return GR_GL_VER(major, minor);
    }
    
    n = sscanf(versionString, "OpenGL ES %d.%d", &major, &minor);
    if (2 == n) {
        return GR_GL_VER(major, minor);
    }

    return 0;
}

GrGLSLVersion GrGLGetGLSLVersionFromString(const char* versionString) {
    if (NULL == versionString) {
        GrAssert(!"NULL GLSL version string.");
        return 0;
    }

    int major, minor;

    int n = sscanf(versionString, "%d.%d", &major, &minor);
    if (2 == n) {
        return GR_GLSL_VER(major, minor);
    }
    
    n = sscanf(versionString, "OpenGL ES GLSL ES %d.%d", &major, &minor);
    if (2 == n) {
        return GR_GLSL_VER(major, minor);
    }
    return 0;
}

bool GrGLHasExtensionFromString(const char* ext, const char* extensionString) {
    int extLength = strlen(ext);

    while (true) {
        int n = strcspn(extensionString, " ");
        if (n == extLength && 0 == strncmp(ext, extensionString, n)) {
            return true;
        }
        if (0 == extensionString[n]) {
            return false;
        }
        extensionString += n+1;
    }

    return false;
}

bool GrGLHasExtension(const GrGLInterface* gl, const char* ext) {
    const GrGLubyte* glstr;
    GR_GL_CALL_RET(gl, glstr, GetString(GR_GL_EXTENSIONS));
    return GrGLHasExtensionFromString(ext, (const char*) glstr);
}

GrGLVersion GrGLGetVersion(const GrGLInterface* gl) {
    const GrGLubyte* v;
    GR_GL_CALL_RET(gl, v, GetString(GR_GL_VERSION));
    return GrGLGetVersionFromString((const char*) v);
}

GrGLSLVersion GrGLGetGLSLVersion(const GrGLInterface* gl) {
    const GrGLubyte* v;
    GR_GL_CALL_RET(gl, v, GetString(GR_GL_SHADING_LANGUAGE_VERSION));
    return GrGLGetGLSLVersionFromString((const char*) v);
}

GrGLInterface::GrGLInterface() {
    fBindingsExported = (GrGLBinding)0;

    fActiveTexture = NULL;
    fAttachShader = NULL;
    fBeginQuery = NULL;
    fBindAttribLocation = NULL;
    fBindBuffer = NULL;
    fBindFragDataLocation = NULL;
    fBindTexture = NULL;
    fBlendColor = NULL;
    fBlendFunc = NULL;
    fBufferData = NULL;
    fBufferSubData = NULL;
    fClear = NULL;
    fClearColor = NULL;
    fClearStencil = NULL;
    fColorMask = NULL;
    fColorPointer = NULL;
    fCompileShader = NULL;
    fCompressedTexImage2D = NULL;
    fCreateProgram = NULL;
    fCreateShader = NULL;
    fCullFace = NULL;
    fDeleteBuffers = NULL;
    fDeleteProgram = NULL;
    fDeleteQueries = NULL;
    fDeleteShader = NULL;
    fDeleteTextures = NULL;
    fDepthMask = NULL;
    fDisable = NULL;
    fDisableVertexAttribArray = NULL;
    fDrawArrays = NULL;
    fDrawBuffer = NULL;
    fDrawBuffers = NULL;
    fDrawElements = NULL;
    fEndQuery = NULL;
    fFinish = NULL;
    fFlush = NULL;
    fEnable = NULL;
    fEnableVertexAttribArray = NULL;
    fFrontFace = NULL;
    fGenBuffers = NULL;
    fGenQueries = NULL;
    fGenTextures = NULL;
    fGetBufferParameteriv = NULL;
    fGetError = NULL;
    fGetIntegerv = NULL;
    fGetQueryiv = NULL;
    fGetQueryObjecti64v = NULL;
    fGetQueryObjectiv = NULL;
    fGetQueryObjectui64v = NULL;
    fGetQueryObjectuiv = NULL;
    fGetProgramInfoLog = NULL;
    fGetProgramiv = NULL;
    fGetShaderInfoLog = NULL;
    fGetShaderiv = NULL;
    fGetString = NULL;
    fGetTexLevelParameteriv = NULL;
    fGetUniformLocation = NULL;
    fLineWidth = NULL;
    fLinkProgram = NULL;
    fPixelStorei = NULL;
    fQueryCounter = NULL;
    fReadBuffer = NULL;
    fReadPixels = NULL;
    fScissor = NULL;
    fShaderSource = NULL;
    fStencilFunc = NULL;
    fStencilFuncSeparate = NULL;
    fStencilMask = NULL;
    fStencilMaskSeparate = NULL;
    fStencilOp = NULL;
    fStencilOpSeparate = NULL;
    fTexImage2D = NULL;
    fTexParameteri = NULL;
    fTexStorage2D = NULL;
    fTexSubImage2D = NULL;
    fUniform1f = NULL;
    fUniform1i = NULL;
    fUniform1fv = NULL;
    fUniform1iv = NULL;
    fUniform2f = NULL;
    fUniform2i = NULL;
    fUniform2fv = NULL;
    fUniform2iv = NULL;
    fUniform3f = NULL;
    fUniform3i = NULL;
    fUniform3fv = NULL;
    fUniform3iv = NULL;
    fUniform4f = NULL;
    fUniform4i = NULL;
    fUniform4fv = NULL;
    fUniform4iv = NULL;
    fUniformMatrix2fv = NULL;
    fUniformMatrix3fv = NULL;
    fUniformMatrix4fv = NULL;
    fUseProgram = NULL;
    fVertexAttrib4fv = NULL;
    fVertexAttribPointer = NULL;
    fViewport = NULL;
    fBindFramebuffer = NULL;
    fBindRenderbuffer = NULL;
    fCheckFramebufferStatus = NULL;
    fDeleteFramebuffers = NULL;
    fDeleteRenderbuffers = NULL;
    fFramebufferRenderbuffer = NULL;
    fFramebufferTexture2D = NULL;
    fGenFramebuffers = NULL;
    fGenRenderbuffers = NULL;
    fGetFramebufferAttachmentParameteriv = NULL;
    fGetRenderbufferParameteriv = NULL;
    fRenderbufferStorage = NULL;
    fRenderbufferStorageMultisample = NULL;
    fBlitFramebuffer = NULL;
    fResolveMultisampleFramebuffer = NULL;
    fMapBuffer = NULL;
    fUnmapBuffer = NULL;
    fBindFragDataLocationIndexed = NULL;

#if GR_GL_PER_GL_FUNC_CALLBACK
    fCallback = GrGLDefaultInterfaceCallback;
    fCallbackData = 0;
#endif
}

bool GrGLInterface::validate() const {

    bool isDesktop = this->supportsDesktop();

    bool isES2 = this->supportsES2();
    
    if (isDesktop == isES2) {
        // must have one, don't support both in same interface
        return false;
    }

    // functions that are always required
    if (NULL == fActiveTexture ||
        NULL == fAttachShader ||
        NULL == fBindAttribLocation ||
        NULL == fBindBuffer ||
        NULL == fBindTexture ||
        NULL == fBlendFunc ||
        NULL == fBufferData ||
        NULL == fBufferSubData ||
        NULL == fClear ||
        NULL == fClearColor ||
        NULL == fClearStencil ||
        NULL == fColorMask ||
        NULL == fCompileShader ||
        NULL == fCreateProgram ||
        NULL == fCreateShader ||
        NULL == fCullFace ||
        NULL == fDeleteBuffers ||
        NULL == fDeleteProgram ||
        NULL == fDeleteShader ||
        NULL == fDeleteTextures ||
        NULL == fDepthMask ||
        NULL == fDisable ||
        NULL == fDisableVertexAttribArray ||
        NULL == fDrawArrays ||
        NULL == fDrawElements ||
        NULL == fEnable ||
        NULL == fEnableVertexAttribArray ||
        NULL == fFrontFace ||
        NULL == fGenBuffers ||
        NULL == fGenTextures ||
        NULL == fGetBufferParameteriv ||
        NULL == fGetError ||
        NULL == fGetIntegerv ||
        NULL == fGetProgramInfoLog ||
        NULL == fGetProgramiv ||
        NULL == fGetShaderInfoLog ||
        NULL == fGetShaderiv ||
        NULL == fGetString ||
        NULL == fGetUniformLocation ||
        NULL == fLinkProgram ||
        NULL == fPixelStorei ||
        NULL == fReadPixels ||
        NULL == fScissor ||
        NULL == fShaderSource ||
        NULL == fStencilFunc ||
        NULL == fStencilMask ||
        NULL == fStencilOp ||
        NULL == fTexImage2D ||
        NULL == fTexParameteri ||
        NULL == fTexSubImage2D ||
        NULL == fUniform1f ||
        NULL == fUniform1i ||
        NULL == fUniform1fv ||
        NULL == fUniform1iv ||
        NULL == fUniform2f ||
        NULL == fUniform2i ||
        NULL == fUniform2fv ||
        NULL == fUniform2iv ||
        NULL == fUniform3f ||
        NULL == fUniform3i ||
        NULL == fUniform3fv ||
        NULL == fUniform3iv ||
        NULL == fUniform4f ||
        NULL == fUniform4i ||
        NULL == fUniform4fv ||
        NULL == fUniform4iv ||
        NULL == fUniformMatrix2fv ||
        NULL == fUniformMatrix3fv ||
        NULL == fUniformMatrix4fv ||
        NULL == fUseProgram ||
        NULL == fVertexAttrib4fv ||
        NULL == fVertexAttribPointer ||
        NULL == fViewport ||
        NULL == fBindFramebuffer ||
        NULL == fBindRenderbuffer ||
        NULL == fCheckFramebufferStatus ||
        NULL == fDeleteFramebuffers ||
        NULL == fDeleteRenderbuffers ||
        NULL == fFinish ||
        NULL == fFlush ||
        NULL == fFramebufferRenderbuffer ||
        NULL == fFramebufferTexture2D ||
        NULL == fGetFramebufferAttachmentParameteriv ||
        NULL == fGetRenderbufferParameteriv ||
        NULL == fGenFramebuffers ||
        NULL == fGenRenderbuffers ||
        NULL == fRenderbufferStorage) {
        return false;
    }

    const char* ext;
    GrGLVersion glVer = GrGLGetVersion(this);
    ext = (const char*)fGetString(GR_GL_EXTENSIONS);

    // Now check that baseline ES/Desktop fns not covered above are present
    // and that we have fn pointers for any advertised extensions that we will
    // try to use.

    // these functions are part of ES2, we assume they are available
    // On the desktop we assume they are available if the extension
    // is present or GL version is high enough.
    if ((kES2_GrGLBinding & fBindingsExported)) {
        if (NULL == fBlendColor ||
            NULL == fStencilFuncSeparate ||
            NULL == fStencilMaskSeparate ||
            NULL == fStencilOpSeparate) {
            return false;
        }
    } else if (kDesktop_GrGLBinding == fBindingsExported) {
        if (glVer >= GR_GL_VER(2,0)) {
            if (NULL == fStencilFuncSeparate ||
                NULL == fStencilMaskSeparate ||
                NULL == fStencilOpSeparate) {
                return false;
            }
        }
        if (glVer >= GR_GL_VER(3,0) && NULL == fBindFragDataLocation) {
            return false;
        }
        if (glVer >= GR_GL_VER(2,0) ||
            GrGLHasExtensionFromString("GL_ARB_draw_buffers", ext)) {
            if (NULL == fDrawBuffers) {
                return false;
            }
        }
        if (glVer >= GR_GL_VER(1,4) ||
            GrGLHasExtensionFromString("GL_EXT_blend_color", ext)) {
            if (NULL == fBlendColor) {
                return false;
            }
        }
        if (glVer >= GR_GL_VER(1,5) ||
            GrGLHasExtensionFromString("GL_ARB_occlusion_query", ext)) {
            if (NULL == fGenQueries ||
                NULL == fDeleteQueries ||
                NULL == fBeginQuery ||
                NULL == fEndQuery ||
                NULL == fGetQueryiv ||
                NULL == fGetQueryObjectiv ||
                NULL == fGetQueryObjectuiv) {
                return false;
            }
        }
        if (glVer >= GR_GL_VER(3,3) ||
            GrGLHasExtensionFromString("GL_ARB_timer_query", ext) ||
            GrGLHasExtensionFromString("GL_EXT_timer_query", ext)) {
            if (NULL == fGetQueryObjecti64v ||
                NULL == fGetQueryObjectui64v) {
                return false;
            }
        }
        if (glVer >= GR_GL_VER(3,3) ||
            GrGLHasExtensionFromString("GL_ARB_timer_query", ext)) {
            if (NULL == fQueryCounter) {
                return false;
            }
        }
    }

    // optional function on desktop before 1.3
    if (kDesktop_GrGLBinding != fBindingsExported ||
        (glVer >= GR_GL_VER(1,3) ||
        GrGLHasExtensionFromString("GL_ARB_texture_compression", ext))) {
        if (NULL == fCompressedTexImage2D) {
            return false;
        }
    }

    // part of desktop GL, but not ES
    if (kDesktop_GrGLBinding == fBindingsExported &&
        (NULL == fLineWidth ||
         NULL == fGetTexLevelParameteriv ||
         NULL == fDrawBuffer ||
         NULL == fReadBuffer)) {
        return false;
    }

    // GL_EXT_texture_storage is part of desktop 4.2
    // There is a desktop ARB extension and an ES+desktop EXT extension
    if ((kDesktop_GrGLBinding == fBindingsExported &&
         (glVer >= GR_GL_VER(4,2)) ||
          GrGLHasExtensionFromString("GL_ARB_texture_storage", ext)) ||
        GrGLHasExtensionFromString("GL_EXT_texture_storage", ext)) {
    }

    // FBO MSAA
    if (kDesktop_GrGLBinding == fBindingsExported) {
        // GL 3.0 and the ARB extension have multisample + blit
        if (glVer >= GR_GL_VER(3,0) || GrGLHasExtensionFromString("GL_ARB_framebuffer_object", ext)) {
            if (NULL == fRenderbufferStorageMultisample ||
                NULL == fBlitFramebuffer) {
                return false;
            }
        } else {
            if (GrGLHasExtensionFromString("GL_EXT_framebuffer_blit", ext) &&
                NULL == fBlitFramebuffer) {
                return false;
            }
            if (GrGLHasExtensionFromString("GL_EXT_framebuffer_multisample", ext) &&
                NULL == fRenderbufferStorageMultisample) {
                return false;
            }
        }
    } else {
        if (GrGLHasExtensionFromString("GL_CHROMIUM_framebuffer_multisample", ext)) {
            if (NULL == fRenderbufferStorageMultisample ||
                NULL == fBlitFramebuffer) {
                return false;
            }
        }
        if (GrGLHasExtensionFromString("GL_APPLE_framebuffer_multisample", ext)) {
            if (NULL == fRenderbufferStorageMultisample ||
                NULL == fResolveMultisampleFramebuffer) {
                return false;
            }
        }
    }

    // On ES buffer mapping is an extension. On Desktop
    // buffer mapping was part of original VBO extension
    // which we require.
    if (kDesktop_GrGLBinding == fBindingsExported  || 
        GrGLHasExtensionFromString("GL_OES_mapbuffer", ext)) {
        if (NULL == fMapBuffer ||
            NULL == fUnmapBuffer) {
            return false;
        }
    }

    // Dual source blending
    if (kDesktop_GrGLBinding == fBindingsExported  &&
        (glVer >= GR_GL_VER(3,3) || 
         GrGLHasExtensionFromString("GL_ARB_blend_func_extended", ext))) {
        if (NULL == fBindFragDataLocationIndexed) {
            return false;
        }
    }

    return true;
}

