fix(nvenc): dynamic nv codec sdk selection (#5451)

This commit is contained in:
Dave Lane
2026-07-26 21:06:28 -04:00
committed by GitHub
parent 7cb92071ac
commit fb3d85cff4
32 changed files with 3284 additions and 501 deletions
+4
View File
@@ -38,6 +38,10 @@ endif()
# set the module path, used for includes # set the module path, used for includes
set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake") set(CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# CPM
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/cpm/CPM.cmake)
CPMUsePackageLock(package-lock.cmake)
# export compile_commands.json # export compile_commands.json
set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+50 -4
View File
@@ -59,11 +59,53 @@ elseif(UNIX)
endif() endif()
endif() endif()
include_directories( set(NVENC_PUBLIC_SOURCES
BEFORE SYSTEM "${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_config.h"
"${CMAKE_SOURCE_DIR}/third-party/build-deps/third-party/FFmpeg/nv-codec-headers/include" "${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11_interface.h"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory.cpp"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory.h"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory_versions.h"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_encoded_frame.h"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_encoder.h"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_shared_dll.h"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_version.h"
) )
file(GLOB NVENC_SOURCES CONFIGURE_DEPENDS "src/nvenc/*.cpp" "src/nvenc/*.h") set(NVENC_SOURCES ${NVENC_PUBLIC_SOURCES})
if(WIN32)
set(NVENC_IMPLEMENTATION_SOURCES
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_base.cpp"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11.cpp"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11_native.cpp"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_d3d11_on_cuda.cpp"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_dynamic_factory_impl.cpp"
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_utils.cpp"
)
# Add a version-isolated NVENC implementation object library.
# add_nvenc_sdk_implementation: args = `target_name`, `sdk_version`, `sdk_include_dir`
function(add_nvenc_sdk_implementation target_name sdk_version sdk_include_dir)
add_library(${target_name} OBJECT ${NVENC_IMPLEMENTATION_SOURCES})
target_include_directories(${target_name} BEFORE PRIVATE "${sdk_include_dir}")
target_compile_definitions(${target_name} PRIVATE
NVENC_FACTORY_SUFFIX=${sdk_version}
NVENC_NAMESPACE=nvenc_${sdk_version}
NVENC_SDK_VERSION=${sdk_version}
)
target_compile_options(${target_name} PRIVATE ${SUNSHINE_COMPILE_OPTIONS})
endfunction()
add_nvenc_sdk_implementation(nvenc_sdk_1100 1100 "${NV_CODEC_HEADERS_11_INCLUDE_DIR}")
add_nvenc_sdk_implementation(nvenc_sdk_1200 1200 "${NV_CODEC_HEADERS_12_INCLUDE_DIR}")
add_nvenc_sdk_implementation(nvenc_sdk_1300 1300 "${NV_CODEC_HEADERS_13_INCLUDE_DIR}")
list(APPEND NVENC_SOURCES
$<TARGET_OBJECTS:nvenc_sdk_1100>
$<TARGET_OBJECTS:nvenc_sdk_1200>
$<TARGET_OBJECTS:nvenc_sdk_1300>
)
endif()
list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES}) list(APPEND PLATFORM_TARGET_FILES ${NVENC_SOURCES})
set(SUNSHINE_TARGET_FILES set(SUNSHINE_TARGET_FILES
@@ -155,6 +197,10 @@ include_directories(
${Boost_INCLUDE_DIRS} # has to be the last, or we get runtime error on macOS ffmpeg encoder ${Boost_INCLUDE_DIRS} # has to be the last, or we get runtime error on macOS ffmpeg encoder
) )
if(WIN32)
include_directories(BEFORE SYSTEM "${NV_CODEC_HEADERS_13_INCLUDE_DIR}")
endif()
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${MINIUPNP_LIBRARIES} ${MINIUPNP_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT} ${CMAKE_THREAD_LIBS_INIT}
View File
+1379
View File
File diff suppressed because it is too large Load Diff
+1
View File
@@ -29,6 +29,7 @@ if(SUNSHINE_ENABLE_TRAY)
endif() endif()
# common dependencies # common dependencies
include("${CMAKE_MODULE_PATH}/dependencies/nv_codec_headers.cmake")
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake") include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
find_package(PkgConfig REQUIRED) find_package(PkgConfig REQUIRED)
find_package(Threads REQUIRED) find_package(Threads REQUIRED)
@@ -0,0 +1,9 @@
if(WIN32)
CPMGetPackage(nv_codec_headers_13)
CPMGetPackage(nv_codec_headers_11)
CPMGetPackage(nv_codec_headers_12)
set(NV_CODEC_HEADERS_11_INCLUDE_DIR "${nv_codec_headers_11_SOURCE_DIR}/include")
set(NV_CODEC_HEADERS_12_INCLUDE_DIR "${nv_codec_headers_12_SOURCE_DIR}/include")
set(NV_CODEC_HEADERS_13_INCLUDE_DIR "${nv_codec_headers_13_SOURCE_DIR}/include")
endif()
+67
View File
@@ -0,0 +1,67 @@
# CPM Package Lock
# This file should be committed to version control
# The first argument of CPMDeclarePackage can be freely chosen and is used as argument in CPMGetPackage.
# The NAME argument should be package name that would also be used in a find_package call.
# Ideally, both are the same, which might not always be possible: https://github.com/cpm-cmake/CPM.cmake/issues/603
# This is needed to support CPM_USE_LOCAL_PACKAGES
# Renovate-bot will update the versions and hashes in this file when a new version of a dependency is released.
# The comments above each dependency are used by renovate to identify the dependencies and extract the version numbers.
# See https://github.com/LizardByte/.github/blob/master/renovate-config.json5 for the configuration of renovate.
#
# Expected dependency structure for new entries:
# - Start each block with a human-readable comment, for example `# Example dependency`.
# - Follow it with consecutive renovate metadata comments.
# - The first metadata line must start with `# renovate:` and include `datasource=` and `depName=`.
# - Optional metadata keys are `packageName=`, `versioning=`, `extractVersion=`, and `registryUrl=`.
# - Optional metadata may stay on the `# renovate:` line or continue on the next consecutive `#` lines.
# - Keep metadata keys in this order: `datasource`, `depName`, `packageName`, `versioning`,
# `extractVersion`, `registryUrl`.
# - After metadata, declare the tracked value with `set(NAME_VERSION ...)` or `set(NAME_TAG ...)`.
# - If the dependency also tracks a SHA256, keep `set(NAME_SHA256 ...)` immediately after the
# matching `NAME_VERSION` or `NAME_TAG` line with no unrelated lines between them.
# - Keep `CPMDeclarePackage(...)` below the tracked values.
#
# Example layout:
# - `# Example dependency`
# - `# renovate: datasource=github-tags depName=owner/repo`
# - `# versioning=regex:^v(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)$`
# - `set(EXAMPLE_TAG v1.2.3)`
# - `set(EXAMPLE_SHA256 <sha256>)`
# - `CPMDeclarePackage(...)`
set(PATCH_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/patches")
# NVENC SDK 11.0 headers
# renovate: datasource=github-tags depName=FFmpeg/nv-codec-headers
# versioning=regex:^n(?<major>11)\.(?<minor>0)\.(?<patch>\d+)\.(?<build>\d+)$
set(NV_CODEC_HEADERS_11_TAG n11.0.10.3)
CPMDeclarePackage(nv_codec_headers_11
NAME nv_codec_headers_11
GIT_REPOSITORY https://github.com/FFmpeg/nv-codec-headers.git
GIT_TAG ${NV_CODEC_HEADERS_11_TAG}
DOWNLOAD_ONLY YES
)
# NVENC SDK 12.0 headers
# renovate: datasource=github-tags depName=FFmpeg/nv-codec-headers
# versioning=regex:^n(?<major>12)\.(?<minor>0)\.(?<patch>\d+)\.(?<build>\d+)$
set(NV_CODEC_HEADERS_12_TAG n12.0.16.2)
CPMDeclarePackage(nv_codec_headers_12
NAME nv_codec_headers_12
GIT_REPOSITORY https://github.com/FFmpeg/nv-codec-headers.git
GIT_TAG ${NV_CODEC_HEADERS_12_TAG}
DOWNLOAD_ONLY YES
)
# NVENC SDK 13.0 headers
# renovate: datasource=github-tags depName=FFmpeg/nv-codec-headers
# versioning=regex:^n(?<major>13)\.(?<minor>0)\.(?<patch>\d+)\.(?<build>\d+)$
set(NV_CODEC_HEADERS_13_TAG n13.0.19.1)
CPMDeclarePackage(nv_codec_headers_13
NAME nv_codec_headers_13
GIT_REPOSITORY https://github.com/FFmpeg/nv-codec-headers.git
GIT_TAG ${NV_CODEC_HEADERS_13_TAG}
DOWNLOAD_ONLY YES
)
+539 -395
View File
File diff suppressed because it is too large Load Diff
+202 -11
View File
@@ -4,32 +4,33 @@
*/ */
#pragma once #pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes // local includes
#include "nvenc_colorspace.h" #include "nvenc_colorspace.h"
#include "nvenc_config.h" #include "nvenc_config.h"
#include "nvenc_encoded_frame.h" #include "nvenc_encoded_frame.h"
#include "nvenc_encoder.h"
#include "nvenc_sdk.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/video.h" #include "src/video.h"
#include "src/video_colorspace.h"
/** /**
* @brief Standalone NVENC encoder * @brief Standalone NVENC encoder
*/ */
namespace nvenc { namespace NVENC_NAMESPACE {
/** /**
* @brief Abstract platform-agnostic base of standalone NVENC encoder. * @brief Abstract platform-agnostic base of standalone NVENC encoder.
* Derived classes perform platform-specific operations. * Derived classes perform platform-specific operations.
*/ */
class nvenc_base { // Virtual inheritance is required because platform implementations also inherit their SDK-neutral interface.
class nvenc_base: public virtual ::nvenc::nvenc_encoder { // NOSONAR(cpp:S1011)
public: public:
/** /**
* @param device_type Underlying device type used by derived class. * @param device_type Underlying device type used by derived class.
*/ */
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type); explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
virtual ~nvenc_base(); ~nvenc_base() override;
nvenc_base(const nvenc_base &) = delete; nvenc_base(const nvenc_base &) = delete;
nvenc_base &operator=(const nvenc_base &) = delete; nvenc_base &operator=(const nvenc_base &) = delete;
@@ -42,13 +43,18 @@ namespace nvenc {
* @param buffer_format Platform-agnostic input surface format. * @param buffer_format Platform-agnostic input surface format.
* @return `true` on success, `false` on error * @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); bool create_encoder(
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
const video::sunshine_colorspace_t &colorspace,
platf::pix_fmt_e buffer_format
) override;
/** /**
* @brief Destroy the encoder. * @brief Destroy the encoder.
* Derived classes classes call it in the destructor. * Derived classes classes call it in the destructor.
*/ */
void destroy_encoder(); void destroy_encoder() override;
/** /**
* @brief Encode the next frame using platform-specific input surface. * @brief Encode the next frame using platform-specific input surface.
@@ -58,7 +64,7 @@ namespace nvenc {
* @param force_idr Whether to encode frame as forced IDR. * @param force_idr Whether to encode frame as forced IDR.
* @return Encoded frame. * @return Encoded frame.
*/ */
nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr); ::nvenc::nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr) override;
/** /**
* @brief Perform reference frame invalidation (RFI) procedure. * @brief Perform reference frame invalidation (RFI) procedure.
@@ -67,7 +73,7 @@ namespace nvenc {
* @return `true` on success, `false` on error. * @return `true` on success, `false` on error.
* After error next frame must be encoded with `force_idr = true`. * After error next frame must be encoded with `force_idr = true`.
*/ */
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame); bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) override;
protected: protected:
/** /**
@@ -137,6 +143,191 @@ namespace nvenc {
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`. ///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
private: private:
/**
* @brief Query one encoder capability.
*
* @param encode_guid Codec GUID to query.
* @param cap Capability identifier.
* @return Capability value, or zero when the query fails.
*/
int get_encoder_cap(const GUID &encode_guid, NV_ENC_CAPS cap) const;
/**
* @brief Validate the requested input format and dimensions against encoder capabilities.
*
* @param encode_guid Selected codec GUID.
* @param buffer_format Selected NVENC input format.
* @return `true` when the request is supported, otherwise `false`.
*/
bool validate_encoder_capabilities(const GUID &encode_guid, NV_ENC_BUFFER_FORMAT buffer_format);
/**
* @brief Configure split-frame encoding for the selected SDK.
*
* @param init_params Encoder initialization parameters to update.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
*/
void configure_split_frame(
NV_ENC_INITIALIZE_PARAMS &init_params,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config
) const;
/**
* @brief Configure rate control and VBV options.
*
* @param enc_config Encoder configuration to update.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param encode_guid Selected codec GUID.
*/
void configure_rate_control(
NV_ENC_CONFIG &enc_config,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
const GUID &encode_guid
);
/**
* @brief Configure the requested reference-frame count.
*
* @param ref_frames_option Codec-specific reference-frame option.
* @param list0_option Codec-specific list-zero option.
* @param default_count Default reference-frame count.
* @param requested_count Client-requested reference-frame count.
* @param encode_guid Selected codec GUID.
*/
void configure_reference_frames(
std::uint32_t &ref_frames_option,
NV_ENC_NUM_REF_FRAMES &list0_option,
std::uint32_t default_count,
int requested_count,
const GUID &encode_guid
);
/**
* @brief Configure VUI metadata and intra-refresh options shared by H.264 and HEVC.
*
* @tparam FormatConfig Codec-specific NVENC configuration type.
* @param format_config Codec-specific encoder configuration to update.
* @param client_config Stream configuration requested by the client.
* @param colorspace NVENC colorspace metadata.
* @param buffer_format Selected NVENC input format.
* @param encode_guid Selected codec GUID.
*/
template<typename FormatConfig>
void configure_h264_hevc_metadata(
FormatConfig &format_config,
const video::config_t &client_config,
const nvenc_colorspace_t &colorspace,
NV_ENC_BUFFER_FORMAT buffer_format,
const GUID &encode_guid
);
/**
* @brief Configure H.264 codec options.
*
* @param enc_config Encoder configuration to update.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param colorspace NVENC colorspace metadata.
* @param buffer_format Selected NVENC input format.
* @param encode_guid Selected codec GUID.
*/
void configure_h264(
NV_ENC_CONFIG &enc_config,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
const nvenc_colorspace_t &colorspace,
NV_ENC_BUFFER_FORMAT buffer_format,
const GUID &encode_guid
);
/**
* @brief Configure HEVC codec options.
*
* @param enc_config Encoder configuration to update.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param colorspace NVENC colorspace metadata.
* @param buffer_format Selected NVENC input format.
* @param encode_guid Selected codec GUID.
*/
void configure_hevc(
NV_ENC_CONFIG &enc_config,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
const nvenc_colorspace_t &colorspace,
NV_ENC_BUFFER_FORMAT buffer_format,
const GUID &encode_guid
);
#if NVENC_SDK_VERSION >= 1200
/**
* @brief Configure AV1 codec options.
*
* @param enc_config Encoder configuration to update.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param colorspace NVENC colorspace metadata.
* @param buffer_format Selected NVENC input format.
* @param encode_guid Selected codec GUID.
*/
void configure_av1(
NV_ENC_CONFIG &enc_config,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
const nvenc_colorspace_t &colorspace,
NV_ENC_BUFFER_FORMAT buffer_format,
const GUID &encode_guid
);
#endif
/**
* @brief Configure codec-specific encoder options.
*
* @param enc_config Encoder configuration to update.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param colorspace NVENC colorspace metadata.
* @param buffer_format Selected NVENC input format.
* @param encode_guid Selected codec GUID.
*/
void configure_codec(
NV_ENC_CONFIG &enc_config,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
const nvenc_colorspace_t &colorspace,
NV_ENC_BUFFER_FORMAT buffer_format,
const GUID &encode_guid
);
/**
* @brief Initialize the encoder and its registered input and output resources.
*
* @param init_params Completed encoder initialization parameters.
* @return `true` on success, otherwise `false`.
*/
bool initialize_encoder_resources(NV_ENC_INITIALIZE_PARAMS &init_params);
/**
* @brief Log the selected encoder configuration.
*
* @param init_params Encoder initialization parameters.
* @param enc_config Encoder configuration.
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param buffer_format Selected NVENC input format.
*/
void log_created_encoder(
const NV_ENC_INITIALIZE_PARAMS &init_params,
const NV_ENC_CONFIG &enc_config,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
NV_ENC_BUFFER_FORMAT buffer_format
) const;
NV_ENC_OUTPUT_PTR output_bitstream = nullptr; NV_ENC_OUTPUT_PTR output_bitstream = nullptr;
struct { struct {
@@ -147,4 +338,4 @@ namespace nvenc {
} encoder_state; } encoder_state;
}; };
} // namespace nvenc } // namespace NVENC_NAMESPACE
+4 -4
View File
@@ -4,10 +4,10 @@
*/ */
#pragma once #pragma once
// lib includes // local includes
#include <ffnvcodec/nvEncodeAPI.h> #include "nvenc_sdk.h"
namespace nvenc { namespace NVENC_NAMESPACE {
/** /**
* @brief YUV colorspace and color range. * @brief YUV colorspace and color range.
@@ -19,4 +19,4 @@ namespace nvenc {
bool full_range; ///< Whether the video range is full-range instead of limited. bool full_range; ///< Whether the video range is full-range instead of limited.
}; };
} // namespace nvenc } // namespace NVENC_NAMESPACE
+19 -34
View File
@@ -8,62 +8,47 @@
#ifdef _WIN32 #ifdef _WIN32
#include "nvenc_d3d11.h" #include "nvenc_d3d11.h"
namespace nvenc { namespace NVENC_NAMESPACE {
nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type): nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type, ::nvenc::shared_dll dll):
nvenc_base(device_type) { nvenc_base(device_type),
dll(std::move(dll)) {
async_event_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr); async_event_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr);
} }
nvenc_d3d11::~nvenc_d3d11() { nvenc_d3d11::~nvenc_d3d11() {
if (dll) {
FreeLibrary(dll);
dll = nullptr;
}
if (async_event_handle) { if (async_event_handle) {
CloseHandle(async_event_handle); CloseHandle(async_event_handle);
} }
} }
bool nvenc_d3d11::init_library() { bool nvenc_d3d11::init_library() {
if (dll) { if (nvenc) {
return true; return true;
} }
#ifdef _WIN64 auto create_instance = reinterpret_cast<decltype(NvEncodeAPICreateInstance) *>(
constexpr auto dll_name = "nvEncodeAPI64.dll"; GetProcAddress(dll.get(), "NvEncodeAPICreateInstance")
#else );
constexpr auto dll_name = "nvEncodeAPI.dll"; if (!create_instance) {
#endif BOOST_LOG(error) << "NvEnc: No NvEncodeAPICreateInstance() in NVENC driver library";
return false;
if ((dll = LoadLibraryEx(dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32))) {
if (auto create_instance = (decltype(NvEncodeAPICreateInstance) *) GetProcAddress(dll, "NvEncodeAPICreateInstance")) {
auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
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 " << dll_name;
}
} else {
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << dll_name;
} }
if (dll) { auto new_nvenc = std::make_unique<NV_ENCODE_API_FUNCTION_LIST>();
FreeLibrary(dll); new_nvenc->version = NV_ENCODE_API_FUNCTION_LIST_VER;
dll = nullptr; if (nvenc_failed(create_instance(new_nvenc.get()))) {
BOOST_LOG(error) << "NvEnc: NvEncodeAPICreateInstance() failed: " << last_nvenc_error_string;
return false;
} }
return false; nvenc = std::move(new_nvenc);
return true;
} }
bool nvenc_d3d11::wait_for_async_event(uint32_t timeout_ms) { bool nvenc_d3d11::wait_for_async_event(uint32_t timeout_ms) {
return WaitForSingleObject(async_event_handle, timeout_ms) == WAIT_OBJECT_0; return WaitForSingleObject(async_event_handle, timeout_ms) == WAIT_OBJECT_0;
} }
} // namespace nvenc } // namespace NVENC_NAMESPACE
#endif #endif
+10 -7
View File
@@ -11,8 +11,10 @@
// local includes // local includes
#include "nvenc_base.h" #include "nvenc_base.h"
#include "nvenc_d3d11_interface.h"
#include "nvenc_shared_dll.h"
namespace nvenc { namespace NVENC_NAMESPACE {
#ifdef DOXYGEN #ifdef DOXYGEN
/** /**
@@ -42,29 +44,30 @@ namespace nvenc {
* @brief Abstract Direct3D11 NVENC encoder. * @brief Abstract Direct3D11 NVENC encoder.
* Encapsulates common code used by native and interop implementations. * Encapsulates common code used by native and interop implementations.
*/ */
class nvenc_d3d11: public nvenc_base { class nvenc_d3d11: public nvenc_base, public ::nvenc::nvenc_d3d11_interface {
public: public:
/** /**
* @brief Initialize an NVENC session wrapper for D3D11 input textures. * @brief Initialize an NVENC session wrapper for D3D11 input textures.
* *
* @param device_type NVENC device type used by the encoder session. * @param device_type NVENC device type used by the encoder session.
* @param dll Shared NVENC driver module.
*/ */
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type); explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type, ::nvenc::shared_dll dll);
~nvenc_d3d11(); ~nvenc_d3d11() override;
/** /**
* @brief Get input surface texture. * @brief Get input surface texture.
* @return Input surface texture. * @return Input surface texture.
*/ */
virtual ID3D11Texture2D *get_input_texture() = 0; ID3D11Texture2D *get_input_texture() override = 0;
protected: protected:
bool init_library() override; bool init_library() override;
bool wait_for_async_event(uint32_t timeout_ms) override; bool wait_for_async_event(uint32_t timeout_ms) override;
private: private:
HMODULE dll = nullptr; ::nvenc::shared_dll dll;
}; };
} // namespace nvenc } // namespace NVENC_NAMESPACE
#endif #endif
+36
View File
@@ -0,0 +1,36 @@
/**
* @file src/nvenc/nvenc_d3d11_interface.h
* @brief Declarations for the SDK-neutral Direct3D11 NVENC interface.
*/
#pragma once
#ifdef _WIN32
// lib includes
#include <d3d11.h>
// local includes
#include "nvenc_encoder.h"
namespace nvenc {
/**
* @brief SDK-neutral Direct3D11 NVENC encoder interface.
*/
// Virtual inheritance is required because concrete encoders also inherit their SDK-specific implementation.
class nvenc_d3d11_interface: public virtual nvenc_encoder { // NOSONAR(cpp:S1011)
public:
/**
* @brief Destroy the SDK-neutral Direct3D11 encoder interface.
*/
~nvenc_d3d11_interface() override = default;
/**
* @brief Get the input surface texture.
*
* @return Input surface texture.
*/
virtual ID3D11Texture2D *get_input_texture() = 0;
};
} // namespace nvenc
#endif
+4 -4
View File
@@ -9,10 +9,10 @@
// local includes // local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace NVENC_NAMESPACE {
nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device): nvenc_d3d11_native::nvenc_d3d11_native(ID3D11Device *d3d_device, ::nvenc::shared_dll dll):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX), nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX, std::move(dll)),
d3d_device(d3d_device) { d3d_device(d3d_device) {
device = d3d_device; device = d3d_device;
} }
@@ -70,5 +70,5 @@ namespace nvenc {
return true; return true;
} }
} // namespace nvenc } // namespace NVENC_NAMESPACE
#endif #endif
+5 -4
View File
@@ -11,7 +11,7 @@
// local includes // local includes
#include "nvenc_d3d11.h" #include "nvenc_d3d11.h"
namespace nvenc { namespace NVENC_NAMESPACE {
/** /**
* @brief Native Direct3D11 NVENC encoder. * @brief Native Direct3D11 NVENC encoder.
@@ -20,9 +20,10 @@ namespace nvenc {
public: public:
/** /**
* @param d3d_device Direct3D11 device used for encoding. * @param d3d_device Direct3D11 device used for encoding.
* @param dll Shared NVENC driver module.
*/ */
explicit nvenc_d3d11_native(ID3D11Device *d3d_device); explicit nvenc_d3d11_native(ID3D11Device *d3d_device, ::nvenc::shared_dll dll);
~nvenc_d3d11_native(); ~nvenc_d3d11_native() override;
ID3D11Texture2D *get_input_texture() override; ID3D11Texture2D *get_input_texture() override;
@@ -33,5 +34,5 @@ namespace nvenc {
ID3D11Texture2DPtr d3d_input_texture; ID3D11Texture2DPtr d3d_input_texture;
}; };
} // namespace nvenc } // namespace NVENC_NAMESPACE
#endif #endif
+4 -4
View File
@@ -9,10 +9,10 @@
// local includes // local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace NVENC_NAMESPACE {
nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device): nvenc_d3d11_on_cuda::nvenc_d3d11_on_cuda(ID3D11Device *d3d_device, ::nvenc::shared_dll dll):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA), nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA, std::move(dll)),
d3d_device(d3d_device) { d3d_device(d3d_device) {
} }
@@ -265,5 +265,5 @@ namespace nvenc {
} }
} }
} // namespace nvenc } // namespace NVENC_NAMESPACE
#endif #endif
+6 -7
View File
@@ -4,13 +4,11 @@
*/ */
#pragma once #pragma once
#ifdef _WIN32 #ifdef _WIN32
// lib includes
#include <ffnvcodec/dynlink_cuda.h>
// local includes // local includes
#include "nvenc_d3d11.h" #include "nvenc_d3d11.h"
#include "nvenc_sdk.h"
namespace nvenc { namespace NVENC_NAMESPACE {
/** /**
* @brief Interop Direct3D11 on CUDA NVENC encoder. * @brief Interop Direct3D11 on CUDA NVENC encoder.
@@ -21,9 +19,10 @@ namespace nvenc {
/** /**
* @param d3d_device Direct3D11 device that will create input surface texture. * @param d3d_device Direct3D11 device that will create input surface texture.
* CUDA encoding device will be derived from it. * CUDA encoding device will be derived from it.
* @param dll Shared NVENC driver module.
*/ */
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device); explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device, ::nvenc::shared_dll dll);
~nvenc_d3d11_on_cuda(); ~nvenc_d3d11_on_cuda() override;
ID3D11Texture2D *get_input_texture() override; ID3D11Texture2D *get_input_texture() override;
@@ -84,5 +83,5 @@ namespace nvenc {
size_t cuda_surface_pitch = 0; size_t cuda_surface_pitch = 0;
}; };
} // namespace nvenc } // namespace NVENC_NAMESPACE
#endif #endif
+129
View File
@@ -0,0 +1,129 @@
/**
* @file src/nvenc/nvenc_dynamic_factory.cpp
* @brief Definitions for runtime NVENC SDK selection on Windows.
*/
#ifdef _WIN32
// this include
#include "nvenc_dynamic_factory.h"
// standard includes
#include <bit>
#include <utility>
// local includes
#include "nvenc_dynamic_factory_versions.h"
#include "src/logging.h"
namespace {
#ifdef _WIN64
constexpr auto nvenc_dll_name = "nvEncodeAPI64.dll";
#else
constexpr auto nvenc_dll_name = "nvEncodeAPI.dll";
#endif
constexpr auto minimum_driver_version = "456.71";
using get_max_supported_version_fn = std::uint32_t(WINAPI *)(std::uint32_t *);
} // namespace
namespace nvenc {
nvenc_dynamic_factory::nvenc_dynamic_factory(
shared_dll dll,
nvenc_sdk_version sdk_version,
create_encoder_fn create_native,
create_encoder_fn create_on_cuda
):
dll(std::move(dll)),
selected_sdk_version(sdk_version),
create_native(std::move(create_native)),
create_on_cuda(std::move(create_on_cuda)) {
}
std::shared_ptr<nvenc_dynamic_factory> nvenc_dynamic_factory::get() {
return get({
[]() {
return make_shared_dll(LoadLibraryEx(nvenc_dll_name, nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32));
},
[](HMODULE dll, const char *symbol) {
return GetProcAddress(dll, symbol);
},
});
}
std::shared_ptr<nvenc_dynamic_factory> nvenc_dynamic_factory::get(const nvenc_runtime_api &runtime_api) {
auto dll = runtime_api.load_driver();
if (!dll) {
BOOST_LOG(debug) << "NvEnc: Couldn't load NvEnc library " << nvenc_dll_name;
return {};
}
const auto get_max_version = std::bit_cast<get_max_supported_version_fn>(
runtime_api.get_symbol(dll.get(), "NvEncodeAPIGetMaxSupportedVersion")
);
if (!get_max_version) {
BOOST_LOG(error) << "NvEnc: No NvEncodeAPIGetMaxSupportedVersion() in " << nvenc_dll_name;
return {};
}
std::uint32_t packed_max_version = 0;
if (get_max_version(&packed_max_version) != 0U) {
BOOST_LOG(error) << "NvEnc: NvEncodeAPIGetMaxSupportedVersion() failed";
return {};
}
const auto max_version = decode_nvenc_driver_version(packed_max_version);
const auto sdk_version = select_nvenc_sdk_version(max_version);
switch (sdk_version) {
case nvenc_sdk_version::sdk_13_0:
return std::make_shared<nvenc_dynamic_factory>(
std::move(dll),
sdk_version,
detail::create_nvenc_d3d11_native_1300,
detail::create_nvenc_d3d11_on_cuda_1300
);
case nvenc_sdk_version::sdk_12_0:
return std::make_shared<nvenc_dynamic_factory>(
std::move(dll),
sdk_version,
detail::create_nvenc_d3d11_native_1200,
detail::create_nvenc_d3d11_on_cuda_1200
);
case nvenc_sdk_version::sdk_11_0:
return std::make_shared<nvenc_dynamic_factory>(
std::move(dll),
sdk_version,
detail::create_nvenc_d3d11_native_1100,
detail::create_nvenc_d3d11_on_cuda_1100
);
case nvenc_sdk_version::unsupported:
default:
BOOST_LOG(error) << "NvEnc: minimum required driver version is " << minimum_driver_version;
return {};
}
}
std::unique_ptr<nvenc_d3d11_interface> nvenc_dynamic_factory::create_nvenc_d3d11_native(
ID3D11Device *d3d_device
) const {
return create_native(d3d_device, dll);
}
std::unique_ptr<nvenc_d3d11_interface> nvenc_dynamic_factory::create_nvenc_d3d11_on_cuda(
ID3D11Device *d3d_device
) const {
return create_on_cuda(d3d_device, dll);
}
nvenc_sdk_version nvenc_dynamic_factory::sdk_version() const {
return selected_sdk_version;
}
} // namespace nvenc
#endif
+106
View File
@@ -0,0 +1,106 @@
/**
* @file src/nvenc/nvenc_dynamic_factory.h
* @brief Declarations for runtime NVENC SDK selection on Windows.
*/
#pragma once
#ifdef _WIN32
// standard includes
#include <functional>
#include <memory>
// local includes
#include "nvenc_d3d11_interface.h"
#include "nvenc_shared_dll.h"
#include "nvenc_version.h"
namespace nvenc {
/**
* @brief Windows runtime operations used to discover the installed NVENC API.
*/
struct nvenc_runtime_api {
using load_driver_fn = std::function<shared_dll()>; ///< Load the NVENC driver module.
/**
* @brief Resolve an export from the loaded NVENC driver module.
*/
using get_symbol_fn = std::function<FARPROC(HMODULE, const char *)>;
load_driver_fn load_driver; ///< Function that loads the NVENC driver module.
get_symbol_fn get_symbol; ///< Resolve a driver export.
};
/**
* @brief Factory bound to the newest NVENC SDK supported by the installed driver.
*/
class nvenc_dynamic_factory {
public:
/**
* @brief SDK-specific encoder constructor.
*/
using create_encoder_fn =
std::function<std::unique_ptr<nvenc_d3d11_interface>(ID3D11Device *, shared_dll)>;
/**
* @brief Construct a factory bound to one NVENC SDK implementation.
*
* @param dll Shared NVENC driver module.
* @param sdk_version Selected SDK version.
* @param create_native Native Direct3D11 encoder constructor.
* @param create_on_cuda CUDA-interoperability encoder constructor.
*/
nvenc_dynamic_factory(
shared_dll dll,
nvenc_sdk_version sdk_version,
create_encoder_fn create_native,
create_encoder_fn create_on_cuda
);
/**
* @brief Load the NVENC driver and select a compatible SDK implementation.
*
* @return Initialized factory, or an empty pointer when NVENC is unavailable.
*/
static std::shared_ptr<nvenc_dynamic_factory> get();
/**
* @brief Select a compatible SDK implementation using the supplied runtime operations.
*
* @param runtime_api Runtime operations used to load and query the NVENC driver.
* @return Initialized factory, or an empty pointer when NVENC is unavailable.
*/
static std::shared_ptr<nvenc_dynamic_factory> get(const nvenc_runtime_api &runtime_api);
/**
* @brief Create a native Direct3D11 NVENC encoder.
*
* @param d3d_device Direct3D11 device used for encoding.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native(ID3D11Device *d3d_device) const;
/**
* @brief Create a CUDA NVENC encoder with Direct3D11 input surfaces.
*
* @param d3d_device Direct3D11 device used to create input textures.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda(ID3D11Device *d3d_device) const;
/**
* @brief Get the SDK implementation selected for this factory.
*
* @return Selected NVENC SDK version.
*/
nvenc_sdk_version sdk_version() const;
private:
shared_dll dll;
nvenc_sdk_version selected_sdk_version;
create_encoder_fn create_native;
create_encoder_fn create_on_cuda;
};
} // namespace nvenc
#endif
+48
View File
@@ -0,0 +1,48 @@
/**
* @file src/nvenc/nvenc_dynamic_factory_impl.cpp
* @brief SDK-specific constructors used by the runtime NVENC factory.
*/
#ifdef _WIN32
#ifndef NVENC_NAMESPACE
#error NVENC_NAMESPACE must identify the version-specific implementation namespace
#endif
#ifndef NVENC_FACTORY_SUFFIX
#error NVENC_FACTORY_SUFFIX must identify the version-specific constructor suffix
#endif
// standard includes
#include <utility>
// local includes
#include "nvenc_d3d11_native.h"
#include "nvenc_d3d11_on_cuda.h"
#include "nvenc_dynamic_factory_versions.h"
#ifndef DOXYGEN
#define NVENC_CONCAT_IMPL(left, right) left##right
#define NVENC_CONCAT(left, right) NVENC_CONCAT_IMPL(left, right)
namespace nvenc::detail {
std::unique_ptr<nvenc_d3d11_interface> NVENC_CONCAT(create_nvenc_d3d11_native_, NVENC_FACTORY_SUFFIX)(
ID3D11Device *device,
shared_dll dll
) {
return std::make_unique<NVENC_NAMESPACE::nvenc_d3d11_native>(device, std::move(dll));
}
std::unique_ptr<nvenc_d3d11_interface> NVENC_CONCAT(create_nvenc_d3d11_on_cuda_, NVENC_FACTORY_SUFFIX)(
ID3D11Device *device,
shared_dll dll
) {
return std::make_unique<NVENC_NAMESPACE::nvenc_d3d11_on_cuda>(device, std::move(dll));
}
} // namespace nvenc::detail
#undef NVENC_CONCAT
#undef NVENC_CONCAT_IMPL
#endif
#endif
@@ -0,0 +1,67 @@
/**
* @file src/nvenc/nvenc_dynamic_factory_versions.h
* @brief Declarations for SDK-specific NVENC encoder constructors.
*/
#pragma once
#ifdef _WIN32
// standard includes
#include <memory>
// local includes
#include "nvenc_d3d11_interface.h"
#include "nvenc_shared_dll.h"
namespace nvenc::detail {
/**
* @brief Create an SDK 11.0 native Direct3D11 encoder.
*
* @param device Direct3D11 device used for encoding.
* @param dll Shared NVENC driver module.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native_1100(ID3D11Device *device, shared_dll dll);
/**
* @brief Create an SDK 11.0 CUDA-interoperability encoder.
*
* @param device Direct3D11 device used for input surfaces.
* @param dll Shared NVENC driver module.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda_1100(ID3D11Device *device, shared_dll dll);
/**
* @brief Create an SDK 12.0 native Direct3D11 encoder.
*
* @param device Direct3D11 device used for encoding.
* @param dll Shared NVENC driver module.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native_1200(ID3D11Device *device, shared_dll dll);
/**
* @brief Create an SDK 12.0 CUDA-interoperability encoder.
*
* @param device Direct3D11 device used for input surfaces.
* @param dll Shared NVENC driver module.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda_1200(ID3D11Device *device, shared_dll dll);
/**
* @brief Create an SDK 13.0 native Direct3D11 encoder.
*
* @param device Direct3D11 device used for encoding.
* @param dll Shared NVENC driver module.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_native_1300(ID3D11Device *device, shared_dll dll);
/**
* @brief Create an SDK 13.0 CUDA-interoperability encoder.
*
* @param device Direct3D11 device used for input surfaces.
* @param dll Shared NVENC driver module.
* @return SDK-neutral encoder instance.
*/
std::unique_ptr<nvenc_d3d11_interface> create_nvenc_d3d11_on_cuda_1300(ID3D11Device *device, shared_dll dll);
} // namespace nvenc::detail
#endif
+75
View File
@@ -0,0 +1,75 @@
/**
* @file src/nvenc/nvenc_encoder.h
* @brief Declarations for the SDK-neutral NVENC encoder interface.
*/
#pragma once
// standard includes
#include <cstdint>
// local includes
#include "nvenc_config.h"
#include "nvenc_encoded_frame.h"
namespace platf {
enum class pix_fmt_e;
}
namespace video {
struct config_t;
struct sunshine_colorspace_t;
} // namespace video
namespace nvenc {
/**
* @brief SDK-neutral standalone NVENC encoder interface.
*/
class nvenc_encoder {
public:
/**
* @brief Destroy the SDK-neutral encoder interface.
*/
virtual ~nvenc_encoder() = default;
/**
* @brief Create the encoder.
*
* @param config NVENC encoder configuration.
* @param client_config Stream configuration requested by the client.
* @param colorspace Sunshine colorspace metadata.
* @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.
*/
virtual void destroy_encoder() = 0;
/**
* @brief Encode the next frame using the platform-specific input surface.
*
* @param frame_index Frame index that uniquely identifies the frame.
* @param force_idr Whether to encode the frame as a forced IDR.
* @return Encoded frame.
*/
virtual nvenc_encoded_frame encode_frame(std::uint64_t frame_index, bool force_idr) = 0;
/**
* @brief Invalidate reference frames in the requested range.
*
* @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.
*/
virtual bool invalidate_ref_frames(std::uint64_t first_frame, std::uint64_t last_frame) = 0;
};
} // namespace nvenc
+125
View File
@@ -0,0 +1,125 @@
/**
* @file src/nvenc/nvenc_sdk.h
* @brief Imports one NVENC SDK into its version-specific implementation namespace.
*/
#pragma once
#ifdef DOXYGEN
/**
* @def NVENC_NAMESPACE
* @brief Namespace used for the documented NVENC implementation.
*/
#define NVENC_NAMESPACE nvenc
/**
* @def NVENC_SDK_VERSION
* @brief NVENC SDK version used while generating documentation.
*/
// Doxygen must select the same preprocessor interface used by versioned build targets.
#define NVENC_SDK_VERSION 1300 // NOSONAR(cpp:S5028)
#endif
#ifndef NVENC_NAMESPACE
#error NVENC_NAMESPACE must identify the version-specific implementation namespace
#endif
#ifndef NVENC_SDK_VERSION
#error NVENC_SDK_VERSION must identify the version-specific NVENC SDK
#endif
// Include common platform and integer declarations outside the SDK namespace.
#include <cstddef>
#include <cstdint>
#ifdef _WIN32
#include <Windows.h>
#endif
// The version-specific namespace is populated by the SDK headers included below.
namespace NVENC_NAMESPACE { // NOSONAR(cpp:S3261)
#ifndef DOXYGEN
#include <ffnvcodec/dynlink_cuda.h>
#include <ffnvcodec/nvEncodeAPI.h>
#if NVENCAPI_MAJOR_VERSION * 100 + NVENCAPI_MINOR_VERSION != NVENC_SDK_VERSION
#error NVENC_SDK_VERSION does not match the selected nv-codec-headers package
#endif
#endif
#if NVENC_SDK_VERSION < 1200
/**
* @brief Video format values added as named types after SDK 11.0.
*/
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,
};
/**
* @brief Color-primary values added as named types after SDK 11.0.
*/
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,
};
/**
* @brief Transfer-characteristic values added as named types after SDK 11.0.
*/
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,
};
/**
* @brief Matrix-coefficient values added as named types after SDK 11.0.
*/
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,
};
#endif
} // namespace NVENC_NAMESPACE
+49
View File
@@ -0,0 +1,49 @@
/**
* @file src/nvenc/nvenc_shared_dll.h
* @brief Windows module lifetime helpers shared by NVENC factories and encoders.
*/
#pragma once
#ifdef _WIN32
// standard includes
#include <memory>
#include <type_traits>
// platform includes
#include <Windows.h>
namespace nvenc {
/**
* @brief Shared ownership wrapper for a loaded Windows module.
*/
using shared_dll = std::shared_ptr<std::remove_pointer_t<HMODULE>>;
/**
* @brief Release a Windows module when its final shared owner is destroyed.
*/
struct shared_dll_deleter {
/**
* @brief Release the module.
*
* @param dll Module handle to release.
*/
void operator()(HMODULE dll) const {
if (dll) {
FreeLibrary(dll);
}
}
};
/**
* @brief Wrap a Windows module handle in shared ownership.
*
* @param dll Module handle returned by `LoadLibraryEx()`.
* @return Shared module handle.
*/
inline shared_dll make_shared_dll(HMODULE dll) {
return shared_dll(dll, shared_dll_deleter {});
}
} // namespace nvenc
#endif
+2 -2
View File
@@ -8,7 +8,7 @@
// local includes // local includes
#include "nvenc_utils.h" #include "nvenc_utils.h"
namespace nvenc { namespace NVENC_NAMESPACE {
#ifdef _WIN32 #ifdef _WIN32
/** /**
@@ -102,4 +102,4 @@ namespace nvenc {
return colorspace; return colorspace;
} }
} // namespace nvenc } // namespace NVENC_NAMESPACE
+3 -5
View File
@@ -9,15 +9,13 @@
#include <dxgiformat.h> #include <dxgiformat.h>
#endif #endif
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes // local includes
#include "nvenc_colorspace.h" #include "nvenc_colorspace.h"
#include "nvenc_sdk.h"
#include "src/platform/common.h" #include "src/platform/common.h"
#include "src/video_colorspace.h" #include "src/video_colorspace.h"
namespace nvenc { namespace NVENC_NAMESPACE {
#ifdef _WIN32 #ifdef _WIN32
/** /**
@@ -45,4 +43,4 @@ namespace nvenc {
*/ */
nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace); nvenc_colorspace_t nvenc_colorspace_from_sunshine_colorspace(const video::sunshine_colorspace_t &sunshine_colorspace);
} // namespace nvenc } // namespace NVENC_NAMESPACE
+53
View File
@@ -0,0 +1,53 @@
/**
* @file src/nvenc/nvenc_version.h
* @brief NVENC SDK version selection helpers.
*/
#pragma once
// standard includes
#include <cstdint>
#include <utility>
namespace nvenc {
/**
* @brief NVENC SDK implementations compiled into Sunshine.
*/
enum class nvenc_sdk_version : std::uint32_t {
unsupported = 0U, ///< No compatible SDK implementation is available.
sdk_11_0 = 1100U, ///< Video Codec SDK 11.0.
sdk_12_0 = 1200U, ///< Video Codec SDK 12.0.
sdk_13_0 = 1300U, ///< Video Codec SDK 13.0.
};
/**
* @brief Convert the packed driver API version to a comparable integer.
*
* @param version Version returned by `NvEncodeAPIGetMaxSupportedVersion()`.
* @return Version encoded as `major * 100 + minor`.
*/
constexpr std::uint32_t decode_nvenc_driver_version(std::uint32_t version) {
return (version >> 4U) * 100U + (version & 0x0FU);
}
/**
* @brief Select the newest compiled SDK supported by the installed driver.
*
* @param max_version Maximum driver API version encoded as `major * 100 + minor`.
* @return Selected SDK implementation, or `unsupported` when the driver is too old.
*/
constexpr nvenc_sdk_version select_nvenc_sdk_version(std::uint32_t max_version) {
using enum nvenc_sdk_version;
if (max_version >= std::to_underlying(sdk_13_0)) {
return sdk_13_0;
}
if (max_version >= std::to_underlying(sdk_12_0)) {
return sdk_12_0;
}
if (max_version >= std::to_underlying(sdk_11_0)) {
return sdk_11_0;
}
return unsupported;
}
} // namespace nvenc
+2 -2
View File
@@ -69,7 +69,7 @@ namespace video {
} // namespace video } // namespace video
namespace nvenc { namespace nvenc {
class nvenc_base; class nvenc_encoder;
} }
namespace platf { namespace platf {
@@ -638,7 +638,7 @@ namespace platf {
*/ */
virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0; virtual bool init_encoder(const video::config_t &client_config, const video::sunshine_colorspace_t &colorspace) = 0;
nvenc::nvenc_base *nvenc = nullptr; ///< NVENC encoder instance owned by the encode device. nvenc::nvenc_encoder *nvenc = nullptr; ///< NVENC encoder instance owned by the encode device.
}; };
/** /**
+18 -17
View File
@@ -24,9 +24,7 @@ extern "C" {
#include "src/config.h" #include "src/config.h"
#include "src/logging.h" #include "src/logging.h"
#include "src/nvenc/nvenc_config.h" #include "src/nvenc/nvenc_config.h"
#include "src/nvenc/nvenc_d3d11_native.h" #include "src/nvenc/nvenc_dynamic_factory.h"
#include "src/nvenc/nvenc_d3d11_on_cuda.h"
#include "src/nvenc/nvenc_utils.h"
#include "src/video.h" #include "src/video.h"
#include "utf_utils.h" #include "utf_utils.h"
@@ -1240,21 +1238,25 @@ namespace platf::dxgi {
* @return True when the D3D11 device resources are initialized. * @return True when the D3D11 device resources are initialized.
*/ */
bool init_device(std::shared_ptr<platf::display_t> display, adapter_t::pointer adapter_p, pix_fmt_e pix_fmt) { bool init_device(std::shared_ptr<platf::display_t> 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)) { if (base.init(display, adapter_p, pix_fmt)) {
return false; return false;
} }
if (pix_fmt == pix_fmt_e::yuv444p16) { auto factory = nvenc::nvenc_dynamic_factory::get();
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_on_cuda>(base.device.get()); if (!factory) {
} else { return false;
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_native>(base.device.get());
} }
if (pix_fmt == pix_fmt_e::yuv444p16) {
nvenc_d3d = factory->create_nvenc_d3d11_on_cuda(base.device.get());
} else {
nvenc_d3d = factory->create_nvenc_d3d11_native(base.device.get());
}
if (!nvenc_d3d) {
return false;
}
buffer_format = pix_fmt;
nvenc = nvenc_d3d.get(); nvenc = nvenc_d3d.get();
return true; return true;
@@ -1272,8 +1274,7 @@ namespace platf::dxgi {
return false; return false;
} }
auto nvenc_colorspace = nvenc::nvenc_colorspace_from_sunshine_colorspace(colorspace); if (!nvenc_d3d->create_encoder(config::video.nv, client_config, colorspace, buffer_format)) {
if (!nvenc_d3d->create_encoder(config::video.nv, client_config, nvenc_colorspace, buffer_format)) {
return false; return false;
} }
@@ -1293,8 +1294,8 @@ namespace platf::dxgi {
private: private:
d3d_base_encode_device base; d3d_base_encode_device base;
std::unique_ptr<nvenc::nvenc_d3d11> nvenc_d3d; std::unique_ptr<nvenc::nvenc_d3d11_interface> nvenc_d3d;
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED; platf::pix_fmt_e buffer_format = platf::pix_fmt_e::unknown;
}; };
/** /**
+4 -1
View File
@@ -18,6 +18,9 @@ extern "C" {
#include <libavutil/mastering_display_metadata.h> #include <libavutil/mastering_display_metadata.h>
#include <libavutil/opt.h> #include <libavutil/opt.h>
#include <libavutil/pixdesc.h> #include <libavutil/pixdesc.h>
#if !defined(_WIN32) && !defined(__APPLE__)
#include <ffnvcodec/nvEncodeAPI.h>
#endif
} }
// local includes // local includes
@@ -27,7 +30,7 @@ extern "C" {
#include "globals.h" #include "globals.h"
#include "input.h" #include "input.h"
#include "logging.h" #include "logging.h"
#include "nvenc/nvenc_base.h" #include "nvenc/nvenc_encoder.h"
#include "platform/common.h" #include "platform/common.h"
#include "sync.h" #include "sync.h"
#include "video.h" #include "video.h"
+212
View File
@@ -0,0 +1,212 @@
/**
* @file tests/unit/test_nvenc_dynamic_factory.cpp
* @brief Tests for the Windows runtime NVENC SDK factory.
*/
#ifdef _WIN32
// standard includes
#include <array>
#include <bit>
#include <cstdint>
#include <memory>
#include <string_view>
#include <utility>
// lib includes
#include <gtest/gtest.h>
// local includes
#include "src/nvenc/nvenc_dynamic_factory.h"
namespace {
std::uint32_t reported_version; ///< Version returned by the fake NVENC driver.
std::uint32_t reported_status; ///< Status returned by the fake NVENC driver.
/**
* @brief Fake implementation of `NvEncodeAPIGetMaxSupportedVersion()`.
*
* @param version Receives the configured packed API version.
* @return Configured NVENC status code.
*/
std::uint32_t WINAPI get_fake_max_supported_version(std::uint32_t *version) {
*version = reported_version;
return reported_status;
}
/**
* @brief Minimal SDK-neutral encoder used to verify factory callbacks.
*/
class fake_nvenc_encoder final: public nvenc::nvenc_d3d11_interface {
public:
bool create_encoder(
const nvenc::nvenc_config &,
const video::config_t &,
const video::sunshine_colorspace_t &,
platf::pix_fmt_e
) override {
return true;
}
void destroy_encoder() override {
}
nvenc::nvenc_encoded_frame encode_frame(std::uint64_t frame_index, bool force_idr) override {
return {{}, frame_index, force_idr, false};
}
bool invalidate_ref_frames(std::uint64_t, std::uint64_t) override {
return true;
}
ID3D11Texture2D *get_input_texture() override {
return nullptr;
}
};
/**
* @brief Make a non-owning fake Windows module handle for factory discovery tests.
*
* @return Shared fake module handle.
*/
nvenc::shared_dll make_fake_dll() {
constexpr std::uintptr_t fake_handle_value = 1U;
return {
reinterpret_cast<HMODULE>(fake_handle_value),
[](HMODULE) {
},
};
}
/**
* @brief Make runtime operations that expose the fake version query function.
*
* @return Runtime operations for a successfully loaded fake NVENC driver.
*/
nvenc::nvenc_runtime_api make_fake_runtime_api() {
return {
[]() {
return make_fake_dll();
},
[](HMODULE dll, const char *symbol) {
EXPECT_EQ(dll, make_fake_dll().get());
EXPECT_EQ(std::string_view {symbol}, "NvEncodeAPIGetMaxSupportedVersion");
return std::bit_cast<FARPROC>(&get_fake_max_supported_version);
},
};
}
TEST(NvencDynamicFactoryTest, HandlesUnavailableDriver) {
bool resolved_symbol = false;
const nvenc::nvenc_runtime_api runtime_api {
[]() {
return nvenc::shared_dll {};
},
[&resolved_symbol](HMODULE, const char *) {
resolved_symbol = true;
return FARPROC {};
},
};
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(runtime_api));
EXPECT_FALSE(resolved_symbol);
}
TEST(NvencDynamicFactoryTest, HandlesMissingVersionQuery) {
const nvenc::nvenc_runtime_api runtime_api {
[]() {
return make_fake_dll();
},
[](HMODULE, const char *) {
return FARPROC {};
},
};
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(runtime_api));
}
TEST(NvencDynamicFactoryTest, HandlesFailedVersionQuery) {
reported_version = 1300U;
reported_status = 1U;
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(make_fake_runtime_api()));
}
TEST(NvencDynamicFactoryTest, RejectsUnsupportedDriver) {
reported_version = 10U << 4U;
reported_status = 0U;
EXPECT_FALSE(nvenc::nvenc_dynamic_factory::get(make_fake_runtime_api()));
}
TEST(NvencDynamicFactoryTest, SelectsSupportedSdkImplementations) {
using enum nvenc::nvenc_sdk_version;
constexpr std::array test_cases {
std::pair {11U << 4U, sdk_11_0},
std::pair {12U << 4U, sdk_12_0},
std::pair {13U << 4U, sdk_13_0},
std::pair {14U << 4U, sdk_13_0},
};
reported_status = 0U;
for (const auto &[version, expected] : test_cases) {
reported_version = version;
const auto factory = nvenc::nvenc_dynamic_factory::get(make_fake_runtime_api());
ASSERT_TRUE(factory);
EXPECT_EQ(factory->sdk_version(), expected);
}
}
TEST(NvencDynamicFactoryTest, UsesConfiguredEncoderConstructors) {
bool created_native = false;
bool created_on_cuda = false;
const auto dll = make_fake_dll();
nvenc::nvenc_dynamic_factory factory {
dll,
nvenc::nvenc_sdk_version::sdk_13_0,
[&created_native, &dll](ID3D11Device *, nvenc::shared_dll callback_dll) {
created_native = true;
EXPECT_EQ(callback_dll, dll);
return std::make_unique<fake_nvenc_encoder>();
},
[&created_on_cuda, &dll](ID3D11Device *, nvenc::shared_dll callback_dll) {
created_on_cuda = true;
EXPECT_EQ(callback_dll, dll);
return std::make_unique<fake_nvenc_encoder>();
},
};
EXPECT_EQ(factory.sdk_version(), nvenc::nvenc_sdk_version::sdk_13_0);
EXPECT_TRUE(factory.create_nvenc_d3d11_native(nullptr));
EXPECT_TRUE(factory.create_nvenc_d3d11_on_cuda(nullptr));
EXPECT_TRUE(created_native);
EXPECT_TRUE(created_on_cuda);
}
TEST(NvencDynamicFactoryTest, UsesDefaultWindowsRuntime) {
const auto factory = nvenc::nvenc_dynamic_factory::get();
EXPECT_TRUE(!factory || factory->sdk_version() != nvenc::nvenc_sdk_version::unsupported);
}
TEST(NvencSharedDllTest, OwnsLoadedModuleUntilLastReference) {
const auto handle = LoadLibraryEx("version.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32);
ASSERT_NE(handle, nullptr);
auto dll = nvenc::make_shared_dll(handle);
EXPECT_EQ(dll.get(), handle);
auto copy = dll;
EXPECT_EQ(copy.use_count(), 2);
dll.reset();
EXPECT_EQ(copy.use_count(), 1);
copy.reset();
}
TEST(NvencSharedDllTest, AcceptsNullModule) {
auto dll = nvenc::make_shared_dll(nullptr);
EXPECT_FALSE(dll);
dll.reset();
}
} // namespace
#endif
+52
View File
@@ -0,0 +1,52 @@
/**
* @file tests/unit/test_nvenc_version.cpp
* @brief Tests for runtime NVENC SDK version selection.
*/
// standard includes
#include <array>
#include <cstdint>
// lib includes
#include <gtest/gtest.h>
// local includes
#include "src/nvenc/nvenc_version.h"
namespace {
/**
* @brief Expected SDK selection for a driver API version.
*/
struct nvenc_version_test_case {
std::uint32_t max_version; ///< Maximum API version reported by the driver.
nvenc::nvenc_sdk_version expected; ///< SDK implementation Sunshine should select.
};
TEST(NvencVersionTest, DecodesPackedDriverVersion) {
EXPECT_EQ(nvenc::decode_nvenc_driver_version((11U << 4U) | 0U), 1100U);
EXPECT_EQ(nvenc::decode_nvenc_driver_version((13U << 4U) | 1U), 1301U);
}
TEST(NvencVersionTest, SelectsNewestCompatibleSdk) {
using enum nvenc::nvenc_sdk_version;
constexpr std::array test_cases {
nvenc_version_test_case {1000U, unsupported},
nvenc_version_test_case {1099U, unsupported},
nvenc_version_test_case {1100U, sdk_11_0},
nvenc_version_test_case {1101U, sdk_11_0},
nvenc_version_test_case {1199U, sdk_11_0},
nvenc_version_test_case {1200U, sdk_12_0},
nvenc_version_test_case {1201U, sdk_12_0},
nvenc_version_test_case {1299U, sdk_12_0},
nvenc_version_test_case {1300U, sdk_13_0},
nvenc_version_test_case {1301U, sdk_13_0},
nvenc_version_test_case {1400U, sdk_13_0},
};
for (const auto &[max_version, expected] : test_cases) {
EXPECT_EQ(nvenc::select_nvenc_sdk_version(max_version), expected);
}
}
} // namespace