Window initialization and rendering in different threads

Discussion in 'Visualization' started by Ilija Stanojkovic, May 8, 2019.

  1. I'm writing a robot simulator using Mujoco in C++, and I run into a certain inconvenience that I don't know the answer to.

    The general architecture of the program is to encapsulate the robot simulator inside a class. Inside a constructor initialization of Mujoco and GLFW would first be run, then model would be loaded, and afterwards two additional threads would be spawned, one for rendering inside a GLFW window, and the other for Mujoco physics simulation. The problem with this is that when run, Mujoco simulation seems to work as expected, while rendering only leaves a black screen and doesn't display either the robot or the movement.

    After debugging for a while, I noticed that if I put initialization and rendering inside the same thread (either the main one, or the newly spawned), the result is as expected. However, I have no idea why is this the case, so I would be grateful if someone could explain a bit.

    I'm decently familiar with C++, but my knowledge of GLFW is fairly limited, so maybe I'm missing something in that regard.

    Here are the code samples for constructor, initialization, model loading, rendering, and visualization and simulation loops in the case of unsuccessful run. The changes that I made to be able to run correctly were moving `initilize()` and `load_model()` into `vis_loop()`, and adding a bool parameter `ready` to mark when all the inits are done so `sim_loop()` could start as well.

    Code:
    MujocoFinger::MujocoFinger(char *model_path, bool debug = false)
    {
        DEBUG = debug;
        filename = model_path;
    
        initialize();
        std::cout << "Initialized" << std::endl;
    
        load_model();
        std::cout << "Model loaded" << std::endl;
    
        std::thread{&MujocoFinger::vis_loop, this}.detach();
        std::thread{&MujocoFinger::sim_loop, this}.detach();
    }
    Code:
    void MujocoFinger::initialize()
    {
        std::cout << std::fixed;
        // print version, check compatibility
        std::cout << "MuJoCo Pro version " << std::setprecision(2) << 0.01 * mj_version() << std::endl;
        if (mjVERSION_HEADER != mj_version())
            mju_error("Headers and library have different versions");
    
        // activate software
        auto mujoco_licence_path = std::getenv("MUJOCO_LICENCE");
        if (mujoco_licence_path == NULL)
        {
            mj_activate("mjkey.txt");
            std::cout << "MUJOCO_LICENCE env var not found. Default licence location the exec folder." << std::endl;
        } else
        {
            mj_activate(mujoco_licence_path);
        }
    
        // init GLFW
        if (!glfwInit())
            mju_error("Could not initialize GLFW");
    
        // multisampling
        glfwWindowHint(GLFW_SAMPLES, 4);
        glfwWindowHint(GLFW_VISIBLE, 1);
    
        // get videomode and save
        GLFWvidmode vmode = *glfwGetVideoMode(glfwGetPrimaryMonitor());
    
        // create window, make OpenGL context current, request v-sync
        window = glfwCreateWindow((2 * vmode.width) / 3, (2 * vmode.height) / 3,
                                  "Mujoco Finger Simulation", NULL, NULL);
        glfwMakeContextCurrent(window);
        glfwSwapInterval(settings.vsync);
    
        // initialize visualization data structures
        mjv_defaultCamera(&cam);
        mjv_defaultOption(&opt);
        mjv_defaultScene(&scn);
        mjr_defaultContext(&con);
    
        // create scene and context
        mjv_makeScene(m, &scn, 2000);
        mjr_makeContext(m, &con, mjFONTSCALE_150);
    
        set_glfw_callbacks(this);
    }
    Code:
    // load mjb or xml model
    void MujocoFinger::load_model()
    {
        // make sure filename is not empty
        if( !filename[0]  )
            return;
    
        // load and compile
        char error[500] = "";
        mjModel* mnew = 0;
        if( strlen(filename)>4 && !strcmp(filename+strlen(filename)-4, ".mjb") )
        {
            mnew = mj_loadModel(filename, NULL);
            if( !mnew )
                strcpy(error, "could not load binary model");
        }
        else
            mnew = mj_loadXML(filename, NULL, error, 500);
        if( !mnew )
        {
            std::cout << error << std::endl;
            return;
        }
    
        // compiler warning
        if( error[0] )
        {
            std::cout << "Model compiled, but simulation warning:" << std::endl << error << std::endl << std::endl;
            clean();
            return;
        }
    
        // delete old model, assign new
        mj_deleteData(d);
        mj_deleteModel(m);
        m = mnew;
        d = mj_makeData(m);
        mj_forward(m, d);
    
        // re-create scene and context
        mjv_makeScene(m, &scn, 2000);
        mjr_makeContext(m, &con, mjFONTSCALE_150);
    
        // align and scale view, update scene
        mjv_updateScene(m, d, &opt, NULL, &cam, mjCAT_ALL, &scn);
    
        // set window title to model name
        if( window && m->names )
        {
            char title[200] = "Simulate : ";
            strcat(title, m->names);
            glfwSetWindowTitle(window, title);
        }
    }
    Code:
    void MujocoFinger::render()
    {
        // get framebuffer viewport
        mjrRect viewport = {0, 0, 0, 0};
        glfwGetFramebufferSize(window, &viewport.width, &viewport.height);
    
        // update scene and render
        mjv_updateScene(m, d, &opt, NULL, &cam, mjCAT_ALL, &scn);
        mjr_render(viewport, &scn, &con);
    
        // swap OpenGL buffers (blocking call due to v-sync)
        glfwSwapBuffers(window);
    
        // process pending GUI events, call GLFW callbacks
        glfwPollEvents();
    }
    Code:
    void MujocoFinger::vis_loop()
    {
        // proprietary lib for establishing correct update frequency
        real_time_tools::Spinner spinner;
        spinner.set_frequency(settings.fps);
    
        // event loop
        while( !glfwWindowShouldClose(window) && !settings.exitrequest )
        {
            // render while simulation is running
            render();
            spinner.spin();
        }
    }
    Code:
    void MujocoFinger::sim_loop()
    {
        // proprietary lib for establishing correct update frequency
        real_time_tools::Spinner spinner;
        spinner.set_period(settings.control_period);
    
        // run until asked to exit
        while( !settings.exitrequest )
        {
            // run only if model is present
            if( m )
            {
                // some control code
    
                // run single step
                mj_step(m, d);
            }
            spinner.spin();
        }
    }