Files
Sunshine/tests/unit/test_nvenc_dynamic_factory.cpp
T
2026-07-26 21:06:28 -04:00

213 lines
6.0 KiB
C++

/**
* @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