Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

nvapi: Reflex support through LatencyFleX #66

Merged
merged 3 commits into from
Feb 9, 2022
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ This implementation currently offers entry points for supporting the following f

- NVIDIA DLSS for Vulkan, by supporting the relevant adapter information by querying from [Vulkan](https://www.vulkan.org/).
- NVIDIA DLSS for D3D11 and D3D12, by querying from Vulkan and forwarding the relevant calls into DXVK / VKD3D-Proton.
- NVIDIA Reflex Low-Latency bridged to [LatencyFleX](https://github.com/ishitatsuyuki/LatencyFleX). This is not the official implementation and this requires additional setup.
- Several NVAPI D3D11 extensions, among others `SetDepthBoundsTest` and `UAVOverlap`, by forwarding the relevant calls into DXVK.
- NVIDIA PhysX, by supporting entry points for querying PhysX capability.
- Several GPU topology related methods for adapter and display information, by querying from DXVK and Vulkan.
Expand Down
58 changes: 58 additions & 0 deletions src/d3d/lfx.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
#include "lfx.h"

#include <windows.h>
#include "../util/util_string.h"
#include "../util/util_log.h"

namespace dxvk {
Lfx::Lfx() {
const auto lfxModuleName = "latencyflex_layer.dll";
const auto lfxModuleNameFallback = "latencyflex_wine.dll";
m_lfxModule = ::LoadLibraryA(lfxModuleName);
if (m_lfxModule) {
ishitatsuyuki marked this conversation as resolved.
Show resolved Hide resolved
m_lfx_WaitAndBeginFrame = reinterpret_cast<PFN_lfx_WaitAndBeginFrame>(GetProcAddress(m_lfxModule,
"lfx_WaitAndBeginFrame"));
m_lfx_SetTargetFrameTime = reinterpret_cast<PFN_lfx_SetTargetFrameTime>(GetProcAddress(m_lfxModule,
"lfx_SetTargetFrameTime"));
} else {
auto lastError = ::GetLastError();
if (lastError != ERROR_MOD_NOT_FOUND) {
log::write(str::format("Loading ", lfxModuleName, " failed with error code: ", lastError));
} else {
// Try fallback entrypoints. These were used by an older version of LatencyFleX.
m_lfxModule = ::LoadLibraryA(lfxModuleNameFallback);
if (m_lfxModule) {
m_lfx_WaitAndBeginFrame = reinterpret_cast<PFN_lfx_WaitAndBeginFrame>(GetProcAddress(m_lfxModule,
"winelfx_WaitAndBeginFrame"));
m_lfx_SetTargetFrameTime = reinterpret_cast<PFN_lfx_SetTargetFrameTime>(GetProcAddress(m_lfxModule,
"winelfx_SetTargetFrameTime"));
} else {
lastError = ::GetLastError();
if (lastError != ERROR_MOD_NOT_FOUND) // Ignore library not found
log::write(
str::format("Loading ", lfxModuleNameFallback, " failed with error code: ", lastError));
}
}
}
}

Lfx::~Lfx() {
if (m_lfxModule == nullptr) return;
::FreeLibrary(m_lfxModule);
m_lfxModule = nullptr;
}

bool Lfx::IsAvailable() const {
return m_lfxModule != nullptr;
}

void Lfx::WaitAndBeginFrame() {
if (m_lfx_WaitAndBeginFrame)
m_lfx_WaitAndBeginFrame();
}

void Lfx::SetTargetFrameTime(uint64_t frame_time_ns) {
if (m_lfx_SetTargetFrameTime)
m_lfx_SetTargetFrameTime(static_cast<__int64>(frame_time_ns));
}
}
26 changes: 26 additions & 0 deletions src/d3d/lfx.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <minwindef.h>
#include <cstdint>

namespace dxvk {
class Lfx {
public:
Lfx();
virtual ~Lfx();

[[nodiscard]] virtual bool IsAvailable() const;
virtual void WaitAndBeginFrame();
virtual void SetTargetFrameTime(uint64_t frame_time_ns);

private:
HMODULE m_lfxModule{};
bool m_enabled = false;

typedef void (*PFN_lfx_WaitAndBeginFrame)();
typedef void (*PFN_lfx_SetTargetFrameTime)(__int64);

PFN_lfx_WaitAndBeginFrame m_lfx_WaitAndBeginFrame{};
PFN_lfx_SetTargetFrameTime m_lfx_SetTargetFrameTime{};
};
}
37 changes: 37 additions & 0 deletions src/d3d/nvapi_d3d_instance.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
#include "../util/util_log.h"
#include "nvapi_d3d_instance.h"

namespace dxvk {
NvapiD3dInstance::NvapiD3dInstance(ResourceFactory &resourceFactory)
: m_resourceFactory(resourceFactory) {}

NvapiD3dInstance::~NvapiD3dInstance() = default;

void NvapiD3dInstance::Initialize() {
m_lfx = m_resourceFactory.CreateLfx();
ishitatsuyuki marked this conversation as resolved.
Show resolved Hide resolved
if (m_lfx->IsAvailable())
log::write("LatencyFleX loaded and initialized successfully");
}

bool NvapiD3dInstance::IsReflexAvailable() {
return m_lfx->IsAvailable();
}

bool NvapiD3dInstance::IsReflexEnabled() const {
return m_isLfxEnabled;
}

void NvapiD3dInstance::SetReflexEnabled(bool value) {
m_isLfxEnabled = value;
}

void NvapiD3dInstance::Sleep() {
if (m_isLfxEnabled)
m_lfx->WaitAndBeginFrame();
}

void NvapiD3dInstance::SetTargetFrameTime(uint64_t frameTimeUs) {
constexpr uint64_t kNanoInMicro = 1000;
m_lfx->SetTargetFrameTime(frameTimeUs * kNanoInMicro);
}
}
23 changes: 23 additions & 0 deletions src/d3d/nvapi_d3d_instance.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#pragma once

#include "../resource_factory.h"

namespace dxvk {
class NvapiD3dInstance {
public:
explicit NvapiD3dInstance(ResourceFactory &resourceFactory);
~NvapiD3dInstance();

void Initialize();
[[nodiscard]] bool IsReflexAvailable();
[[nodiscard]] bool IsReflexEnabled() const;
void SetReflexEnabled(bool value);
void Sleep();
void SetTargetFrameTime(uint64_t frameTimeUs);

private:
ResourceFactory& m_resourceFactory;
std::unique_ptr<Lfx> m_lfx;
bool m_isLfxEnabled = false;
};
}
4 changes: 3 additions & 1 deletion src/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ nvapi_src = files([
'sysinfo/nvapi_output.cpp',
'sysinfo/nvapi_adapter.cpp',
'sysinfo/nvapi_adapter_registry.cpp',
'sysinfo/resource_factory.cpp',
'resource_factory.cpp',
'd3d/lfx.cpp',
'd3d/nvapi_d3d_instance.cpp',
'd3d11/nvapi_d3d11_device.cpp',
'd3d12/nvapi_d3d12_device.cpp',
'nvapi_interface.cpp',
Expand Down
3 changes: 3 additions & 0 deletions src/nvapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -264,6 +264,9 @@ extern "C" {
return NvidiaDeviceNotFound(n);
}

nvapiD3dInstance = std::make_unique<NvapiD3dInstance>(*resourceFactory);
nvapiD3dInstance->Initialize();

return Ok(n);
}
}
56 changes: 56 additions & 0 deletions src/nvapi_d3d.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "nvapi_private.h"
#include "nvapi_static.h"
#include "util/util_statuscode.h"

extern "C" {
Expand Down Expand Up @@ -102,4 +103,59 @@ extern "C" {

return Ok(n, alreadyLoggedOk);
}

NvAPI_Status __cdecl NvAPI_D3D_Sleep(IUnknown *pDevice) {
constexpr auto n = __func__;
static bool alreadyLoggedOk = false;
static bool alreadyLoggedUnavailable = false;

if (pDevice == nullptr)
return InvalidArgument(n);

if (!nvapiD3dInstance->IsReflexAvailable())
return NoImplementation(n, alreadyLoggedUnavailable);

nvapiD3dInstance->Sleep();

return Ok(n, alreadyLoggedOk);
}

NvAPI_Status __cdecl NvAPI_D3D_SetSleepMode(IUnknown *pDevice,
NV_SET_SLEEP_MODE_PARAMS *pSetSleepModeParams) {
constexpr auto n = __func__;
static bool alreadyLoggedUnavailable = false;

if (pDevice == nullptr)
return InvalidArgument(n);

if (pSetSleepModeParams->version != NV_SET_SLEEP_MODE_PARAMS_VER1)
return IncompatibleStructVersion(n);

if (!nvapiD3dInstance->IsReflexAvailable())
return NoImplementation(n, alreadyLoggedUnavailable);

nvapiD3dInstance->SetReflexEnabled(pSetSleepModeParams->bLowLatencyMode);
if (pSetSleepModeParams->bLowLatencyMode) {
log::write(str::format(n, ": interval ", pSetSleepModeParams->minimumIntervalUs, " us"));
nvapiD3dInstance->SetTargetFrameTime(pSetSleepModeParams->minimumIntervalUs);
}

auto f = str::format(n, " (", pSetSleepModeParams->bLowLatencyMode ? "Enable" : "Disable", ")");
return Ok(f);
}

NvAPI_Status __cdecl NvAPI_D3D_GetSleepStatus(IUnknown *pDevice,
NV_GET_SLEEP_STATUS_PARAMS *pGetSleepStatusParams) {
constexpr auto n = __func__;
static bool alreadyLoggedUnavailable = false;

if (pGetSleepStatusParams->version != NV_GET_SLEEP_STATUS_PARAMS_VER1)
return IncompatibleStructVersion(n);

if (!nvapiD3dInstance->IsReflexAvailable())
return NoImplementation(n, alreadyLoggedUnavailable);

pGetSleepStatusParams->bLowLatencyMode = nvapiD3dInstance->IsReflexEnabled();
return Ok(n);
}
}
3 changes: 3 additions & 0 deletions src/nvapi_interface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ extern "C" {
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_ImplicitSLIControl)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_BeginResourceRendering)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_EndResourceRendering)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_SetSleepMode)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_GetSleepStatus)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_D3D_Sleep)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_GPU_GetGPUType)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_GPU_GetPCIIdentifiers)
INSERT_AND_RETURN_WHEN_EQUALS(NvAPI_GPU_GetFullName)
Expand Down
4 changes: 3 additions & 1 deletion src/nvapi_static.h
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
#pragma once

#include "sysinfo/resource_factory.h"
#include "resource_factory.h"
#include "sysinfo/nvapi_adapter_registry.h"
#include "d3d/nvapi_d3d_instance.h"

static std::unique_ptr<dxvk::ResourceFactory> resourceFactory;
static std::unique_ptr<dxvk::NvapiAdapterRegistry> nvapiAdapterRegistry;
static std::unique_ptr<dxvk::NvapiD3dInstance> nvapiD3dInstance;

static auto initializationMutex = std::mutex{};
static auto initializationCount = 0ULL;
4 changes: 4 additions & 0 deletions src/sysinfo/resource_factory.cpp → src/resource_factory.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,8 @@ namespace dxvk {
std::unique_ptr<Nvml> ResourceFactory::CreateNvml() {
return std::make_unique<Nvml>();
}

std::unique_ptr <Lfx> ResourceFactory::CreateLfx() {
return std::make_unique<Lfx>();
}
}
10 changes: 6 additions & 4 deletions src/sysinfo/resource_factory.h → src/resource_factory.h
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#pragma once

#include "../nvapi_private.h"
#include "../util/com_pointer.h"
#include "vulkan.h"
#include "nvml.h"
#include "nvapi_private.h"
#include "util/com_pointer.h"
#include "sysinfo/vulkan.h"
#include "sysinfo/nvml.h"
#include "d3d/lfx.h"

namespace dxvk {
class ResourceFactory {
Expand All @@ -15,5 +16,6 @@ namespace dxvk {
virtual Com<IDXGIFactory1> CreateDXGIFactory1();
virtual std::unique_ptr<Vulkan> CreateVulkan();
virtual std::unique_ptr<Nvml> CreateNvml();
virtual std::unique_ptr<Lfx> CreateLfx();
};
}
2 changes: 1 addition & 1 deletion src/sysinfo/nvapi_adapter_registry.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#pragma once

#include "../nvapi_private.h"
#include "resource_factory.h"
#include "../resource_factory.h"
#include "nvapi_adapter.h"
#include "nvapi_output.h"
#include "vulkan.h"
Expand Down
4 changes: 3 additions & 1 deletion tests/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ nvapi_src = files([
'../src/util/util_log.cpp',
'../src/sysinfo/vulkan.cpp',
'../src/sysinfo/nvml.cpp',
'../src/d3d/lfx.cpp',
'../src/d3d/nvapi_d3d_instance.cpp',
'../src/sysinfo/nvapi_output.cpp',
'../src/sysinfo/nvapi_adapter.cpp',
'../src/sysinfo/nvapi_adapter_registry.cpp',
'../src/sysinfo/resource_factory.cpp',
'../src/resource_factory.cpp',
'../src/d3d11/nvapi_d3d11_device.cpp',
'../src/d3d12/nvapi_d3d12_device.cpp',
])
Expand Down
15 changes: 11 additions & 4 deletions tests/mock_factory.cpp
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
class MockFactory : public ResourceFactory {

public:
MockFactory(std::unique_ptr<IDXGIFactory1> dxgiFactory1Mock, std::unique_ptr<Vulkan> vulkanMock, std::unique_ptr<Nvml> nvmlMock)
: m_dxgiFactoryMock(std::move(dxgiFactory1Mock)), m_vulkanMock(std::move(vulkanMock)), m_nvmlMock(std::move(nvmlMock)) {};
public:
MockFactory(std::unique_ptr<IDXGIFactory1> dxgiFactory1Mock, std::unique_ptr<Vulkan> vulkanMock,
std::unique_ptr<Nvml> nvmlMock, std::unique_ptr<Lfx> lfxMock)
: m_dxgiFactoryMock(std::move(dxgiFactory1Mock)), m_vulkanMock(std::move(vulkanMock)),
m_nvmlMock(std::move(nvmlMock)), m_lfxMock(std::move(lfxMock)) {};

Com<IDXGIFactory1> CreateDXGIFactory1() override {
Com<IDXGIFactory1> dxgiFactory = m_dxgiFactoryMock.get();
Expand All @@ -17,8 +19,13 @@ class MockFactory : public ResourceFactory {
return std::move(m_nvmlMock);
}

private:
std::unique_ptr<Lfx> CreateLfx() override {
return std::move(m_lfxMock);
}

private:
std::unique_ptr<IDXGIFactory1> m_dxgiFactoryMock;
std::unique_ptr<Vulkan> m_vulkanMock;
std::unique_ptr<Nvml> m_nvmlMock;
std::unique_ptr<Lfx> m_lfxMock;
};
Loading