diff --git a/tests/meson.build b/tests/meson.build index 00e88274..a7ca382d 100644 --- a/tests/meson.build +++ b/tests/meson.build @@ -41,6 +41,7 @@ nvapi_tests_src = files([ 'nvapi_sysinfo_nvml.cpp', 'nvapi_sysinfo_hdr.cpp', 'nvapi_system.cpp', + 'nvapi_vulkan.cpp', 'util.cpp', ]) diff --git a/tests/nvapi_vulkan.cpp b/tests/nvapi_vulkan.cpp new file mode 100644 index 00000000..17e9f48a --- /dev/null +++ b/tests/nvapi_vulkan.cpp @@ -0,0 +1,243 @@ +#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(); + auto vk = std::make_unique(); + auto nvml = std::make_unique(); + auto lfx = std::make_unique(); + + 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(); + VkSemaphore result; + REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), reinterpret_cast(&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(); + VkSemaphore result; + REQUIRE(NvAPI_Vulkan_InitLowLatencyDevice(vkDevice.get(), reinterpret_cast(&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(0x12345678); + auto vkDevice = std::make_unique(); + auto vkQueue = std::make_unique(); + + REQUIRE_CALL(*vkDevice, vkCreateSemaphore(_, _, _, _)) + .LR_SIDE_EFFECT(vkSemaphore = *_4) + .RETURN(VK_SUCCESS); + REQUIRE_CALL(*vkDevice, vkDestroySemaphore(_, _, _)) + .RETURN(VK_SUCCESS); + + 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(signalSemaphoreHandle != VK_NULL_HANDLE); + + REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK); + } + + SECTION("Other entrypoints succeeds") { + ALLOW_CALL(*vk, IsAvailable()).RETURN(true); + auto e1 = VkMock::ConfigureDefaultPFN(*vk); + auto e2 = VkMock::ConfigureLL2PFN(*vk); + + auto vkDevice = std::make_unique(); + auto vkQueue = std::make_unique(); + HANDLE signalSemaphoreHandle = VK_NULL_HANDLE; + + ALLOW_CALL(*vkDevice, vkCreateSemaphore(_, _, _, _)) + .RETURN(VK_SUCCESS); + ALLOW_CALL(*vkDevice, vkDestroySemaphore(_, _, _)) + .RETURN(VK_SUCCESS); + + 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 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(), ¶ms) == 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(), ¶ms) != 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(), ¶ms) == 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(), ¶ms) != 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->pTimings[0].presentID = 5; + _3->pTimings[0].inputSampleTimeUs = 8; + _3->pTimings[1].presentID = 6; + _3->pTimings[1].inputSampleTimeUs = 7; + }) + .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_LATENCY_RESULT_PARAMS params{}; + params.version = NV_VULKAN_LATENCY_RESULT_PARAMS_VER1; + REQUIRE(NvAPI_Vulkan_GetLatency(vkDevice.get(), ¶ms) == 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 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(), ¶ms) == 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(), ¶ms) != NVAPI_INCOMPATIBLE_STRUCT_VERSION); + } + + SECTION("SetLatencyMarker returns OK") { + REQUIRE_CALL(*vkDevice, vkSetLatencyMarkerNV(_, _, _)) + .WITH(_3->marker == VK_LATENCY_MARKER_PRESENT_START_NV) + .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_LATENCY_MARKER_PARAMS params; + params.version = NV_VULKAN_LATENCY_MARKER_PARAMS_VER1; + params.markerType = VULKAN_PRESENT_START; + REQUIRE(NvAPI_Vulkan_SetLatencyMarker(vkDevice.get(), ¶ms) == 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(), ¶ms) == 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(), ¶ms) != NVAPI_INCOMPATIBLE_STRUCT_VERSION); + } + + SECTION("NotifyOutOfBandVkQueue returns OK") { + REQUIRE_CALL(*vkQueue, vkQueueNotifyOutOfBandNV(_, _)) + .WITH(_2->queueType == VK_OUT_OF_BAND_QUEUE_TYPE_PRESENT_NV) + .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_NotifyOutOfBandVkQueue(vkDevice.get(), vkQueue.get(), VULKAN_OUT_OF_BAND_QUEUE_TYPE_PRESENT) == NVAPI_OK); + REQUIRE(NvAPI_Vulkan_DestroyLowLatencyDevice(vkDevice.get()) == NVAPI_OK); + } + } +} diff --git a/tests/nvapi_vulkan_mocks.h b/tests/nvapi_vulkan_mocks.h index 016b4286..78302ad1 100644 --- a/tests/nvapi_vulkan_mocks.h +++ b/tests/nvapi_vulkan_mocks.h @@ -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, VkResult(VkDevice, VkSemaphore, const VkAllocationCallbacks*)); @@ -39,9 +41,31 @@ class VkQueueMock { } }; -class VkMock final : public trompeloeil::mock_interface { +class VkMock final : public mock_interface { IMPLEMENT_CONST_MOCK0(IsAvailable); IMPLEMENT_CONST_MOCK2(GetDeviceProcAddr); IMPLEMENT_CONST_MOCK2(GetDeviceExtensions); IMPLEMENT_CONST_MOCK3(GetPhysicalDeviceProperties2); -}; \ No newline at end of file + + [[nodiscard]] static std::array, 2> ConfigureDefaultPFN(VkMock& mock) { + return { + NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkCreateSemaphore")))) + .RETURN(reinterpret_cast(VkDeviceMock::PFN_vkCreateSemaphore)), + NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkDestroySemaphore")))) + .RETURN(reinterpret_cast(VkDeviceMock::PFN_vkDestroySemaphore))}; + } + + [[nodiscard]] static std::array, 5> ConfigureLL2PFN(VkMock& mock) { + return { + NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkSetLatencySleepModeNV")))) + .RETURN(reinterpret_cast(VkDeviceMock::PFN_vkSetLatencySleepModeNV)), + NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkLatencySleepNV")))) + .RETURN(reinterpret_cast(VkDeviceMock::PFN_vkLatencySleepNV)), + NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkGetLatencyTimingsNV")))) + .RETURN(reinterpret_cast(VkDeviceMock::PFN_vkGetLatencyTimingsNV)), + NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkSetLatencyMarkerNV")))) + .RETURN(reinterpret_cast(VkDeviceMock::PFN_vkSetLatencyMarkerNV)), + NAMED_ALLOW_CALL(mock, GetDeviceProcAddr(_, eq(std::string_view("vkQueueNotifyOutOfBandNV")))) + .RETURN(reinterpret_cast(VkQueueMock::PFN_vkQueueNotifyOutOfBandNV))}; + } +};