view gldragon.cpp @ 23:f080349584b8

Rename from glxdragon to gldragon.
author Matti Hamalainen <ccr@tnsp.org>
date Fri, 22 Nov 2019 05:29:34 +0200
parents glxdragon.cpp@03b86b9c2f29
children c1897cfc8463
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 <GL/glext.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 dmInitSDLGL(const int width, const int height, const char *title)
{
    int ret;
    std::string msg;

    // 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_DEPTH_SIZE, 24);
    SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);

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

    // 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)
    {
        printf("ERROR: Could not create SDL window: %s",
            SDL_GetError());
        return false;
    }

    if ((dmGLContext = SDL_GL_CreateContext(dmWindow)) == NULL)
    {
        printf("ERROR: 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;

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

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

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

    // 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 true;
}


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

    if (optUseShaders)
    {
        // Enable shader program
        glUseProgram(model.id_prog);
    }
    else
    {
        // Set the color of the model
        glEnable(GL_LIGHTING);
        glColor3ub(0x90, 0x80, 0x90);
    }

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

    glPushMatrix();

    // Add transforms
    glScalef(model.scale.x, model.scale.y, model.scale.z);
    glTranslatef(model.translate.x, model.translate.y, model.translate. z);
    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]);
    }

    glPopMatrix();

    // 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);

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


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

    glShaderSource(shader, 1, &tmp, 0);
    glCompileShader(shader);

    return shader;
}


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);
}


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++;

            if (opt[0] == 'g')
                optUseShaders = true;
            else
            switch (opt[0])
            {
                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)
            {
                printf("ERROR: Please specify only one scene file.\n");
                goto exit;
            }

            optSetInputFilename = true;
            optInputFilename = std::string(arg);
            if (optInputFilename.empty())
            {
                printf("ERROR: 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<1-3>       Set vsync mode: 1 = no vsync, 2 = vsync, 3 = adaptive\n"
            "              Default is no vsync. Using vsync will result in FPS being\n"
            "              approx whatever your monitor refresh rate is.\n"
            "\n",
            argv[0],
            SET_DEF_WIDTH, SET_DEF_HEIGHT
            );

        goto exit;
    }

    if (optWidth < 100 || optWidth > 8192 || optHeight < 100 || optHeight > 8192)
    {
        printf("ERROR: 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)
    {
        printf("ERROR: Scenefile '%s' contains no models.\n",
            optInputFilename.c_str());
        goto exit;
    }

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

    basePath = dmGetPath(optInputFilename);
    printf("INFO: Model 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)
        {
            model.id_fs = dmCompileShader(GL_FRAGMENT_SHADER, model.fragShaderStr);
            model.id_vs = dmCompileShader(GL_VERTEX_SHADER, model.vertShaderStr);
            dmLinkModelShaders(model);
        }
    }
    else
    {
        float specReflection[] = { 0.8f, 0.8f, 0.8f, 1.0f };

        glEnable(GL_COLOR_MATERIAL);

        glColorMaterial(GL_FRONT, GL_AMBIENT_AND_DIFFUSE);
        glMateriali(GL_FRONT, GL_SHININESS, 96);
        glMaterialfv(GL_FRONT, GL_SPECULAR, specReflection);

        glEnable(GL_LIGHT0);

        // Define the light components and position
        GLfloat ambient[]  = {   0.2f,  0.2f,  0.2f,  1.0f };
        GLfloat diffuse[]  = {   0.8f,  0.8f,  0.8f,  1.0f };
        GLfloat specular[] = {   0.5f,  0.5f,  0.5f,  1.0f };
        GLfloat position[] = {  10.0f, 10.0f,  0.0f,  0.0f };

        // Define the light components and position
        glLightfv(GL_LIGHT0, GL_AMBIENT, ambient);
        glLightfv(GL_LIGHT0, GL_DIFFUSE, diffuse);
        glLightfv(GL_LIGHT0, GL_SPECULAR, specular);
        glLightfv(GL_LIGHT0, GL_POSITION, position);
    }

    // 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);

        // 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;
}