diff --git a/CMakeLists.txt b/CMakeLists.txt index 72a245b0a77c4ebe81720bffcd75974d3b04f6de..271e3ecc586523d41dc23de78f56f1dc578ba7e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -488,7 +488,7 @@ set(EXT_INCLUDE_DIRS ${LIBLAVA_EXT_DIR}/spdlog/include # Since the encoder is written against a specific version of the vulkan headers, the required version of the headers has to be added to the project. # ${LIBLAVA_EXT_DIR}/Vulkan-Headers/include - ${LIBLAVA_INC_DIR}/vulkan_headers + ${LIBLAVA_INC_DIR}/vulkan_headers ${LIBLAVA_EXT_DIR}/VulkanMemoryAllocator/include ${LIBLAVA_EXT_DIR}/volk #${LIBLAVA_EXT_DIR}/stb @@ -689,6 +689,11 @@ message("======================================================================= ${SRC_DIR}/vr_application.hpp ${SRC_DIR}/vr_application.cpp ${SRC_DIR}/scene.hpp ${SRC_DIR}/scene.cpp + #Encoder + ${SRC_DIR}/encoder/encoder.hpp ${SRC_DIR}/encoder/encoder.cpp + ${SRC_DIR}/encoder/vulkan_encoder.hpp ${SRC_DIR}/encoder/vulkan_encoder.cpp + ${SRC_DIR}/encoder/nvidia_encoder.hpp ${SRC_DIR}/encoder/nvidia_encoder.cpp + #Headset ${SRC_DIR}/headset/headset.hpp ${SRC_DIR}/headset/headset.cpp ${SRC_DIR}/headset/emulated_headset.hpp ${SRC_DIR}/headset/emulated_headset.cpp @@ -713,7 +718,6 @@ message("======================================================================= #Utility ${SRC_DIR}/utility/command_parser.hpp ${SRC_DIR}/utility/command_parser.cpp ${SRC_DIR}/utility/companion_window.hpp ${SRC_DIR}/utility/companion_window.cpp - ${SRC_DIR}/utility/encoder.hpp ${SRC_DIR}/utility/encoder.cpp ${SRC_DIR}/utility/extern_fence.hpp ${SRC_DIR}/utility/extern_fence.cpp ${SRC_DIR}/utility/frame_capture.hpp ${SRC_DIR}/utility/frame_capture.cpp ${SRC_DIR}/utility/geometry_buffer.hpp ${SRC_DIR}/utility/geometry_buffer.cpp @@ -803,6 +807,7 @@ message("======================================================================= source_group("" FILES ${SRC_DIR}/scene.cpp) source_group("" FILES ${SRC_DIR}/scene.hpp) + source_group("Encoder" REGULAR_EXPRESSION ${SRC_DIR}/encoder/*) source_group("Headset" REGULAR_EXPRESSION ${SRC_DIR}/headset/*) source_group("Strategy" REGULAR_EXPRESSION ${SRC_DIR}/strategy/*) source_group("Stream" REGULAR_EXPRESSION ${SRC_DIR}/stream/*) diff --git a/src/encoder/encoder.cpp b/src/encoder/encoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..6c54c434527947a5a79d57204a0d9c41a0744619 --- /dev/null +++ b/src/encoder/encoder.cpp @@ -0,0 +1,51 @@ +#include "encoder.hpp" +#include "vulkan_encoder.hpp" +#include "nvidia_encoder.hpp" + +bool setup_instance_for_encoder(EncoderType encoder_type, lava::frame_config& config) +{ + switch (encoder_type) + { + case ENCODER_TYPE_VULKAN: + return setup_instance_for_vulkan_encoder(config); + case ENCODER_TYPE_NVIDIA: + return setup_instance_for_nvidia_encoder(config); + default: + lava::log()->error("Unkown encoder type!"); + break; + } + + return false; +} + +bool setup_device_for_encoder(EncoderType encoder_type, lava::device::create_param& parameters) +{ + switch (encoder_type) + { + case ENCODER_TYPE_VULKAN: + return setup_device_for_vulkan_encoder(parameters); + case ENCODER_TYPE_NVIDIA: + return setup_device_for_nvidia_encoder(parameters); + default: + lava::log()->error("Unkown encoder type!"); + break; + } + + return false; +} + +Encoder::Ptr make_encoder(EncoderType encoder_type) +{ + switch (encoder_type) + { + case ENCODER_TYPE_VULKAN: + return std::make_shared<VulkanEncoder>(); + case ENCODER_TYPE_NVIDIA: + return std::make_shared<NvidiaEncoder>(); + default: + lava::log()->error("Unkown encoder type!"); + break; + } + + return nullptr; +} \ No newline at end of file diff --git a/src/encoder/encoder.hpp b/src/encoder/encoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a766aed117c8b8c12f5e50526be1ff09b1ffc419 --- /dev/null +++ b/src/encoder/encoder.hpp @@ -0,0 +1,92 @@ +/* + The encoder can be used to create a h264 stream out of a sequence of images. + In order to be able to use the encoder, it is neccessary to call the function setup_instance_for_encoder(...) during the setup of the vulkan instance. + Besides that, it is neccessary to call the function setup_device_for_encoder(...) during the setup of the vulkan device. + A frame can be submitted for encoding using the function encode(...). + After the completion of an encode task, a callback function is executed to which the resulting ouput of the task is passed. + Example: + + //During on_setup_instance(...) + setup_instance_for_encoder(...); + + //During on_setup_device(...) + setup_device_for_encoder(...); + + //During creation and submission of a frame + Encoder::Ptr encoder = make_encoder(); + + encoder->create(...); + + encoder->encode(..., image, ..., [](const std::span<uint8_t>& content, bool is_config) + { + //Use content + }); +*/ + +#pragma once +#include <liblava/lava.hpp> +#include <glm/glm.hpp> +#include <functional> +#include <span> +#include <memory> +#include <cstdint> + +enum EncoderMode +{ + ENCODER_MODE_CONSTANT_BITRATE, + ENCODER_MODE_CONSTANT_QUALITY +}; + +// When adding or removing an encoder, the functions make_encoder(...), setup_instance_for_encoder(...), setup_device_for_encoder(...) as well as +// the function set_encoder(...) of the CommandParser need to be adapted accordingly. +enum EncoderType +{ + ENCODER_TYPE_VULKAN, //Use vulkan video for encoding + ENCODER_TYPE_NVIDIA //Use Nvenc for encoding +}; + +class Encoder +{ +public: + typedef std::shared_ptr<Encoder> Ptr; + + //NOTE: If the is_config flag is true, the content passed to this callback function contains a sequence parameter set or picture parameter set. + typedef std::function<void(const std::span<uint8_t>& content, bool is_config)> OnEncodeComplete; + typedef std::function<void()> OnEncodeError; + +public: + Encoder() = default; + virtual ~Encoder() = default; + + //NOTE: The parameter input_buffers defines how many input images the encoder should provide. + virtual bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers) = 0; + virtual void destroy() = 0; + + //NOTE: The following functions should be thread safe. + //NOTE: The callback function is executed only by the worker thread of the encoder. + virtual bool encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function) = 0; + + //NOTE: The callbacks are executed only by the worker thread of the encoder. + //NOTE: Set the callbacks before calling create and don't do it again after calling create. + virtual void set_on_encode_error(OnEncodeError function) = 0; + + //NOTE: The following functions should be thread safe. + //NOTE: The following settings are only suggestions and can be ignored if for example the feature is not supported. + virtual void set_mode(EncoderMode mode) = 0; //NOTE: Defines if the encoder should use constant quality or constant bitrate. Default: constant quality + virtual void set_quality(double quality) = 0; //NOTE: The quality ranges from 0.0 (low quality) to 1.0 (high quality). Default: 0.0 + virtual void set_bitrate(double bitrate) = 0; //NOTE: The bitrate is given in MBits/s. Default: 5.0 + virtual void set_key_rate(uint32_t key_rate) = 0; //NOTE: The keyrate is defined as the number of frames between two i-frames. Default: 90 + virtual void set_frame_rate(uint32_t frame_rate) = 0; //NOTE: The frame rate is given in frames per second. Default: 90 + + //NOTE: The following functions should be thread safe. + virtual EncoderMode get_mode() const = 0; + virtual double get_quality() const = 0; + virtual double get_bitrate() const = 0; + virtual uint32_t get_key_rate() const = 0; + virtual uint32_t get_frame_rate() const = 0; +}; + +bool setup_instance_for_encoder(EncoderType encoder_type, lava::frame_config& config); +bool setup_device_for_encoder(EncoderType encoder_type, lava::device::create_param& parameters); + +Encoder::Ptr make_encoder(EncoderType encoder_type); \ No newline at end of file diff --git a/src/encoder/nvidia_encoder.cpp b/src/encoder/nvidia_encoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..a60a23ec33d54fd0ddf48feff964881f7b1d321b --- /dev/null +++ b/src/encoder/nvidia_encoder.cpp @@ -0,0 +1,81 @@ +#include "nvidia_encoder.hpp" + +bool NvidiaEncoder::create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers) +{ + return false; +} + +void NvidiaEncoder::destroy() +{ + +} + +bool NvidiaEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function) +{ + return false; +} + +void NvidiaEncoder::set_on_encode_error(OnEncodeError function) +{ + this->on_encoder_error = std::move(function); +} + +void NvidiaEncoder::set_mode(EncoderMode mode) +{ + this->mode = mode; +} + +void NvidiaEncoder::set_quality(double quality) +{ + this->quality = quality; +} + +void NvidiaEncoder::set_bitrate(double bitrate) +{ + this->bitrate = bitrate; +} + +void NvidiaEncoder::set_key_rate(uint32_t key_rate) +{ + this->key_rate = key_rate; +} + +void NvidiaEncoder::set_frame_rate(uint32_t frame_rate) +{ + this->frame_rate = frame_rate; +} + +EncoderMode NvidiaEncoder::get_mode() const +{ + return this->mode; +} + +double NvidiaEncoder::get_quality() const +{ + return this->quality; +} + +double NvidiaEncoder::get_bitrate() const +{ + return this->bitrate; +} + +uint32_t NvidiaEncoder::get_key_rate() const +{ + return this->key_rate; +} + +uint32_t NvidiaEncoder::get_frame_rate() const +{ + return this->frame_rate; +} + +bool setup_instance_for_nvidia_encoder(lava::frame_config& config) +{ + return false; +} + +bool setup_device_for_nvidia_encoder(lava::device::create_param& parameters) +{ + return false; +} \ No newline at end of file diff --git a/src/encoder/nvidia_encoder.hpp b/src/encoder/nvidia_encoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..d80c96ee6908ca1c0dea303e82bd34d3fbadce10 --- /dev/null +++ b/src/encoder/nvidia_encoder.hpp @@ -0,0 +1,49 @@ +#pragma once +#include <liblava/lava.hpp> +#include <glm/glm.hpp> +#include <functional> +#include <span> +#include <memory> +#include <cstdint> + +#include "encoder.hpp" + +class NvidiaEncoder : public Encoder +{ +public: + typedef std::shared_ptr<NvidiaEncoder> Ptr; + +public: + NvidiaEncoder() = default; + + bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers); + void destroy(); + + bool encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function); + + void set_on_encode_error(OnEncodeError function); + + void set_mode(EncoderMode mode); + void set_quality(double quality); + void set_bitrate(double bitrate); + void set_key_rate(uint32_t key_rate); + void set_frame_rate(uint32_t frame_rate); + + EncoderMode get_mode() const; + double get_quality() const; + double get_bitrate() const; + uint32_t get_key_rate() const; + uint32_t get_frame_rate() const; + +private: + OnEncodeError on_encoder_error; + + EncoderMode mode = ENCODER_MODE_CONSTANT_QUALITY; + double quality = 0.0; + double bitrate = 5.0; + uint32_t key_rate = 90; + uint32_t frame_rate = 90; +}; + +bool setup_instance_for_nvidia_encoder(lava::frame_config& config); +bool setup_device_for_nvidia_encoder(lava::device::create_param& parameters); \ No newline at end of file diff --git a/src/utility/encoder.cpp b/src/encoder/vulkan_encoder.cpp similarity index 92% rename from src/utility/encoder.cpp rename to src/encoder/vulkan_encoder.cpp index 51112a6c23cc50819cb5db8795edaa421bdec583..b11d8b5e71d18068b60ef90367b17ffc72400e1d 100644 --- a/src/utility/encoder.cpp +++ b/src/encoder/vulkan_encoder.cpp @@ -1,8 +1,8 @@ -#include "encoder.hpp" +#include "vulkan_encoder.hpp" #include <array> #include <vk_video/vulkan_video_codecs_common.h> -bool Encoder::create(lava::device_ptr device, const lava::renderer& renderer, asio::executor executor, const glm::uvec2& size, uint32_t frame_count) +bool VulkanEncoder::create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t frame_count) { //Get the default graphics queue of lava for querey ownership transiations this->default_queue = renderer.get_queue(); @@ -97,7 +97,7 @@ bool Encoder::create(lava::device_ptr device, const lava::renderer& renderer, a //Create a pool of frames. Each frame is used for a single input image that needs to be encoded. for (uint32_t index = 0; index < frame_count; index++) { - EncoderFrame::Ptr frame = make_encoder_frame(); + VulkanEncoderFrame::Ptr frame = std::make_shared<VulkanEncoderFrame>(); frame->output_query_index = index; //Create the images that are used for the encoding as input images. @@ -144,7 +144,7 @@ bool Encoder::create(lava::device_ptr device, const lava::renderer& renderer, a } //Allocate a command buffer as well as a semaphore and a fence for the frame. - if (!this->create_encode_resources(device, executor, frame)) + if (!this->create_encode_resources(device, this->worker_executor.get_executor(), frame)) { return false; } @@ -158,14 +158,19 @@ bool Encoder::create(lava::device_ptr device, const lava::renderer& renderer, a this->frame_count = frame_count; this->frame_index = 0; + this->worker_thread = std::thread([this]() + { + this->worker_executor.run(); + }); + return true; } -void Encoder::destroy() +void VulkanEncoder::destroy() { lava::device_ptr device = this->descriptor_layout->get_device(); - for (EncoderFrame::Ptr frame : this->frame_list) + for (VulkanEncoderFrame::Ptr frame : this->frame_list) { this->destroy_frame(device, frame); } @@ -177,43 +182,7 @@ void Encoder::destroy() this->destroy_session(device); } -void Encoder::set_mode(EncoderMode mode) -{ - this->setting_mode = mode; - this->control_change = true; -} - -void Encoder::set_key_rate(uint32_t key_rate) -{ - this->setting_key_rate = key_rate; - this->control_change = true; -} - -void Encoder::set_bit_rate(double bit_rate) -{ - this->setting_bit_rate = bit_rate; - this->control_change = true; -} - -void Encoder::set_frame_rate(uint32_t frame_rate) -{ - this->setting_frame_rate = frame_rate; - this->control_change = true; -} - -void Encoder::set_quality_iframe(uint32_t quality) -{ - this->setting_quality_iframe = glm::clamp(quality, 0u, 51u); - this->control_change = true; -} - -void Encoder::set_quality_pframe(uint32_t quality) -{ - this->setting_quality_pframe = glm::clamp(quality, 0u, 51u); - this->control_change = true; -} - -bool Encoder::encode_frame(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, EncoderCallback callback) +bool VulkanEncoder::encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete callback) { if (!this->encode_control(renderer, callback)) { @@ -226,7 +195,7 @@ bool Encoder::encode_frame(VkCommandBuffer command_buffer, lava::renderer& rende } lava::device_ptr device = image->get_device(); - EncoderFrame::Ptr frame = nullptr; + VulkanEncoderFrame::Ptr frame = nullptr; //Get an unused image form the frame pool if (!this->aquire_frame(frame)) @@ -235,7 +204,7 @@ bool Encoder::encode_frame(VkCommandBuffer command_buffer, lava::renderer& rende } frame->frame_index = this->frame_index; - frame->type = ENCODER_FRAME_TYPE_FRAME; + frame->type = VULKAN_ENCODER_FRAME_TYPE_FRAME; frame->callback = std::move(callback); //Check if the input image has the right layout. If not change the layout. @@ -329,7 +298,68 @@ bool Encoder::encode_frame(VkCommandBuffer command_buffer, lava::renderer& rende return true; } -bool Encoder::encode_control(lava::renderer& renderer, EncoderCallback callback) +void VulkanEncoder::set_on_encode_error(OnEncodeError function) +{ + this->on_encode_error = std::move(function); +} + +void VulkanEncoder::set_mode(EncoderMode mode) +{ + this->setting_mode = mode; + this->control_change = true; +} + +void VulkanEncoder::set_quality(double quality) +{ + this->setting_quality_iframe = glm::clamp((uint32_t)((1.0 - quality) * 51), 0u, 51u); + this->setting_quality_pframe = glm::clamp((uint32_t)((1.0 - quality) * 51), 0u, 51u); + this->control_change = true; +} + +void VulkanEncoder::set_bitrate(double bitrate) +{ + this->setting_bit_rate = bitrate; + this->control_change = true; +} + +void VulkanEncoder::set_key_rate(uint32_t key_rate) +{ + this->setting_key_rate = key_rate; + this->control_change = true; +} + +void VulkanEncoder::set_frame_rate(uint32_t frame_rate) +{ + this->setting_frame_rate = frame_rate; + this->control_change = true; +} + +EncoderMode VulkanEncoder::get_mode() const +{ + return this->setting_mode; +} + +double VulkanEncoder::get_quality() const +{ + return 1.0 - ((double)this->setting_quality_iframe / 51.0); +} + +double VulkanEncoder::get_bitrate() const +{ + return this->setting_bit_rate; +} + +uint32_t VulkanEncoder::get_key_rate() const +{ + return this->setting_key_rate; +} + +uint32_t VulkanEncoder::get_frame_rate() const +{ + return this->setting_frame_rate; +} + +bool VulkanEncoder::encode_control(lava::renderer& renderer, OnEncodeComplete callback) { if (!this->control_change) { @@ -337,7 +367,7 @@ bool Encoder::encode_control(lava::renderer& renderer, EncoderCallback callback) } lava::device_ptr device = this->descriptor_layout->get_device(); - EncoderFrame::Ptr frame = nullptr; + VulkanEncoderFrame::Ptr frame = nullptr; //Get an unused image form the frame pool if (!this->aquire_frame(frame)) @@ -346,7 +376,7 @@ bool Encoder::encode_control(lava::renderer& renderer, EncoderCallback callback) } frame->frame_index = this->frame_index; - frame->type = ENCODER_FRAME_TYPE_CONTROL; + frame->type = VULKAN_ENCODER_FRAME_TYPE_CONTROL; frame->callback = std::move(callback); //Create the encode command buffer. @@ -366,7 +396,7 @@ bool Encoder::encode_control(lava::renderer& renderer, EncoderCallback callback) return true; } -bool Encoder::encode_config(lava::renderer& renderer, EncoderCallback callback) +bool VulkanEncoder::encode_config(lava::renderer& renderer, OnEncodeComplete callback) { if (!this->config_change) { @@ -374,7 +404,7 @@ bool Encoder::encode_config(lava::renderer& renderer, EncoderCallback callback) } lava::device_ptr device = this->descriptor_layout->get_device(); - EncoderFrame::Ptr frame = nullptr; + VulkanEncoderFrame::Ptr frame = nullptr; //Get an unused image form the frame pool if (!this->aquire_frame(frame)) @@ -383,7 +413,7 @@ bool Encoder::encode_config(lava::renderer& renderer, EncoderCallback callback) } frame->frame_index = this->frame_index; - frame->type = ENCODER_FRAME_TYPE_CONFIG; + frame->type = VULKAN_ENCODER_FRAME_TYPE_CONFIG; frame->callback = std::move(callback); //Create the encode command buffer. @@ -403,7 +433,7 @@ bool Encoder::encode_config(lava::renderer& renderer, EncoderCallback callback) return true; } -bool Encoder::aquire_frame(EncoderFrame::Ptr& frame) +bool VulkanEncoder::aquire_frame(VulkanEncoderFrame::Ptr& frame) { std::unique_lock<std::mutex> lock(this->frame_mutex); @@ -418,14 +448,14 @@ bool Encoder::aquire_frame(EncoderFrame::Ptr& frame) return false; } -void Encoder::release_frame(EncoderFrame::Ptr frame) +void VulkanEncoder::release_frame(VulkanEncoderFrame::Ptr frame) { std::unique_lock<std::mutex> lock(this->frame_mutex); this->frame_queue.push_back(frame); } -bool Encoder::submit_frame(lava::device_ptr device, EncoderFrame::Ptr frame, lava::renderer& renderer) +bool VulkanEncoder::submit_frame(lava::device_ptr device, VulkanEncoderFrame::Ptr frame, lava::renderer& renderer) { frame->encode_fence->async_wait([this, device, frame](const asio::error_code& error_code) { @@ -499,7 +529,7 @@ bool Encoder::submit_frame(lava::device_ptr device, EncoderFrame::Ptr frame, lav return true; } -void Encoder::write_image(lava::device_ptr device, EncoderFrame::Ptr frame, lava::image::ptr image) +void VulkanEncoder::write_image(lava::device_ptr device, VulkanEncoderFrame::Ptr frame, lava::image::ptr image) { VkDescriptorImageInfo image_info; image_info.sampler = this->sampler; @@ -521,7 +551,7 @@ void Encoder::write_image(lava::device_ptr device, EncoderFrame::Ptr frame, lava vkUpdateDescriptorSets(device->get(), 1, &write_info, 0, nullptr); } -bool Encoder::record_encoding(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::record_encoding(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { VkFence fence = frame->encode_fence->get(); @@ -548,13 +578,13 @@ bool Encoder::record_encoding(lava::device_ptr device, EncoderFrame::Ptr frame) switch (frame->type) { - case ENCODER_FRAME_TYPE_CONTROL: + case VULKAN_ENCODER_FRAME_TYPE_CONTROL: this->encode_pass_control_setup(frame->encode_command_buffer, frame); break; - case ENCODER_FRAME_TYPE_CONFIG: + case VULKAN_ENCODER_FRAME_TYPE_CONFIG: this->encode_pass_config_setup(frame->encode_command_buffer, frame); break; - case ENCODER_FRAME_TYPE_FRAME: + case VULKAN_ENCODER_FRAME_TYPE_FRAME: this->encode_pass_frame_setup(frame->encode_command_buffer, frame); break; default: @@ -569,42 +599,41 @@ bool Encoder::record_encoding(lava::device_ptr device, EncoderFrame::Ptr frame) return true; } -void Encoder::retrive_encoding(lava::device_ptr device, EncoderFrame::Ptr frame) +void VulkanEncoder::retrive_encoding(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { - if (frame->type == ENCODER_FRAME_TYPE_CONTROL) + if (frame->type == VULKAN_ENCODER_FRAME_TYPE_CONTROL) { - frame->callback(std::span<uint8_t>(), frame->type); + return; } - else + std::array<uint32_t, 3> buffer_range; + memset(buffer_range.data(), 0, sizeof(uint32_t) * buffer_range.size()); + + if (vkGetQueryPoolResults(device->get(), this->query_pool, frame->output_query_index, 1, sizeof(uint32_t) * buffer_range.size(), buffer_range.data(), 0, VK_QUERY_RESULT_WAIT_BIT | VK_QUERY_RESULT_WITH_STATUS_BIT_KHR) != VK_SUCCESS) { - std::array<uint32_t, 3> buffer_range; - memset(buffer_range.data(), 0, sizeof(uint32_t) * buffer_range.size()); + return; + } - if (vkGetQueryPoolResults(device->get(), this->query_pool, frame->output_query_index, 1, sizeof(uint32_t) * buffer_range.size(), buffer_range.data(), 0, VK_QUERY_RESULT_WAIT_BIT | VK_QUERY_RESULT_WITH_STATUS_BIT_KHR) != VK_SUCCESS) - { - return; - } + uint32_t range_offset = buffer_range[0]; + uint32_t range_size = buffer_range[1]; - uint32_t range_offset = buffer_range[0]; - uint32_t range_size = buffer_range[1]; + vmaInvalidateAllocation(device->alloc(), frame->output_memory, range_offset, range_size); - vmaInvalidateAllocation(device->alloc(), frame->output_memory, range_offset, range_size); + uint8_t* range_pointer = nullptr; - uint8_t* range_pointer = nullptr; + if (vmaMapMemory(device->alloc(), frame->output_memory, (void**) (&range_pointer))) + { + return; + } - if (vmaMapMemory(device->alloc(), frame->output_memory, (void**) (&range_pointer))) - { - return; - } + bool is_config = (frame->type == VULKAN_ENCODER_FRAME_TYPE_CONFIG); - frame->callback(std::span(range_pointer + range_offset, range_size), frame->type); + frame->callback(std::span(range_pointer + range_offset, range_size), is_config); - vmaUnmapMemory(device->alloc(), frame->output_memory); - } + vmaUnmapMemory(device->alloc(), frame->output_memory); } -bool Encoder::create_profiles(lava::device_ptr device) +bool VulkanEncoder::create_profiles(lava::device_ptr device) { this->encode_profile.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PROFILE_INFO_EXT; this->encode_profile.pNext = nullptr; @@ -641,7 +670,7 @@ bool Encoder::create_profiles(lava::device_ptr device) return true; } -bool Encoder::compute_settings(const glm::uvec2& size) +bool VulkanEncoder::compute_settings(const glm::uvec2& size) { const glm::uvec2 block_size = glm::uvec2(16); //Macroblock Size of 16x16. TODO: Look up this value form the capabillities. const glm::uvec2 image_alignment = glm::uvec2(this->encode_capabillities.inputImageDataFillAlignment.width, this->encode_capabillities.inputImageDataFillAlignment.height); @@ -657,7 +686,7 @@ bool Encoder::compute_settings(const glm::uvec2& size) return true; } -bool Encoder::create_session(lava::device_ptr device, const glm::uvec2& size) +bool VulkanEncoder::create_session(lava::device_ptr device, const glm::uvec2& size) { VkExtent2D max_video_extend; max_video_extend.width = size.x; @@ -688,7 +717,7 @@ bool Encoder::create_session(lava::device_ptr device, const glm::uvec2& size) return true; } -bool Encoder::create_parameters(lava::device_ptr device) +bool VulkanEncoder::create_parameters(lava::device_ptr device) { //H.264 Specification Section 7.4.2.1.1: //https://www.itu.int/rec/dologin_pub.asp?lang=e&id=T-REC-H.264-202108-I!!PDF-E&type=items @@ -791,7 +820,7 @@ bool Encoder::create_parameters(lava::device_ptr device) return true; } -bool Encoder::bind_session_memory(lava::device_ptr device) +bool VulkanEncoder::bind_session_memory(lava::device_ptr device) { uint32_t requirement_count = 0; if (vkGetVideoSessionMemoryRequirementsKHR(device->get(), this->video_session, &requirement_count, nullptr) != VK_SUCCESS) @@ -855,7 +884,7 @@ bool Encoder::bind_session_memory(lava::device_ptr device) return true; } -bool Encoder::create_sampler(lava::device_ptr device) +bool VulkanEncoder::create_sampler(lava::device_ptr device) { VkSamplerCreateInfo sampler_create_info; sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; @@ -885,7 +914,7 @@ bool Encoder::create_sampler(lava::device_ptr device) return true; } -bool Encoder::create_pools(lava::device_ptr device, uint32_t frame_count) +bool VulkanEncoder::create_pools(lava::device_ptr device, uint32_t frame_count) { VkCommandPoolCreateInfo command_create_info; command_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; @@ -921,7 +950,7 @@ bool Encoder::create_pools(lava::device_ptr device, uint32_t frame_count) return true; } -bool Encoder::create_layouts(lava::device_ptr device) +bool VulkanEncoder::create_layouts(lava::device_ptr device) { this->descriptor_layout = lava::make_descriptor(); this->descriptor_layout->add_binding(0, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT); @@ -942,7 +971,7 @@ bool Encoder::create_layouts(lava::device_ptr device) return true; } -bool Encoder::create_image_memory(lava::device_ptr device, VkImage image, VkImageAspectFlagBits image_aspect, VmaAllocation& memory) +bool VulkanEncoder::create_image_memory(lava::device_ptr device, VkImage image, VkImageAspectFlagBits image_aspect, VmaAllocation& memory) { bool is_plane = false; @@ -985,7 +1014,7 @@ bool Encoder::create_image_memory(lava::device_ptr device, VkImage image, VkImag return true; } -bool Encoder::create_image_view(lava::device_ptr device, VkImage image, VkFormat image_format, VkImageView& image_view) +bool VulkanEncoder::create_image_view(lava::device_ptr device, VkImage image, VkFormat image_format, VkImageView& image_view) { VkComponentMapping components; components.r = VK_COMPONENT_SWIZZLE_IDENTITY; @@ -1018,7 +1047,7 @@ bool Encoder::create_image_view(lava::device_ptr device, VkImage image, VkFormat return true; } -bool Encoder::create_input_images(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_input_images(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { //TODO: Share memory between combined image and luma, chroma image. //The input image (input_combined) for the encoding is a multi plane images. @@ -1222,7 +1251,7 @@ bool Encoder::create_input_images(lava::device_ptr device, EncoderFrame::Ptr fra return true; } -bool Encoder::create_slot_image(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_slot_image(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { VkExtent3D image_extend; image_extend.width = this->setting_image_size.x; @@ -1308,7 +1337,7 @@ bool Encoder::create_slot_image(lava::device_ptr device, EncoderFrame::Ptr frame return true; } -bool Encoder::create_output_buffer(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_output_buffer(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { VkVideoProfileListInfoKHR profile_list; profile_list.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_LIST_INFO_KHR; @@ -1374,7 +1403,7 @@ bool Encoder::create_output_buffer(lava::device_ptr device, EncoderFrame::Ptr fr return true; } -bool Encoder::create_convert_pass(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_convert_pass(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { VkClearValue luma_clear_value; luma_clear_value.color.float32[0] = 0.0f; @@ -1453,7 +1482,7 @@ bool Encoder::create_convert_pass(lava::device_ptr device, EncoderFrame::Ptr fra return true; } -bool Encoder::create_convert_pipeline(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_convert_pipeline(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { VkPipelineColorBlendAttachmentState luma_blend_state; luma_blend_state.blendEnable = VK_FALSE; @@ -1507,7 +1536,7 @@ bool Encoder::create_convert_pipeline(lava::device_ptr device, EncoderFrame::Ptr return true; } -bool Encoder::create_subsample_pass(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_subsample_pass(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { VkClearValue chroma_clear_value; chroma_clear_value.color.float32[0] = 0.0f; @@ -1567,7 +1596,7 @@ bool Encoder::create_subsample_pass(lava::device_ptr device, EncoderFrame::Ptr f return true; } -bool Encoder::create_subsample_pipeline(lava::device_ptr device, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_subsample_pipeline(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { VkPipelineColorBlendAttachmentState chroma_blend_state; chroma_blend_state.blendEnable = VK_FALSE; @@ -1629,7 +1658,7 @@ bool Encoder::create_subsample_pipeline(lava::device_ptr device, EncoderFrame::P return true; } -bool Encoder::create_encode_resources(lava::device_ptr device, asio::executor executor, EncoderFrame::Ptr frame) +bool VulkanEncoder::create_encode_resources(lava::device_ptr device, asio::executor executor, VulkanEncoderFrame::Ptr frame) { frame->encode_fence = make_extern_fence(); @@ -1668,7 +1697,7 @@ bool Encoder::create_encode_resources(lava::device_ptr device, asio::executor ex return true; } -void Encoder::destroy_session(lava::device_ptr device) +void VulkanEncoder::destroy_session(lava::device_ptr device) { vkDestroyVideoSessionKHR(device->get(), this->video_session, lava::memory::alloc()); vkDestroyVideoSessionParametersKHR(device->get(), this->video_session_paremeters, lava::memory::alloc()); @@ -1681,7 +1710,7 @@ void Encoder::destroy_session(lava::device_ptr device) this->video_session_memory.clear(); } -void Encoder::destroy_resources(lava::device_ptr device) +void VulkanEncoder::destroy_resources(lava::device_ptr device) { vkDestroySampler(device->get(), this->sampler, lava::memory::alloc()); vkDestroyCommandPool(device->get(), this->command_pool, lava::memory::alloc()); @@ -1692,7 +1721,7 @@ void Encoder::destroy_resources(lava::device_ptr device) this->pipeline_layout->destroy(); } -void Encoder::destroy_frame(lava::device_ptr device, EncoderFrame::Ptr frame) +void VulkanEncoder::destroy_frame(lava::device_ptr device, VulkanEncoderFrame::Ptr frame) { vkDestroyImageView(device->get(), frame->input_view_luma, lava::memory::alloc()); vkDestroyImageView(device->get(), frame->input_view_chroma, lava::memory::alloc()); @@ -1731,7 +1760,7 @@ void Encoder::destroy_frame(lava::device_ptr device, EncoderFrame::Ptr frame) frame->encode_fence->destroy(device); } -bool Encoder::check_encode_support(lava::device_ptr device, const lava::queue& queue) const +bool VulkanEncoder::check_encode_support(lava::device_ptr device, const lava::queue& queue) const { uint32_t property_count = 0; vkGetPhysicalDeviceQueueFamilyProperties2(device->get_physical_device()->get(), &property_count, nullptr); @@ -1759,7 +1788,7 @@ bool Encoder::check_encode_support(lava::device_ptr device, const lava::queue& q return false; } -bool Encoder::check_format_support(lava::device_ptr device, VkImageUsageFlags usage, VkFormat format) const +bool VulkanEncoder::check_format_support(lava::device_ptr device, VkImageUsageFlags usage, VkFormat format) const { VkVideoProfileListInfoKHR video_profiles; video_profiles.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_LIST_INFO_KHR; @@ -1804,17 +1833,17 @@ bool Encoder::check_format_support(lava::device_ptr device, VkImageUsageFlags us return false; } -void Encoder::convert_pass(VkCommandBuffer command_buffer) +void VulkanEncoder::convert_pass(VkCommandBuffer command_buffer) { vkCmdDraw(command_buffer, 4, 1, 0, 0); //Draw fullscreen quad } -void Encoder::subsample_pass(VkCommandBuffer command_buffer) +void VulkanEncoder::subsample_pass(VkCommandBuffer command_buffer) { vkCmdDraw(command_buffer, 4, 1, 0, 0); //Draw fullscreen quad } -void Encoder::encode_pass_control_setup(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame) +void VulkanEncoder::encode_pass_control_setup(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame) { VkVideoBeginCodingInfoKHR begin_info; begin_info.sType = VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR; @@ -1837,7 +1866,7 @@ void Encoder::encode_pass_control_setup(VkCommandBuffer command_buffer, EncoderF vkCmdEndVideoCodingKHR(command_buffer, &end_info); } -void Encoder::encode_pass_control_command(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame) +void VulkanEncoder::encode_pass_control_command(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame) { VkVideoEncodeH264RateControlLayerInfoEXT codec_layer_info; codec_layer_info.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_RATE_CONTROL_LAYER_INFO_EXT; @@ -1897,7 +1926,7 @@ void Encoder::encode_pass_control_command(VkCommandBuffer command_buffer, Encode this->invalidate_slots(); } -void Encoder::encode_pass_config_setup(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame) +void VulkanEncoder::encode_pass_config_setup(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame) { VkVideoBeginCodingInfoKHR begin_info; begin_info.sType = VK_STRUCTURE_TYPE_VIDEO_BEGIN_CODING_INFO_KHR; @@ -1951,7 +1980,7 @@ void Encoder::encode_pass_config_setup(VkCommandBuffer command_buffer, EncoderFr vkCmdPipelineBarrier2KHR(command_buffer, &buffer_dependency); } -void Encoder::encode_pass_config_command(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame) +void VulkanEncoder::encode_pass_config_command(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame) { VkVideoPictureResourceInfoKHR input_resource; input_resource.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR; @@ -1988,9 +2017,9 @@ void Encoder::encode_pass_config_command(VkCommandBuffer command_buffer, Encoder vkCmdEncodeVideoKHR(command_buffer, &encode_info); } -void Encoder::encode_pass_frame_setup(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame) +void VulkanEncoder::encode_pass_frame_setup(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame) { - std::vector<EncoderFrame::Ptr> reference_slots; + std::vector<VulkanEncoderFrame::Ptr> reference_slots; this->process_slots(frame, reference_slots); VkImageSubresourceRange image_range; @@ -2037,7 +2066,7 @@ void Encoder::encode_pass_frame_setup(VkCommandBuffer command_buffer, EncoderFra for (uint32_t index = 0; index < reference_slots.size(); index++) { - EncoderFrame::Ptr reference_slot = reference_slots[index]; + VulkanEncoderFrame::Ptr reference_slot = reference_slots[index]; VkImageMemoryBarrier2& slot_barrier = slot_barriers[index + 2]; slot_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2; @@ -2135,7 +2164,7 @@ void Encoder::encode_pass_frame_setup(VkCommandBuffer command_buffer, EncoderFra vkCmdPipelineBarrier2KHR(command_buffer, &buffer_dependency); } -void Encoder::encode_pass_frame_command(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame, const std::vector<EncoderFrame::Ptr>& reference_slots) +void VulkanEncoder::encode_pass_frame_command(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame, const std::vector<VulkanEncoderFrame::Ptr>& reference_slots) { VkOffset2D encode_offset; encode_offset.x = 0; @@ -2180,7 +2209,7 @@ void Encoder::encode_pass_frame_command(VkCommandBuffer command_buffer, EncoderF for (uint32_t index = 0; index < reference_slots.size(); index++) { - EncoderFrame::Ptr reference_slot = reference_slots[index]; + VulkanEncoderFrame::Ptr reference_slot = reference_slots[index]; VkVideoPictureResourceInfoKHR& slot_resource = reference_slot_resources[index]; slot_resource.sType = VK_STRUCTURE_TYPE_VIDEO_PICTURE_RESOURCE_INFO_KHR; @@ -2295,13 +2324,13 @@ void Encoder::encode_pass_frame_command(VkCommandBuffer command_buffer, EncoderF vkCmdEncodeVideoKHR(command_buffer, &encode_info); } -void Encoder::setup_slots() +void VulkanEncoder::setup_slots() { this->frame_slots.resize(this->setting_reference_frames + 1); this->invalidate_slots(); } -void Encoder::process_slots(EncoderFrame::Ptr frame, std::vector<EncoderFrame::Ptr>& reference_slots) +void VulkanEncoder::process_slots(VulkanEncoderFrame::Ptr frame, std::vector<VulkanEncoderFrame::Ptr>& reference_slots) { if (this->is_key_frame()) { @@ -2342,7 +2371,7 @@ void Encoder::process_slots(EncoderFrame::Ptr frame, std::vector<EncoderFrame::P reference_slots.clear(); - for (EncoderFrame::Ptr slot : this->frame_slots) + for (VulkanEncoderFrame::Ptr slot : this->frame_slots) { if (slot != nullptr && slot != frame) { @@ -2350,7 +2379,7 @@ void Encoder::process_slots(EncoderFrame::Ptr frame, std::vector<EncoderFrame::P } } - std::sort(reference_slots.begin(), reference_slots.end(), [](EncoderFrame::Ptr frame1, EncoderFrame::Ptr frame2) + std::sort(reference_slots.begin(), reference_slots.end(), [](VulkanEncoderFrame::Ptr frame1, VulkanEncoderFrame::Ptr frame2) { return frame1->frame_index > frame2->frame_index; }); @@ -2359,7 +2388,7 @@ void Encoder::process_slots(EncoderFrame::Ptr frame, std::vector<EncoderFrame::P reference_slots.resize(reference_count); } -void Encoder::invalidate_slots() +void VulkanEncoder::invalidate_slots() { for (uint32_t index = 0; index < this->frame_slots.size(); index++) { @@ -2367,14 +2396,24 @@ void Encoder::invalidate_slots() } } -bool Encoder::is_key_frame() const +bool VulkanEncoder::is_key_frame() const { return (this->frame_index % this->setting_key_rate) == 0; } VkPhysicalDeviceSynchronization2Features sync_feature; -bool setup_encoder(lava::device::create_param& parameters) +bool setup_instance_for_vulkan_encoder(lava::frame_config& config) +{ + if (config.info.req_api_version < lava::api_version::v1_1) + { + config.info.req_api_version = lava::api_version::v1_1; + } + + return true; +} + +bool setup_device_for_vulkan_encoder(lava::device::create_param& parameters) { parameters.extensions.push_back(VK_KHR_VIDEO_QUEUE_EXTENSION_NAME); parameters.extensions.push_back(VK_KHR_VIDEO_ENCODE_QUEUE_EXTENSION_NAME); @@ -2386,19 +2425,9 @@ bool setup_encoder(lava::device::create_param& parameters) parameters.add_queue(VK_QUEUE_VIDEO_ENCODE_BIT_KHR); sync_feature.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SYNCHRONIZATION_2_FEATURES; - sync_feature.pNext = (void*)parameters.next; + sync_feature.pNext = (void*) parameters.next; sync_feature.synchronization2 = VK_TRUE; parameters.next = &sync_feature; return true; -} - -Encoder::Ptr make_encoder() -{ - return std::make_shared<Encoder>(); -} - -EncoderFrame::Ptr make_encoder_frame() -{ - return std::make_shared<EncoderFrame>(); } \ No newline at end of file diff --git a/src/utility/encoder.hpp b/src/encoder/vulkan_encoder.hpp similarity index 54% rename from src/utility/encoder.hpp rename to src/encoder/vulkan_encoder.hpp index 4e221f85d39a4e64cd12d31b43284998c5c05ac9..78a18994798c3c1d51445482a207619b4a3f7af8 100644 --- a/src/utility/encoder.hpp +++ b/src/encoder/vulkan_encoder.hpp @@ -1,60 +1,28 @@ -/* - The encoder can be used to create a h264 stream out of a sequence of images. - In order to be able to use the encoder, it is neccessary to call the function setup_encoder(...) during the setup of the vulkan device. - Besides that, the requested vulkan version has to be set to Vulkan 1.1 during the setup of the vulkan instance. - A frame can be submitted for encoding using the function encode_frame(...). - After the completion of an encode task, a callback function is executed to which the resulting ouput of the task is passed. - Example: - - //During on_setup_instance(...) - req_api_version = lava::api_version::v1_1; - - //During on_setup_device(...) - setup_encoder(...); - - //During creation and submission of a frame - Encoder::Ptr encoder = make_encoder(); - - encoder->create(...); - - encoder->encoder_frame(..., image, ..., [](const std::span<uint8_t>& content, EncoderFrameType type) - { - //Use content - }); -*/ - #pragma once #include <liblava/lava.hpp> #include <asio.hpp> +#include <thread> +#include <mutex> +#include <functional> #include <optional> #include <vector> #include <span> #include <memory> -#include <mutex> -#include <functional> -#include <optional> -#include "extern_fence.hpp" +#include "encoder.hpp" +#include "utility/extern_fence.hpp" -enum EncoderMode +enum VulkanEncoderFrameType { - ENCODER_MODE_CONSTANT_BITRATE, - ENCODER_MODE_CONSTANT_QUALITY + VULKAN_ENCODER_FRAME_TYPE_CONTROL, + VULKAN_ENCODER_FRAME_TYPE_CONFIG, + VULKAN_ENCODER_FRAME_TYPE_FRAME }; -enum EncoderFrameType -{ - ENCODER_FRAME_TYPE_CONTROL, - ENCODER_FRAME_TYPE_CONFIG, - ENCODER_FRAME_TYPE_FRAME -}; - -typedef std::function<void(const std::span<uint8_t>& content, EncoderFrameType type)> EncoderCallback; - -struct EncoderFrame +struct VulkanEncoderFrame { public: - typedef std::shared_ptr<EncoderFrame> Ptr; + typedef std::shared_ptr<VulkanEncoderFrame> Ptr; public: VmaAllocation input_memory_combined; @@ -97,47 +65,48 @@ public: ExternFence::Ptr encode_fence; uint32_t frame_index = 0; - EncoderFrameType type; - EncoderCallback callback; + VulkanEncoderFrameType type; + Encoder::OnEncodeComplete callback; }; -class Encoder +class VulkanEncoder : public Encoder { public: - typedef std::shared_ptr<Encoder> Ptr; + typedef std::shared_ptr<VulkanEncoder> Ptr; public: - Encoder() = default; + VulkanEncoder() = default; - bool create(lava::device_ptr device, const lava::renderer& renderer, asio::executor executor, const glm::uvec2& size, uint32_t frame_count); - //NOTE: Assume all async operations has been completed. + bool create(lava::device_ptr device, const lava::renderer& renderer, const glm::uvec2& size, uint32_t input_buffers); void destroy(); + bool encode(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, OnEncodeComplete function); + + void set_on_encode_error(OnEncodeError function); + void set_mode(EncoderMode mode); - //NOTE: The number of frames between two key frames. + void set_quality(double quality); + void set_bitrate(double bitrate); void set_key_rate(uint32_t key_rate); - //NOTE: The bit rate in Mbit per second. - void set_bit_rate(double bit_rate); - //NOTE: The frame rate in frames per second. void set_frame_rate(uint32_t frame_rate); - //NOTE: The quality of i-frames in range from 0 to 51, where 0 means a lowest compression and 51 means highest compression. - void set_quality_iframe(uint32_t quality); - //NOTE: The quality of p-frames in range from 0 to 51, where 0 means a lowest compression and 51 means highest compression. - void set_quality_pframe(uint32_t quality); - bool encode_frame(VkCommandBuffer command_buffer, lava::renderer& renderer, lava::image::ptr image, VkImageLayout image_layout, EncoderCallback callback); + EncoderMode get_mode() const; + double get_quality() const; + double get_bitrate() const; + uint32_t get_key_rate() const; + uint32_t get_frame_rate() const; private: - bool encode_control(lava::renderer& renderer, EncoderCallback callback); - bool encode_config(lava::renderer& renderer, EncoderCallback callback); + bool encode_control(lava::renderer& renderer, OnEncodeComplete callback); + bool encode_config(lava::renderer& renderer, OnEncodeComplete callback); - bool aquire_frame(EncoderFrame::Ptr& frame); - void release_frame(EncoderFrame::Ptr frame); - bool submit_frame(lava::device_ptr device, EncoderFrame::Ptr frame, lava::renderer& renderer); + bool aquire_frame(VulkanEncoderFrame::Ptr& frame); + void release_frame(VulkanEncoderFrame::Ptr frame); + bool submit_frame(lava::device_ptr device, VulkanEncoderFrame::Ptr frame, lava::renderer& renderer); - void write_image(lava::device_ptr device, EncoderFrame::Ptr frame, lava::image::ptr image); - bool record_encoding(lava::device_ptr device, EncoderFrame::Ptr frame); - void retrive_encoding(lava::device_ptr device, EncoderFrame::Ptr frame); + void write_image(lava::device_ptr device, VulkanEncoderFrame::Ptr frame, lava::image::ptr image); + bool record_encoding(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); + void retrive_encoding(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); bool create_profiles(lava::device_ptr device); bool compute_settings(const glm::uvec2& size); @@ -153,19 +122,19 @@ private: bool create_image_memory(lava::device_ptr device, VkImage image, VkImageAspectFlagBits image_aspect, VmaAllocation& memory); bool create_image_view(lava::device_ptr device, VkImage image, VkFormat image_format, VkImageView& image_view); - bool create_input_images(lava::device_ptr device, EncoderFrame::Ptr frame); - bool create_slot_image(lava::device_ptr device, EncoderFrame::Ptr frame); - bool create_output_buffer(lava::device_ptr device, EncoderFrame::Ptr frame); + bool create_input_images(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); + bool create_slot_image(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); + bool create_output_buffer(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); - bool create_convert_pass(lava::device_ptr device, EncoderFrame::Ptr frame); - bool create_convert_pipeline(lava::device_ptr device, EncoderFrame::Ptr frame); - bool create_subsample_pass(lava::device_ptr device, EncoderFrame::Ptr frame); - bool create_subsample_pipeline(lava::device_ptr device, EncoderFrame::Ptr frame); - bool create_encode_resources(lava::device_ptr device, asio::executor executor, EncoderFrame::Ptr frame); + bool create_convert_pass(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); + bool create_convert_pipeline(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); + bool create_subsample_pass(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); + bool create_subsample_pipeline(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); + bool create_encode_resources(lava::device_ptr device, asio::executor executor, VulkanEncoderFrame::Ptr frame); void destroy_session(lava::device_ptr device); void destroy_resources(lava::device_ptr device); - void destroy_frame(lava::device_ptr device, EncoderFrame::Ptr frame); + void destroy_frame(lava::device_ptr device, VulkanEncoderFrame::Ptr frame); bool check_encode_support(lava::device_ptr device, const lava::queue& queue) const; bool check_format_support(lava::device_ptr device, VkImageUsageFlags usage, VkFormat format) const; @@ -173,15 +142,15 @@ private: void convert_pass(VkCommandBuffer command_buffer); void subsample_pass(VkCommandBuffer command_buffer); - void encode_pass_control_setup(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame); - void encode_pass_control_command(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame); - void encode_pass_config_setup(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame); - void encode_pass_config_command(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame); - void encode_pass_frame_setup(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame); - void encode_pass_frame_command(VkCommandBuffer command_buffer, EncoderFrame::Ptr frame, const std::vector<EncoderFrame::Ptr>& reference_slots); + void encode_pass_control_setup(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame); + void encode_pass_control_command(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame); + void encode_pass_config_setup(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame); + void encode_pass_config_command(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame); + void encode_pass_frame_setup(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame); + void encode_pass_frame_command(VkCommandBuffer command_buffer, VulkanEncoderFrame::Ptr frame, const std::vector<VulkanEncoderFrame::Ptr>& reference_slots); void setup_slots(); - void process_slots(EncoderFrame::Ptr frame, std::vector<EncoderFrame::Ptr>& reference_slots); + void process_slots(VulkanEncoderFrame::Ptr frame, std::vector<VulkanEncoderFrame::Ptr>& reference_slots); void invalidate_slots(); bool is_key_frame() const; @@ -208,6 +177,9 @@ private: uint32_t setting_buffer_size; private: + std::thread worker_thread; + asio::io_context worker_executor; + VkSampler sampler = nullptr; VkCommandPool command_pool = nullptr; VkQueryPool query_pool = nullptr; @@ -219,9 +191,11 @@ private: VkSemaphore previous_semaphore = VK_NULL_HANDLE; std::mutex frame_mutex; - std::vector<EncoderFrame::Ptr> frame_queue; //NOTE: Protected by frame_mutex - std::vector<EncoderFrame::Ptr> frame_list; - std::vector<EncoderFrame::Ptr> frame_slots; + std::vector<VulkanEncoderFrame::Ptr> frame_queue; //NOTE: Protected by frame_mutex + std::vector<VulkanEncoderFrame::Ptr> frame_list; + std::vector<VulkanEncoderFrame::Ptr> frame_slots; + + OnEncodeError on_encode_error; private: lava::queue default_queue; @@ -243,7 +217,5 @@ private: const uint8_t picture_parameter_id = 0; }; -bool setup_encoder(lava::device::create_param& parameters); - -Encoder::Ptr make_encoder(); -EncoderFrame::Ptr make_encoder_frame(); \ No newline at end of file +bool setup_instance_for_vulkan_encoder(lava::frame_config& config); +bool setup_device_for_vulkan_encoder(lava::device::create_param& parameters); \ No newline at end of file diff --git a/src/headset/remote_headset.cpp b/src/headset/remote_headset.cpp index 61e57da81c95e889b03f810b55f2b493f3a80bc6..22bda66a2d834532ef623f408f8608ccd93ee412 100644 --- a/src/headset/remote_headset.cpp +++ b/src/headset/remote_headset.cpp @@ -17,23 +17,27 @@ RemoteHeadset::RemoteHeadset() bool RemoteHeadset::on_setup_instance(lava::frame_config& config) { - // Required in oder to get: - // VK_KHR_external_fence, - // VK_KHR_external_fence_capabilities, - // VK_KHR_get_physical_device_properties2 - config.info.req_api_version = lava::api_version::v1_1; + if (!setup_instance_for_extern_fence(config)) + { + return false; + } + + if (!setup_instance_for_encoder(this->get_application()->get_command_parser().get_encoder(), config)) + { + return false; + } return true; } bool RemoteHeadset::on_setup_device(lava::device::create_param& parameters) { - if (!setup_extern_fence(this->get_application()->get_instance(), parameters.extensions)) + if (!setup_device_for_extern_fence(this->get_application()->get_instance(), parameters)) { return false; } - if (!setup_encoder(parameters)) + if (!setup_device_for_encoder(this->get_application()->get_command_parser().get_encoder(), parameters)) { return false; } @@ -147,7 +151,7 @@ bool RemoteHeadset::on_interface() { for (Encoder::Ptr encoder : this->encoders) { - encoder->set_bit_rate(this->encoder_bit_rate * 1000); + encoder->set_bitrate(this->encoder_bit_rate * 1000); } } @@ -162,19 +166,11 @@ bool RemoteHeadset::on_interface() else if (this->encoder_mode == ENCODER_MODE_CONSTANT_QUALITY) { - if (ImGui::SliderInt("Quality I-Frame", (int32_t*)&this->encoder_quality_iframe, 1, 51)) + if (ImGui::SliderFloat("Quality", &this->encoder_quality, 0.0, 1.0)) { for (Encoder::Ptr encoder : this->encoders) { - encoder->set_quality_iframe(this->encoder_quality_iframe); - } - } - - if (ImGui::SliderInt("Quality P-Frame", (int32_t*)&this->encoder_quality_pframe, 1, 51)) - { - for (Encoder::Ptr encoder : this->encoders) - { - encoder->set_quality_pframe(this->encoder_quality_pframe); + encoder->set_quality(this->encoder_quality); } } } @@ -258,19 +254,16 @@ void RemoteHeadset::submit_frame(VkCommandBuffer command_buffer, VkImageLayout f uint32_t transform_id = this->transform_id; pass_timer->begin_pass(command_buffer, "Encode Setup " + std::to_string(frame_id)); - bool result = encoder->encode_frame(command_buffer, renderer, framebuffer, frame_layout, [this, frame_number, frame_id, transform_id](const std::span<uint8_t>& content, EncoderFrameType type) + bool result = encoder->encode(command_buffer, renderer, framebuffer, frame_layout, [this, frame_number, frame_id, transform_id](const std::span<uint8_t>& content, bool is_config) { - switch (type) + if (is_config) { - case ENCODER_FRAME_TYPE_CONFIG: this->transport->send_frame_config_nal(frame_id, content); - break; - case ENCODER_FRAME_TYPE_FRAME: + } + + else + { this->transport->send_frame_nal(frame_number, frame_id, transform_id, content); - break; - case ENCODER_FRAME_TYPE_CONTROL: - default: - break; } }); pass_timer->end_pass(command_buffer); @@ -446,24 +439,28 @@ bool RemoteHeadset::create_encoders() for (uint32_t index = 0; index < this->encoders.size(); index++) { - Encoder::Ptr encoder = make_encoder(); + Encoder::Ptr encoder = make_encoder(this->get_application()->get_command_parser().get_encoder()); + + encoder->set_on_encode_error([this]() + { + this->on_encode_error(); + }); + + encoder->set_mode((EncoderMode) this->encoder_mode); + encoder->set_quality(this->encoder_quality); + encoder->set_bitrate(this->encoder_bit_rate); + encoder->set_key_rate(this->encoder_key_rate); + encoder->set_frame_rate(this->encoder_frame_rate); lava::device_ptr device = this->get_application()->get_device(); lava::renderer& renderer = this->get_application()->get_renderer(); uint32_t frame_count = this->get_application()->get_frame_count(); - if (!encoder->create(device, renderer, this->transport->get_context().get_executor(), this->resolution, frame_count)) + if (!encoder->create(device, renderer, this->resolution, frame_count)) { return false; } - encoder->set_mode((EncoderMode)this->encoder_mode); - encoder->set_key_rate(this->encoder_key_rate); - encoder->set_bit_rate(this->encoder_bit_rate); - encoder->set_frame_rate(this->encoder_frame_rate); - encoder->set_quality_iframe(this->encoder_quality_iframe); - encoder->set_quality_pframe(this->encoder_quality_pframe); - this->encoders[index] = encoder; } @@ -612,6 +609,11 @@ void RemoteHeadset::on_transport_error() lava::log()->error("Remote Headset: Transport error detected!"); } +void RemoteHeadset::on_encode_error() +{ + lava::log()->error("Remote Headset: Encode error detected!"); +} + bool RemoteHeadset::convert_strategy(StereoStrategyType strategy_type, PacketStereoStrategyId& strategy_id) { switch (strategy_type) diff --git a/src/headset/remote_headset.hpp b/src/headset/remote_headset.hpp index 0719de7f9f884c5b3bdd666c7735107f12de75d0..bbb492460333ec4ffba5b14b9e9b9ec454dbd83e 100644 --- a/src/headset/remote_headset.hpp +++ b/src/headset/remote_headset.hpp @@ -8,11 +8,11 @@ #include <chrono> #include "headset.hpp" -#include "strategy/stereo_strategy.hpp" +#include "strategy/stereo_strategy.hpp" #include "stream/transport.hpp" +#include "encoder/encoder.hpp" -#include "utility/encoder.hpp" #include "utility/extern_fence.hpp" #include "utility/latency.hpp" @@ -73,6 +73,7 @@ private: void on_controller_thumbstick(PacketControllerId controller_id, const glm::vec2& thumbstick); void on_latency(PacketFrameNumber frame_number, PacketFrameId frame_id, PacketTransformId transform_id, double timestamp_transform_query, double timestamp_server_response, double timestamp_frame_decoded, double timestamp_command_construct, double timestamp_command_executed); void on_transport_error(); + void on_encode_error(); static bool convert_strategy(StereoStrategyType strategy_type, PacketStereoStrategyId& strategy_id); @@ -116,8 +117,7 @@ private: uint32_t encoder_input_rate = 90; uint32_t encoder_key_rate = 90; uint32_t encoder_frame_rate = 90; - uint32_t encoder_quality_iframe = 45; - uint32_t encoder_quality_pframe = 45; + float encoder_quality = 0.0; float encoder_bit_rate = 8.0; //NOTE: Bitrate in Mbit per seconds float encoder_last_submit = 0.0f; bool encoder_enable_submit = true; diff --git a/src/stream/transport.hpp b/src/stream/transport.hpp index 1fd57a1994c1073cd6f1997007cca95ed0a5bde4..9aa0dbe108358d20874d3a29079f97ac6afcbcb4 100644 --- a/src/stream/transport.hpp +++ b/src/stream/transport.hpp @@ -117,7 +117,7 @@ private: uint32_t bits_received = 0; //NOTE: Owned by worker_thread double bitrate_send = 0.0; //NOTE: Protected by worker_mutex double bitrate_received = 0.0; //NOTE: Protected by worker_mutex - //double max_bitrate_send = 1.0; //NOTE: Protected by worker_mutex + //double max_bitrate_send = 1.0; //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 766e860d29bc4fe21ca8bcbe2908e0c99db636d8..33680e700232e3b4edffe73f86d44710c27748a5 100644 --- a/src/utility/command_parser.cpp +++ b/src/utility/command_parser.cpp @@ -74,6 +74,14 @@ bool CommandParser::parse_command(const argh::parser& cmd_line) } } + else if (parameter.first == "encoder") + { + if (!this->set_encoder(parameter.second)) + { + return false; + } + } + else if (parameter.first == "animation") { this->animation_name = parameter.second; @@ -213,6 +221,11 @@ StereoStrategyType CommandParser::get_stereo_strategy() const return this->stereo_strategy; } +EncoderType CommandParser::get_encoder() const +{ + return this->encoder; +} + SceneUnit CommandParser::get_scene_unit() const { return this->scene_unit; @@ -278,12 +291,14 @@ void CommandParser::show_help() std::cout << " If no file is specified, sky stays black." << 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 << " --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 << " --headset={headset} The headset that should be used." << std::endl; std::cout << " Options: emulated (default), openvr, openxr, remote" << std::endl; std::cout << " --stereo-strategy={strategy} The stereo strategy that should be used." << std::endl; std::cout << " Options: native-forward (default), native-deferred, multi-view, dpr" << std::endl; + std::cout << " --encoder={encoder} The encoder that should be used when a remote headset is used." << std::endl; + std::cout << " Options: vulkan (default), nvidia" << 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; std::cout << " This parameter is only allowed during a benchmark." << std::endl; std::cout << " --animation-index={animation_index} The index of the animation that should be played." << std::endl; @@ -365,6 +380,28 @@ bool CommandParser::set_stero_strategy(const std::string& name) return true; } +bool CommandParser::set_encoder(const std::string& name) +{ + if (name == "vulkan") + { + this->encoder = ENCODER_TYPE_VULKAN; + } + + else if (name == "nvidia") + { + this->encoder = ENCODER_TYPE_NVIDIA; + } + + else + { + std::cout << "Invalid option set of parameter 'encoder'. Use option --help to get more information." << std::endl; + + return false; + } + + return true; +} + bool CommandParser::set_scene_unit(const std::string& name) { if (name == "meters") diff --git a/src/utility/command_parser.hpp b/src/utility/command_parser.hpp index 8cc7ea9509db65a27dc2c3b222e3e9f96bc9d477..dddd706599d7b7073b1bd1c5123863971f759f2b 100644 --- a/src/utility/command_parser.hpp +++ b/src/utility/command_parser.hpp @@ -11,6 +11,7 @@ #include "../scene.hpp" #include "headset/headset.hpp" #include "strategy/stereo_strategy.hpp" +#include "encoder/encoder.hpp" class CommandParser { @@ -21,6 +22,7 @@ public: HeadsetType get_headset() const; StereoStrategyType get_stereo_strategy() const; + EncoderType get_encoder() const; SceneUnit get_scene_unit() const; const std::string& get_scene_path() const; @@ -43,11 +45,13 @@ private: bool set_headset(const std::string& name); bool set_stero_strategy(const std::string& name); + bool set_encoder(const std::string& name); bool set_scene_unit(const std::string& name); private: HeadsetType headset = HEADSET_TYPE_EMULATED; StereoStrategyType stereo_strategy = STEREO_STRATEGY_TYPE_NATIVE_FORWARD; + EncoderType encoder = ENCODER_TYPE_VULKAN; SceneUnit scene_unit = SCENE_UNIT_METERS; std::string scene_path = ""; diff --git a/src/utility/extern_fence.cpp b/src/utility/extern_fence.cpp index 1cad711b040ce2128b2752f1fefdd397101e3eaf..b14ef16fb4f692ba89b9966ceaa5d6b9ab20d9c7 100644 --- a/src/utility/extern_fence.cpp +++ b/src/utility/extern_fence.cpp @@ -141,15 +141,28 @@ public: }; #endif -bool setup_extern_fence(lava::instance& instance, std::vector<const char*>& extensions) +bool setup_instance_for_extern_fence(lava::frame_config& config) +{ + if (config.info.req_api_version < lava::api_version::v1_1) + { + // Required in oder to get: + // VK_KHR_external_fence, + // VK_KHR_external_fence_capabilities + config.info.req_api_version = lava::api_version::v1_1; + } + + return true; +} + +bool setup_device_for_extern_fence(lava::instance& instance, lava::device::create_param& parameters) { #if defined(WIN32) vkGetFenceWin32HandleKHR = (PFN_vkGetFenceWin32HandleKHR)vkGetInstanceProcAddr(instance.get(), "vkGetFenceWin32HandleKHR"); - extensions.push_back(VK_KHR_EXTERNAL_FENCE_WIN32_EXTENSION_NAME); + parameters.extensions.push_back(VK_KHR_EXTERNAL_FENCE_WIN32_EXTENSION_NAME); return true; #elif defined(__unix__) - extensions.push_back(VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME); + parameters.extensions.push_back(VK_KHR_EXTERNAL_FENCE_FD_EXTENSION_NAME); return true; #else diff --git a/src/utility/extern_fence.hpp b/src/utility/extern_fence.hpp index dd525ea7f35553638c7c7584073ef1fc5835e2a9..fd5d86908f284fab8644f5a588e66350b1ad2412 100644 --- a/src/utility/extern_fence.hpp +++ b/src/utility/extern_fence.hpp @@ -1,14 +1,14 @@ /* An extren fence can be used to create a special vulkan fence that can also be used together with asio. - In order to be able to use extern fences, it is neccessary to call the function setup_extern_fence(...) during the setup of the vulkan device. - Besides that, the requested vulkan version has to be set to Vulkan 1.1 during the setup of the vulkan instance. + In order to be able to use extern fences, it is neccessary to call the function setup_instance_for_extern_fence(...) during the setup of the vulkan instance. + Besides that, the function setup_device_for_extern_fence(...) has to be called during the setup of the vulkan device. Example: //During on_setup_instance(...) - req_api_version = lava::api_version::v1_1; + setup_instance_for_extern_fence(); //During on_setup_device(...) - setup_extern_fence(...); + setup_device_for_extern_fence(...); //During execution ExternFence::Ptr extern_fence = make_extern_fence(); @@ -48,6 +48,7 @@ public: VkFence get() const; }; -bool setup_extern_fence(lava::instance& instance, std::vector<const char*>& extensions); +bool setup_instance_for_extern_fence(lava::frame_config& config); +bool setup_device_for_extern_fence(lava::instance& instance, lava::device::create_param& parameters); ExternFence::Ptr make_extern_fence(); \ No newline at end of file diff --git a/src/vr_application.cpp b/src/vr_application.cpp index d01d36c9858ccc0cdb9bdf5811da0144a0847da8..e4f7dec562ec3f02ce4579dbbbc6aa83b4762c3e 100644 --- a/src/vr_application.cpp +++ b/src/vr_application.cpp @@ -178,6 +178,11 @@ IndirectCache::Ptr VRApplication::get_indirect_cache() const return this->indirect_cache; } +const CommandParser& VRApplication::get_command_parser() const +{ + return this->command_parser; +} + lava::device_ptr VRApplication::get_device() const { return this->app->device; diff --git a/src/vr_application.hpp b/src/vr_application.hpp index 5ff7ab6275800a475f67cd4a956a6e8950cebd46..d1cd77da3371449f666df7e401c68e84cdcaeb4a 100644 --- a/src/vr_application.hpp +++ b/src/vr_application.hpp @@ -46,6 +46,8 @@ public: ShadowCache::Ptr get_shadow_cache() const; IndirectCache::Ptr get_indirect_cache() const; + const CommandParser& get_command_parser() const; + lava::device_ptr get_device() const; lava::render_target::ptr get_target() const;