From 166c1f428292912d0fc1d7e6db26a18e45aaf2a8 Mon Sep 17 00:00:00 2001
From: Jens Koenen <koenen@vr.rwth-aachen.de>
Date: Fri, 14 Jul 2023 23:17:12 +0200
Subject: [PATCH] Improved scene loading as well as several smaller bug fixes
 and improvements

---
 src/encoder/encoder.cpp          |  2 +
 src/encoder/nvidia_encoder.cpp   |  3 +-
 src/headset/emulated_headset.cpp |  2 +-
 src/headset/remote_headset.cpp   |  8 ++--
 src/scene.cpp                    | 82 ++++++++++++++++++++------------
 src/scene.hpp                    |  5 +-
 src/utility/frame_capture.cpp    | 23 +++++++--
 src/utility/statistic.cpp        | 32 +++++++++----
 src/utility/statistic.hpp        |  5 +-
 src/vr_application.cpp           | 30 ++++++++++--
 10 files changed, 135 insertions(+), 57 deletions(-)

diff --git a/src/encoder/encoder.cpp b/src/encoder/encoder.cpp
index 99f7d1aa..43b29bf0 100644
--- a/src/encoder/encoder.cpp
+++ b/src/encoder/encoder.cpp
@@ -95,8 +95,10 @@ bool shutdown_encoder(EncoderType encoder_type)
     {
     case ENCODER_TYPE_VULKAN:
         shutdown_vulkan_encoder();
+        break;
     case ENCODER_TYPE_NVIDIA:
         shutdown_nvidia_encoder();
+        break;
     default:
         lava::log()->error("Unkown encoder type!");
         return false;
diff --git a/src/encoder/nvidia_encoder.cpp b/src/encoder/nvidia_encoder.cpp
index f75b0202..29ec2ae8 100644
--- a/src/encoder/nvidia_encoder.cpp
+++ b/src/encoder/nvidia_encoder.cpp
@@ -1158,7 +1158,8 @@ bool NvidiaEncoder::create_semaphore(NvidiaEncoderFrame::Ptr frame, lava::device
     #error "Not implemented for this platform!"
 #endif
     semaphore_description.flags = 0;
- 
+    memset(semaphore_description.reserved, 0, sizeof(semaphore_description.reserved));
+
     if (cuImportExternalSemaphore(&frame->cuda_external_semaphore, &semaphore_description) != CUDA_SUCCESS)
     {
         lava::log()->error("Nvidia Encoder: Can't import semaphore!");
diff --git a/src/headset/emulated_headset.cpp b/src/headset/emulated_headset.cpp
index 30925cbf..35a8b299 100644
--- a/src/headset/emulated_headset.cpp
+++ b/src/headset/emulated_headset.cpp
@@ -93,7 +93,7 @@ void EmulatedHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout
         image_barriers.push_back(frame_barrier);
     }
 
-    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, image_barriers.size(), image_barriers.data());
+    vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT | VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, image_barriers.size(), image_barriers.data());
 
     VkImageSubresourceLayers subresource_layers;
     subresource_layers.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
diff --git a/src/headset/remote_headset.cpp b/src/headset/remote_headset.cpp
index 30eb43b2..aa5702dd 100644
--- a/src/headset/remote_headset.cpp
+++ b/src/headset/remote_headset.cpp
@@ -145,11 +145,11 @@ bool RemoteHeadset::on_interface()
         }
     }
 
-    ImGui::SliderInt("Input-Rate (Fps)", (int32_t*) &this->encoder_input_rate, 1, 180);
+    ImGui::SliderInt("Input-Rate [Frames/s]", (int32_t*) &this->encoder_input_rate, 1, 180);
 
     if (encoder->is_supported(ENCODER_SETTING_KEY_RATE))
     {
-        if (ImGui::SliderInt("Key-Rate (Frames)", (int32_t*) &this->encoder_key_rate, 1, 180))
+        if (ImGui::SliderInt("Key-Rate [Frames]", (int32_t*) &this->encoder_key_rate, 1, 180))
         {
             for (Encoder::Ptr encoder : this->encoders)
             {
@@ -162,7 +162,7 @@ bool RemoteHeadset::on_interface()
     {
         if (encoder->is_supported(ENCODER_SETTING_BITRATE))
         {
-            if (ImGui::SliderFloat("Bit-Rate (Mbps)", &this->encoder_bit_rate, 0.1f, 100.0f))
+            if (ImGui::SliderFloat("Bit-Rate [Mbit/s]", &this->encoder_bit_rate, 0.1f, 100.0f))
             {
                 for (Encoder::Ptr encoder : this->encoders)
                 {
@@ -173,7 +173,7 @@ bool RemoteHeadset::on_interface()
       
         if (encoder->is_supported(ENCODER_SETTING_FRAME_RATE))
         {
-            if (ImGui::SliderInt("Frame-Rate (Fps)", (int32_t*) &this->encoder_frame_rate, 1, 180))
+            if (ImGui::SliderInt("Frame-Rate [Frames/s]", (int32_t*) &this->encoder_frame_rate, 1, 180))
             {
                 for (Encoder::Ptr encoder : this->encoders)
                 {
diff --git a/src/scene.cpp b/src/scene.cpp
index ac4e400f..11404f12 100644
--- a/src/scene.cpp
+++ b/src/scene.cpp
@@ -236,7 +236,7 @@ bool Scene::update(lava::delta delta_time, Headset::Ptr headset)
     if (this->animation_active && !this->animations.empty())
     {
         const SceneAnimation& active_animation = this->animations[this->animation_index];
-        this->animation_time += delta_time * active_animation.ticks_per_second * this->animation_playback_speed;
+        this->animation_time += delta_time * this->animation_playback_speed;
 
         if (this->animation_loop)
         {
@@ -985,7 +985,7 @@ bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, u
 {
     lava::log()->info("Loading sky-sphere file '{}'", file_name.c_str());
 
-    if (!this->load_texture(file_name, device, true, false, this->sky_emissive_texture))
+    if (!this->load_texture(file_name, device, true, false, false, this->sky_emissive_texture))
     {
         lava::log()->error("Faild to load sky-sphere '{}'", file_name.c_str());
 
@@ -1174,8 +1174,7 @@ bool Scene::load_animations(const aiScene* scene, float scale, const std::map<st
         SceneAnimation& scene_animation = this->animations.emplace_back();
 
         scene_animation.name = animation->mName.C_Str();
-        scene_animation.duration = animation->mDuration;
-        scene_animation.ticks_per_second = animation->mTicksPerSecond;
+        scene_animation.duration = animation->mDuration / animation->mTicksPerSecond;
         scene_animation.channels.resize(animation->mNumChannels);
 
         for (uint32_t channel_index = 0; channel_index < animation->mNumChannels; channel_index++)
@@ -1198,19 +1197,19 @@ bool Scene::load_animations(const aiScene* scene, float scale, const std::map<st
             for (uint32_t frame_index = 0; frame_index < channel->mNumPositionKeys; frame_index++)
             {
                 const aiVectorKey key = channel->mPositionKeys[frame_index];
-                scene_channel.position_frames[frame_index] = std::make_pair((float)key.mTime, glm::make_vec3(&key.mValue.x) * scale);
+                scene_channel.position_frames[frame_index] = std::make_pair((float)key.mTime / animation->mTicksPerSecond, glm::make_vec3(&key.mValue.x) * scale);
             }
 
             for (uint32_t frame_index = 0; frame_index < channel->mNumRotationKeys; frame_index++)
             {
                 const aiQuatKey key = channel->mRotationKeys[frame_index];
-                scene_channel.rotation_frames[frame_index] = std::make_pair((float)key.mTime, key.mValue);
+                scene_channel.rotation_frames[frame_index] = std::make_pair((float)key.mTime / animation->mTicksPerSecond, key.mValue);
             }
 
             for (uint32_t frame_index = 0; frame_index < channel->mNumScalingKeys; frame_index++)
             {
                 const aiVectorKey key = channel->mScalingKeys[frame_index];
-                scene_channel.scaling_frames[frame_index] = std::make_pair((float)key.mTime, glm::make_vec3(&key.mValue.x));
+                scene_channel.scaling_frames[frame_index] = std::make_pair((float)key.mTime / animation->mTicksPerSecond, glm::make_vec3(&key.mValue.x));
             }
         }
     }
@@ -1231,15 +1230,6 @@ bool Scene::load_lights(const aiScene* scene, float scale, const std::map<std::s
             return false;
         }
 
-        float light_scale = 1.0;
-
-        if (light->mType == aiLightSource_DIRECTIONAL)
-        {
-            //NOTE: If the scene is in centimeters assume that directional light intensity is given as watt / cm^2.
-            //      In this case the intensity has to be converted into watt / meter^2
-            light_scale = glm::pow(scale, 1); //It should be a 2 instead of a 1
-        }
-
         glm::vec3 color = glm::make_vec3(&light->mColorDiffuse.r);
         glm::vec3 position = glm::make_vec3(&light->mPosition.x);
         glm::vec3 direction = glm::make_vec3(&light->mDirection.x);
@@ -1257,7 +1247,7 @@ bool Scene::load_lights(const aiScene* scene, float scale, const std::map<std::s
 
         glsl::LightData& light_data = scene_light.data;
         light_data.position = scene_light.initial_position;
-        light_data.color = color * light_scale;
+        light_data.color = color;
         light_data.outer_angle = light->mAngleOuterCone;
         light_data.direction = scene_light.initial_direction;
         light_data.inner_angle = light->mAngleInnerCone;
@@ -1360,7 +1350,6 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
     {
         const aiMaterial* material = scene->mMaterials[index];
         SceneMaterial& scene_material = this->materials.emplace_back();
-        scene_material.emissive = this->dummy_emissive_texture;
 
         aiColor3D diffuse_color = aiColor3D(0.0f, 0.0f, 0.0f);
         material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse_color);
@@ -1380,16 +1369,16 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
         glsl::MaterialData& material_data = scene_material.material_data;
         material_data.base_color = glm::vec3(1.0f);
         material_data.opacity = 1.0f;
-        material_data.emissive = glm::make_vec3(&emissive_color.r);
+        material_data.emissive = glm::vec3(1.0f);
         material_data.roughness = 1.0f;
         material_data.padding1 = glm::uvec3(0);
-        material_data.metallic = 0.0f;
+        material_data.metallic = 1.0f;
         material_data.padding2 = glm::uvec4(0);
         material_data.padding3 = glm::mat4(0);
         material_data.padding4 = glm::mat4(0);
         material_data.padding5 = glm::mat4(0);
 
-        if (!this->load_texture(material, base_name, aiTextureType_DIFFUSE, device, scene_material.diffuse))
+        if (!this->load_texture(material, base_name, aiTextureType_DIFFUSE, true, device, scene_material.diffuse))
         {
             scene_material.diffuse = this->dummy_diffuse_texture;
 
@@ -1397,7 +1386,7 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
             material_data.opacity = glm::clamp(opacity, 0.0f, 1.0f);
         }
 
-        if (!this->load_texture(material, base_name, aiTextureType_SPECULAR, device, scene_material.specular))
+        if (!this->load_texture(material, base_name, aiTextureType_SPECULAR, false, device, scene_material.specular))
         {
             scene_material.specular = this->dummy_specular_texture;
 
@@ -1405,16 +1394,23 @@ bool Scene::load_materials(const aiScene* scene, const std::string& base_name, l
             material_data.metallic = glm::clamp(reflectivity, 0.0f, 1.0f);
         }
 
-        if (!this->load_texture(material, base_name, aiTextureType_NORMALS, device, scene_material.normal))
+        if (!this->load_texture(material, base_name, aiTextureType_NORMALS, false, device, scene_material.normal))
         {
             scene_material.normal = this->dummy_normal_texture;
         }
+
+        if (!this->load_texture(material, base_name, aiTextureType_EMISSIVE, false, device, scene_material.emissive))
+        {
+            scene_material.emissive = this->dummy_emissive_texture;
+
+            material_data.emissive = glm::make_vec3(&emissive_color.r);
+        }
     }
 
     return true;
 }
 
-bool Scene::load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::texture::ptr& texture)
+bool Scene::load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, bool use_srgb, lava::device_ptr device, lava::texture::ptr& texture)
 {
     aiString texture_path;
 
@@ -1436,7 +1432,7 @@ bool Scene::load_texture(const aiMaterial* material, const std::string& base_nam
     std::string file_name = (base_path / texture_path.C_Str()).string();
     std::replace(file_name.begin(), file_name.end(), '\\', '/');
     
-    if (!this->load_texture(file_name, device, false, true, texture))
+    if (!this->load_texture(file_name, device, false, true, use_srgb, texture))
     {
         lava::log()->warn("Faild to load texture file '{}'. Using dummy texture instead.", file_name.c_str());
 
@@ -1446,7 +1442,7 @@ bool Scene::load_texture(const aiMaterial* material, const std::string& base_nam
     return true;
 }
 
-bool Scene::load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, lava::texture::ptr& texture)
+bool Scene::load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, bool use_srgb, lava::texture::ptr& texture)
 {
     for (const SceneTexture& scene_texture : this->textures)
     {
@@ -1509,11 +1505,29 @@ bool Scene::load_texture(const std::string& file_name, lava::device_ptr device,
 
         switch (texture.format())
         {
+        case gli::FORMAT_RGBA_DXT1_SRGB_BLOCK8:
         case gli::FORMAT_RGBA_DXT1_UNORM_BLOCK8:
-            format = VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+            if (use_srgb)
+            {
+                format = VK_FORMAT_BC1_RGBA_SRGB_BLOCK;
+            }
+
+            else
+            {
+                format = VK_FORMAT_BC1_RGBA_UNORM_BLOCK;
+            }
             break;
+        case gli::FORMAT_RGBA_DXT5_SRGB_BLOCK16:
         case gli::FORMAT_RGBA_DXT5_UNORM_BLOCK16:
-            format = VK_FORMAT_BC3_UNORM_BLOCK;
+            if (use_srgb)
+            {
+                format = VK_FORMAT_BC3_SRGB_BLOCK;
+            }
+
+            else
+            {
+                format = VK_FORMAT_BC3_UNORM_BLOCK;
+            }
             break;
         case gli::FORMAT_RG_ATI2N_UNORM_BLOCK16:
             format = VK_FORMAT_BC5_UNORM_BLOCK;
@@ -1562,7 +1576,15 @@ bool Scene::load_texture(const std::string& file_name, lava::device_ptr device,
 
         else
         {
-            format = VK_FORMAT_R8G8B8A8_SRGB;
+            if (use_srgb)
+            {
+                format = VK_FORMAT_R8G8B8A8_SRGB;
+            }
+
+            else
+            {
+                format = VK_FORMAT_R8G8B8A8_UNORM;
+            }
 
             data_pointer = (uint8_t*) stbi_load_from_memory((const stbi_uc*) image_data.ptr, image_data.size, &width, &height, nullptr, STBI_rgb_alpha);
             data_bitdepth = 4 * sizeof(uint8_t);
@@ -2117,7 +2139,7 @@ void Scene::apply_transforms()
         const SceneNode& node = this->nodes[camera.node_index];
 
         glm::vec3 global_location = node.current_global_transform * glm::vec4(camera.local_position, 1.0f);
-        glm::vec3 global_look_at = node.current_global_transform * glm::vec4(camera.local_look_at, 0.0f);
+        glm::vec3 global_look_at = node.current_global_transform * glm::vec4(camera.local_look_at, 1.0f);
         glm::vec3 global_up = node.current_global_transform * glm::vec4(camera.local_up, 0.0f);
 
         camera.view_matrix = glm::lookAt(global_location, global_look_at, global_up);
diff --git a/src/scene.hpp b/src/scene.hpp
index ad9cb9fe..32ea34cc 100644
--- a/src/scene.hpp
+++ b/src/scene.hpp
@@ -145,7 +145,6 @@ struct SceneAnimation
 {
     std::string name;
     float duration = 0.0f;
-    float ticks_per_second = 0.0f;
     std::vector<SceneAnimationChannel> channels;
 };
 
@@ -216,8 +215,8 @@ private:
     bool load_lights(const aiScene* scene, float scale, const std::map<std::string, SceneNodeIndex>& node_indices);
     bool load_meshes(const aiScene* scene, float scale, uint32_t base_material, bool scene_bounds, lava::device_ptr device);
     bool load_materials(const aiScene* scene, const std::string& base_name, lava::device_ptr device);
-    bool load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, lava::device_ptr device, lava::texture::ptr& texture);
-    bool load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, lava::texture::ptr& texture);
+    bool load_texture(const aiMaterial* material, const std::string& base_name, aiTextureType type, bool use_srgb, lava::device_ptr device, lava::texture::ptr& texture);
+    bool load_texture(const std::string& file_name, lava::device_ptr device, bool use_float, bool use_mipmaps, bool use_srgb, lava::texture::ptr& texture);
 
     bool stage_textures(lava::device_ptr device);
 
diff --git a/src/utility/frame_capture.cpp b/src/utility/frame_capture.cpp
index 5c8680b5..2ff93093 100644
--- a/src/utility/frame_capture.cpp
+++ b/src/utility/frame_capture.cpp
@@ -211,13 +211,28 @@ bool FrameCapture::write_to_file(const ImageCapture& image_capture, const uint8_
         std::filesystem::create_directory(directory_path);
     }
 
-    std::vector<uint8_t> converted_content = this->convert_content(image_content, image_capture.size, image_capture.format);
+    if (image_capture.format == VK_FORMAT_R8G8B8_UNORM || image_capture.format == VK_FORMAT_R8G8B8_SRGB || image_capture.format == VK_FORMAT_R8G8B8A8_UNORM || image_capture.format == VK_FORMAT_R8G8B8A8_SRGB)
+    {
+        uint32_t component_count = this->get_component_count(image_capture.format);
+
+        if (stbi_write_png(file_name.c_str(), image_capture.size.x, image_capture.size.y, component_count, image_content, image_capture.size.x * component_count * sizeof(uint8_t)) == 0)
+        {
+            lava::log()->error("Can't store image '" + image_capture.name + "'");
+
+            return false;
+        }
+    }
 
-    if (stbi_write_png(file_name.c_str(), image_capture.size.x, image_capture.size.y, 4, converted_content.data(), image_capture.size.x * 4 * sizeof(uint8_t)) == 0)
+    else
     {
-        lava::log()->error("can't store image '" + image_capture.name + "'");
+        std::vector<uint8_t> converted_content = this->convert_content(image_content, image_capture.size, image_capture.format);
 
-        return false;
+        if (stbi_write_png(file_name.c_str(), image_capture.size.x, image_capture.size.y, 4, converted_content.data(), image_capture.size.x * 4 * sizeof(uint8_t)) == 0)
+        {
+            lava::log()->error("can't store image '" + image_capture.name + "'");
+
+            return false;
+        }
     }
 
     return true;
diff --git a/src/utility/statistic.cpp b/src/utility/statistic.cpp
index 045edc98..6b0da71c 100644
--- a/src/utility/statistic.cpp
+++ b/src/utility/statistic.cpp
@@ -60,6 +60,7 @@ void Statistic::add_sample(float value)
 {
     StatisticSample sample;
     sample.value = value;
+    sample.time_point = std::chrono::high_resolution_clock::now();
 
     this->samples.push_back(sample);
 }
@@ -258,6 +259,11 @@ bool Statistic::is_log_only() const
     return this->log_only;
 }
 
+StatisticLog::StatisticLog()
+{
+    this->time_origin = std::chrono::high_resolution_clock::now();
+}
+
 bool StatisticLog::write(const std::string& directory)
 {
     if (!std::filesystem::exists(directory))
@@ -287,18 +293,22 @@ bool StatisticLog::write(const std::string& directory)
     {
         for (const StatisticSample& sample : statistic->get_samples())
         {
-            std::string label;
+            std::string label_value;
+            std::string label_time;
 
             if (sample.frame_id.has_value())
             {
-                label = statistic->get_name() + "_" + std::to_string(sample.frame_id.value()) + " [" + statistic->get_unit_name() + "]";
+                label_value = statistic->get_name() + "_" + std::to_string(sample.frame_id.value()) + " [" + statistic->get_unit_name() + "]";
+                label_time = statistic->get_name() + "_" + std::to_string(sample.frame_id.value()) + "_time [ms]";
             }
 
             else
             {
-                label = statistic->get_name() + " [" + statistic->get_unit_name() + "]";
+                label_value = statistic->get_name() + " [" + statistic->get_unit_name() + "]";
+                label_time = statistic->get_name() + "_time [ms]";
             }
 
+            double sample_time = std::chrono::duration_cast<std::chrono::duration<double, std::chrono::milliseconds::period>>(sample.time_point - this->time_origin).count();
             bool ordered = sample.frame_number.has_value() || sample.transform_id.has_value();
             
             if (ordered)
@@ -312,7 +322,7 @@ bool StatisticLog::write(const std::string& directory)
 
                 for (uint32_t index = 0; index < ordered_sample_labels.size(); index++)
                 {
-                    if (ordered_sample_labels[index] == label)
+                    if (ordered_sample_labels[index] == label_value)
                     {
                         column_index = index;
                         column_found = true;
@@ -324,17 +334,19 @@ bool StatisticLog::write(const std::string& directory)
                 if (!column_found)
                 {
                     column_index = ordered_sample_labels.size();
-                    ordered_sample_labels.push_back(label);
+                    ordered_sample_labels.push_back(label_value);
+                    ordered_sample_labels.push_back(label_time);
                 }
 
                 std::vector<std::optional<double>>& row = ordered_samples[key];
 
                 if (row.size() <= column_index)
                 {
-                    row.resize(column_index + 1);
+                    row.resize(column_index + 2);
                 }
 
                 row[column_index] = sample.value;
+                row[column_index + 1] = sample_time;
             }
 
             else
@@ -344,7 +356,7 @@ bool StatisticLog::write(const std::string& directory)
 
                 for (uint32_t index = 0; index < unordered_sample_labels.size(); index++)
                 {
-                    if (unordered_sample_labels[index] == label)
+                    if (unordered_sample_labels[index] == label_value)
                     {
                         column_index = index;
                         column_found = true;
@@ -356,15 +368,17 @@ bool StatisticLog::write(const std::string& directory)
                 if (!column_found)
                 {
                     column_index = unordered_sample_labels.size();
-                    unordered_sample_labels.push_back(label);
+                    unordered_sample_labels.push_back(label_value);
+                    unordered_sample_labels.push_back(label_time);
                 }
 
                 if (unordered_samples.size() <= column_index)
                 {
-                    unordered_samples.resize(column_index + 1);
+                    unordered_samples.resize(column_index + 2);
                 }
 
                 unordered_samples[column_index].push_back(sample.value);
+                unordered_samples[column_index + 1].push_back(sample_time);
             }
         }
     }
diff --git a/src/utility/statistic.hpp b/src/utility/statistic.hpp
index f8883be7..84c73ac7 100644
--- a/src/utility/statistic.hpp
+++ b/src/utility/statistic.hpp
@@ -14,6 +14,7 @@
 */
 
 #pragma once
+#include <chrono>
 #include <memory>
 #include <string>
 #include <vector>
@@ -25,6 +26,7 @@
 struct StatisticSample
 {
     double value;
+    std::chrono::high_resolution_clock::time_point time_point;
 
     std::optional<FrameNumber> frame_number;
     std::optional<FrameId> frame_id;
@@ -89,7 +91,7 @@ public:
     typedef std::shared_ptr<StatisticLog> Ptr;
 
 public:
-    StatisticLog() = default;
+    StatisticLog();
 
     bool write(const std::string& directory);
 
@@ -100,6 +102,7 @@ private:
 
 private:
     std::vector<Statistic::Ptr> statistic_list;
+    std::chrono::high_resolution_clock::time_point time_origin;
 };
 
 Statistic::Ptr make_statistic(const std::string& name, Unit unit, bool log_only = false);
diff --git a/src/vr_application.cpp b/src/vr_application.cpp
index 56adfede..8ddde89a 100644
--- a/src/vr_application.cpp
+++ b/src/vr_application.cpp
@@ -331,10 +331,13 @@ bool VRApplication::on_create()
 
     if (!this->command_parser.should_benchmark())
     {
-        config.controller_left_file = std::string(RESOURCE_FOLDER) + "/dpr-controller/ObjModelViveFocus3ControllerLeft.fbx";
+        std::string left_controller_file = "/dpr-controller/ObjModelViveFocus3ControllerLeft.fbx";
+        std::string right_controller_file = "/dpr-controller/ObjModelViveFocus3ControllerRight.fbx";
+
+        config.controller_left_file = lava::file_system::get_real_dir(right_controller_file.c_str()) + left_controller_file;
         config.controller_left_unit = SCENE_UNIT_CENTIMETERS;
         
-        config.controller_right_file = std::string(RESOURCE_FOLDER) + "/dpr-controller/ObjModelViveFocus3ControllerRight.fbx";
+        config.controller_right_file = lava::file_system::get_real_dir(right_controller_file.c_str()) + right_controller_file;
         config.controller_right_unit = SCENE_UNIT_CENTIMETERS;
     }
 
@@ -528,9 +531,12 @@ void VRApplication::on_destroy()
         this->scene->destroy();
     }
 
-    if (!this->statistic_log->write("statistics"))
+    if (this->statistic_log != nullptr)
     {
-        lava::log()->error("Can't write statistic!");
+        if (!this->statistic_log->write("statistics"))
+        {
+            lava::log()->error("Can't write statistic!");
+        }
     }
 }
 
@@ -562,6 +568,8 @@ bool VRApplication::on_interface()
 
     if (ImGui::CollapsingHeader("Scene", ImGuiTreeNodeFlags_DefaultOpen))
     {
+        ImGui::PushID("SceneInterface");
+
         if (!this->scene->interface())
         {
             lava::log()->error("Error during scene interface!");
@@ -569,10 +577,14 @@ bool VRApplication::on_interface()
 
             return false;
         }
+
+        ImGui::PopID();
     }
 
     if (ImGui::CollapsingHeader("Headset", ImGuiTreeNodeFlags_DefaultOpen))
     {        
+        ImGui::PushID("HeadsetInterface");
+
         if (!this->headset->on_interface())
         {
             lava::log()->error("Error during headset interface!");
@@ -580,10 +592,14 @@ bool VRApplication::on_interface()
 
             return false;
         }
+
+        ImGui::PopID();
     }
 
     if (ImGui::CollapsingHeader("Strategy", ImGuiTreeNodeFlags_DefaultOpen))
     {
+        ImGui::PushID("StrategyInterface");
+
         std::vector<const char*> strategy_names;
 
         for (StereoStrategy::Ptr strategy : this->strategies)
@@ -605,10 +621,14 @@ bool VRApplication::on_interface()
 
             return false;
         }
+
+        ImGui::PopID();
     }
 
     if (ImGui::CollapsingHeader("Debug"))
     {
+        ImGui::PushID("DebugInterface");
+
         bool companion_enabled = this->companion_window->is_enabled();
         ImGui::Checkbox("Companion Window", &companion_enabled);
         this->companion_window->set_enabled(companion_enabled);
@@ -621,6 +641,8 @@ bool VRApplication::on_interface()
         this->pass_timer->interface();
 
         this->app->draw_about(true);
+
+        ImGui::PopID();
     }
 
     ImGui::End();
-- 
GitLab