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