Skip to content

Commit

Permalink
tests: Add tests for NvAPI_Vulkan_* entrypoints
Browse files Browse the repository at this point in the history
  • Loading branch information
jp7677 committed Jan 24, 2025
1 parent 67c4357 commit fad21ab
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 2 deletions.
1 change: 1 addition & 0 deletions tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ nvapi_tests_src = files([
'nvapi_sysinfo_nvml.cpp',
'nvapi_sysinfo_hdr.cpp',
'nvapi_system.cpp',
'nvapi_vulkan.cpp',
'util.cpp',
])

Expand Down
312 changes: 312 additions & 0 deletions tests/nvapi_vulkan.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,312 @@
#include "nvapi_tests_private.h"
#include "resource_factory_util.h"
#include "nvapi_vulkan_mocks.h"

using namespace trompeloeil;

TEST_CASE("Vulkan methods succeed", "[.vulkan]") {
auto dxgiFactory = std::make_unique<DXGIDxvkFactoryMock>();
auto vk = std::make_unique<VkMock>();
auto nvml = std::make_unique<NvmlMock>();
auto lfx = std::make_unique<LfxMock>();

SECTION("InitLowLatencyDevice fails to initialize when Vulkan is not available") {
ALLOW_CALL(*vk, IsAvailable()).RETURN(false);
FORBID_CALL(*vk, GetDeviceProcAddr(_, _));

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

auto vkDevice = std::make_unique<VkDeviceMock>();
VkSemaphore result;
REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), reinterpret_cast<HANDLE*>(&result)) == NVAPI_ERROR);
}

SECTION("InitLowLatencyDevice fails to initialize when Vulkan Reflex layer is not available") {
ALLOW_CALL(*vk, IsAvailable()).RETURN(true);
auto e = VkMock::ConfigureDefaultPFN(*vk);

REQUIRE_CALL(*vk, GetDeviceProcAddr(_, eq(std::string_view("vkSetLatencySleepModeNV"))))
.RETURN(nullptr);
REQUIRE_CALL(*vk, GetDeviceProcAddr(_, eq(std::string_view("vkLatencySleepNV"))))
.RETURN(nullptr);
REQUIRE_CALL(*vk, GetDeviceProcAddr(_, eq(std::string_view("vkGetLatencyTimingsNV"))))
.RETURN(nullptr);
REQUIRE_CALL(*vk, GetDeviceProcAddr(_, eq(std::string_view("vkSetLatencyMarkerNV"))))
.RETURN(nullptr);
REQUIRE_CALL(*vk, GetDeviceProcAddr(_, eq(std::string_view("vkQueueNotifyOutOfBandNV"))))
.RETURN(nullptr);

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

auto vkDevice = std::make_unique<VkDeviceMock>();
VkSemaphore result;
REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), reinterpret_cast<HANDLE*>(&result)) == NVAPI_NOT_SUPPORTED);
}

SECTION("InitLowLatencyDevice and DestroyLowLatencyDevice returns OK") {
ALLOW_CALL(*vk, IsAvailable()).RETURN(true);
auto e1 = VkMock::ConfigureDefaultPFN(*vk);
auto e2 = VkMock::ConfigureLL2PFN(*vk);

auto vkSemaphore = reinterpret_cast<VkSemaphore>(0x12345678);
auto vkDevice = std::make_unique<VkDeviceMock>();

REQUIRE_CALL(*vkDevice, vkCreateSemaphore(_, _, _, _))
.LR_SIDE_EFFECT(*_4 = vkSemaphore)
.RETURN(VK_SUCCESS);
REQUIRE_CALL(*vkDevice, vkDestroySemaphore(_, _, _));

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

HANDLE signalSemaphoreHandle = VK_NULL_HANDLE;
REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);
REQUIRE(signalSemaphoreHandle == vkSemaphore);

REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("Other entrypoints succeed") {
ALLOW_CALL(*vk, IsAvailable()).RETURN(true);
auto e1 = VkMock::ConfigureDefaultPFN(*vk);
auto e2 = VkMock::ConfigureLL2PFN(*vk);

auto vkDevice = std::make_unique<VkDeviceMock>();
auto vkQueue = std::make_unique<VkQueueMock>();
HANDLE signalSemaphoreHandle = VK_NULL_HANDLE;

ALLOW_CALL(*vkDevice, vkCreateSemaphore(_, _, _, _))
.RETURN(VK_SUCCESS);
ALLOW_CALL(*vkDevice, vkDestroySemaphore(_, _, _));

SECTION("SetSleepMode / GetSleepStatus returns OK") {
sequence seq1, seq2;

REQUIRE_CALL(*vkDevice, vkSetLatencySleepModeNV(_, _, _))
.IN_SEQUENCE(seq1)
.WITH(_3->lowLatencyMode == true && _3->lowLatencyBoost == true && _3->minimumIntervalUs == 4)
.RETURN(VK_SUCCESS);
REQUIRE_CALL(*vkDevice, vkSetLatencySleepModeNV(_, _, _))
.IN_SEQUENCE(seq2)
.WITH(_3->lowLatencyMode == false && _3->lowLatencyBoost == false)
.RETURN(VK_SUCCESS);

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);

NV_VULKAN_SET_SLEEP_MODE_PARAMS setSleepModeParams;
setSleepModeParams.version = NV_VULKAN_SET_SLEEP_MODE_PARAMS_VER1;
setSleepModeParams.bLowLatencyMode = true;
setSleepModeParams.bLowLatencyBoost = true;
setSleepModeParams.minimumIntervalUs = 4;
REQUIRE(NvAPI_Vulkan_SetSleepMode(vkDevice.get(), &setSleepModeParams) == NVAPI_OK);

NV_VULKAN_GET_SLEEP_STATUS_PARAMS getSleepStatusParams{};
getSleepStatusParams.version = NV_VULKAN_GET_SLEEP_STATUS_PARAMS_VER1;
REQUIRE(NvAPI_Vulkan_GetSleepStatus(vkDevice.get(), &getSleepStatusParams) == NVAPI_OK);
REQUIRE(getSleepStatusParams.bLowLatencyMode == true);

setSleepModeParams.bLowLatencyMode = false;
setSleepModeParams.bLowLatencyBoost = false;
REQUIRE(NvAPI_Vulkan_SetSleepMode(vkDevice.get(), &setSleepModeParams) == NVAPI_OK);

REQUIRE(NvAPI_Vulkan_GetSleepStatus(vkDevice.get(), &getSleepStatusParams) == NVAPI_OK);
REQUIRE(getSleepStatusParams.bLowLatencyMode == false);

REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("GetSleepStatus returns previous value if `vkSetLatencySleepModeNV` fails") {
sequence seq1, seq2;

REQUIRE_CALL(*vkDevice, vkSetLatencySleepModeNV(_, _, _))
.IN_SEQUENCE(seq1)
.WITH(_3->lowLatencyMode == true && _3->lowLatencyBoost == true && _3->minimumIntervalUs == 4)
.RETURN(VK_SUCCESS);
REQUIRE_CALL(*vkDevice, vkSetLatencySleepModeNV(_, _, _))
.IN_SEQUENCE(seq2)
.WITH(_3->lowLatencyMode == false && _3->lowLatencyBoost == false)
.RETURN(VK_ERROR_UNKNOWN);

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);

NV_VULKAN_SET_SLEEP_MODE_PARAMS setSleepModeParams;
setSleepModeParams.version = NV_VULKAN_SET_SLEEP_MODE_PARAMS_VER1;
setSleepModeParams.bLowLatencyMode = true;
setSleepModeParams.bLowLatencyBoost = true;
setSleepModeParams.minimumIntervalUs = 4;
REQUIRE(NvAPI_Vulkan_SetSleepMode(vkDevice.get(), &setSleepModeParams) == NVAPI_OK);

NV_VULKAN_GET_SLEEP_STATUS_PARAMS getSleepStatusParams{};
getSleepStatusParams.version = NV_VULKAN_GET_SLEEP_STATUS_PARAMS_VER1;
REQUIRE(NvAPI_Vulkan_GetSleepStatus(vkDevice.get(), &getSleepStatusParams) == NVAPI_OK);
REQUIRE(getSleepStatusParams.bLowLatencyMode == true);

setSleepModeParams.bLowLatencyMode = false;
setSleepModeParams.bLowLatencyBoost = false;
REQUIRE(NvAPI_Vulkan_SetSleepMode(vkDevice.get(), &setSleepModeParams) == NVAPI_ERROR);

REQUIRE(NvAPI_Vulkan_GetSleepStatus(vkDevice.get(), &getSleepStatusParams) == NVAPI_OK);
REQUIRE(getSleepStatusParams.bLowLatencyMode == true);

REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("GetSleepStatus with unknown struct version returns incompatible-struct-version") {
NV_VULKAN_GET_SLEEP_STATUS_PARAMS params;
params.version = NV_VULKAN_GET_SLEEP_STATUS_PARAMS_VER1 + 1;
REQUIRE(NvAPI_Vulkan_GetSleepStatus(vkDevice.get(), &params) == NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("GetSleepStatus with current struct version returns not incompatible-struct-version") {
// This test should fail when a header update provides a newer not yet implemented struct version
NV_VULKAN_GET_SLEEP_STATUS_PARAMS params;
params.version = NV_VULKAN_GET_SLEEP_STATUS_PARAMS_VER;
REQUIRE(NvAPI_Vulkan_GetSleepStatus(vkDevice.get(), &params) != NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("SetSleepMode with unknown struct version returns incompatible-struct-version") {
NV_VULKAN_SET_SLEEP_MODE_PARAMS params;
params.version = NV_VULKAN_SET_SLEEP_MODE_PARAMS_VER1 + 1;
REQUIRE(NvAPI_Vulkan_SetSleepMode(vkDevice.get(), &params) == NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("SetSleepMode with current struct version returns not incompatible-struct-version") {
// This test should fail when a header update provides a newer not yet implemented struct version
NV_VULKAN_SET_SLEEP_MODE_PARAMS params;
params.version = NV_VULKAN_SET_SLEEP_MODE_PARAMS_VER;
REQUIRE(NvAPI_Vulkan_SetSleepMode(vkDevice.get(), &params) != NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("Sleep returns OK") {
auto signal = 1365ULL;

REQUIRE_CALL(*vkDevice, vkLatencySleepNV(_, _, _))
.WITH(_3->value == signal)
.RETURN(VK_SUCCESS);

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);
REQUIRE(NvAPI_Vulkan_Sleep(vkDevice.get(), signal) == NVAPI_OK);
REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("GetLatency returns OK") {
REQUIRE_CALL(*vkDevice, vkGetLatencyTimingsNV(_, _, _))
.SIDE_EFFECT({
_3->timingCount = 64;
_3->pTimings[0].presentID = 5;
_3->pTimings[0].inputSampleTimeUs = 8;
_3->pTimings[1].presentID = 6;
_3->pTimings[1].inputSampleTimeUs = 7;
});

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);

NV_VULKAN_LATENCY_RESULT_PARAMS params{};
params.version = NV_VULKAN_LATENCY_RESULT_PARAMS_VER1;
REQUIRE(NvAPI_Vulkan_GetLatency(vkDevice.get(), &params) == NVAPI_OK);
REQUIRE(params.frameReport[0].frameID == 5);
REQUIRE(params.frameReport[0].inputSampleTime == 8);
REQUIRE(params.frameReport[1].frameID == 6);
REQUIRE(params.frameReport[1].inputSampleTime == 7);

REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("GetLatency with other than 64 reports clears timings returns OK") {
REQUIRE_CALL(*vkDevice, vkGetLatencyTimingsNV(_, _, _))
.SIDE_EFFECT({
_3->timingCount = 1;
_3->pTimings[0].presentID = 5;
});

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);

NV_VULKAN_LATENCY_RESULT_PARAMS params{};
params.version = NV_VULKAN_LATENCY_RESULT_PARAMS_VER1;
for (auto i = 0U; i < 64; i++)
params.frameReport[i].frameID = std::numeric_limits<uint32_t>::max();
REQUIRE(NvAPI_Vulkan_GetLatency(vkDevice.get(), &params) == NVAPI_OK);
for (auto i = 0U; i < 64; i++)
REQUIRE(params.frameReport[i].frameID == 0);

REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("GetLatency with unknown struct version returns incompatible-struct-version") {
NV_VULKAN_LATENCY_RESULT_PARAMS params;
params.version = NV_VULKAN_LATENCY_RESULT_PARAMS_VER1 + 1;
REQUIRE(NvAPI_Vulkan_GetLatency(vkDevice.get(), &params) == NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("GetLatency with current struct version returns not incompatible-struct-version") {
// This test should fail when a header update provides a newer not yet implemented struct version
NV_VULKAN_LATENCY_RESULT_PARAMS params;
params.version = NV_VULKAN_LATENCY_RESULT_PARAMS_VER;
REQUIRE(NvAPI_Vulkan_GetLatency(vkDevice.get(), &params) != NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("SetLatencyMarker returns OK") {
REQUIRE_CALL(*vkDevice, vkSetLatencyMarkerNV(_, _, _))
.WITH(_3->marker == VK_LATENCY_MARKER_PRESENT_START_NV);

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);

NV_VULKAN_LATENCY_MARKER_PARAMS params;
params.version = NV_VULKAN_LATENCY_MARKER_PARAMS_VER1;
params.markerType = VULKAN_PRESENT_START;
REQUIRE(NvAPI_Vulkan_SetLatencyMarker(vkDevice.get(), &params) == NVAPI_OK);

REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("SetLatencyMarker drops unknown marker types and returns OK") {
FORBID_CALL(*vkDevice, vkSetLatencyMarkerNV(_, _, _));

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);

NV_VULKAN_LATENCY_MARKER_PARAMS params;
params.version = NV_VULKAN_LATENCY_MARKER_PARAMS_VER1;
params.markerType = static_cast<NV_VULKAN_LATENCY_MARKER_TYPE>(15);
REQUIRE(NvAPI_Vulkan_SetLatencyMarker(vkDevice.get(), &params) == NVAPI_OK);

REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}

SECTION("SetLatencyMarker with unknown struct version returns incompatible-struct-version") {
NV_VULKAN_LATENCY_MARKER_PARAMS params;
params.version = NV_VULKAN_LATENCY_MARKER_PARAMS_VER1 + 1;
REQUIRE(NvAPI_Vulkan_SetLatencyMarker(vkDevice.get(), &params) == NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("SetLatencyMarker with current struct version returns not incompatible-struct-version") {
// This test should fail when a header update provides a newer not yet implemented struct version
NV_VULKAN_LATENCY_MARKER_PARAMS params;
params.version = NV_VULKAN_LATENCY_MARKER_PARAMS_VER;
REQUIRE(NvAPI_Vulkan_SetLatencyMarker(vkDevice.get(), &params) != NVAPI_INCOMPATIBLE_STRUCT_VERSION);
}

SECTION("NotifyOutOfBandVkQueue returns OK") {
REQUIRE_CALL(*vkQueue, vkQueueNotifyOutOfBandNV(_, _))
.WITH(_2->queueType == VK_OUT_OF_BAND_QUEUE_TYPE_PRESENT_NV);

SetupResourceFactory(std::move(dxgiFactory), std::move(vk), std::move(nvml), std::move(lfx));

REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), &signalSemaphoreHandle) == NVAPI_OK);
REQUIRE(NvAPI_Vulkan_NotifyOutOfBandVkQueue(vkDevice.get(), vkQueue.get(), VULKAN_OUT_OF_BAND_QUEUE_TYPE_PRESENT) == NVAPI_OK);
REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK);
}
}
}
28 changes: 26 additions & 2 deletions tests/nvapi_vulkan_mocks.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
#include "nvapi_tests_private.h"
#include "../src/nvapi/vk.h"

using namespace trompeloeil;

class VkDeviceMock {
MAKE_MOCK4(vkCreateSemaphore, VkResult(VkDevice, const VkSemaphoreCreateInfo*, const VkAllocationCallbacks*, VkSemaphore*));
MAKE_MOCK3(vkDestroySemaphore, void(VkDevice, VkSemaphore, const VkAllocationCallbacks*));
Expand Down Expand Up @@ -39,9 +41,31 @@ class VkQueueMock {
}
};

class VkMock final : public trompeloeil::mock_interface<dxvk::Vk> {
class VkMock final : public mock_interface<dxvk::Vk> {
IMPLEMENT_CONST_MOCK0(IsAvailable);
IMPLEMENT_CONST_MOCK2(GetDeviceProcAddr);
IMPLEMENT_CONST_MOCK2(GetDeviceExtensions);
IMPLEMENT_CONST_MOCK3(GetPhysicalDeviceProperties2);
};

[[nodiscard]] static std::array<std::unique_ptr<expectation>, 2> ConfigureDefaultPFN(VkMock& mock) {
return {
NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkCreateSemaphore"))))
.RETURN(reinterpret_cast<PFN_vkVoidFunction>(VkDeviceMock::CreateSemaphore)),
NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkDestroySemaphore"))))
.RETURN(reinterpret_cast<PFN_vkVoidFunction>(VkDeviceMock::DestroySemaphore))};
}

[[nodiscard]] static std::array<std::unique_ptr<expectation>, 5> ConfigureLL2PFN(VkMock& mock) {
return {
NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkSetLatencySleepModeNV"))))
.RETURN(reinterpret_cast<PFN_vkVoidFunction>(VkDeviceMock::SetLatencySleepModeNV)),
NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkLatencySleepNV"))))
.RETURN(reinterpret_cast<PFN_vkVoidFunction>(VkDeviceMock::LatencySleepNV)),
NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkGetLatencyTimingsNV"))))
.RETURN(reinterpret_cast<PFN_vkVoidFunction>(VkDeviceMock::GetLatencyTimingsNV)),
NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkSetLatencyMarkerNV"))))
.RETURN(reinterpret_cast<PFN_vkVoidFunction>(VkDeviceMock::SetLatencyMarkerNV)),
NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkQueueNotifyOutOfBandNV"))))
.RETURN(reinterpret_cast<PFN_vkVoidFunction>(VkQueueMock::QueueNotifyOutOfBandNV))};
}
};

0 comments on commit fad21ab

Please sign in to comment.