view gldragon.cpp @ 55:bf73a2a70ec7

Add OpenGL error checking.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 06 Dec 2019 23:29:26 +0200
parents 73fa5fb437a0
children 3f635b7ea2cf
line wrap: on
line source

//
// GLDragon - OpenGL PLY model viewer / simple benchmark
// Programmed and designed by Matti 'ccr' Hämäläinen <ccr@tnsp.org>
// (C) Copyright 2019 Tecnic Software productions (TNSP)
//
// See file "COPYING" for license information.
//
// Originally based on 'glxdragon' Copyright (c) 2009, Thomas Trummer
//
#include <SDL.h>
#include <SDL_opengl.h>
#include <GL/glu.h>

#include "dmutil.h"
#include "dmmodel.h"


/* Default settings etc. constants
 */
#define SET_DEF_WIDTH        1280
#define SET_DEF_HEIGHT       960
#define SET_FRAMES           (180)
#define SET_MAX_SHADER_SIZE  (128 * 1024)


/* Options
 */
bool   optUseShaders = false;
int    optWidth = SET_DEF_WIDTH,
       optHeight = SET_DEF_HEIGHT,
       optVSyncMode = 1;


/* Globals
 */
SDL_Window *dmWindow = NULL;
SDL_GLContext dmGLContext = NULL;


/* Helpers
 */
bool dmGLCheckErrors(void)
{
    bool ok = true;
    GLenum err;
    while ((err = glGetError()) != GL_NO_ERROR)
    {
        dmError("OpenGL error code: 0x%x (%d)\n", err);
        ok = false;
    }
    return ok;
}


#ifdef GL_GLEXT_PROTOTYPES
#define DM_GLEXT_INIT(extproctype, extprocname) /* stub */
#else
#define DM_GLEXT_INIT(extproctype, extprocname) extproctype extprocname = NULL;
#include "dmglexts.h"
#undef DM_GLEXT_INIT


void dmGLCheckExtension(const std::string &name, bool &status)
{
    if (!SDL_GL_ExtensionSupported(name.c_str()))
    {
        status = false;
        dmMsg(" - '%s' NOT supported.\n",
            name.c_str());
    }
}


void * dmGLGetProcAddr(const std::string &name)
{
    void *ptr = SDL_GL_GetProcAddress(name.c_str());

    if (ptr == NULL)
        dmMsg(" - '%s' NOT supported.\n");

    return ptr;
}


void * dmGLExtInit(const std::string &name, bool &status)
{
    void *ptr;
    bool ok =
        (ptr = dmGLGetProcAddr(name)) != NULL ||
        (ptr = dmGLGetProcAddr(name + "EXT")) != NULL ||
        (ptr = dmGLGetProcAddr(name + "ARB")) != NULL;

    if (!ok)
        status = false;

    return ptr;
}
#endif


bool dmInitGLExtensions(void)
{
    bool status = true;
    dmMsg("Checking for required OpenGL extensions ..\n");

#ifndef GL_GLEXT_PROTOTYPES
    dmGLCheckExtension("GL_ARB_shader_objects", status);
    dmGLCheckExtension("GL_ARB_shading_language_100", status);
    dmGLCheckExtension("GL_ARB_vertex_shader", status);
    dmGLCheckExtension("GL_ARB_fragment_shader", status);
    if (!status)
    {
        dmError("One or more of the required OpenGL extensions not supported.\n");
        return false;
    }

#define DM_GLEXT_INIT(extproctype, extprocname) \
    extprocname = (extproctype) dmGLExtInit(#extprocname, status);
#include "dmglexts.h"
#endif

    return status;
}


bool dmInitSDLGL(const int width, const int height, const char *title)
{
    int ret;
    std::string msg;

    // Attempt to initialize libSDL
    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_EVENTS) != 0)
    {
        dmError("Unable to initialize SDL: %s\n",
            SDL_GetError());
        return false;
    }

    // Set GL attributes
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 2);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 1);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);

    SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
    SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8);

    SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
    //SDL_GL_SetAttribute(SDL_GL_BUFFER_SIZE, 32);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

    // Attempt to create a window
    if ((dmWindow = SDL_CreateWindow(title,
            SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
            width, height,
            SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE)) == NULL)
    {
        dmError("Could not create SDL window: %s",
            SDL_GetError());
        return false;
    }

    if ((dmGLContext = SDL_GL_CreateContext(dmWindow)) == NULL)
    {
        dmError("Unable to create SDL OpenGL context: %s\n",
            SDL_GetError());
        return false;
    }

    // Check if we want to attempt to use vsync
    switch (optVSyncMode)
    {
        case 3:
            ret = SDL_GL_SetSwapInterval(-1);
            msg = "adaptive vsync";
            break;

        case 2:
            ret = SDL_GL_SetSwapInterval(1);
            msg = "synchronized (vsync)";
            break;

        case 1:
            ret = SDL_GL_SetSwapInterval(0);
            msg = "immediate (no vsync)";
            break;

        case 0:
            msg = "vsync handling disabled";
            ret = 0;
            break;

        default:
            ret = -1;
            msg = "INVALID VSYNC MODE";
            break;
    }

    if (ret != 0)
    {
        dmError("Could not set vsync mode to %s.\n",
            msg.c_str());
        return false;
    }

    // Get/initialize OpenGL extension function pointers
    if (optUseShaders && !dmInitGLExtensions())
        return false;

    // Dump some information
    dmMsg("GL_VENDOR   : %s\n", glGetString(GL_VENDOR));
    dmMsg("GL_RENDERER : %s\n", glGetString(GL_RENDERER));
    dmMsg("GL_VERSION  : %s\n", glGetString(GL_VERSION));
    dmMsg("VSync mode  : %s\n", msg.c_str());

    if (!dmGLCheckErrors())
        return false;

    // Setup the window and view port
    glViewport(0, 0, width, height);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();

    gluPerspective(45.0f, GLfloat(width) / GLfloat(height), 0.1f, 1000.0f);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();

    // Enable back face culling
    glEnable(GL_CULL_FACE);

    // Enable smooth shading
    glShadeModel(GL_SMOOTH);

    // Enable the depth buffer
    glEnable(GL_DEPTH_TEST);

    // Enable normal rescaling
    glEnable(GL_RESCALE_NORMAL);

    // Setup depth buffer
    glClearDepth(1.0f);

    // Set the depth buffer function
    glDepthFunc(GL_LEQUAL);

    // Enable vertex and and normal arrays
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);

    // Set correct perspective correction
    glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

    return dmGLCheckErrors();
}


void dmDrawModel(const DMSimpleScene &scene, const DMModel &model)
{
    int maxIndices;

    if (optUseShaders)
    {
        // Enable shader program
        glUseProgram(model.id_prog);
        glUniform1i(glGetUniformLocation(model.id_prog, "nlights"), scene.lights.size());
    }

    // Set the material of the model
    glEnable(GL_COLOR_MATERIAL);
    glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
    glMateriali(GL_FRONT, GL_SHININESS, model.material.shininess);
    glMaterialfv(GL_FRONT, GL_SPECULAR, model.material.specular.values);
    glColor4fv(model.material.diffuse.values);

    // Render the model
    glGetIntegerv(GL_MAX_ELEMENTS_INDICES, &maxIndices);

    // Add transforms
    if (model.scaleSet)
        glScalef(model.scale.x, model.scale.y, model.scale.z);

    if (model.translateSet)
        glTranslatef(model.translate.x, model.translate.y, model.translate.z);

    if (model.rotateSet)
    {
        glRotatef(model.rotate.x, 1.0f, 0.0f, 0.0f);
        glRotatef(model.rotate.y, 0.0f, 1.0f, 0.0f);
        glRotatef(model.rotate.z, 0.0f, 0.0f, 1.0f);
    }

    glVertexPointer(3, GL_FLOAT, 0, &model.vertices[0]);
    glNormalPointer(   GL_FLOAT, 0, &model.normals[0]);

    for (int n = 0; n < model.nfaces; n += maxIndices)
    {
        const int count = std::min(maxIndices, model.nfaces - n);
        glDrawElements(GL_TRIANGLES, count * 3, GL_UNSIGNED_INT, &model.faces[n * 3]);
    }

    // Restore
    if (optUseShaders)
    {
        glUseProgram(0);
    }
}


void dmDrawScene(const DMSimpleScene &scene)
{
    glClear(GL_DEPTH_BUFFER_BIT);

    glMatrixMode(GL_PROJECTION);
    glPushMatrix();
    glLoadIdentity();

    glOrtho(0.0, 1.0, 0.0, 1.0, -1, 1);

    glMatrixMode(GL_MODELVIEW);
    glPushMatrix();
    glLoadIdentity();

    // Draw the background gradient
    glDisable(GL_DEPTH_TEST);
    glDisable(GL_LIGHTING);
    glBegin(GL_QUADS);
    {
        glColor3ub(0x3B, 0x3B, 0x75);
        glVertex2f(0.0f, 0.0f);
        glVertex2f(1.0f, 0.0f);

        glColor3ub(0x00, 0x00, 0x00);
        glVertex2f(1.0f, 1.0f);
        glVertex2f(0.0f, 1.0f);
    }
    glEnd();

    // Restore the 3D projection
    glMatrixMode(GL_PROJECTION);
    glPopMatrix();

    glMatrixMode(GL_MODELVIEW);
    glPopMatrix();

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);

    // Draw models
    for (const DMModel &model : scene.models)
    {
        glPushMatrix();
        dmDrawModel(scene, model);
        glPopMatrix();
    }
}


bool dmCompileShader(const GLenum stype, const std::string &src, GLuint &shader)
{
    GLint status;
    const char *tmp = src.c_str();

    shader = glCreateShader(stype);
    glShaderSource(shader, 1, &tmp, 0);
    glCompileShader(shader);

    glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
    if (status == GL_TRUE)
        return true;
    else
    {
        GLint bufLen = 0;
        glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &bufLen);

        if (bufLen > 0)
        {
            char *buf = new char[bufLen];
            glGetShaderInfoLog(shader, bufLen, NULL, buf);
            dmError("Shader compliation error:\n%s\n",
                buf);
            delete buf;
        }
        else
        {
            dmError("Shader compilation error occured, but no error information got.\n");
        }
        return false;
    }
}


void dmLinkModelShaders(DMModel &model)
{
    model.id_prog = glCreateProgram();
    glAttachShader(model.id_prog, model.id_fs);
    glAttachShader(model.id_prog, model.id_vs);
    glLinkProgram(model.id_prog);
}


void dmSetupLight(const int n, const DMLight &light)
{
    glEnable(GL_LIGHT0 + n);
    glLightfv(GL_LIGHT0 + n, GL_AMBIENT, light.color.ambient.values);
    glLightfv(GL_LIGHT0 + n, GL_DIFFUSE, light.color.diffuse.values);
    glLightfv(GL_LIGHT0 + n, GL_SPECULAR, light.color.specular.values);
    glLightfv(GL_LIGHT0 + n, GL_POSITION, light.position.values);
}


int main(int argc, char *argv[])
{
    int startTime, cycleStart, cycleFrames = 0, totalFrames = 0;
    double totalTime;
    bool
        exitFlag = false,
        optShowHelp = false,
        optSetInputFilename = false;
    std::string optInputFilename = "dragon.scene", basePath;
    DMSimpleScene scene;

    // Check commandline argument for enabling shaders
    for (int narg = 1; narg < argc; narg++)
    {
        char *arg = argv[narg];
        if (arg[0] == '-')
        {
            char *opt = arg + 1;

            if ((opt[0] == '-' && opt[1] == 'h' && opt[2] == 'e') ||
                opt[0] == '?' || (opt[0] == '-' && opt[1] == '?'))
            {
                optShowHelp = true;
                break;
            }
            else
            if (opt[0] == '-')
                opt++;

            switch (opt[0])
            {
                case 'g':
                    optUseShaders = true;
                    break;

                case 'w':
                case 'h':
                case 'm':
                case 'v':
                    if (opt[1] == 0)
                    {
                        printf("Option '%s' requires an argument.\n", opt);
                        goto exit;
                    }

                    switch (opt[0])
                    {
                        case 'w': optWidth = atoi(opt + 1); break;
                        case 'h': optHeight = atoi(opt + 1); break;
                        case 'v': optVSyncMode = atoi(opt + 1); break;
                    }
                    break;

                default:
                    printf("Unknown option '%s'.\n", opt);
                    goto exit;
            }
        }
        else
        {
            if (optSetInputFilename)
            {
                dmError("Please specify only one scene file.\n");
                goto exit;
            }

            optSetInputFilename = true;
            optInputFilename = std::string(arg);
            if (optInputFilename.empty())
            {
                dmError("Invalid input filename.\n");
                goto exit;
            }

        }
    }

    if (optShowHelp)
    {
        printf(
            "Usage: %s [options] [<scenefile.scene>]\n"
            "-?            Show this help\n"
            "-g            Use GLSL shader instead of basic OpenGL lighting\n"
            "-w<width>     Window width (default %d)\n"
            "-h<height>    Window height (default %d)\n"
            "-v<0-3>       Set vsync mode: 0 = do not attempt to set vsync mode\n"
            "              (may be required for software rendering backends),\n"
            "              1 = no vsync, 2 = vsync, 3 = adaptive.\n"
            "              Using vsync (2) will result in FPS being approximately\n"
            "              whatever your monitor refresh rate is. The default\n"
            "              value is 1 (no vsync).\n"
            "\n",
            argv[0],
            SET_DEF_WIDTH, SET_DEF_HEIGHT
            );

        goto exit;
    }

    if (optWidth < 100 || optWidth > 8192 || optHeight < 100 || optHeight > 8192)
    {
        dmError("Invalid window width or height (%d x %d).\n",
            optWidth, optHeight);
        goto exit;
    }

    // Load the scene
    if (!scene.loadInfo(optInputFilename))
        goto exit;

    if (scene.models.size() == 0)
    {
        dmError("Scenefile '%s' contains no models.\n",
            optInputFilename.c_str());
        goto exit;
    }

    // Define a default light if none defined in scene file
    if (scene.lights.size() == 0)
    {
        DMLight light; // Default light
        scene.lights.push_back(light);
    }

    dmMsg("Loading %ld model(s) ..\n",
        scene.models.size());

    basePath = dmGetPath(optInputFilename);
    dmMsg("Scene base path '%s'\n", basePath.c_str());

    for (DMModel &model : scene.models)
    {
        if (!model.loadFromPLY(basePath + model.modelFile))
            goto exit;

        if (optUseShaders)
        {
            std::string
                fragFile = model.fragShaderFile.empty() ? "shader.frag" : basePath + model.fragShaderFile,
                vertFile = model.vertShaderFile.empty() ? "shader.vert" : basePath + model.vertShaderFile;

            if (!dmReadText(fragFile, model.fragShaderStr, SET_MAX_SHADER_SIZE) ||
                !dmReadText(vertFile, model.vertShaderStr, SET_MAX_SHADER_SIZE))
                goto exit;
        }
    }

    // Initialize SDL + OpenGL
    if (!dmInitSDLGL(optWidth, optHeight, "GLDragon"))
        goto exit;

    // According to our mode ..
    if (optUseShaders)
    {
        for (DMModel &model : scene.models)
        {
            if (!dmCompileShader(GL_FRAGMENT_SHADER, model.fragShaderStr, model.id_fs) ||
                !dmCompileShader(GL_VERTEX_SHADER, model.vertShaderStr, model.id_vs))
                goto exit;

            dmLinkModelShaders(model);
        }
    }

    // Setup lights
    for (size_t n = 0; n < scene.lights.size(); n++)
        dmSetupLight(n, scene.lights[n]);

    // Define the camera
    gluLookAt(0, 0.12, 0.24, 0, 0.12, 0, 0, 1, 0);


    // Main loop starts
    startTime = cycleStart = SDL_GetTicks();

    while (!exitFlag)
    {
        SDL_Event event;

        // Check for quit events
        while (SDL_PollEvent(&event))
        switch (event.type)
        {
            case SDL_QUIT:
                exitFlag = true;
                break;

            case SDL_KEYDOWN:
                switch (event.key.keysym.sym)
                {
                    case SDLK_ESCAPE:
                    case SDLK_q:
                        exitFlag = true;
                        break;
                }
        }

        // Render the next frame
        dmDrawScene(scene);

        // Draw the current frame
        SDL_GL_SwapWindow(dmWindow);

        // Rotate for 2 degrees
        glRotatef(2.0f, 0, 1, 0);

        // Check for errors
        dmGLCheckErrors();

        // Return true if a full rotation was done
        totalFrames++;
        if (cycleFrames++ == SET_FRAMES)
        {
            // Reset cycleFrames
            cycleFrames = 0;

            // Get the time it took to render a full turn
            int cycleEnd = SDL_GetTicks();
            double cycleTime = cycleEnd - cycleStart;

            // Restart the timer
            cycleStart = SDL_GetTicks();

            // Print the current frames per second
            printf("%.1lf ms for %d frames = %.1lf FPS\n",
                cycleTime, SET_FRAMES, (SET_FRAMES * 1000.0f) / cycleTime);
        }
    }

    // Show totals
    totalTime = SDL_GetTicks() - startTime;
    printf("%.1lf ms total for %d total frames = %.2lf FPS average\n",
        totalTime, totalFrames, (totalFrames * 1000.0f) / totalTime);

exit:
    if (dmGLContext != NULL)
        SDL_GL_DeleteContext(dmGLContext);

    if (dmWindow != NULL)
        SDL_DestroyWindow(dmWindow);

    SDL_Quit();

    return 0;
}