diff --git a/CMakeLists.txt b/CMakeLists.txt index 939cb0d1e0fa32e82d385f0c1f5230ec56e4c04c..92ce11efb92496fafd46679b212efed5ed642a98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -670,6 +670,7 @@ message("======================================================================= ${SRC_DIR}/scene.hpp ${SRC_DIR}/scene.cpp ${SRC_DIR}/frame_capture.hpp ${SRC_DIR}/frame_capture.cpp ${SRC_DIR}/pass_timer.hpp ${SRC_DIR}/pass_timer.cpp + ${SRC_DIR}/encoder.hpp ${SRC_DIR}/encoder.cpp ) target_include_directories(${NAME} PUBLIC diff --git a/src/encoder.cpp b/src/encoder.cpp new file mode 100644 index 0000000000000000000000000000000000000000..d01458b53f68334bc6f433fb7e1abc6098f7e68a --- /dev/null +++ b/src/encoder.cpp @@ -0,0 +1,340 @@ +#include "encoder.hpp" + +bool encoder::create(lava::device_ptr device, const glm::uvec2& size) +{ + for(const lava::queue& queue : device->get_queues()) + { + if((queue.flags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR) == 0) + { + continue; + } + + if(!this->check_encode_support(device, queue)) + { + continue; + } + + this->encode_queue = queue; + + break; + } + + if(!this->encode_queue.has_value()) + { + return false; + } + + if(!this->create_profiles(device)) + { + return false; + } + + if(!this->check_format_support(device, VK_IMAGE_USAGE_VIDEO_ENCODE_SRC_BIT_KHR, VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT)) + { + return false; + } + + if(!this->check_format_support(device, VK_IMAGE_USAGE_VIDEO_ENCODE_DPB_BIT_KHR, VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT)) + { + return false; + } + + if(!this->create_session(device, size)) + { + return false; + } + + if(!this->bind_memory(device)) + { + return false; + } + + if(!this->create_parameters(device, size)) + { + return false; + } + + this->size = size; + + return true; +} + +void encoder::destroy() +{ + +} + +bool encoder::create_profiles(lava::device_ptr device) +{ + this->encode_profile.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_PROFILE_EXT; + this->encode_profile.pNext = nullptr; + this->encode_profile.stdProfileIdc = std_video_h264_profile_idc_baseline; + + this->video_profile.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILE_KHR; + this->video_profile.pNext = &this->encode_profile; + this->video_profile.videoCodecOperation = VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_EXT; + this->video_profile.chromaSubsampling = VK_VIDEO_CHROMA_SUBSAMPLING_444_BIT_KHR; //No subsampling + this->video_profile.lumaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR; + this->video_profile.chromaBitDepth = VK_VIDEO_COMPONENT_BIT_DEPTH_8_BIT_KHR; + + this->encode_capabillities.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_CAPABILITIES_EXT; + this->encode_capabillities.pNext = nullptr; + this->video_capabillities.sType = VK_STRUCTURE_TYPE_VIDEO_CAPABILITIES_KHR; + this->video_capabillities.pNext = &this->encode_capabillities; + + if(vkGetPhysicalDeviceVideoCapabilitiesKHR(device->get_physical_device()->get(), &this->video_profile, &this->video_capabillities) != VK_SUCCESS) + { + return false; + } + + return true; +} + +bool encoder::create_session(lava::device_ptr device, const glm::uvec2& size) +{ + VkExtent2D video_extend; + video_extend.width = size.x; + video_extend.height = size.y; + + VkExtent2D macro_block_extend; + macro_block_extend.width = (video_extend.width / this->video_capabillities.videoPictureExtentGranularity.width) + 1; + macro_block_extend.height = (video_extend.height / this->video_capabillities.videoPictureExtentGranularity.height) + 1; + + VkVideoEncodeH264SessionCreateInfoEXT encode_create_info; + encode_create_info.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_CREATE_INFO_EXT; + encode_create_info.pNext = nullptr; + encode_create_info.flags = VK_VIDEO_ENCODE_H264_CREATE_RESERVED_0_BIT_EXT; //not 0 ??? + encode_create_info.maxPictureSizeInMbs = macro_block_extend; + encode_create_info.pStdExtensionVersion = &this->encode_capabillities.stdExtensionVersion; + + VkVideoSessionCreateInfoKHR video_create_info; + video_create_info.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_CREATE_INFO_KHR; + video_create_info.pNext = &encode_create_info; + video_create_info.queueFamilyIndex = this->encode_queue->family; + video_create_info.flags = VK_VIDEO_SESSION_CREATE_DEFAULT_KHR; + video_create_info.pVideoProfile = &this->video_profile; + video_create_info.pictureFormat = VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT; + video_create_info.maxCodedExtent = video_extend; + video_create_info.referencePicturesFormat = VK_FORMAT_G8_B8R8_2PLANE_444_UNORM_EXT; + video_create_info.maxReferencePicturesSlotsCount = 1; + video_create_info.maxReferencePicturesActiveCount = 1; + + if(vkCreateVideoSessionKHR(device->get(), &video_create_info, lava::memory::alloc(), &this->video_session) != VK_SUCCESS) + { + return false; + } + + return true; +} + +bool encoder::bind_memory(lava::device_ptr device) +{ + uint32_t property_count = 0; + if(vkGetVideoSessionMemoryRequirementsKHR(device->get(), this->video_session, &property_count, nullptr) != VK_SUCCESS) + { + return false; + } + + std::vector<VkMemoryRequirements2> requirement_list; + requirement_list.resize(property_count); + std::vector<VkVideoGetMemoryPropertiesKHR> property_list; + property_list.resize(property_count); + + for(uint32_t index = 0; index < property_count; index++) + { + requirement_list[index].sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2; + requirement_list[index].pNext = nullptr; + property_list[index].sType = VK_STRUCTURE_TYPE_VIDEO_GET_MEMORY_PROPERTIES_KHR; + property_list[index].pNext = nullptr; + property_list[index].pMemoryRequirements = &requirement_list[index]; + } + + if(vkGetVideoSessionMemoryRequirementsKHR(device->get(), this->video_session, &property_count, property_list.data()) != VK_SUCCESS) + { + return false; + } + + std::vector<VkVideoBindMemoryKHR> bind_list; + + for(const VkVideoGetMemoryPropertiesKHR& property : property_list) + { + VmaAllocationCreateInfo create_info; + create_info.flags = 0; + create_info.usage = VMA_MEMORY_USAGE_UNKNOWN; + create_info.requiredFlags = 0; + create_info.preferredFlags = 0; + create_info.memoryTypeBits = 0; + create_info.pool = nullptr; + create_info.pUserData = nullptr; + create_info.priority = 1.0f; + + VmaAllocation memory; + VmaAllocationInfo info; + if(vmaAllocateMemory(device->alloc(), &property.pMemoryRequirements->memoryRequirements, &create_info, &memory, &info) != VK_SUCCESS) + { + return false; + } + + this->video_session_memory.push_back(memory); + + VkVideoBindMemoryKHR bind; + bind.sType = VK_STRUCTURE_TYPE_VIDEO_BIND_MEMORY_KHR; + bind.pNext = nullptr; + bind.memoryBindIndex = property.memoryBindIndex; + bind.memory = info.deviceMemory; + bind.memoryOffset = info.offset; + bind.memorySize = info.size; + + bind_list.push_back(bind); + } + + if(vkBindVideoSessionMemoryKHR(device->get(), this->video_session, bind_list.size(), bind_list.data()) != VK_SUCCESS) + { + return false; + } + + return true; +} + +bool encoder::create_parameters(lava::device_ptr device, const glm::uvec2& size) +{ + StdVideoH264SequenceParameterSet sps_parameter; + sps_parameter.profile_idc; + sps_parameter.level_idc; + sps_parameter.seq_parameter_set_id; + sps_parameter.chroma_format_idc; + sps_parameter.bit_depth_luma_minus8; + sps_parameter.bit_depth_chroma_minus8; + sps_parameter.log2_max_frame_num_minus4; + sps_parameter.pic_order_cnt_type; + sps_parameter.log2_max_pic_order_cnt_lsb_minus4; + sps_parameter.offset_for_non_ref_pic; + sps_parameter.offset_for_top_to_bottom_field; + sps_parameter.num_ref_frames_in_pic_order_cnt_cycle; + sps_parameter.max_num_ref_frames; + sps_parameter.pic_width_in_mbs_minus1; + sps_parameter.pic_height_in_map_units_minus1; + sps_parameter.frame_crop_left_offset; + sps_parameter.frame_crop_right_offset; + sps_parameter.frame_crop_top_offset; + sps_parameter.frame_crop_bottom_offset; + sps_parameter.flags; + sps_parameter.offset_for_ref_frame[255]; // The number of valid values are defined by the num_ref_frames_in_pic_order_cnt_cycle + sps_parameter.pScalingLists; // Must be a valid pointer if scaling_matrix_present_flag is set + sps_parameter.pSequenceParameterSetVui; // Must be a valid pointer if StdVideoH264SpsFlags:vui_parameters_present_flag is set + + StdVideoH264PictureParameterSet pps_parameter; + pps_parameter.seq_parameter_set_id; + pps_parameter.pic_parameter_set_id; + pps_parameter.num_ref_idx_l0_default_active_minus1; + pps_parameter.num_ref_idx_l1_default_active_minus1; + pps_parameter.weighted_bipred_idc; + pps_parameter.pic_init_qp_minus26; + pps_parameter.pic_init_qs_minus26; + pps_parameter.chroma_qp_index_offset; + pps_parameter.second_chroma_qp_index_offset; + pps_parameter.flags; + pps_parameter.pScalingLists; // Must be a valid pointer if StdVideoH264PpsFlags::scaling_matrix_present_flag is set. + + VkVideoEncodeH264SessionParametersAddInfoEXT add_info; + add_info.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_ADD_INFO_EXT; + add_info.pNext = nullptr; + add_info.spsStdCount = 1; + add_info.pSpsStd = &sps_parameter; + add_info.ppsStdCount = 1; + add_info.pPpsStd = &pps_parameter; + + VkVideoEncodeH264SessionParametersCreateInfoEXT encode_create_info; + encode_create_info.sType = VK_STRUCTURE_TYPE_VIDEO_ENCODE_H264_SESSION_PARAMETERS_CREATE_INFO_EXT; + encode_create_info.pNext = nullptr; + encode_create_info.maxSpsStdCount = 1; + encode_create_info.maxPpsStdCount = 1; + encode_create_info.pParametersAddInfo = &add_info; + + VkVideoSessionParametersCreateInfoKHR video_create_info; + video_create_info.sType = VK_STRUCTURE_TYPE_VIDEO_SESSION_PARAMETERS_CREATE_INFO_KHR; + video_create_info.pNext = &encode_create_info; + video_create_info.videoSessionParametersTemplate = nullptr; + video_create_info.videoSession = this->video_session; + + if(vkCreateVideoSessionParametersKHR(device->get(), &video_create_info, lava::memory::alloc(), &this->video_session_paremeters) != VK_SUCCESS) + { + return false; + } + + return true; +} + +bool encoder::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); + + std::vector<VkVideoQueueFamilyProperties2KHR> video_property_list; + video_property_list.resize(property_count); + std::vector<VkQueueFamilyProperties2> property_list; + property_list.resize(property_count); + + for(uint32_t index = 0; index < property_count; index++) + { + video_property_list[index].sType = VK_STRUCTURE_TYPE_VIDEO_QUEUE_FAMILY_PROPERTIES_2_KHR; + video_property_list[index].pNext = nullptr; + property_list[index].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2; + property_list[index].pNext = &video_property_list[index]; + } + + vkGetPhysicalDeviceQueueFamilyProperties2(device->get_physical_device()->get(), &property_count, property_list.data()); + + if((video_property_list[queue.family].videoCodecOperations & VK_VIDEO_CODEC_OPERATION_ENCODE_H264_BIT_EXT) != 0) + { + return true; + } + + return false; +} + +bool encoder::check_format_support(lava::device_ptr device, VkImageUsageFlags usage, VkFormat format) const +{ + VkVideoProfilesKHR video_profiles; + video_profiles.sType = VK_STRUCTURE_TYPE_VIDEO_PROFILES_KHR; + video_profiles.pNext = nullptr; + video_profiles.profileCount = 1; + video_profiles.pProfiles = &this->video_profile; + + VkPhysicalDeviceVideoFormatInfoKHR video_format_info; + video_format_info.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VIDEO_FORMAT_INFO_KHR; + video_format_info.pNext = nullptr; + video_format_info.imageUsage = usage; + video_format_info.pVideoProfiles = &video_profiles; + + uint32_t format_count = 0; + + if(vkGetPhysicalDeviceVideoFormatPropertiesKHR(device->get_physical_device()->get(), &video_format_info, &format_count, nullptr) != VK_SUCCESS) + { + return false; + } + + std::vector<VkVideoFormatPropertiesKHR> format_list; + format_list.resize(format_count); + + for(VkVideoFormatPropertiesKHR& format_properties : format_list) + { + format_properties.sType = VK_STRUCTURE_TYPE_VIDEO_FORMAT_PROPERTIES_KHR; + format_properties.pNext = nullptr; + } + + if(vkGetPhysicalDeviceVideoFormatPropertiesKHR(device->get_physical_device()->get(), &video_format_info, &format_count, format_list.data()) != VK_SUCCESS) + { + return false; + } + + for(const VkVideoFormatPropertiesKHR& format_properties : format_list) + { + if(format_properties.format == format) + { + return true; + } + } + + return false; +} \ No newline at end of file diff --git a/src/encoder.hpp b/src/encoder.hpp new file mode 100644 index 0000000000000000000000000000000000000000..17df818aa5c7b05f85cfbb94fae79cf8f950cc4c --- /dev/null +++ b/src/encoder.hpp @@ -0,0 +1,37 @@ +#pragma once +#include <liblava/lava.hpp> +#include <optional> +#include <vector> + +class encoder +{ +public: + encoder() = default; + + bool create(lava::device_ptr device, const glm::uvec2& size); + void destroy(); + +private: + bool create_profiles(lava::device_ptr device); + bool create_session(lava::device_ptr device, const glm::uvec2& size); + bool create_parameters(lava::device_ptr device, const glm::uvec2& size); + + bool bind_memory(lava::device_ptr device); + + 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; + +private: + glm::uvec2 size; + std::optional<lava::queue> encode_queue; + + VkVideoSessionKHR video_session = nullptr; + VkVideoSessionParametersKHR video_session_paremeters = nullptr; + std::vector<VmaAllocation> video_session_memory; + + VkVideoProfileKHR video_profile; + VkVideoEncodeH264ProfileEXT encode_profile; + + VkVideoCapabilitiesKHR video_capabillities; + VkVideoEncodeH264CapabilitiesEXT encode_capabillities; +}; \ No newline at end of file diff --git a/src/vr_app.cpp b/src/vr_app.cpp index 5e17990c52201ae298bac00b084b4ec8d21d8076..cb61aa52cee92b1681089dcc51a8021781988510 100644 --- a/src/vr_app.cpp +++ b/src/vr_app.cpp @@ -49,6 +49,10 @@ vr_app::vr_app(name name, argh::parser cmd_line) { config.param.extensions.push_back(extension.c_str()); } } + + //Needed in oder to get VK_KHR_get_physical_device_properties2 + config.info.req_api_version = api_version::v1_1; + app_.emplace(config); // Create device @@ -78,6 +82,15 @@ vr_app::vr_app(name name, argh::parser cmd_line) { device_param.features.multiViewport = true; device_param.features.geometryShader = true; device_param.features.tessellationShader = true; + + device_param.extensions.push_back("VK_KHR_video_queue"); + device_param.extensions.push_back("VK_KHR_video_encode_queue"); + device_param.extensions.push_back("VK_EXT_video_encode_h264"); + + //Dependencies of VK_KHR_video_queue and VK_KHR_video_encode_queue that are not included in Vulkan 1.1 + device_param.extensions.push_back("VK_KHR_synchronization2"); + + device_param.add_queue(VK_QUEUE_VIDEO_ENCODE_BIT_KHR); }; // TODO: select proper device! @@ -167,6 +180,11 @@ bool vr_app::setup() { return false; } + if(!encoder_.create(app_->device, app_->target->get_size())) + { + return false; + } + frame_capture_.create(app_->target->get_frame_count()); pass_timer_.create(app_->device, app_->target->get_frame_count(), 10); @@ -231,6 +249,7 @@ bool vr_app::setup() { calculate_projection_matrices(); app_->add_run_end([this]() { + encoder_.destroy(); frame_capture_.destroy(); pass_timer_.write_to_file(app_->device); pass_timer_.destroy(app_->device); diff --git a/src/vr_app.hpp b/src/vr_app.hpp index 32993277f9f16d6259c0a35e985ebb1dc4b64d1c..39b2d92417a96fa5c6e0ba11806ab8bbb8360feb 100644 --- a/src/vr_app.hpp +++ b/src/vr_app.hpp @@ -11,6 +11,7 @@ #include "scene.hpp" #include "frame_capture.hpp" #include "pass_timer.hpp" +#include "encoder.hpp" class vr_app { public: @@ -70,6 +71,7 @@ private: ::scene::ptr scene_; ::frame_capture frame_capture_ = {"captures"}; ::pass_timer pass_timer_ = {"statistics"}; + ::encoder encoder_; bool setup_companion_window_pipeline(); stereo_framebuffer::ptr create_framebuffer(glm::uvec2 size, vr::EVREye eye);