Skip to content

Commit

Permalink
nvapi: Fix application out of order frame id issues
Browse files Browse the repository at this point in the history
  • Loading branch information
esullivan-nvidia committed Mar 23, 2024
1 parent efcc764 commit 5d0daba
Show file tree
Hide file tree
Showing 5 changed files with 186 additions and 7 deletions.
68 changes: 64 additions & 4 deletions src/d3d/nvapi_d3d_low_latency_device.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,42 @@
#include "nvapi_d3d_low_latency_device.h"

namespace dxvk {
LowLatencyFrameIdGenerator::LowLatencyFrameIdGenerator() : m_nextLowLatencyDeviceFrameId(1) {}

LowLatencyFrameIdGenerator::~LowLatencyFrameIdGenerator() {}

uint64_t LowLatencyFrameIdGenerator::GetLowLatencyDeviceFrameId(uint64_t applicationFrameId) {
std::scoped_lock lock(m_frameIdGeneratorMutex);

auto it = m_applicationIdToDeviceId.find(applicationFrameId);
if (it != m_applicationIdToDeviceId.end())
return it->second;

uint64_t lowLatencyDeviceFrameId = m_nextLowLatencyDeviceFrameId++;
uint64_t frameIdIndex = (lowLatencyDeviceFrameId - 1) % applicationIdListSize;

if ((lowLatencyDeviceFrameId - 1) >= applicationIdListSize)
m_applicationIdToDeviceId.erase(m_applicationIdList[frameIdIndex]);

m_applicationIdToDeviceId[applicationFrameId] = lowLatencyDeviceFrameId;
m_applicationIdList[frameIdIndex] = applicationFrameId;

return lowLatencyDeviceFrameId;
}

bool LowLatencyFrameIdGenerator::LowLatencyDeviceFrameIdInWindow(uint64_t lowLatencyDeviceFrameId) {
return ((lowLatencyDeviceFrameId < m_nextLowLatencyDeviceFrameId) && ((m_nextLowLatencyDeviceFrameId - lowLatencyDeviceFrameId) < applicationIdListSize));
}

uint64_t LowLatencyFrameIdGenerator::GetApplicationFrameId(uint64_t lowLatencyDeviceFrameId) {
std::scoped_lock lock(m_frameIdGeneratorMutex);

if (!lowLatencyDeviceFrameId || !LowLatencyDeviceFrameIdInWindow(lowLatencyDeviceFrameId))
return 0;

return m_applicationIdList[((lowLatencyDeviceFrameId - 1) % applicationIdListSize)];
}

bool NvapiD3dLowLatencyDevice::SupportsLowLatency(IUnknown* device) {
auto d3dLowLatencyDevice = GetLowLatencyDevice(device);
if (d3dLowLatencyDevice == nullptr)
Expand Down Expand Up @@ -30,28 +66,43 @@ namespace dxvk {
if (d3dLowLatencyDevice == nullptr)
return false;

return SUCCEEDED(d3dLowLatencyDevice->GetLatencyInfo(latencyResults));
if (FAILED(d3dLowLatencyDevice->GetLatencyInfo(latencyResults)))
return false;

LowLatencyFrameIdGenerator* frameIdGenerator = GetFrameIdGenerator(device);
for (auto& frameReport : latencyResults->frame_reports) {
frameReport.frameID = frameIdGenerator->GetApplicationFrameId(frameReport.frameID);
if (!frameReport.frameID) {
memset(latencyResults->frame_reports, 0, sizeof(latencyResults->frame_reports));
break;
}
}

return true;
}

bool NvapiD3dLowLatencyDevice::SetLatencyMarker(IUnknown* device, uint64_t frameID, uint32_t markerType) {
auto d3dLowLatencyDevice = GetLowLatencyDevice(device);
if (d3dLowLatencyDevice == nullptr)
return false;

return SUCCEEDED(d3dLowLatencyDevice->SetLatencyMarker(frameID, markerType));
uint64_t lowLatencyDeviceFrameId = GetFrameIdGenerator(device)->GetLowLatencyDeviceFrameId(frameID);

return SUCCEEDED(d3dLowLatencyDevice->SetLatencyMarker(lowLatencyDeviceFrameId, markerType));
}

void NvapiD3dLowLatencyDevice::ClearCacheMaps() {
std::scoped_lock lock(m_LowLatencyDeviceMutex);
std::scoped_lock lock(m_lowLatencyDeviceMutex, m_lowLatencyFrameIdGeneratorMutex);

m_lowLatencyDeviceMap.clear();
m_frameIdGeneratorMap.clear();
}

Com<ID3DLowLatencyDevice> NvapiD3dLowLatencyDevice::GetLowLatencyDevice(IUnknown* device) {
if (device == nullptr)
return nullptr;

std::scoped_lock lock(m_LowLatencyDeviceMutex);
std::scoped_lock lock(m_lowLatencyDeviceMutex);
auto it = m_lowLatencyDeviceMap.find(device);
if (it != m_lowLatencyDeviceMap.end())
return it->second;
Expand All @@ -64,4 +115,13 @@ namespace dxvk {

return d3dLowLatencyDevice;
}

LowLatencyFrameIdGenerator* NvapiD3dLowLatencyDevice::GetFrameIdGenerator(IUnknown* device) {
std::scoped_lock lock(m_lowLatencyFrameIdGeneratorMutex);
auto it = m_frameIdGeneratorMap.find(device);
if (it != m_frameIdGeneratorMap.end())
return it->second.get();

return m_frameIdGeneratorMap.emplace(device, std::make_unique<LowLatencyFrameIdGenerator>()).first->second.get();
}
}
23 changes: 22 additions & 1 deletion src/d3d/nvapi_d3d_low_latency_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,24 @@
#include "../util/com_pointer.h"

namespace dxvk {
class LowLatencyFrameIdGenerator {
public:
LowLatencyFrameIdGenerator();
virtual ~LowLatencyFrameIdGenerator();
bool LowLatencyDeviceFrameIdInWindow(uint64_t lowLatencyDeviceFrameId);
uint64_t GetLowLatencyDeviceFrameId(uint64_t applicationFrameId);
uint64_t GetApplicationFrameId(uint64_t lowLatencyDeviceFrameId);

private:
std::mutex m_frameIdGeneratorMutex;

uint64_t m_nextLowLatencyDeviceFrameId;
std::unordered_map<uint64_t, uint64_t> m_applicationIdToDeviceId;

static constexpr uint32_t applicationIdListSize = 1000;
std::array<uint64_t, applicationIdListSize> m_applicationIdList;
};

class NvapiD3dLowLatencyDevice {
public:
static bool SupportsLowLatency(IUnknown* device);
Expand All @@ -17,9 +35,12 @@ namespace dxvk {

private:
inline static std::unordered_map<IUnknown*, ID3DLowLatencyDevice*> m_lowLatencyDeviceMap;
inline static std::unordered_map<IUnknown*, std::unique_ptr<LowLatencyFrameIdGenerator>> m_frameIdGeneratorMap;

inline static std::mutex m_LowLatencyDeviceMutex;
inline static std::mutex m_lowLatencyDeviceMutex;
inline static std::mutex m_lowLatencyFrameIdGeneratorMutex;

[[nodiscard]] static Com<ID3DLowLatencyDevice> GetLowLatencyDevice(IUnknown* device);
[[nodiscard]] static LowLatencyFrameIdGenerator* GetFrameIdGenerator(IUnknown* device);
};
}
1 change: 1 addition & 0 deletions src/nvapi_private.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
#include <unordered_map>
#include <utility>
#include <vector>
#include <array>

#include <dxgi1_6.h>
#include <d3d11_1.h>
Expand Down
99 changes: 98 additions & 1 deletion tests/nvapi_d3d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ TEST_CASE("D3D Reflex/LatencyFleX depending methods succeed", "[.d3d]") {
.RETURN(S_OK);
REQUIRE_CALL(lowLatencyDevice, LatencySleep())
.RETURN(S_OK);
REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(123ULL, SIMULATION_START))
REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(1ULL, SIMULATION_START))
.RETURN(S_OK);

SetupResourceFactory(std::move(dxgiFactory), std::move(vulkan), std::move(nvml), std::move(lfx));
Expand All @@ -402,6 +402,103 @@ TEST_CASE("D3D Reflex/LatencyFleX depending methods succeed", "[.d3d]") {
REQUIRE(NvAPI_D3D_SetLatencyMarker(&unknown, &latencyMarkerParams) == NVAPI_OK);
}

SECTION("SetLatencyMarker correctly produces monotonic frame ids for a sequence of unique application frame ids") {
REQUIRE_CALL(lowLatencyDevice, SetLatencySleepMode(true, false, 750U))
.RETURN(S_OK);

SetupResourceFactory(std::move(dxgiFactory), std::move(vulkan), std::move(nvml), std::move(lfx));
REQUIRE(NvAPI_Initialize() == NVAPI_OK);

NV_SET_SLEEP_MODE_PARAMS sleepModeParams{};
sleepModeParams.version = NV_SET_SLEEP_MODE_PARAMS_VER;
sleepModeParams.bLowLatencyMode = true;
sleepModeParams.minimumIntervalUs = 750;
NV_LATENCY_MARKER_PARAMS latencyMarkerParams{};
latencyMarkerParams.version = NV_LATENCY_MARKER_PARAMS_VER1;
latencyMarkerParams.frameID = 256ULL;
latencyMarkerParams.markerType = SIMULATION_START;
REQUIRE(NvAPI_D3D_SetSleepMode(&unknown, &sleepModeParams) == NVAPI_OK);

for (uint64_t i = 0; i < 1100; i++) {
uint64_t lowLatencyDeviceFrameId = i + 1;

{
latencyMarkerParams.markerType = SIMULATION_START;
REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(lowLatencyDeviceFrameId, SIMULATION_START))
.RETURN(S_OK);
REQUIRE(NvAPI_D3D_SetLatencyMarker(&unknown, &latencyMarkerParams) == NVAPI_OK);
}

{
latencyMarkerParams.markerType = SIMULATION_END;
REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(lowLatencyDeviceFrameId, SIMULATION_END))
.RETURN(S_OK);
REQUIRE(NvAPI_D3D_SetLatencyMarker(&unknown, &latencyMarkerParams) == NVAPI_OK);
}

if (i % 2)
latencyMarkerParams.frameID += 512ULL;
else
latencyMarkerParams.frameID -= 128ULL;
}
}

SECTION("GetLatencyInfo correctly returns application frame ids rather than device frame ids") {
std::unique_ptr<NV_LATENCY_RESULT_PARAMS> latencyResults = std::make_unique<NV_LATENCY_RESULT_PARAMS>();

REQUIRE_CALL(lowLatencyDevice, SetLatencySleepMode(true, false, 750U))
.RETURN(S_OK);

SetupResourceFactory(std::move(dxgiFactory), std::move(vulkan), std::move(nvml), std::move(lfx));
REQUIRE(NvAPI_Initialize() == NVAPI_OK);

NV_SET_SLEEP_MODE_PARAMS sleepModeParams{};
sleepModeParams.version = NV_SET_SLEEP_MODE_PARAMS_VER;
sleepModeParams.bLowLatencyMode = true;
sleepModeParams.minimumIntervalUs = 750;
NV_LATENCY_MARKER_PARAMS latencyMarkerParams{};
latencyMarkerParams.version = NV_LATENCY_MARKER_PARAMS_VER1;
latencyMarkerParams.frameID = 0ULL;
latencyMarkerParams.markerType = SIMULATION_START;
REQUIRE(NvAPI_D3D_SetSleepMode(&unknown, &sleepModeParams) == NVAPI_OK);

for (uint64_t i = 0; i < 1100; i++) {
uint64_t lowLatencyDeviceFrameId = i + 1;

{
latencyMarkerParams.markerType = SIMULATION_START;
REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(lowLatencyDeviceFrameId, SIMULATION_START))
.RETURN(S_OK);
REQUIRE(NvAPI_D3D_SetLatencyMarker(&unknown, &latencyMarkerParams) == NVAPI_OK);
}

{
latencyMarkerParams.markerType = SIMULATION_END;
REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(lowLatencyDeviceFrameId, SIMULATION_END))
.RETURN(S_OK);
REQUIRE(NvAPI_D3D_SetLatencyMarker(&unknown, &latencyMarkerParams) == NVAPI_OK);
}

{
memset(latencyResults.get(), 0, sizeof(NV_LATENCY_RESULT_PARAMS));
latencyResults->version = NV_LATENCY_RESULT_PARAMS_VER1;

REQUIRE_CALL(lowLatencyDevice, GetLatencyInfo(reinterpret_cast<D3D_LATENCY_RESULTS*>(latencyResults.get())))
.SIDE_EFFECT(std::ranges::for_each(_1->frame_reports, [&](auto& report) {
report.frameID = lowLatencyDeviceFrameId;
}))
.RETURN(S_OK);

REQUIRE(NvAPI_D3D_GetLatency(&unknown, latencyResults.get()) == NVAPI_OK);
REQUIRE(std::ranges::all_of(latencyResults->frameReport, [&](auto& report) {
return (report.frameID == latencyMarkerParams.frameID);
}));
}

latencyMarkerParams.frameID += 10ULL;
}
}

SECTION("SetLatencyMarker with unknown struct version returns incompatible-struct-version") {
SetupResourceFactory(std::move(dxgiFactory), std::move(vulkan), std::move(nvml), std::move(lfx));

Expand Down
2 changes: 1 addition & 1 deletion tests/nvapi_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -834,7 +834,7 @@ TEST_CASE("D3D12 methods succeed", "[.d3d12]") {
SECTION("SetAsyncFrameMarker returns OK") {
SetupResourceFactory(std::move(dxgiFactory), std::move(vulkan), std::move(nvml), std::move(lfx));

REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(123ULL, OUT_OF_BAND_RENDERSUBMIT_START))
REQUIRE_CALL(lowLatencyDevice, SetLatencyMarker(1ULL, OUT_OF_BAND_RENDERSUBMIT_START))
.RETURN(S_OK);

REQUIRE(NvAPI_Initialize() == NVAPI_OK);
Expand Down

0 comments on commit 5d0daba

Please sign in to comment.