From e06db26a515823a4f1b04f843a82ffaff73e95aa Mon Sep 17 00:00:00 2001 From: ns6089 <61738816+ns6089@users.noreply.github.com> Date: Fri, 16 Aug 2024 17:33:56 +0300 Subject: [PATCH] feat(win/nvenc): dynamic sdk version selection at runtime --- .gitmodules | 14 +- cmake/compile_definitions/common.cmake | 4 - cmake/compile_definitions/windows.cmake | 8 +- src/nvenc/{ => common_impl}/nvenc_base.cpp | 110 ++++++------ src/nvenc/{ => common_impl}/nvenc_base.h | 80 +++------ src/nvenc/{ => common_impl}/nvenc_utils.cpp | 13 +- src/nvenc/{ => common_impl}/nvenc_utils.h | 24 ++- src/nvenc/nvenc_colorspace.h | 21 --- src/nvenc/nvenc_d3d11.cpp | 58 ------- src/nvenc/nvenc_encoder.h | 69 ++++++++ src/nvenc/win/impl/nvenc_d3d11_base.cpp | 35 ++++ .../impl/nvenc_d3d11_base.h} | 37 ++-- .../{ => win/impl}/nvenc_d3d11_native.cpp | 21 +-- src/nvenc/{ => win/impl}/nvenc_d3d11_native.h | 20 +-- .../{ => win/impl}/nvenc_d3d11_on_cuda.cpp | 33 ++-- .../{ => win/impl}/nvenc_d3d11_on_cuda.h | 20 +-- .../win/impl/nvenc_dynamic_factory_1100.cpp | 65 +++++++ .../win/impl/nvenc_dynamic_factory_1100.h | 6 + .../win/impl/nvenc_dynamic_factory_1200.cpp | 2 + .../win/impl/nvenc_dynamic_factory_1200.h | 6 + .../win/impl/nvenc_dynamic_factory_1202.cpp | 2 + .../win/impl/nvenc_dynamic_factory_1202.h | 6 + .../impl/nvenc_dynamic_factory_blueprint.h | 73 ++++++++ src/nvenc/win/impl/nvenc_shared_dll.h | 28 +++ src/nvenc/win/nvenc_d3d11.h | 28 +++ src/nvenc/win/nvenc_dynamic_factory.cpp | 160 ++++++++++++++++++ src/nvenc/win/nvenc_dynamic_factory.h | 44 +++++ src/platform/common.h | 4 +- src/platform/windows/display_vram.cpp | 27 ++- src/video.cpp | 2 +- third-party/nvenc-headers/1100 | 1 + .../{nv-codec-headers => nvenc-headers/1200} | 0 third-party/nvenc-headers/1202 | 1 + 33 files changed, 723 insertions(+), 299 deletions(-) rename src/nvenc/{ => common_impl}/nvenc_base.cpp (85%) rename src/nvenc/{ => common_impl}/nvenc_base.h (56%) rename src/nvenc/{ => common_impl}/nvenc_utils.cpp (92%) rename src/nvenc/{ => common_impl}/nvenc_utils.h (53%) delete mode 100644 src/nvenc/nvenc_colorspace.h delete mode 100644 src/nvenc/nvenc_d3d11.cpp create mode 100644 src/nvenc/nvenc_encoder.h create mode 100644 src/nvenc/win/impl/nvenc_d3d11_base.cpp rename src/nvenc/{nvenc_d3d11.h => win/impl/nvenc_d3d11_base.h} (52%) rename src/nvenc/{ => win/impl}/nvenc_d3d11_native.cpp (83%) rename src/nvenc/{ => win/impl}/nvenc_d3d11_native.h (62%) rename src/nvenc/{ => win/impl}/nvenc_d3d11_on_cuda.cpp (90%) rename src/nvenc/{ => win/impl}/nvenc_d3d11_on_cuda.h (85%) create mode 100644 src/nvenc/win/impl/nvenc_dynamic_factory_1100.cpp create mode 100644 src/nvenc/win/impl/nvenc_dynamic_factory_1100.h create mode 100644 src/nvenc/win/impl/nvenc_dynamic_factory_1200.cpp create mode 100644 src/nvenc/win/impl/nvenc_dynamic_factory_1200.h create mode 100644 src/nvenc/win/impl/nvenc_dynamic_factory_1202.cpp create mode 100644 src/nvenc/win/impl/nvenc_dynamic_factory_1202.h create mode 100644 src/nvenc/win/impl/nvenc_dynamic_factory_blueprint.h create mode 100644 src/nvenc/win/impl/nvenc_shared_dll.h create mode 100644 src/nvenc/win/nvenc_d3d11.h create mode 100644 src/nvenc/win/nvenc_dynamic_factory.cpp create mode 100644 src/nvenc/win/nvenc_dynamic_factory.h create mode 160000 third-party/nvenc-headers/1100 rename third-party/{nv-codec-headers => nvenc-headers/1200} (100%) create mode 160000 third-party/nvenc-headers/1202 diff --git a/.gitmodules b/.gitmodules index 88198314dd0..0852e2652e1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -26,10 +26,18 @@ path = third-party/nanors url = https://github.com/sleepybishop/nanors.git branch = master -[submodule "third-party/nv-codec-headers"] - path = third-party/nv-codec-headers - url = https://github.com/FFmpeg/nv-codec-headers +[submodule "third-party/nvenc-headers/1100"] + path = third-party/nvenc-headers/1100 + url = https://github.com/FFmpeg/nv-codec-headers.git + branch = sdk/11.0 +[submodule "third-party/nvenc-headers/1200"] + path = third-party/nvenc-headers/1200 + url = https://github.com/FFmpeg/nv-codec-headers.git branch = sdk/12.0 +[submodule "third-party/nvenc-headers/1202"] + path = third-party/nvenc-headers/1202 + url = https://github.com/FFmpeg/nv-codec-headers.git + branch = master [submodule "third-party/nvapi-open-source-sdk"] path = third-party/nvapi-open-source-sdk url = https://github.com/LizardByte/nvapi-open-source-sdk diff --git a/cmake/compile_definitions/common.cmake b/cmake/compile_definitions/common.cmake index a2260595c9a..ad63b6c2671 100644 --- a/cmake/compile_definitions/common.cmake +++ b/cmake/compile_definitions/common.cmake @@ -48,10 +48,6 @@ elseif(UNIX) endif() endif() -include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/nv-codec-headers/include") -file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h") -list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES}) - configure_file("${CMAKE_SOURCE_DIR}/src/version.h.in" version.h @ONLY) include_directories("${CMAKE_CURRENT_BINARY_DIR}") # required for importing version.h diff --git a/cmake/compile_definitions/windows.cmake b/cmake/compile_definitions/windows.cmake index 7643d1d9efc..1bb2088e2c1 100644 --- a/cmake/compile_definitions/windows.cmake +++ b/cmake/compile_definitions/windows.cmake @@ -30,6 +30,11 @@ file(GLOB NVPREFS_FILES CONFIGURE_DEPENDS "${CMAKE_SOURCE_DIR}/src/platform/windows/nvprefs/*.cpp" "${CMAKE_SOURCE_DIR}/src/platform/windows/nvprefs/*.h") +include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/nvenc-headers") +file(GLOB_RECURSE NVENC_SOURCES CONFIGURE_DEPENDS + "${CMAKE_SOURCE_DIR}/src/nvenc/*.h" + "${CMAKE_SOURCE_DIR}/src/nvenc/*.cpp") + # vigem include_directories(SYSTEM "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include") @@ -57,7 +62,8 @@ set(PLATFORM_TARGET_FILES "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Common.h" "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/Util.h" "${CMAKE_SOURCE_DIR}/third-party/ViGEmClient/include/ViGEm/km/BusShared.h" - ${NVPREFS_FILES}) + ${NVPREFS_FILES} + ${NVENC_SOURCES}) set(OPENSSL_LIBRARIES libssl.a diff --git a/src/nvenc/nvenc_base.cpp b/src/nvenc/common_impl/nvenc_base.cpp similarity index 85% rename from src/nvenc/nvenc_base.cpp rename to src/nvenc/common_impl/nvenc_base.cpp index b69d6f26bd6..731ea449132 100644 --- a/src/nvenc/nvenc_base.cpp +++ b/src/nvenc/common_impl/nvenc_base.cpp @@ -1,26 +1,21 @@ /** - * @file src/nvenc/nvenc_base.cpp + * @file src/nvenc/common_impl/nvenc_base.cpp * @brief Definitions for abstract platform-agnostic base of standalone NVENC encoder. */ #include "nvenc_base.h" -#include "src/config.h" -#include "src/logging.h" -#include "src/utility.h" +#include "nvenc_utils.h" -#define MAKE_NVENC_VER(major, minor) ((major) | ((minor) << 24)) +#include "src/utility.h" -// Make sure we check backwards compatibility when bumping the Video Codec SDK version -// Things to look out for: -// - NV_ENC_*_VER definitions where the value inside NVENCAPI_STRUCT_VERSION() was increased -// - Incompatible struct changes in nvEncodeAPI.h (fields removed, semantics changed, etc.) -// - Test both old and new drivers with all supported codecs -#if NVENCAPI_VERSION != MAKE_NVENC_VER(12U, 0U) - #error Check and update NVENC code for backwards compatibility! -#endif +#define NVENC_INT_VERSION (NVENCAPI_MAJOR_VERSION * 100 + NVENCAPI_MINOR_VERSION) namespace { +#ifdef NVENC_NAMESPACE + using namespace NVENC_NAMESPACE; +#endif + GUID quality_preset_guid_from_number(unsigned number) { if (number > 7) number = 7; @@ -83,7 +78,11 @@ namespace { } // namespace +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else namespace nvenc { +#endif nvenc_base::nvenc_base(NV_ENC_DEVICE_TYPE device_type): device_type(device_type) { @@ -94,25 +93,28 @@ namespace nvenc { } bool - nvenc_base::create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format) { - // Pick the minimum NvEncode API version required to support the specified codec - // to maximize driver compatibility. AV1 was introduced in SDK v12.0. - minimum_api_version = (client_config.videoFormat <= 1) ? MAKE_NVENC_VER(11U, 0U) : MAKE_NVENC_VER(12U, 0U); - + nvenc_base::create_encoder( + const nvenc_config &config, + const video::config_t &client_config, + const video::sunshine_colorspace_t &sunshine_colorspace, + platf::pix_fmt_e sunshine_buffer_format) { if (!nvenc && !init_library()) return false; if (encoder) destroy_encoder(); auto fail_guard = util::fail_guard([this] { destroy_encoder(); }); + auto colorspace = nvenc_colorspace_from_sunshine_colorspace(sunshine_colorspace); + auto buffer_format = nvenc_format_from_sunshine_format(sunshine_buffer_format); + encoder_params.width = client_config.width; encoder_params.height = client_config.height; encoder_params.buffer_format = buffer_format; encoder_params.rfi = true; - NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { min_struct_version(NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER) }; + NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS session_params = { NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER }; session_params.device = device; session_params.deviceType = device_type; - session_params.apiVersion = minimum_api_version; + session_params.apiVersion = NVENCAPI_VERSION; if (nvenc_failed(nvenc->nvEncOpenEncodeSessionEx(&session_params, &encoder))) { BOOST_LOG(error) << "NvEnc: NvEncOpenEncodeSessionEx() failed: " << last_nvenc_error_string; return false; @@ -130,7 +132,7 @@ namespace nvenc { return false; } - NV_ENC_INITIALIZE_PARAMS init_params = { min_struct_version(NV_ENC_INITIALIZE_PARAMS_VER) }; + NV_ENC_INITIALIZE_PARAMS init_params = { NV_ENC_INITIALIZE_PARAMS_VER }; switch (client_config.videoFormat) { case 0: @@ -143,10 +145,12 @@ namespace nvenc { init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID; break; +#if NVENC_INT_VERSION >= 1200 case 2: // AV1 init_params.encodeGUID = NV_ENC_CODEC_AV1_GUID; break; +#endif default: BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat; @@ -164,7 +168,8 @@ namespace nvenc { } auto get_encoder_cap = [&](NV_ENC_CAPS cap) { - NV_ENC_CAPS_PARAM param = { min_struct_version(NV_ENC_CAPS_PARAM_VER), cap }; + NV_ENC_CAPS_PARAM param = { NV_ENC_CAPS_PARAM_VER }; + param.capsToQuery = cap; int value = 0; nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, ¶m, &value); return value; @@ -182,7 +187,8 @@ namespace nvenc { auto supported_width = get_encoder_cap(NV_ENC_CAPS_WIDTH_MAX); auto supported_height = get_encoder_cap(NV_ENC_CAPS_HEIGHT_MAX); if (encoder_params.width > supported_width || encoder_params.height > supported_height) { - BOOST_LOG(error) << "NvEnc: gpu max encode resolution " << supported_width << "x" << supported_height << ", requested " << encoder_params.width << "x" << encoder_params.height; + BOOST_LOG(error) << "NvEnc: gpu max encode resolution " << supported_width << "x" << supported_height + << ", requested " << encoder_params.width << "x" << encoder_params.height; return false; } } @@ -217,7 +223,10 @@ namespace nvenc { init_params.frameRateNum = client_config.framerate; init_params.frameRateDen = 1; - NV_ENC_PRESET_CONFIG preset_config = { min_struct_version(NV_ENC_PRESET_CONFIG_VER), { min_struct_version(NV_ENC_CONFIG_VER, 7, 8) } }; + NV_ENC_PRESET_CONFIG preset_config = { + .version = NV_ENC_PRESET_CONFIG_VER, + .presetCfg = { .version = NV_ENC_CONFIG_VER }, + }; if (nvenc_failed(nvenc->nvEncGetEncodePresetConfigEx(encoder, init_params.encodeGUID, init_params.presetGUID, init_params.tuningInfo, &preset_config))) { BOOST_LOG(error) << "NvEnc: NvEncGetEncodePresetConfigEx() failed: " << last_nvenc_error_string; return false; @@ -316,7 +325,12 @@ namespace nvenc { auto &format_config = enc_config.encodeCodecConfig.hevcConfig; set_h264_hevc_common_format_config(format_config); if (buffer_is_10bit()) { +#if NVENC_INT_VERSION >= 1202 + format_config.inputBitDepth = NV_ENC_BIT_DEPTH_10; + format_config.outputBitDepth = NV_ENC_BIT_DEPTH_10; +#else format_config.pixelBitDepthMinus8 = 2; +#endif } set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5); set_minqp_if_enabled(config.min_qp_hevc); @@ -324,6 +338,7 @@ namespace nvenc { break; } +#if NVENC_INT_VERSION >= 1200 case 2: { // AV1 auto &format_config = enc_config.encodeCodecConfig.av1Config; @@ -334,8 +349,13 @@ namespace nvenc { } format_config.enableBitstreamPadding = config.insert_filler_data; if (buffer_is_10bit()) { + #if NVENC_INT_VERSION >= 1202 + format_config.inputBitDepth = NV_ENC_BIT_DEPTH_10; + format_config.outputBitDepth = NV_ENC_BIT_DEPTH_10; + #else format_config.inputPixelBitDepthMinus8 = 2; format_config.pixelBitDepthMinus8 = 2; + #endif } format_config.colorPrimaries = colorspace.primaries; format_config.transferCharacteristics = colorspace.tranfer_function; @@ -353,6 +373,7 @@ namespace nvenc { } break; } +#endif } init_params.encodeConfig = &enc_config; @@ -363,7 +384,7 @@ namespace nvenc { } if (async_event_handle) { - NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; + NV_ENC_EVENT_PARAMS event_params = { NV_ENC_EVENT_PARAMS_VER }; event_params.completionEvent = async_event_handle; if (nvenc_failed(nvenc->nvEncRegisterAsyncEvent(encoder, &event_params))) { BOOST_LOG(error) << "NvEnc: NvEncRegisterAsyncEvent() failed: " << last_nvenc_error_string; @@ -371,7 +392,7 @@ namespace nvenc { } } - NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = { min_struct_version(NV_ENC_CREATE_BITSTREAM_BUFFER_VER) }; + NV_ENC_CREATE_BITSTREAM_BUFFER create_bitstream_buffer = { NV_ENC_CREATE_BITSTREAM_BUFFER_VER }; if (nvenc_failed(nvenc->nvEncCreateBitstreamBuffer(encoder, &create_bitstream_buffer))) { BOOST_LOG(error) << "NvEnc: NvEncCreateBitstreamBuffer() failed: " << last_nvenc_error_string; return false; @@ -397,14 +418,17 @@ namespace nvenc { if (buffer_is_yuv444()) extra += " yuv444"; if (buffer_is_10bit()) extra += " 10-bit"; if (enc_config.rcParams.multiPass != NV_ENC_MULTI_PASS_DISABLED) extra += " two-pass"; - if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) extra += " vbv+" + std::to_string(config.vbv_percentage_increase); + if (config.vbv_percentage_increase > 0 && get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) { + extra += " vbv+" + std::to_string(config.vbv_percentage_increase); + } if (encoder_params.rfi) extra += " rfi"; if (init_params.enableWeightedPrediction) extra += " weighted-prediction"; if (enc_config.rcParams.enableAQ) extra += " spatial-aq"; if (enc_config.rcParams.enableMinQP) extra += " qpmin=" + std::to_string(enc_config.rcParams.minQP.qpInterP); if (config.insert_filler_data) extra += " filler-data"; - BOOST_LOG(info) << "NvEnc: created encoder " << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra; + BOOST_LOG(info) << "NvEnc: created encoder v" << NVENC_INT_VERSION << " " + << video_format_string << quality_preset_string_from_guid(init_params.presetGUID) << extra; } encoder_state = {}; @@ -421,7 +445,7 @@ namespace nvenc { output_bitstream = nullptr; } if (encoder && async_event_handle) { - NV_ENC_EVENT_PARAMS event_params = { min_struct_version(NV_ENC_EVENT_PARAMS_VER) }; + NV_ENC_EVENT_PARAMS event_params = { NV_ENC_EVENT_PARAMS_VER }; event_params.completionEvent = async_event_handle; if (nvenc_failed(nvenc->nvEncUnregisterAsyncEvent(encoder, &event_params))) { BOOST_LOG(error) << "NvEnc: NvEncUnregisterAsyncEvent() failed: " << last_nvenc_error_string; @@ -458,7 +482,7 @@ namespace nvenc { return {}; } - NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = { min_struct_version(NV_ENC_MAP_INPUT_RESOURCE_VER) }; + NV_ENC_MAP_INPUT_RESOURCE mapped_input_buffer = { NV_ENC_MAP_INPUT_RESOURCE_VER }; mapped_input_buffer.registeredResource = registered_input_buffer; if (nvenc_failed(nvenc->nvEncMapInputResource(encoder, &mapped_input_buffer))) { @@ -471,7 +495,7 @@ namespace nvenc { } }); - NV_ENC_PIC_PARAMS pic_params = { min_struct_version(NV_ENC_PIC_PARAMS_VER, 4, 6) }; + NV_ENC_PIC_PARAMS pic_params = { NV_ENC_PIC_PARAMS_VER }; pic_params.inputWidth = encoder_params.width; pic_params.inputHeight = encoder_params.height; pic_params.encodePicFlags = force_idr ? NV_ENC_PIC_FLAG_FORCEIDR : 0; @@ -487,7 +511,7 @@ namespace nvenc { return {}; } - NV_ENC_LOCK_BITSTREAM lock_bitstream = { min_struct_version(NV_ENC_LOCK_BITSTREAM_VER, 1, 2) }; + NV_ENC_LOCK_BITSTREAM lock_bitstream = { NV_ENC_LOCK_BITSTREAM_VER }; lock_bitstream.outputBitstream = output_bitstream; lock_bitstream.doNotWait = 0; @@ -546,7 +570,8 @@ namespace nvenc { return false; } - BOOST_LOG(debug) << "NvEnc: rfi request " << first_frame << "-" << last_frame << " expanding to last encoded frame " << encoder_state.last_encoded_frame_index; + BOOST_LOG(debug) << "NvEnc: rfi request " << first_frame << "-" << last_frame + << " expanding to last encoded frame " << encoder_state.last_encoded_frame_index; last_frame = encoder_state.last_encoded_frame_index; encoder_state.last_rfi_range = { first_frame, last_frame }; @@ -620,21 +645,4 @@ namespace nvenc { return false; } - - uint32_t - nvenc_base::min_struct_version(uint32_t version, uint32_t v11_struct_version, uint32_t v12_struct_version) { - assert(minimum_api_version); - - // Mask off and replace the original NVENCAPI_VERSION - version &= ~NVENCAPI_VERSION; - version |= minimum_api_version; - - // If there's a struct version override, apply that too - if (v11_struct_version || v12_struct_version) { - version &= ~(0xFFu << 16); - version |= (((minimum_api_version & 0xFF) >= 12) ? v12_struct_version : v11_struct_version) << 16; - } - - return version; - } -} // namespace nvenc +} diff --git a/src/nvenc/nvenc_base.h b/src/nvenc/common_impl/nvenc_base.h similarity index 56% rename from src/nvenc/nvenc_base.h rename to src/nvenc/common_impl/nvenc_base.h index c49aa4010e7..256faa845ef 100644 --- a/src/nvenc/nvenc_base.h +++ b/src/nvenc/common_impl/nvenc_base.h @@ -1,77 +1,50 @@ /** - * @file src/nvenc/nvenc_base.h + * @file src/nvenc/common_impl/nvenc_base.h * @brief Declarations for abstract platform-agnostic base of standalone NVENC encoder. */ #pragma once -#include "nvenc_colorspace.h" -#include "nvenc_config.h" -#include "nvenc_encoded_frame.h" +#include "../nvenc_config.h" +#include "../nvenc_encoded_frame.h" +#include "../nvenc_encoder.h" +#include "src/config.h" #include "src/logging.h" #include "src/video.h" -#include - -/** - * @brief Standalone NVENC encoder - */ +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else + #include namespace nvenc { +#endif /** * @brief Abstract platform-agnostic base of standalone NVENC encoder. * Derived classes perform platform-specific operations. */ - class nvenc_base { + class nvenc_base: virtual public nvenc_encoder { public: /** * @param device_type Underlying device type used by derived class. */ explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type); - virtual ~nvenc_base(); + ~nvenc_base(); - nvenc_base(const nvenc_base &) = delete; - nvenc_base & - operator=(const nvenc_base &) = delete; - - /** - * @brief Create the encoder. - * @param config NVENC encoder configuration. - * @param client_config Stream configuration requested by the client. - * @param colorspace YUV colorspace. - * @param buffer_format Platform-agnostic input surface format. - * @return `true` on success, `false` on error - */ bool - create_encoder(const nvenc_config &config, const video::config_t &client_config, const nvenc_colorspace_t &colorspace, NV_ENC_BUFFER_FORMAT buffer_format); + create_encoder(const nvenc_config &config, + const video::config_t &client_config, + const video::sunshine_colorspace_t &sunshine_colorspace, + platf::pix_fmt_e sunshine_buffer_format) override; - /** - * @brief Destroy the encoder. - * Derived classes classes call it in the destructor. - */ void - destroy_encoder(); + destroy_encoder() override; - /** - * @brief Encode the next frame using platform-specific input surface. - * @param frame_index Frame index that uniquely identifies the frame. - * Afterwards serves as parameter for `invalidate_ref_frames()`. - * No restrictions on the first frame index, but later frame indexes must be subsequent. - * @param force_idr Whether to encode frame as forced IDR. - * @return Encoded frame. - */ nvenc_encoded_frame - encode_frame(uint64_t frame_index, bool force_idr); + encode_frame(uint64_t frame_index, bool force_idr) override; - /** - * @brief Perform reference frame invalidation (RFI) procedure. - * @param first_frame First frame index of the invalidation range. - * @param last_frame Last frame index of the invalidation range. - * @return `true` on success, `false` on error. - * After error next frame must be encoded with `force_idr = true`. - */ bool - invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame); + invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) override; protected: /** @@ -111,17 +84,6 @@ namespace nvenc { bool nvenc_failed(NVENCSTATUS status); - /** - * @brief This function returns the corresponding struct version for the minimum API required by the codec. - * @details Reducing the struct versions maximizes driver compatibility by avoiding needless API breaks. - * @param version The raw structure version from `NVENCAPI_STRUCT_VERSION()`. - * @param v11_struct_version Optionally specifies the struct version to use with v11 SDK major versions. - * @param v12_struct_version Optionally specifies the struct version to use with v12 SDK major versions. - * @return A suitable struct version for the active codec. - */ - uint32_t - min_struct_version(uint32_t version, uint32_t v11_struct_version = 0, uint32_t v12_struct_version = 0); - const NV_ENC_DEVICE_TYPE device_type; void *encoder = nullptr; @@ -148,7 +110,6 @@ namespace nvenc { private: NV_ENC_OUTPUT_PTR output_bitstream = nullptr; - uint32_t minimum_api_version = 0; struct { uint64_t last_encoded_frame_index = 0; @@ -157,5 +118,4 @@ namespace nvenc { logging::min_max_avg_periodic_logger frame_size_logger = { debug, "NvEnc: encoded frame sizes in kB", "" }; } encoder_state; }; - -} // namespace nvenc +} diff --git a/src/nvenc/nvenc_utils.cpp b/src/nvenc/common_impl/nvenc_utils.cpp similarity index 92% rename from src/nvenc/nvenc_utils.cpp rename to src/nvenc/common_impl/nvenc_utils.cpp index 26e2dc30d24..5e1a3e084bc 100644 --- a/src/nvenc/nvenc_utils.cpp +++ b/src/nvenc/common_impl/nvenc_utils.cpp @@ -1,12 +1,16 @@ /** - * @file src/nvenc/nvenc_utils.cpp + * @file src/nvenc/common_impl/nvenc_utils.cpp * @brief Definitions for NVENC utilities. */ -#include - #include "nvenc_utils.h" +#include + +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else namespace nvenc { +#endif #ifdef _WIN32 DXGI_FORMAT @@ -90,5 +94,4 @@ namespace nvenc { return colorspace; } - -} // namespace nvenc +} diff --git a/src/nvenc/nvenc_utils.h b/src/nvenc/common_impl/nvenc_utils.h similarity index 53% rename from src/nvenc/nvenc_utils.h rename to src/nvenc/common_impl/nvenc_utils.h index db42867679a..c56d0792cbf 100644 --- a/src/nvenc/nvenc_utils.h +++ b/src/nvenc/common_impl/nvenc_utils.h @@ -1,5 +1,5 @@ /** - * @file src/nvenc/nvenc_utils.h + * @file src/nvenc/common_impl/nvenc_utils.h * @brief Declarations for NVENC utilities. */ #pragma once @@ -8,14 +8,25 @@ #include #endif -#include "nvenc_colorspace.h" - #include "src/platform/common.h" #include "src/video_colorspace.h" -#include - +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else + #include namespace nvenc { +#endif + + /** + * @brief YUV colorspace and color range. + */ + struct nvenc_colorspace_t { + NV_ENC_VUI_COLOR_PRIMARIES primaries; + NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function; + NV_ENC_VUI_MATRIX_COEFFS matrix; + bool full_range; + }; #ifdef _WIN32 DXGI_FORMAT @@ -27,5 +38,4 @@ namespace nvenc { nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace); - -} // namespace nvenc +} diff --git a/src/nvenc/nvenc_colorspace.h b/src/nvenc/nvenc_colorspace.h deleted file mode 100644 index c9ed519318d..00000000000 --- a/src/nvenc/nvenc_colorspace.h +++ /dev/null @@ -1,21 +0,0 @@ -/** - * @file src/nvenc/nvenc_colorspace.h - * @brief Declarations for NVENC YUV colorspace. - */ -#pragma once - -#include - -namespace nvenc { - - /** - * @brief YUV colorspace and color range. - */ - struct nvenc_colorspace_t { - NV_ENC_VUI_COLOR_PRIMARIES primaries; - NV_ENC_VUI_TRANSFER_CHARACTERISTIC tranfer_function; - NV_ENC_VUI_MATRIX_COEFFS matrix; - bool full_range; - }; - -} // namespace nvenc diff --git a/src/nvenc/nvenc_d3d11.cpp b/src/nvenc/nvenc_d3d11.cpp deleted file mode 100644 index 7dd545b4b90..00000000000 --- a/src/nvenc/nvenc_d3d11.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @file src/nvenc/nvenc_d3d11.cpp - * @brief Definitions for abstract Direct3D11 NVENC encoder. - */ -#include "src/logging.h" - -#ifdef _WIN32 - #include "nvenc_d3d11.h" - -namespace nvenc { - - nvenc_d3d11::~nvenc_d3d11() { - if (dll) { - FreeLibrary(dll); - dll = NULL; - } - } - - bool - nvenc_d3d11::init_library() { - if (dll) return true; - - #ifdef _WIN64 - constexpr auto dll_name = "nvEncodeAPI64.dll"; - #else - constexpr auto dll_name = "nvEncodeAPI.dll"; - #endif - - if ((dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { - if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) { - auto new_nvenc = std::make_unique(); - new_nvenc->version = min_struct_version(NV_ENCODE_API_FUNCTION_LIST_VER); - if (nvenc_failed(create_instance(new_nvenc.get()))) { - BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string; - } - else { - nvenc = std::move(new_nvenc); - return true; - } - } - else { - BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in " << dll_name; - } - } - else { - BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name; - } - - if (dll) { - FreeLibrary(dll); - dll = NULL; - } - - return false; - } - -} // namespace nvenc -#endif diff --git a/src/nvenc/nvenc_encoder.h b/src/nvenc/nvenc_encoder.h new file mode 100644 index 00000000000..f5728db4202 --- /dev/null +++ b/src/nvenc/nvenc_encoder.h @@ -0,0 +1,69 @@ +/** + * @file src/nvenc/nvenc_encoder.h + * @brief Declarations for NVENC encoder interface. + */ +#pragma once + +#include "nvenc_config.h" +#include "nvenc_encoded_frame.h" + +#include "src/platform/common.h" +#include "src/video.h" +#include "src/video_colorspace.h" + +/** + * @brief Standalone NVENC encoder + */ +namespace nvenc { + + /** + * @brief Standalone NVENC encoder interface. + */ + class nvenc_encoder { + public: + virtual ~nvenc_encoder() = default; + + /** + * @brief Create the encoder. + * @param config NVENC encoder configuration. + * @param client_config Stream configuration requested by the client. + * @param colorspace YUV colorspace. + * @param buffer_format Platform-agnostic input surface format. + * @return `true` on success, `false` on error + */ + virtual bool + create_encoder(const nvenc_config &config, + const video::config_t &client_config, + const video::sunshine_colorspace_t &colorspace, + platf::pix_fmt_e buffer_format) = 0; + + /** + * @brief Destroy the encoder. + * Also called in the destructor. + */ + virtual void + destroy_encoder() = 0; + + /** + * @brief Encode the next frame using platform-specific input surface. + * @param frame_index Frame index that uniquely identifies the frame. + * Afterwards serves as parameter for `invalidate_ref_frames()`. + * No restrictions on the first frame index, but later frame indexes must be subsequent. + * @param force_idr Whether to encode frame as forced IDR. + * @return Encoded frame. + */ + virtual nvenc_encoded_frame + encode_frame(uint64_t frame_index, bool force_idr) = 0; + + /** + * @brief Perform reference frame invalidation (RFI) procedure. + * @param first_frame First frame index of the invalidation range. + * @param last_frame Last frame index of the invalidation range. + * @return `true` on success, `false` on error. + * After error next frame must be encoded with `force_idr = true`. + */ + virtual bool + invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) = 0; + }; + +} // namespace nvenc diff --git a/src/nvenc/win/impl/nvenc_d3d11_base.cpp b/src/nvenc/win/impl/nvenc_d3d11_base.cpp new file mode 100644 index 00000000000..d8f872e2dc3 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_d3d11_base.cpp @@ -0,0 +1,35 @@ +/** + * @file src/nvenc/win/impl/nvenc_d3d11_base.cpp + * @brief Definitions for abstract Direct3D11 NVENC encoder. + */ +#include "nvenc_d3d11_base.h" + +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else +namespace nvenc { +#endif + + bool + nvenc_d3d11_base::init_library() { + if (nvenc) return true; + if (!dll) return false; + + if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll.get(), "NvEncodeAPICreateInstance")) { + auto new_nvenc = std::make_unique(); + new_nvenc->version = NV_ENCODE_API_FUNCTION_LIST_VER; + if (nvenc_failed(create_instance(new_nvenc.get()))) { + BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string; + } + else { + nvenc = std::move(new_nvenc); + return true; + } + } + else { + BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in dynamic library"; + } + + return false; + } +} diff --git a/src/nvenc/nvenc_d3d11.h b/src/nvenc/win/impl/nvenc_d3d11_base.h similarity index 52% rename from src/nvenc/nvenc_d3d11.h rename to src/nvenc/win/impl/nvenc_d3d11_base.h index 2d4d4fe77dc..9c93e382dbf 100644 --- a/src/nvenc/nvenc_d3d11.h +++ b/src/nvenc/win/impl/nvenc_d3d11_base.h @@ -1,16 +1,21 @@ /** - * @file src/nvenc/nvenc_d3d11.h + * @file src/nvenc/win/impl/nvenc_d3d11_base.h * @brief Declarations for abstract Direct3D11 NVENC encoder. */ #pragma once -#ifdef _WIN32 - #include - #include +#include "../../common_impl/nvenc_base.h" +#include "../nvenc_d3d11.h" +#include "nvenc_shared_dll.h" - #include "nvenc_base.h" +#include +#include +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else namespace nvenc { +#endif _COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device); _COM_SMARTPTR_TYPEDEF(ID3D11Texture2D, IID_ID3D11Texture2D); @@ -21,27 +26,17 @@ namespace nvenc { * @brief Abstract Direct3D11 NVENC encoder. * Encapsulates common code used by native and interop implementations. */ - class nvenc_d3d11: public nvenc_base { + class nvenc_d3d11_base: public nvenc_base, virtual public nvenc_d3d11 { public: - explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type): - nvenc_base(device_type) {} - - ~nvenc_d3d11(); - - /** - * @brief Get input surface texture. - * @return Input surface texture. - */ - virtual ID3D11Texture2D * - get_input_texture() = 0; + nvenc_d3d11_base(NV_ENC_DEVICE_TYPE device_type, shared_dll dll): + nvenc_base(device_type), + dll(dll) {} protected: bool init_library() override; private: - HMODULE dll = NULL; + shared_dll dll; }; - -} // namespace nvenc -#endif +} diff --git a/src/nvenc/nvenc_d3d11_native.cpp b/src/nvenc/win/impl/nvenc_d3d11_native.cpp similarity index 83% rename from src/nvenc/nvenc_d3d11_native.cpp rename to src/nvenc/win/impl/nvenc_d3d11_native.cpp index a563b33d86e..b8b911b3e17 100644 --- a/src/nvenc/nvenc_d3d11_native.cpp +++ b/src/nvenc/win/impl/nvenc_d3d11_native.cpp @@ -1,16 +1,19 @@ /** - * @file src/nvenc/nvenc_d3d11_native.cpp + * @file src/nvenc/win/impl/nvenc_d3d11_native.cpp * @brief Definitions for native Direct3D11 NVENC encoder. */ -#ifdef _WIN32 - #include "nvenc_d3d11_native.h" +#include "nvenc_d3d11_native.h" - #include "nvenc_utils.h" +#include "../../common_impl/nvenc_utils.h" +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else namespace nvenc { +#endif - nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device): - nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX), + nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device, shared_dll dll): + nvenc_d3d11_base(NV_ENC_DEVICE_TYPE_DIRECTX, dll), d3d_device(d3d_device) { device = d3d_device; } @@ -48,7 +51,7 @@ namespace nvenc { } if (!registered_input_buffer) { - NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; + NV_ENC_REGISTER_RESOURCE register_resource = { NV_ENC_REGISTER_RESOURCE_VER }; register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_DIRECTX; register_resource.width = encoder_params.width; register_resource.height = encoder_params.height; @@ -66,6 +69,4 @@ namespace nvenc { return true; } - -} // namespace nvenc -#endif +} diff --git a/src/nvenc/nvenc_d3d11_native.h b/src/nvenc/win/impl/nvenc_d3d11_native.h similarity index 62% rename from src/nvenc/nvenc_d3d11_native.h rename to src/nvenc/win/impl/nvenc_d3d11_native.h index f9d49b18631..1e514b16631 100644 --- a/src/nvenc/nvenc_d3d11_native.h +++ b/src/nvenc/win/impl/nvenc_d3d11_native.h @@ -1,26 +1,26 @@ /** - * @file src/nvenc/nvenc_d3d11_native.h + * @file src/nvenc/win/impl/nvenc_d3d11_native.h * @brief Declarations for native Direct3D11 NVENC encoder. */ #pragma once -#ifdef _WIN32 - #include - #include - - #include "nvenc_d3d11.h" +#include "nvenc_d3d11_base.h" +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else namespace nvenc { +#endif /** * @brief Native Direct3D11 NVENC encoder. */ - class nvenc_d3d11_native final: public nvenc_d3d11 { + class nvenc_d3d11_native final: public nvenc_d3d11_base { public: /** * @param d3d_device Direct3D11 device used for encoding. */ - explicit nvenc_d3d11_native(ID3D11Device *d3d_device); + nvenc_d3d11_native(ID3D11Device *d3d_device, shared_dll dll); ~nvenc_d3d11_native(); ID3D11Texture2D * @@ -33,6 +33,4 @@ namespace nvenc { const ID3D11DevicePtr d3d_device; ID3D11Texture2DPtr d3d_input_texture; }; - -} // namespace nvenc -#endif +} diff --git a/src/nvenc/nvenc_d3d11_on_cuda.cpp b/src/nvenc/win/impl/nvenc_d3d11_on_cuda.cpp similarity index 90% rename from src/nvenc/nvenc_d3d11_on_cuda.cpp rename to src/nvenc/win/impl/nvenc_d3d11_on_cuda.cpp index 37fe896329c..2b69906792d 100644 --- a/src/nvenc/nvenc_d3d11_on_cuda.cpp +++ b/src/nvenc/win/impl/nvenc_d3d11_on_cuda.cpp @@ -1,16 +1,19 @@ /** - * @file src/nvenc/nvenc_d3d11_on_cuda.cpp + * @file src/nvenc/win/impl/nvenc_d3d11_on_cuda.cpp * @brief Definitions for CUDA NVENC encoder with Direct3D11 input surfaces. */ -#ifdef _WIN32 - #include "nvenc_d3d11_on_cuda.h" +#include "nvenc_d3d11_on_cuda.h" - #include "nvenc_utils.h" +#include "../../common_impl/nvenc_utils.h" +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else namespace nvenc { +#endif - nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device): - nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA), + nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device, shared_dll dll): + nvenc_d3d11_base(NV_ENC_DEVICE_TYPE_CUDA, dll), d3d_device(d3d_device) { } @@ -41,11 +44,6 @@ namespace nvenc { } cuda_context = nullptr; } - - if (cuda_functions.dll) { - FreeLibrary(cuda_functions.dll); - cuda_functions = {}; - } } ID3D11Texture2D * @@ -55,13 +53,13 @@ namespace nvenc { bool nvenc_d3d11_on_cuda::init_library() { - if (!nvenc_d3d11::init_library()) return false; + if (!nvenc_d3d11_base::init_library()) return false; constexpr auto dll_name = "nvcuda.dll"; - if ((cuda_functions.dll = LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32))) { + if ((cuda_functions.dll = make_shared_dll(LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)))) { auto load_function = [&](T &location, auto symbol) -> bool { - location = (T) GetProcAddress(cuda_functions.dll, symbol); + location = (T) GetProcAddress(cuda_functions.dll.get(), symbol); return location != nullptr; }; if (!load_function(cuda_functions.cuInit, "cuInit") || @@ -79,7 +77,6 @@ namespace nvenc { !load_function(cuda_functions.cuGraphicsSubResourceGetMappedArray, "cuGraphicsSubResourceGetMappedArray") || !load_function(cuda_functions.cuMemcpy2D, "cuMemcpy2D_v2")) { BOOST_LOG(error) << "NvEnc: missing CUDA functions in " << dll_name; - FreeLibrary(cuda_functions.dll); cuda_functions = {}; } } @@ -164,7 +161,7 @@ namespace nvenc { } if (!registered_input_buffer) { - NV_ENC_REGISTER_RESOURCE register_resource = { min_struct_version(NV_ENC_REGISTER_RESOURCE_VER, 3, 4) }; + NV_ENC_REGISTER_RESOURCE register_resource = { NV_ENC_REGISTER_RESOURCE_VER }; register_resource.resourceType = NV_ENC_INPUT_RESOURCE_TYPE_CUDADEVICEPTR; register_resource.width = encoder_params.width; register_resource.height = encoder_params.height; @@ -262,6 +259,4 @@ namespace nvenc { return { *this, nullptr }; } } - -} // namespace nvenc -#endif +} diff --git a/src/nvenc/nvenc_d3d11_on_cuda.h b/src/nvenc/win/impl/nvenc_d3d11_on_cuda.h similarity index 85% rename from src/nvenc/nvenc_d3d11_on_cuda.h rename to src/nvenc/win/impl/nvenc_d3d11_on_cuda.h index 81114321947..2ca11a58c71 100644 --- a/src/nvenc/nvenc_d3d11_on_cuda.h +++ b/src/nvenc/win/impl/nvenc_d3d11_on_cuda.h @@ -1,27 +1,29 @@ /** - * @file src/nvenc/nvenc_d3d11_on_cuda.h + * @file src/nvenc/win/impl/nvenc_d3d11_on_cuda.h * @brief Declarations for CUDA NVENC encoder with Direct3D11 input surfaces. */ #pragma once -#ifdef _WIN32 - #include "nvenc_d3d11.h" +#include "nvenc_d3d11_base.h" +#ifdef NVENC_NAMESPACE +namespace NVENC_NAMESPACE { +#else #include - namespace nvenc { +#endif /** * @brief Interop Direct3D11 on CUDA NVENC encoder. * Input surface is Direct3D11, encoding is performed by CUDA. */ - class nvenc_d3d11_on_cuda final: public nvenc_d3d11 { + class nvenc_d3d11_on_cuda final: public nvenc_d3d11_base { public: /** * @param d3d_device Direct3D11 device that will create input surface texture. * CUDA encoding device will be derived from it. */ - explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device); + nvenc_d3d11_on_cuda(ID3D11Device *d3d_device, shared_dll dll); ~nvenc_d3d11_on_cuda(); ID3D11Texture2D * @@ -82,7 +84,7 @@ namespace nvenc { tcuGraphicsUnmapResources *cuGraphicsUnmapResources; tcuGraphicsSubResourceGetMappedArray *cuGraphicsSubResourceGetMappedArray; tcuMemcpy2D_v2 *cuMemcpy2D; - HMODULE dll; + shared_dll dll; } cuda_functions = {}; CUresult last_cuda_error = CUDA_SUCCESS; @@ -91,6 +93,4 @@ namespace nvenc { CUdeviceptr cuda_surface = 0; size_t cuda_surface_pitch = 0; }; - -} // namespace nvenc -#endif +} diff --git a/src/nvenc/win/impl/nvenc_dynamic_factory_1100.cpp b/src/nvenc/win/impl/nvenc_dynamic_factory_1100.cpp new file mode 100644 index 00000000000..7fd0919bce3 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_dynamic_factory_1100.cpp @@ -0,0 +1,65 @@ + +namespace nvenc_1100 { + enum NV_ENC_VUI_VIDEO_FORMAT { + NV_ENC_VUI_VIDEO_FORMAT_COMPONENT = 0, + NV_ENC_VUI_VIDEO_FORMAT_PAL = 1, + NV_ENC_VUI_VIDEO_FORMAT_NTSC = 2, + NV_ENC_VUI_VIDEO_FORMAT_SECAM = 3, + NV_ENC_VUI_VIDEO_FORMAT_MAC = 4, + NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED = 5, + }; + enum NV_ENC_VUI_COLOR_PRIMARIES { + NV_ENC_VUI_COLOR_PRIMARIES_UNDEFINED = 0, + NV_ENC_VUI_COLOR_PRIMARIES_BT709 = 1, + NV_ENC_VUI_COLOR_PRIMARIES_UNSPECIFIED = 2, + NV_ENC_VUI_COLOR_PRIMARIES_RESERVED = 3, + NV_ENC_VUI_COLOR_PRIMARIES_BT470M = 4, + NV_ENC_VUI_COLOR_PRIMARIES_BT470BG = 5, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE170M = 6, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE240M = 7, + NV_ENC_VUI_COLOR_PRIMARIES_FILM = 8, + NV_ENC_VUI_COLOR_PRIMARIES_BT2020 = 9, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE428 = 10, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE431 = 11, + NV_ENC_VUI_COLOR_PRIMARIES_SMPTE432 = 12, + NV_ENC_VUI_COLOR_PRIMARIES_JEDEC_P22 = 22, + }; + enum NV_ENC_VUI_TRANSFER_CHARACTERISTIC { + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_UNDEFINED = 0, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT709 = 1, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_UNSPECIFIED = 2, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_RESERVED = 3, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT470M = 4, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT470BG = 5, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE170M = 6, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE240M = 7, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LINEAR = 8, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LOG = 9, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_LOG_SQRT = 10, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_IEC61966_2_4 = 11, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT1361_ECG = 12, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SRGB = 13, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_10 = 14, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_BT2020_12 = 15, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE2084 = 16, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_SMPTE428 = 17, + NV_ENC_VUI_TRANSFER_CHARACTERISTIC_ARIB_STD_B67 = 18, + }; + enum NV_ENC_VUI_MATRIX_COEFFS { + NV_ENC_VUI_MATRIX_COEFFS_RGB = 0, + NV_ENC_VUI_MATRIX_COEFFS_BT709 = 1, + NV_ENC_VUI_MATRIX_COEFFS_UNSPECIFIED = 2, + NV_ENC_VUI_MATRIX_COEFFS_RESERVED = 3, + NV_ENC_VUI_MATRIX_COEFFS_FCC = 4, + NV_ENC_VUI_MATRIX_COEFFS_BT470BG = 5, + NV_ENC_VUI_MATRIX_COEFFS_SMPTE170M = 6, + NV_ENC_VUI_MATRIX_COEFFS_SMPTE240M = 7, + NV_ENC_VUI_MATRIX_COEFFS_YCGCO = 8, + NV_ENC_VUI_MATRIX_COEFFS_BT2020_NCL = 9, + NV_ENC_VUI_MATRIX_COEFFS_BT2020_CL = 10, + NV_ENC_VUI_MATRIX_COEFFS_SMPTE2085 = 11, + }; +} // namespace nvenc_1100 + +#define NVENC_FACTORY_DEFINITION +#include "nvenc_dynamic_factory_1100.h" diff --git a/src/nvenc/win/impl/nvenc_dynamic_factory_1100.h b/src/nvenc/win/impl/nvenc_dynamic_factory_1100.h new file mode 100644 index 00000000000..7c0183d0267 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_dynamic_factory_1100.h @@ -0,0 +1,6 @@ +#pragma once + +#undef NVENC_FACTORY_VERSION +#define NVENC_FACTORY_VERSION 1100 + +#include "nvenc_dynamic_factory_blueprint.h" diff --git a/src/nvenc/win/impl/nvenc_dynamic_factory_1200.cpp b/src/nvenc/win/impl/nvenc_dynamic_factory_1200.cpp new file mode 100644 index 00000000000..b42a904a5ce --- /dev/null +++ b/src/nvenc/win/impl/nvenc_dynamic_factory_1200.cpp @@ -0,0 +1,2 @@ +#define NVENC_FACTORY_DEFINITION +#include "nvenc_dynamic_factory_1200.h" diff --git a/src/nvenc/win/impl/nvenc_dynamic_factory_1200.h b/src/nvenc/win/impl/nvenc_dynamic_factory_1200.h new file mode 100644 index 00000000000..fba3a7a8915 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_dynamic_factory_1200.h @@ -0,0 +1,6 @@ +#pragma once + +#undef NVENC_FACTORY_VERSION +#define NVENC_FACTORY_VERSION 1200 + +#include "nvenc_dynamic_factory_blueprint.h" diff --git a/src/nvenc/win/impl/nvenc_dynamic_factory_1202.cpp b/src/nvenc/win/impl/nvenc_dynamic_factory_1202.cpp new file mode 100644 index 00000000000..d5323b515f4 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_dynamic_factory_1202.cpp @@ -0,0 +1,2 @@ +#define NVENC_FACTORY_DEFINITION +#include "nvenc_dynamic_factory_1202.h" diff --git a/src/nvenc/win/impl/nvenc_dynamic_factory_1202.h b/src/nvenc/win/impl/nvenc_dynamic_factory_1202.h new file mode 100644 index 00000000000..e024243d9f3 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_dynamic_factory_1202.h @@ -0,0 +1,6 @@ +#pragma once + +#undef NVENC_FACTORY_VERSION +#define NVENC_FACTORY_VERSION 1202 + +#include "nvenc_dynamic_factory_blueprint.h" diff --git a/src/nvenc/win/impl/nvenc_dynamic_factory_blueprint.h b/src/nvenc/win/impl/nvenc_dynamic_factory_blueprint.h new file mode 100644 index 00000000000..86385d61545 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_dynamic_factory_blueprint.h @@ -0,0 +1,73 @@ +/** + * @file src/nvenc/win/impl/nvenc_dynamic_factory_blueprint.h + * @brief Special blueprint used for declaring and defining factories for specific NVENC SDK versions. + */ +#include + +#ifndef NVENC_FACTORY_VERSION + #error Missing NVENC_FACTORY_VERSION preprocessor definition +#endif + +#define NVENC_FACTORY_CLASS BOOST_PP_CAT(nvenc_dynamic_factory_, NVENC_FACTORY_VERSION) + +#include "nvenc_shared_dll.h" + +#include "../nvenc_dynamic_factory.h" + +namespace nvenc { + + class NVENC_FACTORY_CLASS: public nvenc_dynamic_factory { + public: + NVENC_FACTORY_CLASS(shared_dll dll): + dll(dll) {} + + static std::shared_ptr + get(shared_dll dll) { + return std::make_shared(dll); + } + + std::unique_ptr + create_nvenc_d3d11_native(ID3D11Device *d3d_device) override; + + std::unique_ptr + create_nvenc_d3d11_on_cuda(ID3D11Device *d3d_device) override; + + private: + shared_dll dll; + }; + +} // namespace nvenc + +#ifdef NVENC_FACTORY_DEFINITION + + #define NVENC_NAMESPACE BOOST_PP_CAT(nvenc_, NVENC_FACTORY_VERSION) + #define NVENC_FACTORY_INCLUDE(x) + +namespace NVENC_NAMESPACE { + #include NVENC_FACTORY_INCLUDE(dynlink_cuda.h) + #include NVENC_FACTORY_INCLUDE(nvEncodeAPI.h) +} // namespace NVENC_NAMESPACE + +using namespace nvenc; + + #include "../../common_impl/nvenc_base.cpp" + #include "../../common_impl/nvenc_utils.cpp" + #include "nvenc_d3d11_base.cpp" + #include "nvenc_d3d11_native.cpp" + #include "nvenc_d3d11_on_cuda.cpp" + +namespace nvenc { + + std::unique_ptr + NVENC_FACTORY_CLASS::create_nvenc_d3d11_native(ID3D11Device *d3d_device) { + return std::make_unique(d3d_device, dll); + } + + std::unique_ptr + NVENC_FACTORY_CLASS::create_nvenc_d3d11_on_cuda(ID3D11Device *d3d_device) { + return std::make_unique(d3d_device, dll); + } + +} // namespace nvenc + +#endif diff --git a/src/nvenc/win/impl/nvenc_shared_dll.h b/src/nvenc/win/impl/nvenc_shared_dll.h new file mode 100644 index 00000000000..7268983d348 --- /dev/null +++ b/src/nvenc/win/impl/nvenc_shared_dll.h @@ -0,0 +1,28 @@ +/** + * @file src/nvenc/win/impl/nvenc_shared_dll.h + * @brief Declarations for Windows HMODULE RAII helpers. + */ +#pragma once + +#include +#include + +#include + +namespace nvenc { + + using shared_dll = std::shared_ptr>; + + struct shared_dll_deleter { + void + operator()(HMODULE dll) { + if (dll) FreeLibrary(dll); + } + }; + + inline shared_dll + make_shared_dll(HMODULE dll) { + return shared_dll(dll, shared_dll_deleter()); + } + +} // namespace nvenc diff --git a/src/nvenc/win/nvenc_d3d11.h b/src/nvenc/win/nvenc_d3d11.h new file mode 100644 index 00000000000..1943af82cad --- /dev/null +++ b/src/nvenc/win/nvenc_d3d11.h @@ -0,0 +1,28 @@ +/** + * @file src/nvenc/win/nvenc_d3d11.h + * @brief Declarations for Direct3D11 NVENC encoder interface. + */ +#pragma once + +#include "../nvenc_encoder.h" + +#include + +namespace nvenc { + + /** + * @brief Direct3D11 NVENC encoder interface. + */ + class nvenc_d3d11: virtual public nvenc_encoder { + public: + virtual ~nvenc_d3d11() = default; + + /** + * @brief Get input surface texture. + * @return Input surface texture. + */ + virtual ID3D11Texture2D * + get_input_texture() = 0; + }; + +} // namespace nvenc diff --git a/src/nvenc/win/nvenc_dynamic_factory.cpp b/src/nvenc/win/nvenc_dynamic_factory.cpp new file mode 100644 index 00000000000..bd56a023523 --- /dev/null +++ b/src/nvenc/win/nvenc_dynamic_factory.cpp @@ -0,0 +1,160 @@ +/** + * @file src/nvenc/win/nvenc_dynamic_factory.cpp + * @brief Definitions for Windows NVENC encoder factory. + */ +#include "nvenc_dynamic_factory.h" + +#include "impl/nvenc_dynamic_factory_1100.h" +#include "impl/nvenc_dynamic_factory_1200.h" +#include "impl/nvenc_dynamic_factory_1202.h" + +#include "impl/nvenc_shared_dll.h" + +#include "src/logging.h" + +#include + +#include +#include + +uint32_t +NvEncodeAPIGetMaxSupportedVersion(uint32_t *version); + +namespace { + using namespace nvenc; + + constexpr std::array factory_priorities = { + std::tuple(&nvenc_dynamic_factory_1202::get, 1202), + std::tuple(&nvenc_dynamic_factory_1200::get, 1200), + std::tuple(&nvenc_dynamic_factory_1100::get, 1100), + }; + constexpr auto min_driver_version = "456.71"; + +#ifdef _WIN64 + constexpr auto dll_name = "nvEncodeAPI64.dll"; +#else + constexpr auto dll_name = "nvEncodeAPI.dll"; +#endif + + std::tuple + load_dll() { + auto dll = make_shared_dll(LoadLibraryEx(dll_name, NULL, LOAD_LIBRARY_SEARCH_SYSTEM32)); + if (!dll) { + BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name; + return {}; + } + + auto get_max_version = (decltype(NvEncodeAPIGetMaxSupportedVersion) *) GetProcAddress(dll.get(), "NvEncodeAPIGetMaxSupportedVersion"); + if (!get_max_version) { + BOOST_LOG(error) << "NvEnc: No NvEncodeAPIGetMaxSupportedVersion() in " << dll_name; + return {}; + } + + uint32_t max_version = 0; + if (get_max_version(&max_version) != 0) { + BOOST_LOG(error) << "NvEnc: NvEncodeAPIGetMaxSupportedVersion() failed"; + return {}; + } + max_version = (max_version >> 4) * 100 + (max_version & 0xf); + + return { dll, max_version }; + } + +} // namespace + +namespace nvenc { + + std::shared_ptr + nvenc_dynamic_factory::get() { + auto [dll, max_version] = load_dll(); + if (!dll) return {}; + + for (const auto &[factory_init, version] : factory_priorities) { + if (max_version >= version) { + return factory_init(dll); + } + } + + BOOST_LOG(error) << "NvEnc: minimum required driver version is " << min_driver_version; + return {}; + } + +} // namespace nvenc + +#ifdef SUNSHINE_TESTS + #include "tests/tests_common.h" + + #include + #include + +namespace { + _COM_SMARTPTR_TYPEDEF(IDXGIFactory1, IID_IDXGIFactory1); + _COM_SMARTPTR_TYPEDEF(IDXGIAdapter, IID_IDXGIAdapter); + _COM_SMARTPTR_TYPEDEF(ID3D11Device, IID_ID3D11Device); +} // namespace + +struct NvencVersionTests: testing::TestWithParam { + static void + SetUpTestSuite() { + std::tie(suite.dll, suite.max_version) = load_dll(); + if (!suite.dll) { + GTEST_SKIP() << "Can't load " << dll_name; + } + + IDXGIFactory1Ptr dxgi_factory; + ASSERT_HRESULT_SUCCEEDED(CreateDXGIFactory1(IID_PPV_ARGS(&dxgi_factory))); + + IDXGIAdapterPtr dxgi_adapter; + for (UINT i = 0; dxgi_factory->EnumAdapters(i, &dxgi_adapter) != DXGI_ERROR_NOT_FOUND; i++) { + DXGI_ADAPTER_DESC desc; + ASSERT_HRESULT_SUCCEEDED(dxgi_adapter->GetDesc(&desc)); + if (desc.VendorId == 0x10de) break; + } + if (!dxgi_adapter) GTEST_SKIP(); + + ASSERT_HRESULT_SUCCEEDED(D3D11CreateDevice(dxgi_adapter, D3D_DRIVER_TYPE_UNKNOWN, NULL, 0, + nullptr, 0, D3D11_SDK_VERSION, &suite.device, nullptr, nullptr)); + } + + static void + TearDownTestSuite() { + suite = {}; + } + + inline static struct { + nvenc::shared_dll dll; + uint32_t max_version; + ID3D11DevicePtr device; + } suite = {}; +}; + +TEST_P(NvencVersionTests, CreateAndEncode) { + auto [factory_init, version] = GetParam(); + if (version > suite.max_version) { + GTEST_SKIP() << "Need dll version " << version << ", have " << suite.max_version; + } + + auto factory = factory_init(suite.dll); + ASSERT_TRUE(factory); + + auto nvenc = factory->create_nvenc_d3d11_native(suite.device); + ASSERT_TRUE(nvenc); + + video::config_t config = { + .width = 1920, + .height = 1080, + .framerate = 60, + .bitrate = 10 * 1000, + }; + video::sunshine_colorspace_t colorspace = { + .colorspace = video::colorspace_e::rec601, + .bit_depth = 8, + }; + ASSERT_TRUE(nvenc->create_encoder({}, config, colorspace, platf::pix_fmt_e::nv12)); + ASSERT_FALSE(nvenc->encode_frame(0, false).data.empty()); +} + +INSTANTIATE_TEST_SUITE_P(NvencFactoryTestsPrivate, NvencVersionTests, testing::ValuesIn(factory_priorities), + [](const auto &info) { return std::to_string(std::get<1>(info.param)); }); + +#endif diff --git a/src/nvenc/win/nvenc_dynamic_factory.h b/src/nvenc/win/nvenc_dynamic_factory.h new file mode 100644 index 00000000000..f4fdd542158 --- /dev/null +++ b/src/nvenc/win/nvenc_dynamic_factory.h @@ -0,0 +1,44 @@ +/** + * @file src/nvenc/win/nvenc_dynamic_factory.h + * @brief Declarations for Windows NVENC encoder factory. + */ +#pragma once + +#include "nvenc_d3d11.h" + +#include + +namespace nvenc { + + /** + * @brief Windows NVENC encoder factory. + */ + class nvenc_dynamic_factory { + public: + virtual ~nvenc_dynamic_factory() = default; + + /** + * @brief Initialize NVENC factory, depends on NVIDIA drivers present in the system. + * @return `shared_ptr` containing factory on success, empty `shared_ptr` on error. + */ + static std::shared_ptr + get(); + + /** + * @brief Create native Direct3D11 NVENC encoder. + * @param d3d_device Direct3D11 device. + * @return `unique_ptr` containing encoder on success, empty `unique_ptr` on error. + */ + virtual std::unique_ptr + create_nvenc_d3d11_native(ID3D11Device *d3d_device) = 0; + + /** + * @brief Create CUDA NVENC encoder with Direct3D11 input surfaces. + * @param d3d_device Direct3D11 device. + * @return `unique_ptr` containing encoder on success, empty `unique_ptr` on error. + */ + virtual std::unique_ptr + create_nvenc_d3d11_on_cuda(ID3D11Device *d3d_device) = 0; + }; + +} // namespace nvenc diff --git a/src/platform/common.h b/src/platform/common.h index 5009c18335a..e48e37c71e5 100644 --- a/src/platform/common.h +++ b/src/platform/common.h @@ -54,7 +54,7 @@ namespace video { struct config_t; } // namespace video namespace nvenc { - class nvenc_base; + class nvenc_encoder; } namespace platf { @@ -428,7 +428,7 @@ namespace platf { virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0; - nvenc::nvenc_base *nvenc = nullptr; + nvenc::nvenc_encoder *nvenc = nullptr; }; enum class capture_e : int { diff --git a/src/platform/windows/display_vram.cpp b/src/platform/windows/display_vram.cpp index 96ddff84258..4290b2f6178 100644 --- a/src/platform/windows/display_vram.cpp +++ b/src/platform/windows/display_vram.cpp @@ -16,10 +16,7 @@ extern "C" { #include "misc.h" #include "src/config.h" #include "src/logging.h" -#include "src/nvenc/nvenc_config.h" -#include "src/nvenc/nvenc_d3d11_native.h" -#include "src/nvenc/nvenc_d3d11_on_cuda.h" -#include "src/nvenc/nvenc_utils.h" +#include "src/nvenc/win/nvenc_dynamic_factory.h" #include "src/video.h" #include @@ -1042,20 +1039,21 @@ namespace platf::dxgi { public: bool init_device(std::shared_ptr display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) { - buffer_format = nvenc::nvenc_format_from_sunshine_format(pix_fmt); - if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) { - BOOST_LOG(error) << "Unexpected pixel format for NvENC ["sv << from_pix_fmt(pix_fmt) << ']'; - return false; - } - if (base.init(display, adapter_p, pix_fmt)) return false; + auto factory = nvenc::nvenc_dynamic_factory::get(); + if (!factory) return false; + if (pix_fmt == pix_fmt_e::yuv444p16) { - nvenc_d3d = std::make_unique(base.device.get()); + nvenc_d3d = factory->create_nvenc_d3d11_on_cuda(base.device.get()); } else { - nvenc_d3d = std::make_unique(base.device.get()); + nvenc_d3d = factory->create_nvenc_d3d11_native(base.device.get()); } + + if (!nvenc_d3d) return false; + + buffer_format = pix_fmt; nvenc = nvenc_d3d.get(); return true; @@ -1065,8 +1063,7 @@ namespace platf::dxgi { init_encoder(const ::video::config_t &client_config, const ::video::sunshine_colorspace_t &colorspace) override { if (!nvenc_d3d) return false; - auto nvenc_colorspace = nvenc::nvenc_colorspace_from_sunshine_colorspace(colorspace); - if (!nvenc_d3d->create_encoder(config::video.nv, client_config, nvenc_colorspace, buffer_format)) return false; + if (!nvenc_d3d->create_encoder(config::video.nv, client_config, colorspace, buffer_format)) return false; base.apply_colorspace(colorspace); return base.init_output(nvenc_d3d->get_input_texture(), client_config.width, client_config.height) == 0; @@ -1080,7 +1077,7 @@ namespace platf::dxgi { private: d3d_base_encode_device base; std::unique_ptr nvenc_d3d; - NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED; + platf::pix_fmt_e buffer_format = platf::pix_fmt_e::unknown; }; bool diff --git a/src/video.cpp b/src/video.cpp index 8c5829a2afb..27edcfda050 100644 --- a/src/video.cpp +++ b/src/video.cpp @@ -21,7 +21,7 @@ extern "C" { #include "globals.h" #include "input.h" #include "logging.h" -#include "nvenc/nvenc_base.h" +#include "nvenc/nvenc_encoder.h" #include "platform/common.h" #include "sync.h" #include "video.h" diff --git a/third-party/nvenc-headers/1100 b/third-party/nvenc-headers/1100 new file mode 160000 index 00000000000..20703c97d32 --- /dev/null +++ b/third-party/nvenc-headers/1100 @@ -0,0 +1 @@ +Subproject commit 20703c97d32089634f67c5f4ab680ad7bd48877e diff --git a/third-party/nv-codec-headers b/third-party/nvenc-headers/1200 similarity index 100% rename from third-party/nv-codec-headers rename to third-party/nvenc-headers/1200 diff --git a/third-party/nvenc-headers/1202 b/third-party/nvenc-headers/1202 new file mode 160000 index 00000000000..9934f17316b --- /dev/null +++ b/third-party/nvenc-headers/1202 @@ -0,0 +1 @@ +Subproject commit 9934f17316b66ce6de12f3b82203a298bc9351d8