Skip to content

Commit

Permalink
Fixed incorrect display topology caching caused by hashing VBlank sta…
Browse files Browse the repository at this point in the history
…tistics as part of SK_RenderBackend_V2::output_s's invariant data
  • Loading branch information
Kaldaien committed Sep 20, 2024
1 parent cb97c44 commit 439d7c9
Show file tree
Hide file tree
Showing 21 changed files with 236 additions and 122 deletions.
9 changes: 8 additions & 1 deletion CHANGELOG.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
24.9.20
24.9.20.1
=========
+ Fixed incorrect display topology caching caused by hashing VBlank statistics
as part of SK_RenderBackend_V2::output_s's invariant data.
+ Reworked Streamline initialization for games that load sl.interposer.dll
but have not loaded any plug-ins yet when SK is injected.

24.9.20
=======
+ Add hook on sl.interposer.dll slInit (...) in order to override various
Streamline configuration for enhanced compatibility with third-party
Expand Down
4 changes: 2 additions & 2 deletions include/SpecialK/DLL_VERSION.H
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
#define SK_YEAR 24
#define SK_MONTH 9
#define SK_DATE 20
#define SK_REV_N 0
#define SK_REV 0
#define SK_REV_N 1
#define SK_REV 1

#ifndef _A2
#define _A2(a) #a
Expand Down
42 changes: 24 additions & 18 deletions include/SpecialK/render/backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -285,24 +285,6 @@ class SK_RenderBackend_V2 : public SK_RenderBackend_V1
monitor_caps = { };
BOOL vrr_enabled = -1;

struct vblank_history_s {
static constexpr int MaxVBlankRecords = 128;
struct record_s {
NvU64 timestamp_qpc = 0;
NvU64 vblank_count = 0;
} records [MaxVBlankRecords];
NvU32 head = 0;
ULONG64 last_qpc_refreshed = 0;
ULONG64 last_frame_sampled = 0;
float last_average =0.0f;
float last_last_average =0.0f;
NvU32 last_polled_time = 0;
bool addRecord (NvDisplayHandle nv_disp, DXGI_FRAME_STATISTICS *pFrameStats,
NvU64 tNow) noexcept;
float getVBlankHz ( NvU64 tNow) noexcept;
void resetStats ( void) noexcept;
} vblank_counter;

static output_s* getDisplayFromId (NvU32 display_id) noexcept;
static output_s* getDisplayFromHandle (NvDisplayHandle display_handle) noexcept;
} nvapi;
Expand Down Expand Up @@ -343,6 +325,30 @@ class SK_RenderBackend_V2 : public SK_RenderBackend_V1

// nits = 0.0 will read the OS preference and apply it
bool setSDRWhiteLevel (float nits);

// Data beyond this point is not static, do not hash it
//
uint8_t cache_end = 0xcd;

struct statistics_s {
struct vblank_history_s {
static constexpr int MaxVBlankRecords = 128;
struct record_s {
NvU64 timestamp_qpc = 0;
NvU64 vblank_count = 0;
} records [MaxVBlankRecords];
NvU32 head = 0;
ULONG64 last_qpc_refreshed = 0;
ULONG64 last_frame_sampled = 0;
float last_average =0.0f;
float last_last_average =0.0f;
NvU32 last_polled_time = 0;
bool addRecord (NvDisplayHandle nv_disp, DXGI_FRAME_STATISTICS *pFrameStats,
NvU64 tNow) noexcept;
float getVBlankHz ( NvU64 tNow) noexcept;
void resetStats ( void) noexcept;
} vblank_counter;
} statistics;
} displays [_MAX_DISPLAYS];

int active_display = 0;
Expand Down
4 changes: 2 additions & 2 deletions include/SpecialK/render/d3d12/d3d12_command_queue.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/**
/**
* This file is part of Special K.
*
* Special K is free software : you can redistribute it
Expand Down Expand Up @@ -30,4 +30,4 @@ D3D12CommandQueue_ExecuteCommandLists_pfn = void
extern D3D12CommandQueue_ExecuteCommandLists_pfn
D3D12CommandQueue_ExecuteCommandLists_Original;

void SK_D3D12_InstallCommandQueueHooks (ID3D12Device *pDev12);
bool SK_D3D12_InstallCommandQueueHooks (ID3D12Device *pDev12);
2 changes: 1 addition & 1 deletion include/SpecialK/render/d3d12/d3d12_device.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ extern D3D12Device9_ShaderCacheControl_pfn
D3D12Device9_ShaderCacheControl_Original;

bool SK_D3D12_HookDeviceCreation (void);
void SK_D3D12_InstallDeviceHooks (ID3D12Device* pDev12);
bool SK_D3D12_InstallDeviceHooks (ID3D12Device* pDev12);

static inline constexpr GUID SKID_D3D12IgnoredTextureCopy = { 0x3d5298cb, 0xd8f0, 0x7233, { 0xa1, 0x9d, 0xb1, 0xd5, 0x97, 0x92, 0x00, 0x70 } };

Expand Down
8 changes: 4 additions & 4 deletions src/control_panel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5350,12 +5350,12 @@ SK_ImGui_ControlPanel (void)

if (rb.gsync_state.active)
{
auto& nvapi_display =
rb.displays [rb.active_display].nvapi;
auto& stats =
rb.displays [rb.active_display].statistics;

float fVBlankHz =
nvapi_display.vblank_counter.getVBlankHz (
SK_QueryPerf ().QuadPart );
stats.vblank_counter.getVBlankHz (
SK_QueryPerf ().QuadPart );

// Is it really "active" if we can't calculate the rate?
if (fVBlankHz == 0.0f)
Expand Down
3 changes: 1 addition & 2 deletions src/core.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1842,8 +1842,7 @@ SK_StartupCore (const wchar_t* backend, void* callback)
// Not a saved INI setting; use an alternate initialization
// strategy when Streamline is detected...
config.compatibility.init_sync_for_streamline =
(SK_GetModuleHandleW (L"sl.interposer.dll") != 0) &&
(SK_GetModuleHandleW (L"sl.dlss_g.dll") != 0);
(SK_GetModuleHandleW (L"sl.interposer.dll") != 0);

try
{
Expand Down
75 changes: 58 additions & 17 deletions src/diagnostics/compatibility.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1150,36 +1150,77 @@ SK_COMPAT_CheckStreamlineSupport (void)
//
}

using PFun_slGetNativeInterface = sl::Result(void* proxyInterface, void** baseInterface);
using PFun_slUpgradeInterface = sl::Result( void** baseInterface);
// It is never necessary to call this, it can be implemented using QueryInterface
using slGetNativeInterface_pfn = sl::Result (*)(void* proxyInterface, void** baseInterface);
// This, on the other hand, requires an import from sl.interposer.dll
using slUpgradeInterface_pfn = sl::Result (*)( void** baseInterface);

struct DECLSPEC_UUID ("ADEC44E2-61F0-45C3-AD9F-1B37379284FF")
IStreamlineBaseInterface : IUnknown { };

sl::Result
SK_slGetNativeInterface (void *proxyInterface, void **baseInterface)
{
// Unsafe to do this for local injection
if (SK_IsInjected ())
{
static PFun_slGetNativeInterface * slGetNativeInterface =
(PFun_slGetNativeInterface *) SK_GetProcAddress (L"sl.interposer.dll", "slGetNativeInterface");
if (proxyInterface == nullptr || baseInterface == nullptr)
return sl::Result::eErrorMissingInputParameter;

if ( slGetNativeInterface != nullptr)
return slGetNativeInterface (proxyInterface, baseInterface);
}
IUnknown* pUnk =
static_cast <IUnknown *> (proxyInterface);

return sl::Result::eErrorNotInitialized;
if (FAILED (pUnk->QueryInterface (__uuidof (IStreamlineBaseInterface), baseInterface)))
return sl::Result::eErrorUnsupportedInterface;

return sl::Result::eOk;
}

sl::Result
SK_slUpgradeInterface (void **baseInterface)
{
// Unsafe to do this for local injection
if (SK_IsInjected ())
if (baseInterface == nullptr)
return sl::Result::eErrorMissingInputParameter;

IUnknown* pUnkInterface = *(IUnknown **)baseInterface;
void* pProxy = nullptr;

if (SUCCEEDED (pUnkInterface->QueryInterface (__uuidof (IStreamlineBaseInterface), &pProxy)))
{
// The passed interface already has a proxy, do nothing...
static_cast <IUnknown *> (pProxy)->Release ();

return sl::Result::eOk;
}

// If slInit (...) has not been called yet, sl.common.dll will not be present
if (! SK_IsModuleLoaded (L"sl.common.dll"))
return sl::Result::eErrorInitNotCalled;

auto slUpgradeInterface =
(slUpgradeInterface_pfn)SK_GetProcAddress (L"sl.interposer.dll",
"slUpgradeInterface");

if (slUpgradeInterface != nullptr)
{
static PFun_slUpgradeInterface * slUpgradeInterface =
(PFun_slUpgradeInterface *) SK_GetProcAddress (L"sl.interposer.dll", "slUpgradeInterface");
auto result =
slUpgradeInterface (baseInterface);

if (result == sl::Result::eOk)
{
static HMODULE
hModPinnedInterposer = nullptr, hModPinnedCommon = nullptr;
if (hModPinnedInterposer == nullptr || hModPinnedCommon == nullptr)
{
// Once we have done this, there's no going back...
// we MUST pin the interposer DLL permanently.
const BOOL bPinnedSL =
(nullptr != hModPinnedInterposer || GetModuleHandleEx (GET_MODULE_HANDLE_EX_FLAG_PIN, L"sl.interposer.dll", &hModPinnedInterposer))
&& (nullptr != hModPinnedCommon || GetModuleHandleEx (GET_MODULE_HANDLE_EX_FLAG_PIN, L"sl.common.dll", &hModPinnedCommon ));

if (! bPinnedSL)
SK_LOGi0 (L"Streamline Integration Has Invalid State!");
}
}

if ( slUpgradeInterface != nullptr)
return slUpgradeInterface (baseInterface);
return result;
}

return sl::Result::eErrorNotInitialized;
Expand Down
2 changes: 1 addition & 1 deletion src/imgui/backends/imgui_d3d11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1852,7 +1852,7 @@ SK_D3D11_RenderCtx::init (IDXGISwapChain* pSwapChain,
auto& rb =
SK_GetCurrentRenderBackend ();

rb.displays [rb.active_display].nvapi.vblank_counter.resetStats ();
rb.displays [rb.active_display].statistics.vblank_counter.resetStats ();


// Re-apply colorspace if necessary
Expand Down
2 changes: 1 addition & 1 deletion src/imgui/backends/imgui_d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1144,7 +1144,7 @@ ImGui_ImplDX12_Init ( ID3D12Device* device,
auto& rb =
SK_GetCurrentRenderBackend ();

rb.displays [rb.active_display].nvapi.vblank_counter.resetStats ();
rb.displays [rb.active_display].statistics.vblank_counter.resetStats ();

#ifdef SK_D3D12_PERSISTENT_IMGUI_DEV_OBJECTS
if (_imgui_d3d12.pDevice.p != _imgui_d3d12.pLastDevice.p)
Expand Down
14 changes: 8 additions & 6 deletions src/nvapi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1193,7 +1193,7 @@ NvAPI_Disp_HdrColorControl_Override ( NvU32 displayId,
}

bool
SK_RenderBackend_V2::output_s::nvapi_ctx_s::vblank_history_s::addRecord (NvDisplayHandle nv_disp, DXGI_FRAME_STATISTICS* pFrameStats, NvU64 tNow) noexcept
SK_RenderBackend_V2::output_s::statistics_s::vblank_history_s::addRecord (NvDisplayHandle nv_disp, DXGI_FRAME_STATISTICS* pFrameStats, NvU64 tNow) noexcept
{
const SK_RenderBackend& rb =
SK_GetCurrentRenderBackend ();
Expand Down Expand Up @@ -1236,23 +1236,25 @@ SK_RenderBackend_V2::output_s::nvapi_ctx_s::vblank_history_s::addRecord (NvDispl

head = std::min (head, (NvU32)MaxVBlankRecords-1);

if (vblank_count > records [head].vblank_count)
// This may not be monotonic increasing if the current monitor changes
// while sampling the data, so only test for inequality.
if (vblank_count != records [head].vblank_count)
{
if ( head == MaxVBlankRecords-1 )
head = 0;
else head++;

records [head] = { tNow, vblank_count };
}

return true;
return true;
}
}

return false;
}

void
SK_RenderBackend_V2::output_s::nvapi_ctx_s::vblank_history_s::resetStats (void) noexcept
SK_RenderBackend_V2::output_s::statistics_s::vblank_history_s::resetStats (void) noexcept
{
for (auto& record : records)
{
Expand All @@ -1266,7 +1268,7 @@ SK_RenderBackend_V2::output_s::nvapi_ctx_s::vblank_history_s::resetStats (void)
}

float
SK_RenderBackend_V2::output_s::nvapi_ctx_s::vblank_history_s::getVBlankHz (NvU64 tNow) noexcept
SK_RenderBackend_V2::output_s::statistics_s::vblank_history_s::getVBlankHz (NvU64 tNow) noexcept
{
NvU32 num_vblanks_in_period = 0;

Expand Down
8 changes: 4 additions & 4 deletions src/osd/text.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -857,12 +857,12 @@ SK_DrawOSD (void)

if (gsync)
{
auto& nvapi_display =
rb.displays [rb.active_display].nvapi;
auto& stats =
rb.displays [rb.active_display].statistics;

fVBlankHz =
nvapi_display.vblank_counter.getVBlankHz (
SK_QueryPerf ().QuadPart );
stats.vblank_counter.getVBlankHz (
SK_QueryPerf ().QuadPart );
}

if (fabs (mean - INFINITY) > std::numeric_limits <double>::epsilon ())
Expand Down
18 changes: 12 additions & 6 deletions src/render/d3d12/d3d12.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,12 @@ HookD3D12 (LPVOID user)

dll_log->Log (L"[ D3D 12 ] Hooking D3D12");

if (SK_D3D12_Init ())
bool hooked_create_device = SK_D3D12_Init ();
bool need_additional_hooks =
SK_D3D12_InstallDeviceHooks (nullptr) == false ||
SK_D3D12_InstallCommandQueueHooks (nullptr) == false;

if (hooked_create_device && need_additional_hooks)
{
SK_ComPtr <ID3D12Device> pDevice;

Expand Down Expand Up @@ -137,8 +142,6 @@ HookD3D12 (LPVOID user)
{
SK_D3D12_InstallDeviceHooks (pDevice.p);
SK_D3D12_InstallCommandQueueHooks (pDevice.p);

SK_ApplyQueuedHooks ();
}
}
}
Expand All @@ -164,10 +167,13 @@ SK_D3D12_HotSwapChainHook ( IDXGISwapChain3* pSwapChain,
if ( pDev12 != nullptr &&
D3D12Device_CreateRenderTargetView_Original == nullptr )
{
SK_D3D12_InstallDeviceHooks (pDev12);
SK_D3D12_InstallCommandQueueHooks (pDev12);
bool new_hooks = false;

SK_ApplyQueuedHooks ();
new_hooks |= SK_D3D12_InstallDeviceHooks (pDev12);
new_hooks |= SK_D3D12_InstallCommandQueueHooks (pDev12);

if (new_hooks)
SK_ApplyQueuedHooks ();

init = true;
}
Expand Down
20 changes: 17 additions & 3 deletions src/render/d3d12/d3d12_command_queue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ _InstallCommandQueueHooksImpl (ID3D12Device* pDevice12)
return;

const bool bHasStreamline =
SK_IsModuleLoaded (L"sl.dlss_g.dll");
SK_IsModuleLoaded (L"sl.interposer.dll");

SK_ComPtr <ID3D12Device> pDev12;

Expand All @@ -212,8 +212,22 @@ _InstallCommandQueueHooksImpl (ID3D12Device* pDevice12)
}
}

void
bool
SK_D3D12_InstallCommandQueueHooks (ID3D12Device *pDev12)
{
SK_RunOnce (_InstallCommandQueueHooksImpl (pDev12));
static bool s_Init = false;

// Check the status of hooks
if (pDev12 == nullptr)
return s_Init;

// Actually install hooks... once.
if (! std::exchange (s_Init, true))
{
_InstallCommandQueueHooksImpl (pDev12);

return true;
}

return false;
}
Loading

0 comments on commit 439d7c9

Please sign in to comment.