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(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
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+50 -4
View File
@@ -59,11 +59,53 @@ elseif(UNIX)
endif()
endif()
include_directories(
BEFORE SYSTEM
"${CMAKE_SOURCE_DIR}/third-party/build-deps/third-party/FFmpeg/nv-codec-headers/include"
set(NVENC_PUBLIC_SOURCES
"${CMAKE_SOURCE_DIR}/src/nvenc/nvenc_config.h"
"${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})
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
)
if(WIN32)
include_directories(BEFORE SYSTEM "${NV_CODEC_HEADERS_13_INCLUDE_DIR}")
endif()
list(APPEND SUNSHINE_EXTERNAL_LIBRARIES
${MINIUPNP_LIBRARIES}
${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()
# common dependencies
include("${CMAKE_MODULE_PATH}/dependencies/nv_codec_headers.cmake")
include("${CMAKE_MODULE_PATH}/dependencies/nlohmann_json.cmake")
find_package(PkgConfig 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
)
+463 -319
View File
@@ -6,30 +6,121 @@
#include "nvenc_base.h"
// standard includes
#include <algorithm>
#include <array>
#include <cmath>
#include <cstring>
#include <format>
#include <string_view>
#include <utility>
#include <vector>
// local includes
#include "nvenc_utils.h"
#include "src/config.h"
#include "src/logging.h"
#include "src/utility.h"
/**
* @def MAKE_NVENC_VER(major, minor)
* @brief Macro for MAKE NVENC VER.
*/
#define MAKE_NVENC_VER(major, minor) ((major) | ((minor) << 24))
// 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(13U, 0U)
#error Check and update NVENC code for backwards compatibility!
#endif
namespace {
using namespace NVENC_NAMESPACE;
/**
* @brief Determine whether an NVENC buffer format stores 10-bit samples.
*
* @param buffer_format NVENC input buffer format.
* @return `true` for a 10-bit format, otherwise `false`.
*/
bool buffer_is_10bit(NV_ENC_BUFFER_FORMAT buffer_format) {
return buffer_format == NV_ENC_BUFFER_FORMAT_YUV420_10BIT || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
}
/**
* @brief Determine whether an NVENC buffer format stores YUV 4:4:4 samples.
*
* @param buffer_format NVENC input buffer format.
* @return `true` for a YUV 4:4:4 format, otherwise `false`.
*/
bool buffer_is_yuv444(NV_ENC_BUFFER_FORMAT buffer_format) {
return buffer_format == NV_ENC_BUFFER_FORMAT_AYUV ||
buffer_format == NV_ENC_BUFFER_FORMAT_YUV444 ||
buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
}
/**
* @brief Determine whether a codec GUID appears in the driver-provided list.
*
* @param encode_guids Driver-provided codec GUIDs.
* @param encode_guid Codec GUID to locate.
* @return `true` when the codec is supported, otherwise `false`.
*/
bool contains_guid(const std::vector<GUID> &encode_guids, const GUID &encode_guid) {
return std::ranges::any_of(encode_guids, [&](const GUID &guid) {
return std::memcmp(&encode_guid, &guid, sizeof(GUID)) == 0;
});
}
/**
* @brief Get the display name for a Sunshine video format.
*
* @param video_format Sunshine video format identifier.
* @return Display name including its trailing separator.
*/
std::string_view video_format_name(int video_format) {
switch (video_format) {
case 0:
return "H.264 ";
case 1:
return "HEVC ";
case 2:
return "AV1 ";
default:
return " ";
}
}
/**
* @brief Convert an NVENC status value to its symbolic name.
*
* @param status NVENC status value.
* @return Symbolic name when known, otherwise its numeric value.
*/
std::string nvenc_status_string(NVENCSTATUS status) {
static constexpr auto names = std::to_array<std::pair<NVENCSTATUS, std::string_view>>({
{NV_ENC_SUCCESS, "NV_ENC_SUCCESS"},
{NV_ENC_ERR_NO_ENCODE_DEVICE, "NV_ENC_ERR_NO_ENCODE_DEVICE"},
{NV_ENC_ERR_UNSUPPORTED_DEVICE, "NV_ENC_ERR_UNSUPPORTED_DEVICE"},
{NV_ENC_ERR_INVALID_ENCODERDEVICE, "NV_ENC_ERR_INVALID_ENCODERDEVICE"},
{NV_ENC_ERR_INVALID_DEVICE, "NV_ENC_ERR_INVALID_DEVICE"},
{NV_ENC_ERR_DEVICE_NOT_EXIST, "NV_ENC_ERR_DEVICE_NOT_EXIST"},
{NV_ENC_ERR_INVALID_PTR, "NV_ENC_ERR_INVALID_PTR"},
{NV_ENC_ERR_INVALID_EVENT, "NV_ENC_ERR_INVALID_EVENT"},
{NV_ENC_ERR_INVALID_PARAM, "NV_ENC_ERR_INVALID_PARAM"},
{NV_ENC_ERR_INVALID_CALL, "NV_ENC_ERR_INVALID_CALL"},
{NV_ENC_ERR_OUT_OF_MEMORY, "NV_ENC_ERR_OUT_OF_MEMORY"},
{NV_ENC_ERR_ENCODER_NOT_INITIALIZED, "NV_ENC_ERR_ENCODER_NOT_INITIALIZED"},
{NV_ENC_ERR_UNSUPPORTED_PARAM, "NV_ENC_ERR_UNSUPPORTED_PARAM"},
{NV_ENC_ERR_LOCK_BUSY, "NV_ENC_ERR_LOCK_BUSY"},
{NV_ENC_ERR_NOT_ENOUGH_BUFFER, "NV_ENC_ERR_NOT_ENOUGH_BUFFER"},
{NV_ENC_ERR_INVALID_VERSION, "NV_ENC_ERR_INVALID_VERSION"},
{NV_ENC_ERR_MAP_FAILED, "NV_ENC_ERR_MAP_FAILED"},
{NV_ENC_ERR_NEED_MORE_INPUT, "NV_ENC_ERR_NEED_MORE_INPUT"},
{NV_ENC_ERR_ENCODER_BUSY, "NV_ENC_ERR_ENCODER_BUSY"},
{NV_ENC_ERR_EVENT_NOT_REGISTERD, "NV_ENC_ERR_EVENT_NOT_REGISTERD"},
{NV_ENC_ERR_GENERIC, "NV_ENC_ERR_GENERIC"},
{NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY, "NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY"},
{NV_ENC_ERR_UNIMPLEMENTED, "NV_ENC_ERR_UNIMPLEMENTED"},
{NV_ENC_ERR_RESOURCE_REGISTER_FAILED, "NV_ENC_ERR_RESOURCE_REGISTER_FAILED"},
{NV_ENC_ERR_RESOURCE_NOT_REGISTERED, "NV_ENC_ERR_RESOURCE_NOT_REGISTERED"},
{NV_ENC_ERR_RESOURCE_NOT_MAPPED, "NV_ENC_ERR_RESOURCE_NOT_MAPPED"},
});
const auto item = std::ranges::find_if(names, [status](const auto &entry) {
return entry.first == status;
});
return item == names.end() ? std::to_string(status) : std::string {item->second};
}
GUID quality_preset_guid_from_number(unsigned number) {
if (number > 7) {
number = 7;
@@ -91,7 +182,7 @@ namespace {
} // namespace
namespace nvenc {
namespace NVENC_NAMESPACE {
nvenc_base::nvenc_base(NV_ENC_DEVICE_TYPE device_type):
device_type(device_type) {
@@ -101,332 +192,305 @@ namespace nvenc {
// Use destroy_encoder() instead
}
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) {
if (!nvenc && !init_library()) {
return false;
}
if (encoder) {
destroy_encoder();
}
auto fail_guard = util::fail_guard([this] {
destroy_encoder();
});
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 = {NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
session_params.device = device;
session_params.deviceType = device_type;
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;
}
uint32_t encode_guid_count = 0;
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDCount(encoder, &encode_guid_count))) {
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDCount() failed: " << last_nvenc_error_string;
return false;
};
std::vector<GUID> encode_guids(encode_guid_count);
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDs(encoder, encode_guids.data(), (uint32_t) encode_guids.size(), &encode_guid_count))) {
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDs() failed: " << last_nvenc_error_string;
return false;
}
NV_ENC_INITIALIZE_PARAMS init_params = {NV_ENC_INITIALIZE_PARAMS_VER};
switch (client_config.videoFormat) {
case 0:
// H.264
init_params.encodeGUID = NV_ENC_CODEC_H264_GUID;
break;
case 1:
// HEVC
init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID;
break;
case 2:
// AV1
init_params.encodeGUID = NV_ENC_CODEC_AV1_GUID;
break;
default:
BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat;
return false;
}
{
auto search_predicate = [&](const GUID &guid) {
return equal_guids(init_params.encodeGUID, guid);
};
if (std::find_if(encode_guids.begin(), encode_guids.end(), search_predicate) == encode_guids.end()) {
BOOST_LOG(error) << "NvEnc: encoding format is not supported by the gpu";
return false;
}
}
auto get_encoder_cap = [&](NV_ENC_CAPS cap) {
int nvenc_base::get_encoder_cap(const GUID &encode_guid, NV_ENC_CAPS cap) const {
NV_ENC_CAPS_PARAM param = {NV_ENC_CAPS_PARAM_VER};
param.capsToQuery = cap;
int value = 0;
if (int ret = nvenc->nvEncGetEncodeCaps(encoder, init_params.encodeGUID, &param, &value); ret == NV_ENC_SUCCESS) {
if (nvenc->nvEncGetEncodeCaps(encoder, encode_guid, &param, &value) == NV_ENC_SUCCESS) {
return value;
}
return 0;
};
}
auto buffer_is_10bit = [&]() {
return buffer_format == NV_ENC_BUFFER_FORMAT_YUV420_10BIT || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
};
auto buffer_is_yuv444 = [&]() {
return buffer_format == NV_ENC_BUFFER_FORMAT_AYUV || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444 || buffer_format == NV_ENC_BUFFER_FORMAT_YUV444_10BIT;
};
{
auto supported_width = get_encoder_cap(NV_ENC_CAPS_WIDTH_MAX);
auto supported_height = get_encoder_cap(NV_ENC_CAPS_HEIGHT_MAX);
bool nvenc_base::validate_encoder_capabilities(const GUID &encode_guid, NV_ENC_BUFFER_FORMAT buffer_format) {
const auto supported_width = get_encoder_cap(encode_guid, NV_ENC_CAPS_WIDTH_MAX);
const auto supported_height = get_encoder_cap(encode_guid, 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;
}
}
if (buffer_is_10bit() && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_10BIT_ENCODE)) {
if (buffer_is_10bit(buffer_format) && !get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_10BIT_ENCODE)) {
BOOST_LOG(error) << "NvEnc: gpu doesn't support 10-bit encode";
return false;
}
if (buffer_is_yuv444() && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_YUV444_ENCODE)) {
if (buffer_is_yuv444(buffer_format) && !get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_YUV444_ENCODE)) {
BOOST_LOG(error) << "NvEnc: gpu doesn't support YUV444 encode";
return false;
}
if (async_event_handle && !get_encoder_cap(NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT)) {
if (async_event_handle && !get_encoder_cap(encode_guid, NV_ENC_CAPS_ASYNC_ENCODE_SUPPORT)) {
BOOST_LOG(warning) << "NvEnc: gpu doesn't support async encode";
async_event_handle = nullptr;
}
encoder_params.rfi = get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION);
return true;
}
encoder_params.rfi = get_encoder_cap(NV_ENC_CAPS_SUPPORT_REF_PIC_INVALIDATION);
void nvenc_base::configure_split_frame(
NV_ENC_INITIALIZE_PARAMS &init_params,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config
) const {
#if NVENC_SDK_VERSION >= 1300
if (client_config.videoFormat <= 0 || get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_NUM_ENCODER_ENGINES) <= 1) {
return;
}
init_params.presetGUID = quality_preset_guid_from_number(config.quality_preset);
init_params.tuningInfo = NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY;
init_params.enablePTD = 1;
init_params.enableEncodeAsync = async_event_handle ? 1 : 0;
init_params.enableWeightedPrediction = config.weighted_prediction && get_encoder_cap(NV_ENC_CAPS_SUPPORT_WEIGHTED_PREDICTION);
init_params.encodeWidth = encoder_params.width;
init_params.darWidth = encoder_params.width;
init_params.encodeHeight = encoder_params.height;
init_params.darHeight = encoder_params.height;
const AVRational fps = video::framerate_to_rational(client_config);
init_params.frameRateNum = fps.num;
init_params.frameRateDen = fps.den;
if (client_config.videoFormat > 0 && get_encoder_cap(NV_ENC_CAPS_NUM_ENCODER_ENGINES) > 1) {
// SFE supports HEVC/AV1 if you have more than 1 nvenc block
using enum nvenc_split_frame_encoding;
NV_ENC_SPLIT_ENCODE_MODE split_mode;
using enum ::nvenc::nvenc_split_frame_encoding;
if (config.split_frame_encoding == disabled) {
split_mode = NV_ENC_SPLIT_DISABLE_MODE;
init_params.splitEncodeMode = NV_ENC_SPLIT_DISABLE_MODE;
} else if (config.split_frame_encoding == force_enabled) {
split_mode = NV_ENC_SPLIT_AUTO_FORCED_MODE;
init_params.splitEncodeMode = NV_ENC_SPLIT_AUTO_FORCED_MODE;
} else {
split_mode = NV_ENC_SPLIT_AUTO_MODE;
init_params.splitEncodeMode = NV_ENC_SPLIT_AUTO_MODE;
}
init_params.splitEncodeMode = split_mode;
#else
if (config.split_frame_encoding == ::nvenc::nvenc_split_frame_encoding::force_enabled) {
BOOST_LOG(warning) << "NvEnc: split-frame encoding requires NVENC API 13.0; ignoring forced mode";
}
#endif
}
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;
}
NV_ENC_CONFIG enc_config = preset_config.presetCfg;
enc_config.profileGUID = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID;
void nvenc_base::configure_rate_control(
NV_ENC_CONFIG &enc_config,
const ::nvenc::nvenc_config &config,
const video::config_t &client_config,
const GUID &encode_guid
) {
enc_config.gopLength = NVENC_INFINITE_GOPLENGTH;
enc_config.frameIntervalP = 1;
enc_config.rcParams.rateControlMode = NV_ENC_PARAMS_RC_CBR;
enc_config.rcParams.zeroReorderDelay = 1;
enc_config.rcParams.enableLookahead = 0;
enc_config.rcParams.lowDelayKeyFrameScale = 1;
enc_config.rcParams.multiPass = config.two_pass == nvenc_two_pass::quarter_resolution ? NV_ENC_TWO_PASS_QUARTER_RESOLUTION :
config.two_pass == nvenc_two_pass::full_resolution ? NV_ENC_TWO_PASS_FULL_RESOLUTION :
NV_ENC_MULTI_PASS_DISABLED;
using enum ::nvenc::nvenc_two_pass;
if (config.two_pass == quarter_resolution) {
enc_config.rcParams.multiPass = NV_ENC_TWO_PASS_QUARTER_RESOLUTION;
} else if (config.two_pass == full_resolution) {
enc_config.rcParams.multiPass = NV_ENC_TWO_PASS_FULL_RESOLUTION;
} else {
enc_config.rcParams.multiPass = NV_ENC_MULTI_PASS_DISABLED;
}
enc_config.rcParams.enableAQ = config.adaptive_quantization;
enc_config.rcParams.averageBitRate = client_config.bitrate * 1000;
if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) {
if (get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) {
enc_config.rcParams.vbvBufferSize = client_config.bitrate * 1000 / client_config.framerate;
if (config.vbv_percentage_increase > 0) {
enc_config.rcParams.vbvBufferSize += enc_config.rcParams.vbvBufferSize * config.vbv_percentage_increase / 100;
}
}
auto set_h264_hevc_common_format_config = [&](auto &format_config) {
format_config.repeatSPSPPS = 1;
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
format_config.sliceMode = 3;
format_config.sliceModeData = client_config.slicesPerFrame;
if (buffer_is_yuv444()) {
format_config.chromaFormatIDC = 3;
}
format_config.enableFillerDataInsertion = config.insert_filler_data;
};
auto set_ref_frames = [&](uint32_t &ref_frames_option, NV_ENC_NUM_REF_FRAMES &L0_option, uint32_t ref_frames_default) {
if (client_config.numRefFrames > 0) {
ref_frames_option = client_config.numRefFrames;
} else {
ref_frames_option = ref_frames_default;
}
if (ref_frames_option > 0 && !get_encoder_cap(NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) {
void nvenc_base::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
) {
ref_frames_option = requested_count > 0 ? static_cast<std::uint32_t>(requested_count) : default_count;
if (ref_frames_option > 0U && !get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_MULTIPLE_REF_FRAMES)) {
ref_frames_option = 1;
encoder_params.rfi = false;
}
encoder_params.ref_frames_in_dpb = ref_frames_option;
// This limits ref frames any frame can use to 1, but allows larger buffer size for fallback if some frames are invalidated through rfi
L0_option = NV_ENC_NUM_REF_FRAMES_1;
// Limit each frame to one reference while keeping a larger DPB for RFI fallback.
list0_option = NV_ENC_NUM_REF_FRAMES_1;
}
template<typename FormatConfig>
void nvenc_base::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
) {
const auto configure_vui = [&](auto &vui) {
vui.videoSignalTypePresentFlag = 1;
vui.videoFormat = NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED;
vui.videoFullRangeFlag = colorspace.full_range;
vui.colourDescriptionPresentFlag = 1;
vui.colourPrimaries = colorspace.primaries;
vui.transferCharacteristics = colorspace.tranfer_function;
vui.colourMatrix = colorspace.matrix;
vui.chromaSampleLocationFlag = buffer_is_yuv444(buffer_format) ? 0 : 1;
vui.chromaSampleLocationTop = 0;
vui.chromaSampleLocationBot = 0;
vui.bitstreamRestrictionFlag = 1;
};
auto set_minqp_if_enabled = [&](int value) {
if constexpr (requires { format_config.h264VUIParameters; }) {
configure_vui(format_config.h264VUIParameters);
} else {
configure_vui(format_config.hevcVUIParameters);
}
if (client_config.enableIntraRefresh != 1) {
return;
}
if (!get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) {
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
return;
}
format_config.enableIntraRefresh = 1;
format_config.intraRefreshPeriod = 300;
format_config.intraRefreshCnt = 299;
if constexpr (requires { format_config.outputRecoveryPointSEI; }) {
format_config.outputRecoveryPointSEI = 1;
}
#if NVENC_SDK_VERSION >= 1200
if (get_encoder_cap(encode_guid, NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
format_config.singleSliceIntraRefresh = 1;
} else {
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
}
#endif
}
void nvenc_base::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
) {
enc_config.profileGUID = buffer_is_yuv444(buffer_format) ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID;
auto &format_config = enc_config.encodeCodecConfig.h264Config;
format_config.repeatSPSPPS = 1;
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
format_config.sliceMode = 3;
format_config.sliceModeData = client_config.slicesPerFrame;
if (buffer_is_yuv444(buffer_format)) {
format_config.chromaFormatIDC = 3;
}
format_config.enableFillerDataInsertion = config.insert_filler_data;
format_config.entropyCodingMode = config.h264_cavlc || !get_encoder_cap(encode_guid, NV_ENC_CAPS_SUPPORT_CABAC) ?
NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC :
NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
configure_reference_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5, client_config.numRefFrames, encode_guid);
if (config.enable_min_qp) {
enc_config.rcParams.enableMinQP = 1;
enc_config.rcParams.minQP.qpInterP = value;
enc_config.rcParams.minQP.qpIntra = value;
}
};
auto fill_h264_hevc_vui = [&](auto &vui_config) {
vui_config.videoSignalTypePresentFlag = 1;
vui_config.videoFormat = NV_ENC_VUI_VIDEO_FORMAT_UNSPECIFIED;
vui_config.videoFullRangeFlag = colorspace.full_range;
vui_config.colourDescriptionPresentFlag = 1;
vui_config.colourPrimaries = colorspace.primaries;
vui_config.transferCharacteristics = colorspace.tranfer_function;
vui_config.colourMatrix = colorspace.matrix;
vui_config.chromaSampleLocationFlag = buffer_is_yuv444() ? 0 : 1;
vui_config.chromaSampleLocationTop = 0;
vui_config.chromaSampleLocationBot = 0;
// This is critical for low decoding latency on certain devices
vui_config.bitstreamRestrictionFlag = 1;
};
switch (client_config.videoFormat) {
case 0:
{
// H.264
enc_config.profileGUID = buffer_is_yuv444() ? NV_ENC_H264_PROFILE_HIGH_444_GUID : NV_ENC_H264_PROFILE_HIGH_GUID;
auto &format_config = enc_config.encodeCodecConfig.h264Config;
set_h264_hevc_common_format_config(format_config);
if (config.h264_cavlc || !get_encoder_cap(NV_ENC_CAPS_SUPPORT_CABAC)) {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CAVLC;
} else {
format_config.entropyCodingMode = NV_ENC_H264_ENTROPY_CODING_MODE_CABAC;
}
set_ref_frames(format_config.maxNumRefFrames, format_config.numRefL0, 5);
set_minqp_if_enabled(config.min_qp_h264);
fill_h264_hevc_vui(format_config.h264VUIParameters);
if (client_config.enableIntraRefresh == 1) {
if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) {
format_config.enableIntraRefresh = 1;
format_config.intraRefreshPeriod = 300;
format_config.intraRefreshCnt = 299;
format_config.outputRecoveryPointSEI = 1;
if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
format_config.singleSliceIntraRefresh = 1;
} else {
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
}
} else {
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
}
}
break;
enc_config.rcParams.minQP.qpInterP = config.min_qp_h264;
enc_config.rcParams.minQP.qpIntra = config.min_qp_h264;
}
case 1:
{
// HEVC
configure_h264_hevc_metadata(format_config, client_config, colorspace, buffer_format, encode_guid);
}
void nvenc_base::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
) {
auto &format_config = enc_config.encodeCodecConfig.hevcConfig;
set_h264_hevc_common_format_config(format_config);
if (buffer_is_10bit()) {
format_config.repeatSPSPPS = 1;
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
format_config.sliceMode = 3;
format_config.sliceModeData = client_config.slicesPerFrame;
if (buffer_is_yuv444(buffer_format)) {
format_config.chromaFormatIDC = 3;
}
format_config.enableFillerDataInsertion = config.insert_filler_data;
if (buffer_is_10bit(buffer_format)) {
#if NVENC_SDK_VERSION >= 1300
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);
fill_h264_hevc_vui(format_config.hevcVUIParameters);
if (client_config.enableIntraRefresh == 1) {
if (get_encoder_cap(NV_ENC_CAPS_SUPPORT_INTRA_REFRESH)) {
format_config.enableIntraRefresh = 1;
format_config.intraRefreshPeriod = 300;
format_config.intraRefreshCnt = 299;
format_config.outputRecoveryPointSEI = 1;
if (get_encoder_cap(NV_ENC_CAPS_SINGLE_SLICE_INTRA_REFRESH)) {
format_config.singleSliceIntraRefresh = 1;
} else {
BOOST_LOG(warning) << "NvEnc: Single Slice Intra Refresh not supported";
}
} else {
BOOST_LOG(error) << "NvEnc: Client asked for intra-refresh but the encoder does not support intra-refresh";
}
}
break;
configure_reference_frames(format_config.maxNumRefFramesInDPB, format_config.numRefL0, 5, client_config.numRefFrames, encode_guid);
if (config.enable_min_qp) {
enc_config.rcParams.enableMinQP = 1;
enc_config.rcParams.minQP.qpInterP = config.min_qp_hevc;
enc_config.rcParams.minQP.qpIntra = config.min_qp_hevc;
}
case 2:
{
// AV1
configure_h264_hevc_metadata(format_config, client_config, colorspace, buffer_format, encode_guid);
}
#if NVENC_SDK_VERSION >= 1200
void nvenc_base::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
) {
auto &format_config = enc_config.encodeCodecConfig.av1Config;
format_config.repeatSeqHdr = 1;
format_config.idrPeriod = NVENC_INFINITE_GOPLENGTH;
if (buffer_is_yuv444()) {
if (buffer_is_yuv444(buffer_format)) {
format_config.chromaFormatIDC = 3;
}
format_config.enableBitstreamPadding = config.insert_filler_data;
if (buffer_is_10bit()) {
if (buffer_is_10bit(buffer_format)) {
#if NVENC_SDK_VERSION >= 1300
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;
format_config.matrixCoefficients = colorspace.matrix;
format_config.colorRange = colorspace.full_range;
format_config.chromaSamplePosition = buffer_is_yuv444() ? 0 : 1;
set_ref_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8);
set_minqp_if_enabled(config.min_qp_av1);
format_config.chromaSamplePosition = buffer_is_yuv444(buffer_format) ? 0 : 1;
configure_reference_frames(format_config.maxNumRefFramesInDPB, format_config.numFwdRefs, 8, client_config.numRefFrames, encode_guid);
if (config.enable_min_qp) {
enc_config.rcParams.enableMinQP = 1;
enc_config.rcParams.minQP.qpInterP = config.min_qp_av1;
enc_config.rcParams.minQP.qpIntra = config.min_qp_av1;
}
if (client_config.slicesPerFrame > 1) {
// NVENC only supports slice counts that are powers of two, so we'll pick powers of two
// with bias to rows due to hopefully more similar macroblocks with a row vs a column.
// NVENC supports power-of-two tile counts, biased toward rows.
format_config.numTileRows = std::pow(2, std::ceil(std::log2(client_config.slicesPerFrame) / 2));
format_config.numTileColumns = std::pow(2, std::floor(std::log2(client_config.slicesPerFrame) / 2));
}
}
#endif
void nvenc_base::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
) {
switch (client_config.videoFormat) {
case 0:
configure_h264(enc_config, config, client_config, colorspace, buffer_format, encode_guid);
break;
case 1:
configure_hevc(enc_config, config, client_config, colorspace, buffer_format, encode_guid);
break;
#if NVENC_SDK_VERSION >= 1200
case 2:
configure_av1(enc_config, config, client_config, colorspace, buffer_format, encode_guid);
break;
#endif
}
}
init_params.encodeConfig = &enc_config;
bool nvenc_base::initialize_encoder_resources(NV_ENC_INITIALIZE_PARAMS &init_params) {
if (nvenc_failed(nvenc->nvEncInitializeEncoder(encoder, &init_params))) {
BOOST_LOG(error) << "NvEnc: NvEncInitializeEncoder() failed: " << last_nvenc_error_string;
return false;
}
if (async_event_handle) {
NV_ENC_EVENT_PARAMS event_params = {NV_ENC_EVENT_PARAMS_VER};
event_params.completionEvent = async_event_handle;
@@ -442,35 +506,30 @@ namespace nvenc {
return false;
}
output_bitstream = create_bitstream_buffer.bitstreamBuffer;
if (!create_and_register_input_buffer()) {
return false;
return create_and_register_input_buffer();
}
{
auto f = stat_trackers::two_digits_after_decimal();
BOOST_LOG(debug) << "NvEnc: requested encoded frame size " << f % (client_config.bitrate / 8. / client_config.framerate) << " kB";
}
{
auto video_format_string = client_config.videoFormat == 0 ? "H.264 " :
client_config.videoFormat == 1 ? "HEVC " :
client_config.videoFormat == 2 ? "AV1 " :
" ";
void nvenc_base::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 {
std::string extra;
if (init_params.enableEncodeAsync) {
extra += " async";
}
if (buffer_is_yuv444()) {
if (buffer_is_yuv444(buffer_format)) {
extra += " yuv444";
}
if (buffer_is_10bit()) {
if (buffer_is_10bit(buffer_format)) {
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)) {
if (config.vbv_percentage_increase > 0 && get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_SUPPORT_CUSTOM_VBV_BUF_SIZE)) {
extra += std::format(" vbv+{}", config.vbv_percentage_increase);
}
if (encoder_params.rfi) {
@@ -488,17 +547,142 @@ namespace nvenc {
if (config.insert_filler_data) {
extra += " filler-data";
}
if (client_config.videoFormat > 0 && get_encoder_cap(NV_ENC_CAPS_NUM_ENCODER_ENGINES) > 1) {
#if NVENC_SDK_VERSION >= 1300
if (client_config.videoFormat > 0 && get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_NUM_ENCODER_ENGINES) > 1) {
if (init_params.splitEncodeMode == NV_ENC_SPLIT_AUTO_MODE) {
extra += " sfe-auto";
} else if (init_params.splitEncodeMode == NV_ENC_SPLIT_AUTO_FORCED_MODE) {
extra += " sfe";
}
}
#endif
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_SDK_VERSION << " "
<< video_format_name(client_config.videoFormat)
<< quality_preset_string_from_guid(init_params.presetGUID) << extra;
}
bool nvenc_base::create_encoder(
const ::nvenc::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();
});
const auto colorspace = nvenc_colorspace_from_sunshine_colorspace(sunshine_colorspace);
const auto buffer_format = nvenc_format_from_sunshine_format(sunshine_buffer_format);
if (buffer_format == NV_ENC_BUFFER_FORMAT_UNDEFINED) {
BOOST_LOG(error) << "NvEnc: unsupported input pixel format";
return false;
}
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 = {NV_ENC_OPEN_ENCODE_SESSION_EX_PARAMS_VER};
session_params.device = device;
session_params.deviceType = device_type;
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;
}
std::uint32_t encode_guid_count = 0;
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDCount(encoder, &encode_guid_count))) {
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDCount() failed: " << last_nvenc_error_string;
return false;
}
std::vector<GUID> encode_guids(encode_guid_count);
if (nvenc_failed(nvenc->nvEncGetEncodeGUIDs(
encoder,
encode_guids.data(),
static_cast<std::uint32_t>(encode_guids.size()),
&encode_guid_count
))) {
BOOST_LOG(error) << "NvEnc: NvEncGetEncodeGUIDs() failed: " << last_nvenc_error_string;
return false;
}
NV_ENC_INITIALIZE_PARAMS init_params = {NV_ENC_INITIALIZE_PARAMS_VER};
switch (client_config.videoFormat) {
case 0:
init_params.encodeGUID = NV_ENC_CODEC_H264_GUID;
break;
case 1:
init_params.encodeGUID = NV_ENC_CODEC_HEVC_GUID;
break;
#if NVENC_SDK_VERSION >= 1200
case 2:
init_params.encodeGUID = NV_ENC_CODEC_AV1_GUID;
break;
#endif
default:
BOOST_LOG(error) << "NvEnc: unknown video format " << client_config.videoFormat;
return false;
}
if (!contains_guid(encode_guids, init_params.encodeGUID)) {
BOOST_LOG(error) << "NvEnc: encoding format is not supported by the gpu";
return false;
}
if (!validate_encoder_capabilities(init_params.encodeGUID, buffer_format)) {
return false;
}
init_params.presetGUID = quality_preset_guid_from_number(config.quality_preset);
init_params.tuningInfo = NV_ENC_TUNING_INFO_ULTRA_LOW_LATENCY;
init_params.enablePTD = 1;
init_params.enableEncodeAsync = async_event_handle ? 1 : 0;
init_params.enableWeightedPrediction = config.weighted_prediction &&
get_encoder_cap(init_params.encodeGUID, NV_ENC_CAPS_SUPPORT_WEIGHTED_PREDICTION);
init_params.encodeWidth = encoder_params.width;
init_params.darWidth = encoder_params.width;
init_params.encodeHeight = encoder_params.height;
init_params.darHeight = encoder_params.height;
const AVRational fps = video::framerate_to_rational(client_config);
init_params.frameRateNum = fps.num;
init_params.frameRateDen = fps.den;
configure_split_frame(init_params, config, client_config);
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;
}
NV_ENC_CONFIG enc_config = preset_config.presetCfg;
enc_config.profileGUID = NV_ENC_CODEC_PROFILE_AUTOSELECT_GUID;
configure_rate_control(enc_config, config, client_config, init_params.encodeGUID);
configure_codec(enc_config, config, client_config, colorspace, buffer_format, init_params.encodeGUID);
init_params.encodeConfig = &enc_config;
if (!initialize_encoder_resources(init_params)) {
return false;
}
auto frame_size_format = stat_trackers::two_digits_after_decimal();
BOOST_LOG(debug) << "NvEnc: requested encoded frame size "
<< frame_size_format % (client_config.bitrate / 8. / client_config.framerate) << " kB";
log_created_encoder(init_params, enc_config, config, client_config, buffer_format);
encoder_state = {};
fail_guard.disable();
return true;
@@ -535,7 +719,7 @@ namespace nvenc {
encoder_params = {};
}
nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
::nvenc::nvenc_encoded_frame nvenc_base::encode_frame(uint64_t frame_index, bool force_idr) {
if (!encoder) {
return {};
}
@@ -592,7 +776,7 @@ namespace nvenc {
}
auto data_pointer = (uint8_t *) lock_bitstream.bitstreamBufferPtr;
nvenc_encoded_frame encoded_frame {
::nvenc::nvenc_encoded_frame encoded_frame {
{data_pointer, data_pointer + lock_bitstream.bitstreamSizeInBytes},
lock_bitstream.outputTimeStamp,
lock_bitstream.pictureType == NV_ENC_PIC_TYPE_IDR,
@@ -657,46 +841,6 @@ namespace nvenc {
}
bool nvenc_base::nvenc_failed(NVENCSTATUS status) {
auto status_string = [](NVENCSTATUS status) -> std::string {
switch (status) {
#ifndef DOXYGEN
#define nvenc_status_case(x) \
case x: \
return #x;
#endif
nvenc_status_case(NV_ENC_SUCCESS);
nvenc_status_case(NV_ENC_ERR_NO_ENCODE_DEVICE);
nvenc_status_case(NV_ENC_ERR_UNSUPPORTED_DEVICE);
nvenc_status_case(NV_ENC_ERR_INVALID_ENCODERDEVICE);
nvenc_status_case(NV_ENC_ERR_INVALID_DEVICE);
nvenc_status_case(NV_ENC_ERR_DEVICE_NOT_EXIST);
nvenc_status_case(NV_ENC_ERR_INVALID_PTR);
nvenc_status_case(NV_ENC_ERR_INVALID_EVENT);
nvenc_status_case(NV_ENC_ERR_INVALID_PARAM);
nvenc_status_case(NV_ENC_ERR_INVALID_CALL);
nvenc_status_case(NV_ENC_ERR_OUT_OF_MEMORY);
nvenc_status_case(NV_ENC_ERR_ENCODER_NOT_INITIALIZED);
nvenc_status_case(NV_ENC_ERR_UNSUPPORTED_PARAM);
nvenc_status_case(NV_ENC_ERR_LOCK_BUSY);
nvenc_status_case(NV_ENC_ERR_NOT_ENOUGH_BUFFER);
nvenc_status_case(NV_ENC_ERR_INVALID_VERSION);
nvenc_status_case(NV_ENC_ERR_MAP_FAILED);
nvenc_status_case(NV_ENC_ERR_NEED_MORE_INPUT);
nvenc_status_case(NV_ENC_ERR_ENCODER_BUSY);
nvenc_status_case(NV_ENC_ERR_EVENT_NOT_REGISTERD);
nvenc_status_case(NV_ENC_ERR_GENERIC);
nvenc_status_case(NV_ENC_ERR_INCOMPATIBLE_CLIENT_KEY);
nvenc_status_case(NV_ENC_ERR_UNIMPLEMENTED);
nvenc_status_case(NV_ENC_ERR_RESOURCE_REGISTER_FAILED);
nvenc_status_case(NV_ENC_ERR_RESOURCE_NOT_REGISTERED);
nvenc_status_case(NV_ENC_ERR_RESOURCE_NOT_MAPPED);
// Newer versions of sdk may add more constants, look for them at the end of NVENCSTATUS enum
#undef nvenc_status_case
default:
return std::to_string(status);
}
};
last_nvenc_error_string.clear();
if (status != NV_ENC_SUCCESS) {
/* This API function gives broken strings more often than not
@@ -705,11 +849,11 @@ namespace nvenc {
if (!last_nvenc_error_string.empty()) last_nvenc_error_string += " ";
}
*/
last_nvenc_error_string += status_string(status);
last_nvenc_error_string += nvenc_status_string(status);
return true;
}
return false;
}
} // namespace nvenc
} // namespace NVENC_NAMESPACE
+202 -11
View File
@@ -4,32 +4,33 @@
*/
#pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h"
#include "nvenc_config.h"
#include "nvenc_encoded_frame.h"
#include "nvenc_encoder.h"
#include "nvenc_sdk.h"
#include "src/logging.h"
#include "src/video.h"
#include "src/video_colorspace.h"
/**
* @brief Standalone NVENC encoder
*/
namespace nvenc {
namespace NVENC_NAMESPACE {
/**
* @brief Abstract platform-agnostic base of standalone NVENC encoder.
* 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:
/**
* @param device_type Underlying device type used by derived class.
*/
explicit nvenc_base(NV_ENC_DEVICE_TYPE device_type);
virtual ~nvenc_base();
~nvenc_base() override;
nvenc_base(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.
* @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.
* 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.
@@ -58,7 +64,7 @@ namespace nvenc {
* @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);
::nvenc::nvenc_encoded_frame encode_frame(uint64_t frame_index, bool force_idr) override;
/**
* @brief Perform reference frame invalidation (RFI) procedure.
@@ -67,7 +73,7 @@ namespace nvenc {
* @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);
bool invalidate_ref_frames(uint64_t first_frame, uint64_t last_frame) override;
protected:
/**
@@ -137,6 +143,191 @@ namespace nvenc {
///< Can be set in constructor or `init_library()`, must override `wait_for_async_event()`.
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;
struct {
@@ -147,4 +338,4 @@ namespace nvenc {
} encoder_state;
};
} // namespace nvenc
} // namespace NVENC_NAMESPACE
+4 -4
View File
@@ -4,10 +4,10 @@
*/
#pragma once
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_sdk.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
/**
* @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.
};
} // namespace nvenc
} // namespace NVENC_NAMESPACE
+16 -31
View File
@@ -8,62 +8,47 @@
#ifdef _WIN32
#include "nvenc_d3d11.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type):
nvenc_base(device_type) {
nvenc_d3d11::nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type, ::nvenc::shared_dll dll):
nvenc_base(device_type),
dll(std::move(dll)) {
async_event_handle = CreateEvent(nullptr, FALSE, FALSE, nullptr);
}
nvenc_d3d11::~nvenc_d3d11() {
if (dll) {
FreeLibrary(dll);
dll = nullptr;
}
if (async_event_handle) {
CloseHandle(async_event_handle);
}
}
bool nvenc_d3d11::init_library() {
if (dll) {
if (nvenc) {
return true;
}
#ifdef _WIN64
constexpr auto dll_name = "nvEncodeAPI64.dll";
#else
constexpr auto dll_name = "nvEncodeAPI.dll";
#endif
auto create_instance = reinterpret_cast<decltype(NvEncodeAPICreateInstance) *>(
GetProcAddress(dll.get(), "NvEncodeAPICreateInstance")
);
if (!create_instance) {
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 {
return false;
}
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 = nullptr;
}
return false;
}
bool nvenc_d3d11::wait_for_async_event(uint32_t timeout_ms) {
return WaitForSingleObject(async_event_handle, timeout_ms) == WAIT_OBJECT_0;
}
} // namespace nvenc
} // namespace NVENC_NAMESPACE
#endif
+10 -7
View File
@@ -11,8 +11,10 @@
// local includes
#include "nvenc_base.h"
#include "nvenc_d3d11_interface.h"
#include "nvenc_shared_dll.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
#ifdef DOXYGEN
/**
@@ -42,29 +44,30 @@ 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: public nvenc_base, public ::nvenc::nvenc_d3d11_interface {
public:
/**
* @brief Initialize an NVENC session wrapper for D3D11 input textures.
*
* @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);
~nvenc_d3d11();
explicit nvenc_d3d11(NV_ENC_DEVICE_TYPE device_type, ::nvenc::shared_dll dll);
~nvenc_d3d11() override;
/**
* @brief Get input surface texture.
* @return Input surface texture.
*/
virtual ID3D11Texture2D *get_input_texture() = 0;
ID3D11Texture2D *get_input_texture() override = 0;
protected:
bool init_library() override;
bool wait_for_async_event(uint32_t timeout_ms) override;
private:
HMODULE dll = nullptr;
::nvenc::shared_dll dll;
};
} // namespace nvenc
} // namespace NVENC_NAMESPACE
#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
#include "nvenc_utils.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
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, ::nvenc::shared_dll dll):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_DIRECTX, std::move(dll)),
d3d_device(d3d_device) {
device = d3d_device;
}
@@ -70,5 +70,5 @@ namespace nvenc {
return true;
}
} // namespace nvenc
} // namespace NVENC_NAMESPACE
#endif
+5 -4
View File
@@ -11,7 +11,7 @@
// local includes
#include "nvenc_d3d11.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
/**
* @brief Native Direct3D11 NVENC encoder.
@@ -20,9 +20,10 @@ namespace nvenc {
public:
/**
* @param d3d_device Direct3D11 device used for encoding.
* @param dll Shared NVENC driver module.
*/
explicit nvenc_d3d11_native(ID3D11Device *d3d_device);
~nvenc_d3d11_native();
explicit nvenc_d3d11_native(ID3D11Device *d3d_device, ::nvenc::shared_dll dll);
~nvenc_d3d11_native() override;
ID3D11Texture2D *get_input_texture() override;
@@ -33,5 +34,5 @@ namespace nvenc {
ID3D11Texture2DPtr d3d_input_texture;
};
} // namespace nvenc
} // namespace NVENC_NAMESPACE
#endif
+4 -4
View File
@@ -9,10 +9,10 @@
// local includes
#include "nvenc_utils.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
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, ::nvenc::shared_dll dll):
nvenc_d3d11(NV_ENC_DEVICE_TYPE_CUDA, std::move(dll)),
d3d_device(d3d_device) {
}
@@ -265,5 +265,5 @@ namespace nvenc {
}
}
} // namespace nvenc
} // namespace NVENC_NAMESPACE
#endif
+6 -7
View File
@@ -4,13 +4,11 @@
*/
#pragma once
#ifdef _WIN32
// lib includes
#include <ffnvcodec/dynlink_cuda.h>
// local includes
#include "nvenc_d3d11.h"
#include "nvenc_sdk.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
/**
* @brief Interop Direct3D11 on CUDA NVENC encoder.
@@ -21,9 +19,10 @@ namespace nvenc {
/**
* @param d3d_device Direct3D11 device that will create input surface texture.
* CUDA encoding device will be derived from it.
* @param dll Shared NVENC driver module.
*/
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device);
~nvenc_d3d11_on_cuda();
explicit nvenc_d3d11_on_cuda(ID3D11Device *d3d_device, ::nvenc::shared_dll dll);
~nvenc_d3d11_on_cuda() override;
ID3D11Texture2D *get_input_texture() override;
@@ -84,5 +83,5 @@ namespace nvenc {
size_t cuda_surface_pitch = 0;
};
} // namespace nvenc
} // namespace NVENC_NAMESPACE
#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
#include "nvenc_utils.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
#ifdef _WIN32
/**
@@ -102,4 +102,4 @@ namespace nvenc {
return colorspace;
}
} // namespace nvenc
} // namespace NVENC_NAMESPACE
+3 -5
View File
@@ -9,15 +9,13 @@
#include <dxgiformat.h>
#endif
// lib includes
#include <ffnvcodec/nvEncodeAPI.h>
// local includes
#include "nvenc_colorspace.h"
#include "nvenc_sdk.h"
#include "src/platform/common.h"
#include "src/video_colorspace.h"
namespace nvenc {
namespace NVENC_NAMESPACE {
#ifdef _WIN32
/**
@@ -45,4 +43,4 @@ namespace nvenc {
*/
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 nvenc {
class nvenc_base;
class nvenc_encoder;
}
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;
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/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/nvenc_dynamic_factory.h"
#include "src/video.h"
#include "utf_utils.h"
@@ -1240,21 +1238,25 @@ namespace platf::dxgi {
* @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) {
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;
}
if (pix_fmt == pix_fmt_e::yuv444p16) {
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_on_cuda>(base.device.get());
} else {
nvenc_d3d = std::make_unique<nvenc::nvenc_d3d11_native>(base.device.get());
auto factory = nvenc::nvenc_dynamic_factory::get();
if (!factory) {
return false;
}
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();
return true;
@@ -1272,8 +1274,7 @@ namespace platf::dxgi {
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)) {
if (!nvenc_d3d->create_encoder(config::video.nv, client_config, colorspace, buffer_format)) {
return false;
}
@@ -1293,8 +1294,8 @@ namespace platf::dxgi {
private:
d3d_base_encode_device base;
std::unique_ptr<nvenc::nvenc_d3d11> nvenc_d3d;
NV_ENC_BUFFER_FORMAT buffer_format = NV_ENC_BUFFER_FORMAT_UNDEFINED;
std::unique_ptr<nvenc::nvenc_d3d11_interface> nvenc_d3d;
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/opt.h>
#include <libavutil/pixdesc.h>
#if !defined(_WIN32) && !defined(__APPLE__)
#include <ffnvcodec/nvEncodeAPI.h>
#endif
}
// local includes
@@ -27,7 +30,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"
+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