diff --git a/README.md b/README.md index 15fe2baf506de8173963919e7821a794b1efde05..6389f33100b6a90fd99819b70a3f6fdcb27d3ad4 100644 --- a/README.md +++ b/README.md @@ -1,145 +1,27 @@ -<a href="https://git.io/liblava"><img align="left" src="https://github.com/liblava.png" width="200" style="margin:0px 40px 0px 0px"></a> - -š **[liblava](https://git.io/liblava) A modern and easy-to-use library for the <a href="https://www.khronos.org/vulkan/" target="_blank">Vulkan</a>Ā® API** - -**lava** is a lean framework that provides **essentials** for **low-level graphics**<br />and is specially well suited for **prototyping**, **tooling** and **education** - -<br /> - -**C++20** + **Modular** / **Windows** + **Linux** / **[Demos](#demos)** + **[Projects](#projects)** - -[](https://git.io/liblava) [](LICENSE) [](https://www.codefactor.io/repository/github/liblava/liblava) [](https://discord.lava-block.com) [](https://paypal.me/liblava) [](https://twitter.com/liblava) - -<br /> - -### [Features](doc/Features.md) - -* written in **modern C++** with latest **Vulkan** support -* **run loop** abstraction for **window** and **input** handling -* plain **renderer** and **command buffer** model -* **texture** and **mesh** loading from virtual **file system** -* **GUI** + **camera** + **logging** + **utils** and much more - -<br /> - -##### ā Download latest **<a href="https://github.com/liblava/liblava/releases/latest">demo</a>** (December 20, 2020) - -<br /> - -<a href="https://www.khronos.org/vulkan/" target="_blank"><img align="right" hspace="20" src="res/Vulkan_170px_Dec16.png" width="300"></a> - -[Requirements](#requirements) [Build](#build) [Template](#template) - -<br /> - -### Docs - - **[Tutorial](doc/Tutorial.md)** [Guide](doc/Guide.md) [Reference](doc/Reference.md) [Tests](doc/Tests.md) [Third-Party](doc/Third-Party.md) [Install](doc/Install.md) - -<br /> - -### [Modules](doc/Modules.md) - -[](liblava/core) [](liblava/util) [](liblava/file) [](liblava/base) [](liblava/resource) [](liblava/asset) [](liblava/frame) [](liblava/block) [](liblava/app) [](liblava-demo) - -<br /> - -### Demos - -##### [lava spawn](liblava-demo/spawn.cpp) ā uniform buffer camera - -<a href="liblava-demo/spawn.cpp"></a> - -<br /> - -##### [lava lamp](liblava-demo/lamp.cpp) ā push constants to shader - -<a href="liblava-demo/lamp.cpp"></a> - -<br /> - -##### [lava triangle](liblava-demo/triangle.cpp) ā classic colored mesh - -<a href="liblava-demo/triangle.cpp"></a> - -<br /> - -### Projects - -##### [raytracing cubes](https://github.com/pezcode/lava-rt/blob/main/demo/cubes.cpp) ā raytraced reflecting cubes ([pezcode/lava-rt](https://github.com/pezcode/lava-rt)) - -<a href="https://github.com/pezcode/lava-rt/blob/main/demo/cubes.cpp"></a> - -<br /> - -## Requirements - -* **C++20** compatible compiler -* CMake **3.20+** -* [Vulkan SDK](https://vulkan.lunarg.com) - -<br /> - -## Build - -[](https://ci.appveyor.com/project/TheLavaBlock/liblava) [](https://travis-ci.com/liblava/liblava) - -```bash -git clone https://github.com/liblava/liblava.git -cd liblava - -git submodule update --init --recursive - -mkdir build -cd build - -cmake .. -make -``` - -<br /> - -## Template - -You can start coding with the **template** project. If you like you can rename it in [CMakeLists](CMakeLists.txt) - -ā Just put your code in the [src](src) folder. Everything you need is in [main.cpp](src/main.cpp) - -<br /> - -## Collaborate - -Use the [issue tracker](https://github.com/liblava/liblava/issues) to report any bug or compatibility issue - -:heart: Thanks to all **[contributors](https://github.com/liblava/liblava/graphs/contributors)** making **liblava** flow... - -<br /> - -### Support - -If you want to contribute, we suggest the following: - -1. Fork the [official repository](https://github.com/liblava/liblava/fork) -2. Apply your changes to your fork -3. Submit a [pull request](https://github.com/liblava/liblava/pulls) describing the changes you have made - -<br /> - -## License - -<a href="https://opensource.org" target="_blank"><img align="right" width="90" src="http://opensource.org/trademarks/opensource/OSI-Approved-License-100x137.png" style="margin:0px 0px 0px 80px"></a> - -**liblava** is licensed under [MIT License](LICENSE.md) which allows you to use the software for any purpose you might like, including commercial and for-profit use! - -<br /> - -However, this library includes several [Third-Party](doc/Third-Party.md) libraries, which are licensed under their own respective **Open Source** licenses ā They all allow static linking with closed source software - -**All copies of liblava must include a copy of the MIT License terms and the copyright notice** - -##### Vulkan and the Vulkan logo are trademarks of the <a href="http://www.khronos.org" target="_blank">Khronos Group Inc.</a> -##### Copyright (c) 2018-present, <a href="https://lava-block.com">Lava Block OĆ</a> and [contributors](https://github.com/liblava/liblava/graphs/contributors) - -<br /> - -<a href="https://git.io/liblava"><img src="https://github.com/liblava.png" width="50"></a> +# Virtual Reality Rendering Framework +This framework is based on [liblava](https://github.com/liblava/liblava) and was extended to include features necessary for developing and evaluating new stereo rendering strategies. +A special focus of the framework lies on remote rendering for standalone consumer HMDs over WiFi. + +In order to support multiple APIs as well as local and remote rendering a [Headset interface](src/headset/headset.hpp) was introduced with implementations for +* [OpenXR Headset](src/headset/openxr_headset.hpp), which uses the [OpenXR standard](https://www.khronos.org/openxr/) developed by Khronos to communicate with an HMD. +* [OpenVR Headset (legacy)](src/headset/openvr_headset.hpp), which uses the [OpenVR API](https://github.com/ValveSoftware/openvr) developed by Valve to communicate with HMDs via the SteamVR platform. +* [Remote Headset](src/headset/remote_headset.hpp), which uses custom protocol to communicate with a standalone HMD running a [custom application](https://git-ce.rwth-aachen.de/vr-vis/VR-Group/hmd-streaming). +* [Emulated Headset](src/headset/emulated_headset.hpp), which is a fallback solution to test out the rendering techniques without the need to having an actual HMD attached. + +## Rendering +This framework is intended to investiage different stereo rendering strategies. +Thus, a [stereo strategy interface](src/strategy/stereo_strategy.hpp) was created to easiliy switch between different strategies and compare them. +Currently, there exist the following stereo strategies: +* [Naive Stereo Forward](src/strategy/native_stereo_forward.hpp): renders the image for one eye at a time using forward shading. +* [Naive Stereo Deferred](src/strategy/native_stereo_deferred.hpp): renders the image for one eye at a time using deferred shading. +* [Multi View Stereo](src/strategy/native_stereo_deferred.hpp): renders both images simulatenously using multi-view. +* [Depth Peeling Reprojection](src/strategy/depth_peeling_reprojection.hpp): a custom rendering technique desribed below. + +In general, the framework supports [shadow mapping](src/utility/shadow_cache.hpp) and approximate global illumination using [light propagation volumes](src/utility/indirect_cache.hpp). +For evaluation of the performance and quality of the different rendering techniques the framework provides utility functions for [measuring gpu times](src/utility/pass_timer.hpp) and [capturing images](src/utility/frame_capture.hpp) for an external comparison to a ground truth. + +## Depth Peeling Reprojection +Depth Peeling Reprojection is a rendering technique that aims to reduce the duplicate shading that occurs when rendering images for the left and right eye in virtual reality applications. +Instead of rendering the scene from two perspectives, it will render the first two layers from a single perspective similar to [Mara et. al, Deep G-Buffers for Stable Global Illumination Approximation](https://research.nvidia.com/publication/2016-06_deep-g-buffers-stable-global-illumination-approximation). +The goal of this approach is to have more information available when reprojecting the resulting images and, thus, having less artifacts due to disoccluded regions. +Especially when considering streaming the result wirelessly to remote clients it is critical to have reprojection strategies that can handle lost or delayed frames nicely. diff --git a/ext/assimp b/ext/assimp index 67eae8ee5afa149b11267de8ec87de1538fa80b6..5b7ff294b83335557f2786ffe70642318851ae17 160000 --- a/ext/assimp +++ b/ext/assimp @@ -1 +1 @@ -Subproject commit 67eae8ee5afa149b11267de8ec87de1538fa80b6 +Subproject commit 5b7ff294b83335557f2786ffe70642318851ae17 diff --git a/src/encoder/encoder.cpp b/src/encoder/encoder.cpp index 99f7d1aa3c0c27336a330f40247cb321a296534f..43b29bf0cc2718a214b31be348c46d121db9f3a6 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 65adb4292e813cbf0c9aba405666abe8eac146f8..db1656d39fca9f70ad3e30ac779900131bced9a9 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 30925cbf152ed40e6706f496040b483d1c401be1..35a8b299b30934189320e95bc71cffa4b073161f 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 373f3e66d5967c8a06ce8a4e803f537911e969b1..aa5702dd9402ab541ccb16fcc2b11b175b024100 100644 --- a/src/headset/remote_headset.cpp +++ b/src/headset/remote_headset.cpp @@ -38,12 +38,20 @@ bool RemoteHeadset::on_setup_device(lava::device::create_param& parameters) bool RemoteHeadset::on_create() { + const CommandParser& command_parser = this->get_application()->get_command_parser(); + this->encoder_mode = command_parser.get_encoder_mode().value_or((EncoderMode)this->encoder_mode); + this->encoder_input_rate = command_parser.get_encoder_input_rate().value_or(this->encoder_input_rate); + this->encoder_key_rate = command_parser.get_encoder_key_rate().value_or(this->encoder_key_rate); + this->encoder_frame_rate = command_parser.get_encoder_frame_rate().value_or(this->encoder_frame_rate); + this->encoder_quality = command_parser.get_encoder_quality().value_or(this->encoder_quality); + this->encoder_bit_rate = command_parser.get_encoder_bitrate().value_or(this->encoder_bit_rate); + StatisticLog::Ptr statistic_log = this->get_application()->get_statistic_log(); this->statistic_send_bitrate = make_statistic("host_send_bitrate", UNIT_MBITS_PER_SECOND); - this->statistic_send_queue_size = make_statistic("host_send_queue", UNIT_KBITS); + this->statistic_send_queue_size = make_statistic("host_send_queue", UNIT_KBYTES); this->statistic_receive_bitrate = make_statistic("host_receive_bitrate", UNIT_MBITS_PER_SECOND); - this->statistic_receive_queue_size = make_statistic("host_receive_queue", UNIT_KBITS); + this->statistic_receive_queue_size = make_statistic("host_receive_queue", UNIT_KBYTES); statistic_log->add_statistic(this->statistic_send_bitrate); statistic_log->add_statistic(this->statistic_send_queue_size); @@ -116,7 +124,7 @@ bool RemoteHeadset::on_interface() return false; } - ImGui::DragFloat("Movement Speed", &this->movement_speed, 1.0f, 0.0f, 1000.0); + ImGui::DragFloat("Movement Speed", &this->movement_speed, 1.0f, 0.0f, 1000.0f); ImGui::Separator(); @@ -137,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) { @@ -154,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.1, 10.0)) + if (ImGui::SliderFloat("Bit-Rate [Mbit/s]", &this->encoder_bit_rate, 0.1f, 100.0f)) { for (Encoder::Ptr encoder : this->encoders) { @@ -165,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) { @@ -179,7 +187,7 @@ bool RemoteHeadset::on_interface() { if (encoder->is_supported(ENCODER_SETTING_QUALITY)) { - if (ImGui::SliderFloat("Quality", &this->encoder_quality, 0.0, 1.0)) + if (ImGui::SliderFloat("Quality", &this->encoder_quality, 0.0f, 1.0f)) { for (Encoder::Ptr encoder : this->encoders) { @@ -196,11 +204,6 @@ bool RemoteHeadset::on_interface() return false; } - if (ImGui::SliderInt("Send-Queue Limit (KBytes)", (int32_t*)&this->send_queue_size_limit, 1, 1000)) - { - this->transport->set_max_send_queue_size(this->send_queue_size_limit * 1000); - } - ImGui::Separator(); if(ImGui::Checkbox("Overlay", &this->overlay_enable)) @@ -417,8 +420,6 @@ bool RemoteHeadset::create_transport() this->on_transport_error(); }); - this->transport->set_max_send_queue_size(this->send_queue_size_limit * 1000); - if (!this->transport->create(this->server_port)) { return false; @@ -493,7 +494,7 @@ bool RemoteHeadset::create_encoders() encoder->set_key_rate(this->encoder_key_rate); encoder->set_frame_rate(this->encoder_frame_rate); - EncoderCodec codec = this->get_application()->get_command_parser().get_codec(); + EncoderCodec codec = this->get_application()->get_command_parser().get_encoder_codec(); lava::device_ptr device = this->get_application()->get_device(); lava::renderer& renderer = this->get_application()->get_renderer(); @@ -512,7 +513,7 @@ bool RemoteHeadset::create_encoders() std::filesystem::create_directory(this->video_directory); } - EncoderCodec codec = this->get_application()->get_command_parser().get_codec(); + EncoderCodec codec = this->get_application()->get_command_parser().get_encoder_codec(); std::string file_extension; switch(codec) @@ -675,7 +676,7 @@ void RemoteHeadset::update_overlay() void RemoteHeadset::on_setup(const glm::u32vec2& resolution) { - EncoderCodec codec = this->get_application()->get_command_parser().get_codec(); + EncoderCodec codec = this->get_application()->get_command_parser().get_encoder_codec(); this->transport->send_setup_complete(this->remote_strategy, codec, this->frame_id_count, this->near_plane, this->far_plane); diff --git a/src/headset/remote_headset.hpp b/src/headset/remote_headset.hpp index 02d97c42b110b0ee58fde8040cadbd19e96ced23..c5acbde69340af0972487ee7d0b29c07fcf9b966 100644 --- a/src/headset/remote_headset.hpp +++ b/src/headset/remote_headset.hpp @@ -113,17 +113,15 @@ private: std::vector<Encoder::Ptr> encoders; std::vector<std::fstream> encoder_files; - uint32_t encoder_mode = ENCODER_MODE_CONSTANT_QUALITY; + uint32_t encoder_mode = ENCODER_MODE_CONSTANT_BITRATE; uint32_t encoder_input_rate = 90; uint32_t encoder_key_rate = 90; uint32_t encoder_frame_rate = 90; float encoder_quality = 0.0; - float encoder_bit_rate = 8.0; //NOTE: Bitrate in Mbit per seconds + float encoder_bit_rate = 64.0; //NOTE: Bitrate in Mbit per seconds float encoder_last_submit = 0.0f; bool encoder_enable_submit = true; - uint32_t send_queue_size_limit = 100; //NOTE: Size in KBytes - bool overlay_enable = false; bool overlay_enable_pressed = false; uint32_t overlay_graph = 0; diff --git a/src/scene.cpp b/src/scene.cpp index c757da3ec5a3d11b3aaf4534e40a36c5709cbb44..11404f1237266739cdde19c509f05fd5f5777743 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -58,7 +58,7 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, uint32_t //NOTE: Need to compute scene bounds for sky-sphere this->reset_transforms(); - if (!this->load_sky_sphere(config.sky_sphere_file, config.sky_sphere_rings, config.sky_sphere_segments, device)) + if (!this->load_sky_sphere(config.sky_sphere_file, config.sky_sphere_rings, config.sky_sphere_segments, config.sky_sphere_intensity, device)) { return false; } @@ -94,6 +94,9 @@ bool Scene::create(const SceneConfig& config, lava::device_ptr device, uint32_t return false; } + this->exposure = config.scene_exposure; + this->exposure_changed = true; + //NOTE: Recompute all transfromations for final scene bounds and matrices this->reset_transforms(); @@ -233,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) { @@ -978,11 +981,11 @@ bool Scene::load_controller(const std::string& file_name, SceneUnit unit, SceneO return true; } -bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device) +bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, float intensity, lava::device_ptr device) { 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()); @@ -999,7 +1002,7 @@ bool Scene::load_sky_sphere(const std::string& file_name, uint32_t ring_count, u glsl::MaterialData& sky_material_data = sky_material.material_data; sky_material_data.base_color = glm::vec3(1.0f); sky_material_data.opacity = 1.0f; - sky_material_data.emissive = glm::vec3(1.0f); + sky_material_data.emissive = glm::vec3(intensity); sky_material_data.roughness = 1.0f; sky_material_data.padding1 = glm::uvec3(0); sky_material_data.metallic = 1.0f; @@ -1171,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++) @@ -1195,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)); } } } @@ -1228,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); @@ -1254,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; @@ -1357,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); @@ -1377,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; @@ -1394,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; @@ -1402,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; @@ -1433,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()); @@ -1443,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) { @@ -1506,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; @@ -1559,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); @@ -2114,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 6c6dd0e2660a7b318e30bd664a58971bc135a35d..32ea34cc6f6798e190862277c2c7b03420e53894 100644 --- a/src/scene.hpp +++ b/src/scene.hpp @@ -43,9 +43,12 @@ struct SceneConfig std::string controller_left_file; std::string controller_right_file; + float scene_exposure = 1.0f; + std::string sky_sphere_file; //NOTE: The given file has to be a .hdr file. uint32_t sky_sphere_rings = 16; //NOTE: Defines the number of rings used in the mesh of the sky-sphere. Needs to be at least 3. uint32_t sky_sphere_segments = 32; //NOTE: Defines the number of segments used in the mesh of the sky-sphere. Needs to be at least 3. + float sky_sphere_intensity = 1.0f; SceneUnit scene_unit = SCENE_UNIT_METERS; SceneUnit controller_left_unit = SCENE_UNIT_METERS; @@ -142,7 +145,6 @@ struct SceneAnimation { std::string name; float duration = 0.0f; - float ticks_per_second = 0.0f; std::vector<SceneAnimationChannel> channels; }; @@ -205,7 +207,7 @@ private: bool load_scene(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, SceneUVs uvs, lava::device_ptr device, std::map<std::string, SceneNodeIndex>& node_indices); bool load_controller(const std::string& file_name, SceneUnit unit, SceneOrientation orientation, SceneUVs uvs, lava::device_ptr device, SceneNodeIndex& node_index, std::map<std::string, SceneNodeIndex>& node_indices); - bool load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, lava::device_ptr device); + bool load_sky_sphere(const std::string& file_name, uint32_t ring_count, uint32_t segment_count, float intensity, lava::device_ptr device); bool load_nodes(const aiNode* node, float scale, uint32_t base_mesh, SceneNodeIndex parent_index, std::map<std::string, SceneNodeIndex>& node_indices); bool load_cameras(const aiScene* scene, float scale, const std::map<std::string, SceneNodeIndex>& node_indices); @@ -213,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/transport/transport.hpp b/src/transport/transport.hpp index 580ed5d670b4494bf7e4291ad9790235e2421125..cafde57896524ad5659d7031cec5e3e6e9155b56 100644 --- a/src/transport/transport.hpp +++ b/src/transport/transport.hpp @@ -181,7 +181,8 @@ public: virtual void set_on_transport_error(OnTransportError function) = 0; // The following functions should be thread safe - virtual void set_max_send_queue_size(uint32_t size) = 0; // The size is given in Bytes + virtual void set_max_send_queue_enabled(bool enabled) = 0; // Default: disabled + virtual void set_max_send_queue_size(uint32_t size) = 0; // The size is given in Bytes. Default: 128 * DATAGRAM_SIZE // The following function should be thread safe virtual TransportState get_state() = 0; @@ -190,6 +191,7 @@ public: virtual uint32_t get_max_send_queue_size() = 0; // The size is given in Bytes virtual uint32_t get_send_queue_size() = 0; // In Bytes virtual uint32_t get_receive_queue_size() = 0; // In Bytes + virtual bool is_max_send_queue_enabled() = 0; }; Transport::Ptr make_transport(TransportType transport_type); \ No newline at end of file diff --git a/src/transport/udp_transport.cpp b/src/transport/udp_transport.cpp index e10ac8139a191215173d8e6a19e3abde0a04551e..9ef30b3e3f8bfadc82cc325143d7636072fcb3ce 100644 --- a/src/transport/udp_transport.cpp +++ b/src/transport/udp_transport.cpp @@ -391,6 +391,12 @@ void UDPTransport::set_on_transport_error(OnTransportError function) this->on_transport_error = std::move(function); } +void UDPTransport::set_max_send_queue_enabled(bool enabled) +{ + std::unique_lock<std::mutex> lock(this->worker_mutex); + this->max_send_queue_enabled = enabled; +} + void UDPTransport::set_max_send_queue_size(uint32_t size) { std::unique_lock<std::mutex> lock(this->worker_mutex); @@ -463,6 +469,15 @@ uint32_t UDPTransport::get_receive_queue_size() return bytes; } +bool UDPTransport::is_max_send_queue_enabled() +{ + std::unique_lock<std::mutex> lock(this->worker_mutex); + bool value = this->max_send_queue_enabled; + lock.unlock(); + + return value; +} + void UDPTransport::set_state(TransportState state) { std::unique_lock<std::mutex> lock(this->worker_mutex); @@ -529,23 +544,26 @@ void UDPTransport::send_datagram(const Datagram& datagram) { std::unique_lock<std::mutex> lock(this->worker_mutex); - uint32_t bytes = 0; - - for (uint32_t index = 1; index < this->send_queue.size(); index++) + if (this->max_send_queue_enabled) { - const Datagram& datagram = this->send_queue[index]; - const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer; - bytes += header->size; - } + uint32_t bytes = 0; - while (bytes > this->max_send_queue_size && this->send_queue.size() > 1) - { - const Datagram& datagram = this->send_queue[1]; - const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer; - bytes -= header->size; + for (uint32_t index = 1; index < this->send_queue.size(); index++) + { + const Datagram& datagram = this->send_queue[index]; + const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer; + bytes += header->size; + } - this->datagram_pool.release_datagram(datagram); - this->send_queue.erase(this->send_queue.begin() + 1); + while (bytes > this->max_send_queue_size && this->send_queue.size() > 1) + { + const Datagram& datagram = this->send_queue[1]; + const UDPPacketHeader* header = (const UDPPacketHeader*)datagram.buffer; + bytes -= header->size; + + this->datagram_pool.release_datagram(datagram); + this->send_queue.erase(this->send_queue.begin() + 1); + } } this->send_queue.push_back(datagram); diff --git a/src/transport/udp_transport.hpp b/src/transport/udp_transport.hpp index 5628fe137fc5157ec60eaf483a5a899eae950007..909918cd8e8fff774e45696edbb4d7499e969c1d 100644 --- a/src/transport/udp_transport.hpp +++ b/src/transport/udp_transport.hpp @@ -42,6 +42,7 @@ public: void set_on_performance_sample(OnPerformanceSample function) override; void set_on_transport_error(OnTransportError function) override; + void set_max_send_queue_enabled(bool enabled) override; void set_max_send_queue_size(uint32_t size) override; TransportState get_state() override; @@ -50,6 +51,7 @@ public: uint32_t get_max_send_queue_size() override; uint32_t get_send_queue_size() override; uint32_t get_receive_queue_size() override; + bool is_max_send_queue_enabled() override; private: void set_state(TransportState state); @@ -92,6 +94,7 @@ private: uint32_t bits_received = 0; //NOTE: Owned by worker_thread double bitrate_send = 0.0; //NOTE: Protected by worker_mutex double bitrate_receive = 0.0; //NOTE: Protected by worker_mutex + bool max_send_queue_enabled = false; //NOTE: Protected by worker_mutex uint32_t max_send_queue_size = DATAGRAM_SIZE * 128; //NOTE: Protected by worker_mutex bool send_active = false; //NOTE: Protected by worker_mutex double inactive_time = 0.0; //NOTE: Owned by worker_thread diff --git a/src/utility/command_parser.cpp b/src/utility/command_parser.cpp index 2f370c8582a21574ded9c2652ccd1747565db536..3bf9d9943fc6db18f5efd4629bbe906239949810 100644 --- a/src/utility/command_parser.cpp +++ b/src/utility/command_parser.cpp @@ -76,6 +76,16 @@ bool CommandParser::parse_command(const argh::parser& cmd_line) this->sky_sphere_path = path; } + else if (parameter.first == "sky-sphere-intensity") + { + this->sky_sphere_intensity = std::atoi(parameter.second.c_str()); + } + + else if (parameter.first == "scene-exposure") + { + this->scene_exposure = std::atof(parameter.second.c_str()); + } + else if (parameter.first == "scene-unit") { if (!this->set_scene_unit(parameter.second)) @@ -132,14 +142,47 @@ bool CommandParser::parse_command(const argh::parser& cmd_line) } } - else if (parameter.first == "codec") + else if (parameter.first == "encoder-codec") { - if(!this->set_codec(parameter.second)) + if(!this->set_encoder_codec(parameter.second)) { return false; } } + else if (parameter.first == "encoder-mode") + { + if (!this->set_encoder_mode(parameter.second)) + { + return false; + } + } + + else if (parameter.first == "encoder-input-rate") + { + this->encoder_input_rate = std::atoi(parameter.second.c_str()); + } + + else if (parameter.first == "encoder-key-rate") + { + this->encoder_key_rate = std::atoi(parameter.second.c_str()); + } + + else if (parameter.first == "encoder-frame-rate") + { + this->encoder_frame_rate = std::atoi(parameter.second.c_str()); + } + + else if (parameter.first == "encoder-quality") + { + this->encoder_quality = (float)std::atof(parameter.second.c_str()); + } + + else if (parameter.first == "encoder-bitrate") + { + this->encoder_bitrate = (float)std::atof(parameter.second.c_str()); + } + else if (parameter.first == "animation") { this->animation_name = parameter.second; @@ -266,9 +309,9 @@ bool CommandParser::parse_command(const argh::parser& cmd_line) } } - if (this->encoder == ENCODER_TYPE_VULKAN && this->codec == ENCODER_CODEC_H265) + if (this->encoder == ENCODER_TYPE_VULKAN && this->encoder_codec == ENCODER_CODEC_H265) { - std::cout << "Configuration 'encoder = vulkan' and 'codec = h265' not supported. Use option --help to get more information" << std::endl; + std::cout << "Configuration 'encoder = vulkan' and 'encoder-codec = h265' not supported. Use option --help to get more information" << std::endl; return false; } @@ -296,9 +339,9 @@ EncoderType CommandParser::get_encoder() const return this->encoder; } -EncoderCodec CommandParser::get_codec() const +EncoderCodec CommandParser::get_encoder_codec() const { - return this->codec; + return this->encoder_codec; } SceneUnit CommandParser::get_scene_unit() const @@ -326,6 +369,16 @@ std::optional<std::string> CommandParser::get_sky_sphere_path() const return this->sky_sphere_path; } +float CommandParser::get_scene_exposure() const +{ + return this->scene_exposure; +} + +float CommandParser::get_sky_sphere_intensity() const +{ + return this->sky_sphere_intensity; +} + std::optional<std::string> CommandParser::get_animation_name() const { return this->animation_name; @@ -351,6 +404,36 @@ std::optional<float> CommandParser::get_update_rate() const return this->update_rate; } +std::optional<EncoderMode> CommandParser::get_encoder_mode() const +{ + return this->encoder_mode; +} + +std::optional<uint32_t> CommandParser::get_encoder_input_rate() const +{ + return this->encoder_input_rate; +} + +std::optional<uint32_t> CommandParser::get_encoder_key_rate() const +{ + return this->encoder_key_rate; +} + +std::optional<uint32_t> CommandParser::get_encoder_frame_rate() const +{ + return this->encoder_frame_rate; +} + +std::optional<float> CommandParser::get_encoder_quality() const +{ + return this->encoder_quality; +} + +std::optional<float> CommandParser::get_encoder_bitrate() const +{ + return this->encoder_bitrate; +} + bool CommandParser::should_benchmark() const { return this->benchmark; @@ -389,6 +472,10 @@ void CommandParser::show_help() std::cout << "Options:" << std::endl; std::cout << " --sky-sphere={file} The sky sphere that should be shown. The given file has to be a .hdr file." << std::endl; std::cout << " If no file is specified, sky stays black." << std::endl; + std::cout << " --sky-sphere-intensity={intensity} The value with which the sky sphere is multiplied." << std::endl; + std::cout << " The default value is 1.0." << std::endl; + std::cout << " --scene-exposure={exposure} The exposure value that should be used during rendering." << std::endl; + std::cout << " The default value is 1.0." << std::endl; std::cout << " --scene-unit={unit} The unit in which the geometry of the scene is defined." << std::endl; std::cout << " Options: meters (default), centimeters" << std::endl; std::cout << " --scene-orientation={orientation} The coordinate system in which the geometry of the scene is defined." << std::endl; @@ -405,10 +492,24 @@ void CommandParser::show_help() std::cout << " --encoder={encoder} The encoder that should be used when a remote headset is used." << std::endl; std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; std::cout << " Options: vulkan (default), nvidia" << std::endl; - std::cout << " --codec={codec} The codec that should be used by the encoder." << std::endl; + std::cout << " --encoder-codec={codec} The codec that should be used by the encoder." << std::endl; std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; std::cout << " The vulkan encoder only supports h264 encoding." << std::endl; std::cout << " Options: h264 (default), h265" << std::endl; + std::cout << " --encoder-mode={mode} The mode which determins the quality of the encoded images." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; + std::cout << " Options: constant-bitrate (default), constant-quality" << std::endl; + std::cout << " --encoder-input-rate={rate} The rate with which images are submitted to the encoders. The default value is 90 hz." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; + std::cout << " --encoder-key-rate={rate} The rate with which the endoder inserts key-frames. The default value is 90 frames." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; + std::cout << " --encoder-frame-rate={rate} The frame rate which is used to determine the quality when constant bitrate is used. The default value is 90 hz." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; + std::cout << " --encoder-quality={quailty} The quality that is used in constant quality mode." << std::endl; + std::cout << " A value of 0.0 specifies the lowest quality and a value of 1.0 specifies the highest quaility. The default value is 0.0." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; + std::cout << " --encoder-bitrate={bitrate} The bitrate which should be used in constant bitrate mode. The default value is 25.0 Mbit/s." << std::endl; + std::cout << " This parameter should only be used when the parameter headset is set to remote." << std::endl; std::cout << " --benchmark Play animation once and close program after completion." << std::endl; std::cout << " If not set, the application runs indefinitely and the interface is enabled." << std::endl; std::cout << " --animation={animation_name} The name of the animation that should be played." << std::endl; @@ -535,21 +636,43 @@ bool CommandParser::set_encoder(const std::string& name) return true; } -bool CommandParser::set_codec(const std::string& name) +bool CommandParser::set_encoder_codec(const std::string& name) { if (name == "h264") { - this->codec = ENCODER_CODEC_H264; + this->encoder_codec = ENCODER_CODEC_H264; } else if(name == "h265") { - this->codec = ENCODER_CODEC_H265; + this->encoder_codec = ENCODER_CODEC_H265; + } + + else + { + std::cout << "Invalid option set of parameter 'encoder-codec'. Use option --help to get more information." << std::endl; + + return false; + } + + return true; +} + +bool CommandParser::set_encoder_mode(const std::string& name) +{ + if (name == "constant-quality") + { + this->encoder_mode = ENCODER_MODE_CONSTANT_QUALITY; + } + + else if (name == "constant-bitrate") + { + this->encoder_mode = ENCODER_MODE_CONSTANT_BITRATE; } else { - std::cout << "Invalid option set of parameter 'codec'. Use option --help to get more information." << std::endl; + std::cout << "Invalid option set of parameter 'encoder-mode'. Use option --help to get more information." << std::endl; return false; } diff --git a/src/utility/command_parser.hpp b/src/utility/command_parser.hpp index c451afa89d4df3c278bbb21b979ea13cd1cfdfa3..6a123b5e7c7a22b9e68f42f203a1a09c188fb223 100644 --- a/src/utility/command_parser.hpp +++ b/src/utility/command_parser.hpp @@ -25,13 +25,15 @@ public: StereoStrategyType get_stereo_strategy() const; TransportType get_transport() const; EncoderType get_encoder() const; - EncoderCodec get_codec() const; + EncoderCodec get_encoder_codec() const; SceneUnit get_scene_unit() const; SceneOrientation get_scene_orientation() const; SceneUVs get_scene_uvs() const; const std::string& get_scene_path() const; std::optional<std::string> get_sky_sphere_path() const; + float get_scene_exposure() const; + float get_sky_sphere_intensity() const; std::optional<std::string> get_animation_name() const; std::optional<uint32_t> get_animation_index() const; @@ -41,6 +43,13 @@ public: std::optional<float> get_update_rate() const; + std::optional<EncoderMode> get_encoder_mode() const; + std::optional<uint32_t> get_encoder_input_rate() const; + std::optional<uint32_t> get_encoder_key_rate() const; + std::optional<uint32_t> get_encoder_frame_rate() const; + std::optional<float> get_encoder_quality() const; + std::optional<float> get_encoder_bitrate() const; + bool should_benchmark() const; bool should_show_companion_window() const; bool should_write_frames() const; @@ -55,7 +64,8 @@ private: bool set_stero_strategy(const std::string& name); bool set_transport(const std::string& name); bool set_encoder(const std::string& name); - bool set_codec(const std::string& name); + bool set_encoder_codec(const std::string& name); + bool set_encoder_mode(const std::string& name); bool set_scene_unit(const std::string& name); bool set_scene_orientation(const std::string& name); bool set_scene_uvs(const std::string& name); @@ -65,13 +75,15 @@ private: StereoStrategyType stereo_strategy = STEREO_STRATEGY_TYPE_NATIVE_FORWARD; TransportType transport = TRANSPORT_TYPE_UDP; EncoderType encoder = ENCODER_TYPE_VULKAN; - EncoderCodec codec = ENCODER_CODEC_H264; + EncoderCodec encoder_codec = ENCODER_CODEC_H264; SceneUnit scene_unit = SCENE_UNIT_METERS; SceneOrientation scene_orientation = SCENE_ORIENTATION_RIGHT_HANDED; SceneUVs scene_uvs = SCENE_UVS_Y_FLIP; std::string scene_path = ""; std::optional<std::string> sky_sphere_path; + float scene_exposure = 1.0f; + float sky_sphere_intensity = 1.0f; std::optional<std::string> animation_name; std::optional<uint32_t> animation_index; @@ -81,6 +93,13 @@ private: std::optional<float> update_rate; + std::optional<EncoderMode> encoder_mode; + std::optional<uint32_t> encoder_input_rate; + std::optional<uint32_t> encoder_key_rate; + std::optional<uint32_t> encoder_frame_rate; + std::optional<float> encoder_quality; + std::optional<float> encoder_bitrate; + bool benchmark = false; bool show_companion_window = false; bool write_frames = false; diff --git a/src/utility/datagram.hpp b/src/utility/datagram.hpp index 7a03502fb452aa463c78da987c8f1a193ce954b7..2f9d9755a6c1b28fffd8d549cad9af172e539dff 100644 --- a/src/utility/datagram.hpp +++ b/src/utility/datagram.hpp @@ -2,7 +2,7 @@ #include <cstdint> #include <vector> -#define DATAGRAM_SIZE 512 +#define DATAGRAM_SIZE 1024 struct Datagram { diff --git a/src/utility/frame_capture.cpp b/src/utility/frame_capture.cpp index 5c8680b53d5229de33baf8bb6c51bb8c69468748..2ff93093529d27401bffc356a6fc8ccc78a26590 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 045edc984971f3bf471b6c52ae987fd84bb19f9e..6b0da71c4609ca3253d4b625168697625bbd8de4 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 f8883be7e2d7875235c17dd3effbdf145bda6e5e..84c73ac779e7fc0300e50390bee5e6e5924ab56f 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 307c70e886c5738955496ccf21b3617807df516f..8ddde89a56a04368e21d287d43a335d1bd100304 100644 --- a/src/vr_application.cpp +++ b/src/vr_application.cpp @@ -321,18 +321,23 @@ bool VRApplication::on_create() config.scene_unit = this->command_parser.get_scene_unit(); config.scene_orientation = this->command_parser.get_scene_orientation(); config.scene_uvs = this->command_parser.get_scene_uvs(); + config.scene_exposure = this->command_parser.get_scene_exposure(); if (this->command_parser.get_sky_sphere_path().has_value()) { config.sky_sphere_file = this->command_parser.get_sky_sphere_path().value(); + config.sky_sphere_intensity = this->command_parser.get_sky_sphere_intensity(); } 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; } @@ -526,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!"); + } } } @@ -560,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!"); @@ -567,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!"); @@ -578,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) @@ -603,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); @@ -619,6 +641,8 @@ bool VRApplication::on_interface() this->pass_timer->interface(); this->app->draw_about(true); + + ImGui::PopID(); } ImGui::End();