view gldragon.cpp @ 93:9fee97e7c5b6

Handle pauseframe differently: skip straight to it.
author Matti Hamalainen <ccr@tnsp.org>
date Tue, 25 May 2021 00:48:42 +0300
parents 28dd29f3a65f
children 5191f8e571d1
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 "dmutil.h"
#include "dmglrender.h"
#include "dmply.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)


/* Helpers
 */
bool dmInitSDL(DMSimpleRenderer &renderer,
    const int width, const int height,
    const int vsyncMode,
    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;
    }

    // Part 1 of initialization
    if (!renderer.initRenderer1(title, width, height,
        SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
        SDL_WINDOW_SHOWN// | SDL_WINDOW_RESIZABLE
        ))
        return false;

    // Check if we want to attempt to use vsync
    switch (vsyncMode)
    {
        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;
    }

    // Part 3 of initialization
    if (!renderer.initRenderer2())
        return false;

    dmMsg("VSync mode  : %s\n", msg.c_str());

    return true;
}


int main(int argc, char *argv[])
{
    int frameDeltas[SET_FRAMES + 1];

    bool
        exitFlag = false,
        pauseFlag = false,
        optShowHelp = false,
        optSetInputFilename = false,
        optUseShaders = false;

    int optWidth = SET_DEF_WIDTH,
        optHeight = SET_DEF_HEIGHT,
        optVSyncMode = 1,
        optPauseFrame = -1;

    std::string optInputFilename = "dragon.scene", basePath;
    DMGLSimpleRenderer renderer;
    DMSimpleScene scene;
    int cycleTime = 0,
        cycleFrames = 0,
        totalTime = 0,
        totalFrames = 0;

    memset(&frameDeltas, 0, sizeof(frameDeltas));

    // 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 's':
                case 'm':
                case 'v':
                case 'p':
                    {
                        std::string marg;
                        if (opt[1] == 0)
                        {
                            if (narg < argc)
                                marg = std::string(argv[++narg]);
                            else
                            {
                                printf("Option '%s' requires an argument.\n", opt);
                                goto exit;
                            }
                        }
                        else
                            marg = std::string(opt + 1);

                        switch (opt[0])
                        {
                            case 's':
                                {
                                std::vector<std::string> mtokens = dmStrSplit(marg, "xX:");
                                if (mtokens.size() != 2)
                                {
                                    printf("Option expects argument of format <width>x<height> in pixels.\n"
                                        "For example: -s 640x480\n");
                                    goto exit;
                                }

                                optWidth = std::stoi(mtokens[0], 0, 0);
                                optHeight = std::stoi(mtokens[1], 0, 0);
                                }
                                break;

                            case 'v':
                                optVSyncMode = std::stoi(marg, 0, 0);
                                break;

                            case 'p':
                                optPauseFrame = std::stoi(marg, 0, 0);
                                break;
                        }
                    }
                    break;

                default:
                    printf("Unknown option '%s'.\n", arg);
                    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"
            "\n"
            " -?            Show this help\n"
            " -g            Use GLSL shader instead of basic OpenGL lighting\n"
            " -s<w>x<h>     Set window dimensions (default %d x %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"
            " -p<frameN>    Pause at cycle frame (max %d).\n"
            "\n"
            "Keyboard controls during runtime:\n"
            " p / space    Toggle pause (does not/should not affect fps measurement)\n"
            " q / esc      Quit\n"
            "\n",
            argv[0],
            SET_DEF_WIDTH, SET_DEF_HEIGHT,
            SET_FRAMES
            );

        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 (!dmLoadFromPLY(model, 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;
        }
    }

    // Set shader usage
    renderer.useShaders = optUseShaders;

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

    // Compile shaders for scene
    if (!renderer.compileSceneShaders(scene))
        goto exit;

    // Setup lights and camera
    renderer.setupLights(scene);
    renderer.setupCamera(scene.camera);

    // Check for pause frame
    if (optPauseFrame >= 0)
    {
        cycleFrames = optPauseFrame;
        pauseFlag = true;
    }

    // Main loop starts
    while (!exitFlag)
    {
        SDL_Event event;
        int frameStart, frameEnd, frameDelta;

        // 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_SPACE:
                    case SDLK_p:
                        pauseFlag = !pauseFlag;
                        break;

                    case SDLK_ESCAPE:
                    case SDLK_q:
                        exitFlag = true;
                        break;
                }
        }

        // Render the frame
        frameStart = SDL_GetTicks();
        renderer.drawScene(scene, fmodf((float) cycleFrames, SET_FRAMES) / SET_FRAMES);
        renderer.swapWindow();
        frameEnd = SDL_GetTicks();

        // Check for errors
        renderer.checkErrors();

        frameDelta = frameEnd - frameStart;

        // Return true if a full rotation was done
        if (!pauseFlag)
        {
            totalFrames++;
            cycleFrames++;

            cycleTime += frameDelta;
            totalTime += frameDelta;

            memmove(&frameDeltas[0], &frameDeltas[1], sizeof(frameDeltas[0]) * (SET_FRAMES - 1));
            frameDeltas[SET_FRAMES - 1] = frameDelta;

            if (cycleFrames >= SET_FRAMES)
            {
                float avgCycleFrame = cycleTime / SET_FRAMES;
                float maxJitter = 0, avgJitter = 0;

                for (int n = 0; n < SET_FRAMES; n++)
                {
                    float mjitter = fabs(avgCycleFrame - frameDeltas[n]);
                    if (mjitter > maxJitter)
                        maxJitter = mjitter;
                    avgJitter += mjitter;
                }

                avgJitter /= SET_FRAMES;

                // Print the current frames per second
                printf("%d ms for %d frames = %.1lf FPS [framejitter %.1lf ms avg, %.1lf ms max]\n",
                    cycleTime, cycleFrames,
                    (cycleFrames * 1000.0f) / cycleTime,
                    avgJitter, maxJitter
                    );

                // Reset cycleFrames
                cycleFrames = 0;
                cycleTime = 0;
            }
        }
        else
        {
            SDL_Delay(100);
        }
    }

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

exit:
    renderer.shutdownRenderer();

    SDL_Quit();

    return 0;
}