vulkan: Driver Pacing through NVAPI when presenting through DXGI

Vulkan presenting through DXGI never calls vkQueuePresentKHR: frames go
to a D3D12 device's swap chain, which VK_NV_low_latency2 cannot pace,
so Driver Pacing stayed off ("no native swapchain") on exactly the
setups that present that way - HDR, and NVIDIA's frame generation.

There the mode now goes through NVAPI's Reflex entry points on the D3D12
device that presents, as the Direct3D drivers' does: the presenter sets
the mode and marks the frame and its presents on that device, and the
frame loop's release is NvAPI_D3D_Sleep on it, shown to the frame loop
only while the mode is in effect there. The device is referenced while
used, so a sleep never outlives it. A failed sleep falls back as the
native path's does, and leaving DXGI takes the mode off the device.
Without NVAPI (Anti-Lag) the status still says the present is in the
way.

The NVAPI backend moves out of d3d_common.c into d3d_driver_pacing.c,
built with Vulkan on Windows as well as with the Direct3D drivers.
samples/gfx/vulkan_driver_pacing builds under mingw-w64 and runs under
Wine against a stand-in nvapi64.dll, driving the real vulkan_common.c
through a DXGI presenter: mode, boost, interval, markers, sleep, a
failed sleep, threaded hand-off and the way back to the native path.
CI runs it there beside the Linux runs.
This commit is contained in:
libretroadmin committed 2026-10-19 20:35:18 +02:00
1 parent 9a4f3815bd
commit 7234101942
17 files changed
+998 -371

No files matched your search

+19 -2
View File
@@ -118,10 +118,22 @@ jobs:
run: make -C samples/gfx/vulkan_swapchain_extent check
vulkan-driver-pacing:
name: Vulkan driver pacing backend (fake VK_NV_low_latency2)
name: Vulkan driver pacing backend (fake VK_NV_low_latency2 and NVAPI)
runs-on: ubuntu-latest
timeout-minutes: 10
timeout-minutes: 15
steps:
- name: Install dependencies
timeout-minutes: 10
run: |
set -eu
pkgs="gcc-mingw-w64-x86-64 wine64"
for i in 1 2 3; do
sudo timeout 90 apt-get update -y \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 && break
sleep 10
done
sudo apt-get install -y -qq \
-o Acquire::Retries=3 -o Acquire::http::Timeout=30 $pkgs
- name: Checkout
uses: actions/checkout@v5
- name: Regression test
@@ -133,6 +145,11 @@ jobs:
make -C samples/gfx/vulkan_driver_pacing check
make -C samples/gfx/vulkan_driver_pacing clean
make -C samples/gfx/vulkan_driver_pacing SANITIZER=address,undefined check
make -C samples/gfx/vulkan_driver_pacing clean
# Windows: presenting through DXGI, the mode, sleep and
# markers go through NVAPI (a stand-in nvapi64.dll)
make -C samples/gfx/vulkan_driver_pacing CC=x86_64-w64-mingw32-gcc RUNNER=wine check
make -C samples/gfx/vulkan_driver_pacing clean
slang-rect:
name: Presets read frames where they lie
+8
View File
@@ -1943,6 +1943,14 @@ ifeq ($(HAVE_D3D_COMMON), 1)
OBJ += gfx/common/d3d_common.o
endif
# Driver Pacing through NVAPI: the Direct3D drivers', and Vulkan's
# when it presents through DXGI
ifneq ($(findstring Win32,$(OS)),)
ifneq ($(findstring 1,$(HAVE_D3D_COMMON) $(HAVE_VULKAN)),)
OBJ += gfx/common/d3d_driver_pacing.o
endif
endif
ifeq ($(HAVE_CG), 1)
DEFINES += -DHAVE_CG
OBJ += gfx/drivers_shader/shader_gl_cg.o
-310
View File
@@ -22,10 +22,6 @@
#include "d3d_common.h"
#include "../video_driver.h"
#if defined(_WIN32) && !defined(__WINRT__) && !defined(_XBOX)
#include <windows.h>
#endif
#define D3D_TEXTURE_FILTER_LINEAR 2
#define D3D_TEXTURE_FILTER_POINT 1
@@ -49,309 +45,3 @@ int32_t d3d_translate_filter(unsigned type)
return (int32_t)D3D_TEXTURE_FILTER_POINT;
}
#if defined(_WIN32) && !defined(__WINRT__) && !defined(_XBOX)
/* NVAPI, by its published interface ids (nvapi_interface.h, MIT):
* resolved once for the process through nvapi_QueryInterface, never
* linked, so a machine without the driver's nvapi loses nothing. The
* structs are the header's, declared here so no SDK is needed; their
* version word is sizeof | (revision << 16), as MAKE_NVAPI_VERSION
* makes it. */
#define NVAPI_ID_INITIALIZE 0x0150e828
#define NVAPI_ID_D3D_SET_SLEEP_MODE 0xac1ca9e0
#define NVAPI_ID_D3D_SLEEP 0x852cd1d2
#define NVAPI_ID_D3D_SET_LATENCY_MARKER 0xd9984c05
#pragma pack(push, 8)
typedef struct
{
uint32_t version;
uint8_t low_latency_mode;
uint8_t low_latency_boost;
uint32_t minimum_interval_us;
uint8_t use_markers_to_optimize;
uint8_t use_min_queue_time;
uint8_t rsvd[30];
} nvapi_sleep_mode_params_t;
typedef struct
{
uint32_t version;
uint64_t frame_id;
uint32_t marker_type;
uint64_t rsvd0;
uint8_t rsvd[56];
} nvapi_latency_marker_params_t;
#pragma pack(pop)
#define NVAPI_SLEEP_MODE_PARAMS_VER \
((uint32_t)(sizeof(nvapi_sleep_mode_params_t) | (1u << 16)))
#define NVAPI_LATENCY_MARKER_PARAMS_VER \
((uint32_t)(sizeof(nvapi_latency_marker_params_t) | (1u << 16)))
typedef void (*nvapi_proc_t)(void);
typedef nvapi_proc_t (__cdecl *nvapi_query_interface_t)(unsigned int id);
typedef int (__cdecl *nvapi_initialize_t)(void);
typedef int (__cdecl *nvapi_set_sleep_mode_t)(void *device,
nvapi_sleep_mode_params_t *params);
typedef int (__cdecl *nvapi_sleep_t)(void *device);
typedef int (__cdecl *nvapi_set_latency_marker_t)(void *device,
nvapi_latency_marker_params_t *params);
static nvapi_set_sleep_mode_t nvapi_set_sleep_mode;
static nvapi_sleep_t nvapi_sleep;
static nvapi_set_latency_marker_t nvapi_set_latency_marker;
static bool nvapi_probed;
static bool d3d_driver_pacing_probe(void)
{
HMODULE dll;
nvapi_query_interface_t query;
nvapi_initialize_t init;
if (nvapi_probed)
return nvapi_set_sleep_mode != NULL;
nvapi_probed = true;
#ifdef _WIN64
dll = LoadLibraryA("nvapi64.dll");
#else
dll = LoadLibraryA("nvapi.dll");
#endif
if (!dll)
return false;
query = (nvapi_query_interface_t)GetProcAddress(dll, "nvapi_QueryInterface");
if (!query)
return false;
init = (nvapi_initialize_t)query(NVAPI_ID_INITIALIZE);
if (!init || init() != 0)
return false;
nvapi_set_sleep_mode = (nvapi_set_sleep_mode_t)
query(NVAPI_ID_D3D_SET_SLEEP_MODE);
nvapi_sleep = (nvapi_sleep_t)query(NVAPI_ID_D3D_SLEEP);
nvapi_set_latency_marker = (nvapi_set_latency_marker_t)
query(NVAPI_ID_D3D_SET_LATENCY_MARKER);
if (!nvapi_set_sleep_mode || !nvapi_sleep || !nvapi_set_latency_marker)
{
nvapi_set_sleep_mode = NULL;
return false;
}
RARCH_LOG("[D3D] Driver Pacing available through NVAPI.\n");
return true;
}
static bool d3d_driver_pacing_apply(d3d_driver_pacing_t *s, void *device,
unsigned mode, unsigned min_interval_us)
{
nvapi_sleep_mode_params_t params;
memset(&params, 0, sizeof(params));
params.version = NVAPI_SLEEP_MODE_PARAMS_VER;
params.low_latency_mode = mode ? 1 : 0;
params.low_latency_boost = (mode == VIDEO_DRIVER_PACING_BOOST) ? 1 : 0;
params.minimum_interval_us = mode ? min_interval_us : 0;
params.use_markers_to_optimize = mode ? 1 : 0;
if (nvapi_set_sleep_mode(device, &params) != 0)
{
s->failed = true;
s->mode = 0;
s->device = NULL;
retro_atomic_store_release_int(&s->mode_pub, 0);
return false;
}
s->mode = (uint8_t)mode;
s->min_interval_us = min_interval_us;
s->device = mode ? device : NULL;
retro_atomic_store_release_int(&s->mode_pub, (int)mode);
return true;
}
unsigned d3d_driver_pacing_set(d3d_driver_pacing_t *s, void *device,
unsigned mode)
{
if (!device)
return VIDEO_DRIVER_PACING_REASON_SWAPCHAIN;
if (s->device != device)
{
s->device = NULL;
s->mode = 0;
}
if (!mode)
{
if (s->mode && !d3d_driver_pacing_apply(s, device, 0, 0))
return VIDEO_DRIVER_PACING_REASON_FAILED;
/* Asked for again is the way back from a failure */
s->failed = false;
return VIDEO_DRIVER_PACING_REASON_OFF;
}
if (s->failed)
return VIDEO_DRIVER_PACING_REASON_FAILED;
if (!d3d_driver_pacing_probe())
return VIDEO_DRIVER_PACING_REASON_DEVICE;
if (mode == s->mode)
return VIDEO_DRIVER_PACING_REASON_NONE;
if (!d3d_driver_pacing_apply(s, device, mode, s->min_interval_us))
return VIDEO_DRIVER_PACING_REASON_FAILED;
return VIDEO_DRIVER_PACING_REASON_NONE;
}
bool d3d_driver_pacing_wait(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned max_wait_us)
{
/* NvAPI_D3D_Sleep holds the thread itself, for as long as the
* driver's own schedule says and no longer; it takes no bound and
* names no frame. */
(void)frame_id;
(void)max_wait_us;
if (!s->mode || s->failed || s->device != device)
return false;
if (nvapi_sleep(device) != 0)
{
s->failed = true;
return false;
}
return true;
}
static void d3d_driver_pacing_marker(void *device, uint64_t frame_id,
unsigned marker)
{
nvapi_latency_marker_params_t params;
memset(&params, 0, sizeof(params));
params.version = NVAPI_LATENCY_MARKER_PARAMS_VER;
params.frame_id = frame_id;
params.marker_type = marker;
nvapi_set_latency_marker(device, &params);
}
void d3d_driver_pacing_mark(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned marker)
{
if (!s->mode || s->failed || s->device != device || !frame_id)
return;
/* NV_LATENCY_MARKER_TYPE matches VkLatencyMarkerNV through
* INPUT_SAMPLE, which is as far as the pokes go */
d3d_driver_pacing_marker(device, frame_id, marker);
}
/* The presenter's view of the mode: what the frame loop last put on
* this device. */
static bool d3d_driver_pacing_presenting(d3d_driver_pacing_t *s,
void *device)
{
return retro_atomic_load_acquire_int(&s->mode_pub) != 0
&& s->frame_id != 0 && device != NULL;
}
void d3d_driver_pacing_frame_begin(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned min_interval_us)
{
if (!retro_atomic_load_acquire_int(&s->mode_pub) || !device)
return;
if (!frame_id || frame_id == s->frame_id)
return;
/* The interval is a sleep-mode parameter: set from here, where
* the frame that wants it arrives, which NVAPI allows from any
* thread. */
if (min_interval_us != s->min_interval_us)
{
nvapi_sleep_mode_params_t params;
memset(&params, 0, sizeof(params));
params.version = NVAPI_SLEEP_MODE_PARAMS_VER;
params.low_latency_mode = 1;
params.low_latency_boost =
(retro_atomic_load_acquire_int(&s->mode_pub) == VIDEO_DRIVER_PACING_BOOST) ? 1 : 0;
params.minimum_interval_us = min_interval_us;
params.use_markers_to_optimize = 1;
if (nvapi_set_sleep_mode(device, &params) == 0)
s->min_interval_us = min_interval_us;
}
s->frame_id = frame_id;
s->stage = 1;
d3d_driver_pacing_marker(device, frame_id, 1); /* SIMULATION_END */
d3d_driver_pacing_marker(device, frame_id, 2); /* RENDERSUBMIT_START */
}
void d3d_driver_pacing_present_begin(d3d_driver_pacing_t *s, void *device)
{
if (!d3d_driver_pacing_presenting(s, device))
return;
if (s->stage == 1)
{
d3d_driver_pacing_marker(device, s->frame_id, 3); /* RENDERSUBMIT_END */
d3d_driver_pacing_marker(device, s->frame_id, 4); /* PRESENT_START */
}
else if (s->stage == 2)
d3d_driver_pacing_marker(device, s->frame_id, 11); /* OUT_OF_BAND_PRESENT_START */
}
void d3d_driver_pacing_present_end(d3d_driver_pacing_t *s, void *device)
{
if (!d3d_driver_pacing_presenting(s, device))
return;
if (s->stage == 1)
{
s->stage = 2;
d3d_driver_pacing_marker(device, s->frame_id, 5); /* PRESENT_END */
}
else if (s->stage == 2)
d3d_driver_pacing_marker(device, s->frame_id, 12); /* OUT_OF_BAND_PRESENT_END */
}
bool d3d_driver_pacing_paced(d3d_driver_pacing_t *s, uint64_t frame_id)
{
return frame_id != 0
&& retro_atomic_load_acquire_int(&s->mode_pub) != 0;
}
void d3d_driver_pacing_free(d3d_driver_pacing_t *s, void *device)
{
if (s->mode && !s->failed && s->device == device && device)
d3d_driver_pacing_apply(s, device, 0, 0);
s->mode = 0;
s->device = NULL;
retro_atomic_store_release_int(&s->mode_pub, 0);
}
#else
unsigned d3d_driver_pacing_set(d3d_driver_pacing_t *s, void *device,
unsigned mode)
{
(void)s; (void)device; (void)mode;
return VIDEO_DRIVER_PACING_REASON_DEVICE;
}
bool d3d_driver_pacing_wait(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned max_wait_us)
{
(void)s; (void)device; (void)frame_id; (void)max_wait_us;
return false;
}
void d3d_driver_pacing_mark(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned marker)
{
(void)s; (void)device; (void)frame_id; (void)marker;
}
void d3d_driver_pacing_frame_begin(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned min_interval_us)
{
(void)s; (void)device; (void)frame_id; (void)min_interval_us;
}
void d3d_driver_pacing_present_begin(d3d_driver_pacing_t *s, void *device)
{
(void)s; (void)device;
}
void d3d_driver_pacing_present_end(d3d_driver_pacing_t *s, void *device)
{
(void)s; (void)device;
}
bool d3d_driver_pacing_paced(d3d_driver_pacing_t *s, uint64_t frame_id)
{
(void)s; (void)frame_id;
return false;
}
void d3d_driver_pacing_free(d3d_driver_pacing_t *s, void *device)
{
(void)s; (void)device;
}
#endif
+1 -43
View File
@@ -38,6 +38,7 @@
#include "../font_driver.h"
#include "../../retroarch.h"
#include "d3d_driver_pacing.h"
RETRO_BEGIN_DECLS
@@ -67,49 +68,6 @@ typedef struct
int32_t d3d_translate_filter(unsigned type);
/* Driver Pacing on the Direct3D drivers, through NVAPI's Reflex entry
* points (NvAPI_D3D_SetSleepMode / NvAPI_D3D_Sleep /
* NvAPI_D3D_SetLatencyMarker), which take any Direct3D 9 to 12
* device. nvapi is resolved at runtime by its published interface ids
* and never linked; a machine without it reports the device reason.
* One of these per driver instance, zeroed with it; the mode is a
* property of @device, the device it was set on. The functions follow
* the driver_pacing_* pokes (gfx/video_driver.h): set, wait and mark
* are the frame loop's and touch only the mode half, which the
* presenter's frame reads through @mode_pub; frame_begin and the
* present pair are the presenter's and own the identity half. NVAPI
* takes all of them from any thread. */
typedef struct d3d_driver_pacing
{
uint64_t frame_id;
void *device;
retro_atomic_int_t mode_pub; /* enum video_driver_pacing_mode on @device */
uint32_t min_interval_us;
uint8_t mode;
uint8_t stage; /* 1 rendering, 2 presented; later presents out of band */
bool failed;
} d3d_driver_pacing_t;
unsigned d3d_driver_pacing_set(d3d_driver_pacing_t *s, void *device,
unsigned mode);
bool d3d_driver_pacing_wait(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned max_wait_us);
void d3d_driver_pacing_mark(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned marker);
/* The driver's frame begins with the frame's identity and interval
* from its info: once per identity, the simulation-end and render-
* submit markers. Around every present: the frame's own gets its
* markers, any other is out of band on the same identity. */
void d3d_driver_pacing_frame_begin(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned min_interval_us);
void d3d_driver_pacing_present_begin(d3d_driver_pacing_t *s, void *device);
void d3d_driver_pacing_present_end(d3d_driver_pacing_t *s, void *device);
/* The presenter's view: whether the frame with @frame_id from its info
* was released by the driver's sleep, the mode on and the frame named. */
bool d3d_driver_pacing_paced(d3d_driver_pacing_t *s, uint64_t frame_id);
/* The driver goes: the mode is taken off its device. */
void d3d_driver_pacing_free(d3d_driver_pacing_t *s, void *device);
RETRO_END_DECLS
#endif
+351
View File
@@ -0,0 +1,351 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2026 - Libretro team
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include "../../verbosity.h"
#include "../video_driver.h"
#include "d3d_driver_pacing.h"
#if defined(_WIN32) && !defined(__WINRT__) && !defined(_XBOX)
#include <windows.h>
#endif
#if defined(_WIN32) && !defined(__WINRT__) && !defined(_XBOX)
/* NVAPI, by its published interface ids (nvapi_interface.h, MIT):
* resolved once for the process through nvapi_QueryInterface, never
* linked, so a machine without the driver's nvapi loses nothing. The
* structs are the header's, declared here so no SDK is needed; their
* version word is sizeof | (revision << 16), as MAKE_NVAPI_VERSION
* makes it. */
#define NVAPI_ID_INITIALIZE 0x0150e828
#define NVAPI_ID_D3D_SET_SLEEP_MODE 0xac1ca9e0
#define NVAPI_ID_D3D_SLEEP 0x852cd1d2
#define NVAPI_ID_D3D_SET_LATENCY_MARKER 0xd9984c05
#pragma pack(push, 8)
typedef struct
{
uint32_t version;
uint8_t low_latency_mode;
uint8_t low_latency_boost;
uint32_t minimum_interval_us;
uint8_t use_markers_to_optimize;
uint8_t use_min_queue_time;
uint8_t rsvd[30];
} nvapi_sleep_mode_params_t;
typedef struct
{
uint32_t version;
uint64_t frame_id;
uint32_t marker_type;
uint64_t rsvd0;
uint8_t rsvd[56];
} nvapi_latency_marker_params_t;
#pragma pack(pop)
#define NVAPI_SLEEP_MODE_PARAMS_VER \
((uint32_t)(sizeof(nvapi_sleep_mode_params_t) | (1u << 16)))
#define NVAPI_LATENCY_MARKER_PARAMS_VER \
((uint32_t)(sizeof(nvapi_latency_marker_params_t) | (1u << 16)))
typedef void (*nvapi_proc_t)(void);
typedef nvapi_proc_t (__cdecl *nvapi_query_interface_t)(unsigned int id);
typedef int (__cdecl *nvapi_initialize_t)(void);
typedef int (__cdecl *nvapi_set_sleep_mode_t)(void *device,
nvapi_sleep_mode_params_t *params);
typedef int (__cdecl *nvapi_sleep_t)(void *device);
typedef int (__cdecl *nvapi_set_latency_marker_t)(void *device,
nvapi_latency_marker_params_t *params);
static nvapi_set_sleep_mode_t nvapi_set_sleep_mode;
static nvapi_sleep_t nvapi_sleep;
static nvapi_set_latency_marker_t nvapi_set_latency_marker;
static bool nvapi_probed;
static bool d3d_driver_pacing_probe(void)
{
HMODULE dll;
nvapi_query_interface_t query;
nvapi_initialize_t init;
if (nvapi_probed)
return nvapi_set_sleep_mode != NULL;
nvapi_probed = true;
#ifdef _WIN64
dll = LoadLibraryA("nvapi64.dll");
#else
dll = LoadLibraryA("nvapi.dll");
#endif
if (!dll)
return false;
query = (nvapi_query_interface_t)GetProcAddress(dll, "nvapi_QueryInterface");
if (!query)
return false;
init = (nvapi_initialize_t)query(NVAPI_ID_INITIALIZE);
if (!init || init() != 0)
return false;
nvapi_set_sleep_mode = (nvapi_set_sleep_mode_t)
query(NVAPI_ID_D3D_SET_SLEEP_MODE);
nvapi_sleep = (nvapi_sleep_t)query(NVAPI_ID_D3D_SLEEP);
nvapi_set_latency_marker = (nvapi_set_latency_marker_t)
query(NVAPI_ID_D3D_SET_LATENCY_MARKER);
if (!nvapi_set_sleep_mode || !nvapi_sleep || !nvapi_set_latency_marker)
{
nvapi_set_sleep_mode = NULL;
return false;
}
RARCH_LOG("[D3D] Driver Pacing available through NVAPI.\n");
return true;
}
static bool d3d_driver_pacing_apply(d3d_driver_pacing_t *s, void *device,
unsigned mode, unsigned min_interval_us)
{
nvapi_sleep_mode_params_t params;
memset(&params, 0, sizeof(params));
params.version = NVAPI_SLEEP_MODE_PARAMS_VER;
params.low_latency_mode = mode ? 1 : 0;
params.low_latency_boost = (mode == VIDEO_DRIVER_PACING_BOOST) ? 1 : 0;
params.minimum_interval_us = mode ? min_interval_us : 0;
params.use_markers_to_optimize = mode ? 1 : 0;
if (nvapi_set_sleep_mode(device, &params) != 0)
{
s->failed = true;
s->mode = 0;
s->device = NULL;
retro_atomic_store_release_int(&s->mode_pub, 0);
return false;
}
s->mode = (uint8_t)mode;
s->min_interval_us = min_interval_us;
s->device = mode ? device : NULL;
retro_atomic_store_release_int(&s->mode_pub, (int)mode);
return true;
}
unsigned d3d_driver_pacing_set(d3d_driver_pacing_t *s, void *device,
unsigned mode)
{
if (!device)
return VIDEO_DRIVER_PACING_REASON_SWAPCHAIN;
if (s->device != device)
{
s->device = NULL;
s->mode = 0;
}
if (!mode)
{
if (s->mode && !d3d_driver_pacing_apply(s, device, 0, 0))
return VIDEO_DRIVER_PACING_REASON_FAILED;
/* Asked for again is the way back from a failure */
s->failed = false;
return VIDEO_DRIVER_PACING_REASON_OFF;
}
if (s->failed)
return VIDEO_DRIVER_PACING_REASON_FAILED;
if (!d3d_driver_pacing_probe())
return VIDEO_DRIVER_PACING_REASON_DEVICE;
if (mode == s->mode)
return VIDEO_DRIVER_PACING_REASON_NONE;
if (!d3d_driver_pacing_apply(s, device, mode, s->min_interval_us))
return VIDEO_DRIVER_PACING_REASON_FAILED;
return VIDEO_DRIVER_PACING_REASON_NONE;
}
bool d3d_driver_pacing_wait(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned max_wait_us)
{
/* NvAPI_D3D_Sleep holds the thread itself, for as long as the
* driver's own schedule says and no longer; it takes no bound and
* names no frame. */
(void)frame_id;
(void)max_wait_us;
if (!s->mode || s->failed || s->device != device)
return false;
if (nvapi_sleep(device) != 0)
{
s->failed = true;
return false;
}
return true;
}
static void d3d_driver_pacing_marker(void *device, uint64_t frame_id,
unsigned marker)
{
nvapi_latency_marker_params_t params;
memset(&params, 0, sizeof(params));
params.version = NVAPI_LATENCY_MARKER_PARAMS_VER;
params.frame_id = frame_id;
params.marker_type = marker;
nvapi_set_latency_marker(device, &params);
}
void d3d_driver_pacing_mark(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned marker)
{
if (!s->mode || s->failed || s->device != device || !frame_id)
return;
/* NV_LATENCY_MARKER_TYPE matches VkLatencyMarkerNV through
* INPUT_SAMPLE, which is as far as the pokes go */
d3d_driver_pacing_marker(device, frame_id, marker);
}
/* The presenter's view of the mode: what the frame loop last put on
* this device. */
static bool d3d_driver_pacing_presenting(d3d_driver_pacing_t *s,
void *device)
{
return retro_atomic_load_acquire_int(&s->mode_pub) != 0
&& s->frame_id != 0 && device != NULL;
}
void d3d_driver_pacing_frame_begin(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned min_interval_us)
{
if (!retro_atomic_load_acquire_int(&s->mode_pub) || !device)
return;
if (!frame_id || frame_id == s->frame_id)
return;
/* The interval is a sleep-mode parameter: set from here, where
* the frame that wants it arrives, which NVAPI allows from any
* thread. */
if (min_interval_us != s->min_interval_us)
{
nvapi_sleep_mode_params_t params;
memset(&params, 0, sizeof(params));
params.version = NVAPI_SLEEP_MODE_PARAMS_VER;
params.low_latency_mode = 1;
params.low_latency_boost =
(retro_atomic_load_acquire_int(&s->mode_pub) == VIDEO_DRIVER_PACING_BOOST) ? 1 : 0;
params.minimum_interval_us = min_interval_us;
params.use_markers_to_optimize = 1;
if (nvapi_set_sleep_mode(device, &params) == 0)
s->min_interval_us = min_interval_us;
}
s->frame_id = frame_id;
s->stage = 1;
d3d_driver_pacing_marker(device, frame_id, 1); /* SIMULATION_END */
d3d_driver_pacing_marker(device, frame_id, 2); /* RENDERSUBMIT_START */
}
void d3d_driver_pacing_present_begin(d3d_driver_pacing_t *s, void *device)
{
if (!d3d_driver_pacing_presenting(s, device))
return;
if (s->stage == 1)
{
d3d_driver_pacing_marker(device, s->frame_id, 3); /* RENDERSUBMIT_END */
d3d_driver_pacing_marker(device, s->frame_id, 4); /* PRESENT_START */
}
else if (s->stage == 2)
d3d_driver_pacing_marker(device, s->frame_id, 11); /* OUT_OF_BAND_PRESENT_START */
}
void d3d_driver_pacing_present_end(d3d_driver_pacing_t *s, void *device)
{
if (!d3d_driver_pacing_presenting(s, device))
return;
if (s->stage == 1)
{
s->stage = 2;
d3d_driver_pacing_marker(device, s->frame_id, 5); /* PRESENT_END */
}
else if (s->stage == 2)
d3d_driver_pacing_marker(device, s->frame_id, 12); /* OUT_OF_BAND_PRESENT_END */
}
bool d3d_driver_pacing_paced(d3d_driver_pacing_t *s, uint64_t frame_id)
{
return frame_id != 0
&& retro_atomic_load_acquire_int(&s->mode_pub) != 0;
}
bool d3d_driver_pacing_sleep_on(void *device)
{
return device && nvapi_sleep && nvapi_sleep(device) == 0;
}
void d3d_driver_pacing_mark_on(void *device, uint64_t frame_id,
unsigned marker)
{
if (device && nvapi_set_latency_marker && frame_id)
d3d_driver_pacing_marker(device, frame_id, marker);
}
void d3d_driver_pacing_free(d3d_driver_pacing_t *s, void *device)
{
if (s->mode && !s->failed && s->device == device && device)
d3d_driver_pacing_apply(s, device, 0, 0);
s->mode = 0;
s->device = NULL;
retro_atomic_store_release_int(&s->mode_pub, 0);
}
#else
unsigned d3d_driver_pacing_set(d3d_driver_pacing_t *s, void *device,
unsigned mode)
{
(void)s; (void)device; (void)mode;
return VIDEO_DRIVER_PACING_REASON_DEVICE;
}
bool d3d_driver_pacing_wait(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned max_wait_us)
{
(void)s; (void)device; (void)frame_id; (void)max_wait_us;
return false;
}
void d3d_driver_pacing_mark(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned marker)
{
(void)s; (void)device; (void)frame_id; (void)marker;
}
void d3d_driver_pacing_frame_begin(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned min_interval_us)
{
(void)s; (void)device; (void)frame_id; (void)min_interval_us;
}
void d3d_driver_pacing_present_begin(d3d_driver_pacing_t *s, void *device)
{
(void)s; (void)device;
}
void d3d_driver_pacing_present_end(d3d_driver_pacing_t *s, void *device)
{
(void)s; (void)device;
}
bool d3d_driver_pacing_paced(d3d_driver_pacing_t *s, uint64_t frame_id)
{
(void)s; (void)frame_id;
return false;
}
bool d3d_driver_pacing_sleep_on(void *device)
{
(void)device;
return false;
}
void d3d_driver_pacing_mark_on(void *device, uint64_t frame_id,
unsigned marker)
{
(void)device; (void)frame_id; (void)marker;
}
void d3d_driver_pacing_free(d3d_driver_pacing_t *s, void *device)
{
(void)s; (void)device;
}
#endif
+79
View File
@@ -0,0 +1,79 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2026 - Libretro team
*
* RetroArch is free software: you can redistribute it and/or modify it under the terms
* of the GNU General Public License as published by the Free Software Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
* PURPOSE. See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef _D3D_DRIVER_PACING_H
#define _D3D_DRIVER_PACING_H
#include <stdint.h>
#include <boolean.h>
#include <retro_atomic.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
/* Driver Pacing on the Direct3D drivers, through NVAPI's Reflex entry
* points (NvAPI_D3D_SetSleepMode / NvAPI_D3D_Sleep /
* NvAPI_D3D_SetLatencyMarker), which take any Direct3D 9 to 12
* device: the Direct3D drivers' own, and the D3D12 device Vulkan's
* DXGI presentation presents with. nvapi is resolved at runtime by its
* published interface ids and never linked; a machine without it
* reports the device reason.
* One of these per driver instance, zeroed with it; the mode is a
* property of @device, the device it was set on. The functions follow
* the driver_pacing_* pokes (gfx/video_driver.h): set, wait and mark
* are the frame loop's and touch only the mode half, which the
* presenter's frame reads through @mode_pub; frame_begin and the
* present pair are the presenter's and own the identity half. NVAPI
* takes all of them from any thread. */
typedef struct d3d_driver_pacing
{
uint64_t frame_id;
void *device;
retro_atomic_int_t mode_pub; /* enum video_driver_pacing_mode on @device */
uint32_t min_interval_us;
uint8_t mode;
uint8_t stage; /* 1 rendering, 2 presented; later presents out of band */
bool failed;
} d3d_driver_pacing_t;
unsigned d3d_driver_pacing_set(d3d_driver_pacing_t *s, void *device,
unsigned mode);
bool d3d_driver_pacing_wait(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned max_wait_us);
void d3d_driver_pacing_mark(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned marker);
/* The driver's frame begins with the frame's identity and interval
* from its info: once per identity, the simulation-end and render-
* submit markers. Around every present: the frame's own gets its
* markers, any other is out of band on the same identity. */
void d3d_driver_pacing_frame_begin(d3d_driver_pacing_t *s, void *device,
uint64_t frame_id, unsigned min_interval_us);
void d3d_driver_pacing_present_begin(d3d_driver_pacing_t *s, void *device);
void d3d_driver_pacing_present_end(d3d_driver_pacing_t *s, void *device);
/* The presenter's view: whether the frame with @frame_id from its info
* was released by the driver's sleep, the mode on and the frame named. */
bool d3d_driver_pacing_paced(d3d_driver_pacing_t *s, uint64_t frame_id);
/* The frame loop's side where the presenter is another thread and
* owns @s: the sleep, and a marker, on the device the presenter last
* put the mode on and published. False when the sleep failed. */
bool d3d_driver_pacing_sleep_on(void *device);
void d3d_driver_pacing_mark_on(void *device, uint64_t frame_id,
unsigned marker);
/* The driver goes: the mode is taken off its device. */
void d3d_driver_pacing_free(d3d_driver_pacing_t *s, void *device);
RETRO_END_DECLS
#endif
+173 -9
View File
@@ -1073,6 +1073,9 @@ static const void *vulkan_anti_lag_chain(gfx_ctx_vulkan_data_t *vk,
const void *next);
static void vulkan_low_latency_device_init(gfx_ctx_vulkan_data_t *vk);
static void vulkan_low_latency_device_deinit(gfx_ctx_vulkan_data_t *vk);
#ifdef VULKAN_DXGI_PACING
static void vulkan_dxgi_pacing_deinit(gfx_ctx_vulkan_data_t *vk);
#endif
static void vulkan_low_latency_marker(gfx_ctx_vulkan_data_t *vk,
uint64_t frame_id, unsigned marker);
@@ -5023,6 +5026,9 @@ static void vulkan_low_latency_device_deinit(gfx_ctx_vulkan_data_t *vk)
{
vulkan_low_latency_t *ll = &vk->low_latency;
unsigned i;
#ifdef VULKAN_DXGI_PACING
vulkan_dxgi_pacing_deinit(vk);
#endif
/* The device is idle and the frame loop is this thread: whatever
* was kept for a marker in flight can go. */
for (i = 0; i < ll->num_retired; i++)
@@ -5200,20 +5206,121 @@ static unsigned vulkan_anti_lag_apply_want(gfx_ctx_vulkan_data_t *vk,
}
if (vk->swapchain == VK_NULL_HANDLE || vk->context.swapchain_external)
return VIDEO_DRIVER_PACING_REASON_SWAPCHAIN;
#if defined(_WIN32) && !defined(__WINRT__)
if (vk->dxgi_presenter)
return VIDEO_DRIVER_PACING_REASON_SWAPCHAIN;
#endif
ll->mode = (uint8_t)mode;
retro_atomic_store_release_int(&ll->amd_mode, (int)mode);
return VIDEO_DRIVER_PACING_REASON_NONE;
}
#ifdef VULKAN_DXGI_PACING
/* COM's first three slots, for the reference held on the device */
typedef struct
{
struct
{
void *query_interface;
unsigned long (__stdcall *add_ref)(void *self);
unsigned long (__stdcall *release)(void *self);
} *lpVtbl;
} vulkan_com_object_t;
/* The D3D12 device the DXGI presenter presents with, referenced until
* the context goes so a sleep on it never outlives it. NULL with no
* presenter, or with more devices than are kept. */
static void *vulkan_dxgi_pacing_device(gfx_ctx_vulkan_data_t *vk)
{
vulkan_low_latency_t *ll = &vk->low_latency;
void *device;
unsigned i;
if (!vk->dxgi_presenter)
return NULL;
if (!(device = ra_vulkan_dxgi_device(
(ra_vulkan_dxgi_t*)vk->dxgi_presenter)))
return NULL;
for (i = 0; i < ll->dxgi_num_held; i++)
if (ll->dxgi_held[i] == device)
return device;
if (ll->dxgi_num_held == ARRAY_SIZE(ll->dxgi_held))
return NULL;
((vulkan_com_object_t*)device)->lpVtbl->add_ref(device);
ll->dxgi_held[ll->dxgi_num_held++] = device;
return device;
}
/* The presenter's side of a mode request while presenting through
* DXGI: the mode goes on the presenting device through NVAPI, and the
* frame loop is shown that device while it is in effect. */
static unsigned vulkan_dxgi_pacing_apply_want(gfx_ctx_vulkan_data_t *vk,
unsigned mode)
{
vulkan_low_latency_t *ll = &vk->low_latency;
void *device = vulkan_dxgi_pacing_device(vk);
unsigned answer;
if (!device)
{
retro_atomic_store_release_size(&ll->dxgi_pub, 0);
return mode ? VIDEO_DRIVER_PACING_REASON_SWAPCHAIN
: VIDEO_DRIVER_PACING_REASON_OFF;
}
if (!mode)
retro_atomic_store_release_int(&ll->stalled, 0);
else if (retro_atomic_load_acquire_int(&ll->stalled))
{
/* The frame loop's sleep failed: off until asked for anew */
retro_atomic_store_release_size(&ll->dxgi_pub, 0);
d3d_driver_pacing_set(&ll->dxgi, device, 0);
return VIDEO_DRIVER_PACING_REASON_FAILED;
}
answer = d3d_driver_pacing_set(&ll->dxgi, device, mode);
/* No NVAPI (Anti-Lag, say), where Vulkan's own present would have
* a backend: the present is what is in the way */
if ( answer == VIDEO_DRIVER_PACING_REASON_DEVICE
&& (ll->supported || ll->anti_lag))
answer = VIDEO_DRIVER_PACING_REASON_SWAPCHAIN;
retro_atomic_store_release_size(&ll->dxgi_pub,
(mode && answer == VIDEO_DRIVER_PACING_REASON_NONE)
? (size_t)device : 0);
return answer;
}
/* The device the presenter put the mode on, when it presents there */
static void *vulkan_dxgi_pacing_on(gfx_ctx_vulkan_data_t *vk)
{
vulkan_low_latency_t *ll = &vk->low_latency;
return (vk->dxgi_presenter && ll->dxgi.mode && !ll->dxgi.failed)
? ll->dxgi.device : NULL;
}
/* The context goes: the mode comes off and the devices are let go */
static void vulkan_dxgi_pacing_deinit(gfx_ctx_vulkan_data_t *vk)
{
vulkan_low_latency_t *ll = &vk->low_latency;
unsigned i;
retro_atomic_store_release_size(&ll->dxgi_pub, 0);
if (ll->dxgi.device)
d3d_driver_pacing_free(&ll->dxgi, ll->dxgi.device);
for (i = 0; i < ll->dxgi_num_held; i++)
((vulkan_com_object_t*)ll->dxgi_held[i])->lpVtbl->release(
ll->dxgi_held[i]);
ll->dxgi_num_held = 0;
memset(&ll->dxgi, 0, sizeof(ll->dxgi));
}
#endif
/* The presenter's side of a mode request. */
static unsigned vulkan_low_latency_apply_want(gfx_ctx_vulkan_data_t *vk,
unsigned mode)
{
vulkan_low_latency_t *ll = &vk->low_latency;
#ifdef VULKAN_DXGI_PACING
if (vk->dxgi_presenter)
return vulkan_dxgi_pacing_apply_want(vk, mode);
/* Native again: the mode left on the DXGI presenter's device goes */
if (ll->dxgi.mode && ll->dxgi.device)
{
retro_atomic_store_release_size(&ll->dxgi_pub, 0);
d3d_driver_pacing_set(&ll->dxgi, ll->dxgi.device, 0);
}
#endif
if (!ll->supported && ll->anti_lag)
return vulkan_anti_lag_apply_want(vk, mode);
if (!ll->supported)
@@ -5244,11 +5351,6 @@ static unsigned vulkan_low_latency_apply_want(gfx_ctx_vulkan_data_t *vk,
}
if (vk->swapchain == VK_NULL_HANDLE || vk->context.swapchain_external)
return VIDEO_DRIVER_PACING_REASON_SWAPCHAIN;
#if defined(_WIN32) && !defined(__WINRT__)
/* Presented through DXGI: vkQueuePresentKHR never runs. */
if (vk->dxgi_presenter)
return VIDEO_DRIVER_PACING_REASON_SWAPCHAIN;
#endif
if (mode == ll->mode)
return VIDEO_DRIVER_PACING_REASON_NONE;
if (!vulkan_low_latency_apply(vk, mode, ll->min_interval_us))
@@ -5293,6 +5395,17 @@ void vulkan_low_latency_frame_begin(gfx_ctx_vulkan_data_t *vk,
retro_atomic_store_release_int(&ll->answer,
(int)vulkan_low_latency_apply_want(vk, want));
}
#ifdef VULKAN_DXGI_PACING
{
void *device = vulkan_dxgi_pacing_on(vk);
if (device)
{
d3d_driver_pacing_frame_begin(&ll->dxgi, device, frame_id,
min_interval_us);
return;
}
}
#endif
if (!ll->supported)
{
/* The frame the PRESENT update names, and the frame limit the
@@ -5323,6 +5436,15 @@ void vulkan_low_latency_frame_begin(gfx_ctx_vulkan_data_t *vk,
void vulkan_low_latency_present_begin(gfx_ctx_vulkan_data_t *vk)
{
vulkan_low_latency_t *ll = &vk->low_latency;
#ifdef VULKAN_DXGI_PACING
if (vk->dxgi_presenter)
{
void *device = vulkan_dxgi_pacing_on(vk);
if (device)
d3d_driver_pacing_present_begin(&ll->dxgi, device);
return;
}
#endif
/* Anti-Lag names the frame's own present only; it has no notion of
* the presents after it */
if (ll->anti_lag && !ll->supported)
@@ -5351,6 +5473,15 @@ void vulkan_low_latency_present_end(gfx_ctx_vulkan_data_t *vk)
{
vulkan_low_latency_t *ll = &vk->low_latency;
VkLatencySleepInfoNV sleep_info;
#ifdef VULKAN_DXGI_PACING
if (vk->dxgi_presenter)
{
void *device = vulkan_dxgi_pacing_on(vk);
if (device)
d3d_driver_pacing_present_end(&ll->dxgi, device);
return;
}
#endif
if (!ll->mode || ll->failed || ll->swapchain != vk->swapchain || !ll->frame_id)
return;
if (ll->stage == 2)
@@ -5388,6 +5519,25 @@ bool vulkan_low_latency_wait(gfx_ctx_vulkan_data_t *vk,
vulkan_low_latency_t *ll = &vk->low_latency;
VkSemaphoreWaitInfo wait_info;
VkResult res;
#ifdef VULKAN_DXGI_PACING
{
/* Through DXGI, NvAPI_D3D_Sleep is the hold, on the device the
* presenter shows */
void *device = (void*)retro_atomic_load_acquire_size(&ll->dxgi_pub);
if (device)
{
if (retro_atomic_load_acquire_int(&ll->answer)
!= VIDEO_DRIVER_PACING_REASON_NONE)
return false;
if (d3d_driver_pacing_sleep_on(device))
return true;
retro_atomic_store_release_int(&ll->stalled, 1);
retro_atomic_store_release_int(&ll->answer,
VIDEO_DRIVER_PACING_REASON_FAILED);
return false;
}
}
#endif
/* Anti-Lag's INPUT update is the hold, inside the driver, for the
* frame about to sample its input; the device-wide call is safe
* beside the presenter's on another thread. */
@@ -5458,6 +5608,16 @@ void vulkan_low_latency_mark_main(gfx_ctx_vulkan_data_t *vk,
VkSetLatencyMarkerInfoNV info;
VkSwapchainKHR swapchain;
size_t gen;
#ifdef VULKAN_DXGI_PACING
{
void *device = (void*)retro_atomic_load_acquire_size(&ll->dxgi_pub);
if (device)
{
d3d_driver_pacing_mark_on(device, frame_id, marker);
return;
}
}
#endif
if (!ll->supported || !frame_id)
return;
/* in_call before pub_gen, a full fence between, against the
@@ -5518,9 +5678,13 @@ void vulkan_present(gfx_ctx_vulkan_data_t *vk, unsigned index)
* resize, never returned. */
#if defined(_WIN32) && !defined(__WINRT__)
if (vk->dxgi_presenter)
{
vulkan_low_latency_present_begin(vk);
err = ra_vulkan_dxgi_present((ra_vulkan_dxgi_t *)vk->dxgi_presenter,
index, present.waitSemaphoreCount, present.pWaitSemaphores,
vk->context.swap_interval > 0 ? (unsigned)vk->context.swap_interval : 0);
vulkan_low_latency_present_end(vk);
}
else
#endif
{
+16
View File
@@ -456,6 +456,11 @@ typedef void (VKAPI_PTR *PFN_vkAntiLagUpdateAMD)(VkDevice device,
const VkAntiLagDataAMD *pData);
#endif
#if defined(_WIN32) && !defined(__WINRT__)
#include "d3d_driver_pacing.h"
#define VULKAN_DXGI_PACING
#endif
typedef struct vulkan_low_latency
{
uint64_t frame_id;
@@ -496,6 +501,17 @@ typedef struct vulkan_low_latency
* negotiation interface, or the core's own) */
bool core_device;
bool noted; /* this device's extensions were seen */
#ifdef VULKAN_DXGI_PACING
/* Presented through DXGI: Reflex through NVAPI on the D3D12 device
* that presents, the presenter's to set as the native path's mode
* is. @dxgi_pub is the device the frame loop sleeps and marks on
* while the mode is in effect there, 0 otherwise; every device it
* has been is referenced in @dxgi_held until the context goes. */
d3d_driver_pacing_t dxgi;
void *dxgi_held[4];
unsigned dxgi_num_held;
retro_atomic_size_t dxgi_pub;
#endif
} vulkan_low_latency_t;
typedef struct gfx_ctx_vulkan_data
+6
View File
@@ -21,6 +21,7 @@ void ra_dxgi_bridge_hdr_metadata(ra_dxgi_bridge_t *b, float peak) { }
unsigned ra_dxgi_bridge_index(ra_dxgi_bridge_t *b, unsigned interval)
{ return 2; }
int64_t ra_dxgi_bridge_present_time(ra_dxgi_bridge_t *b) { return 0; }
void *ra_dxgi_bridge_device(ra_dxgi_bridge_t *b) { return NULL; }
uint64_t ra_dxgi_bridge_last_use(ra_dxgi_bridge_t *b, unsigned index)
{ return 0; }
int ra_dxgi_bridge_present(ra_dxgi_bridge_t *b, uint64_t ready,
@@ -475,6 +476,11 @@ int ra_dxgi_bridge_present(ra_dxgi_bridge_t *b, uint64_t ready, unsigned interva
return SUCCEEDED(hr);
}
void *ra_dxgi_bridge_device(ra_dxgi_bridge_t *b)
{
return (b && !b->failed) ? (void *)b->device : NULL;
}
int64_t ra_dxgi_bridge_present_time(ra_dxgi_bridge_t *b)
{
DXGI_FRAME_STATISTICS stats;
+2
View File
@@ -16,6 +16,8 @@ int ra_dxgi_bridge_failed(ra_dxgi_bridge_t *bridge);
void ra_dxgi_bridge_hdr_metadata(ra_dxgi_bridge_t *bridge, float peak);
unsigned ra_dxgi_bridge_index(ra_dxgi_bridge_t *bridge, unsigned interval);
int64_t ra_dxgi_bridge_present_time(ra_dxgi_bridge_t *bridge);
/* The ID3D12Device the bridge presents with, for NVAPI */
void *ra_dxgi_bridge_device(ra_dxgi_bridge_t *bridge);
uint64_t ra_dxgi_bridge_last_use(ra_dxgi_bridge_t *bridge, unsigned index);
int ra_dxgi_bridge_present(ra_dxgi_bridge_t *bridge, uint64_t ready,
unsigned interval);
+5
View File
@@ -382,6 +382,11 @@ void ra_vulkan_dxgi_hdr_metadata(ra_vulkan_dxgi_t *p, float peak)
ra_dxgi_bridge_hdr_metadata(p->bridge, peak);
}
void *ra_vulkan_dxgi_device(ra_vulkan_dxgi_t *p)
{
return p ? ra_dxgi_bridge_device(p->bridge) : NULL;
}
int64_t ra_vulkan_dxgi_present_time(ra_vulkan_dxgi_t *p)
{
return p ? ra_dxgi_bridge_present_time(p->bridge) : 0;
+2
View File
@@ -14,6 +14,8 @@ int ra_vulkan_dxgi_presentable(ra_vulkan_dxgi_t *presenter);
void ra_vulkan_dxgi_destroy(ra_vulkan_dxgi_t *presenter);
void ra_vulkan_dxgi_hdr_metadata(ra_vulkan_dxgi_t *presenter, float peak);
int64_t ra_vulkan_dxgi_present_time(ra_vulkan_dxgi_t *presenter);
/* The ID3D12Device the presenter presents with, for NVAPI */
void *ra_vulkan_dxgi_device(ra_vulkan_dxgi_t *presenter);
VkImage ra_vulkan_dxgi_image(ra_vulkan_dxgi_t *presenter, unsigned index);
VkResult ra_vulkan_dxgi_acquire(ra_vulkan_dxgi_t *presenter,
VkSemaphore semaphore, VkFence fence, unsigned *index, unsigned interval);
+3
View File
@@ -615,6 +615,9 @@ VIDEO IMAGE
/*============================================================
VIDEO DRIVER
============================================================ */
#if defined(_WIN32) && (defined(HAVE_D3D) || defined(HAVE_VULKAN))
#include "../gfx/common/d3d_driver_pacing.c"
#endif
#if defined(HAVE_D3D)
#include "../gfx/common/d3d_common.c"
+23 -6
View File
@@ -22,13 +22,27 @@ SOURCES := vulkan_driver_pacing_test.c \
$(LIBRETRO_COMM_DIR)/string/stdstring.c \
$(LIBRETRO_COMM_DIR)/encodings/encoding_utf.c
# Windows (mingw-w64, run under Wine): Vulkan presenting through DXGI
# paces through NVAPI, against a stand-in nvapi64.dll beside the test
ifneq ($(findstring mingw,$(CC)),)
EXE := .exe
SOURCES += $(REPO_ROOT)/gfx/common/d3d_driver_pacing.c \
$(LIBRETRO_COMM_DIR)/time/rtime.c
NVAPI := nvapi64.dll
LDLIBS := -lm
else
EXE :=
NVAPI :=
LDLIBS := -lpthread -ldl -lm
endif
CFLAGS += -Wall -std=gnu99 -g1 -O0 \
-DHAVE_VULKAN -DHAVE_THREADS -DHAVE_DYLIB \
-I$(REPO_ROOT) \
-I$(REPO_ROOT)/gfx/include \
-I$(LIBRETRO_COMM_DIR)/include
LDFLAGS += -lpthread -ldl -lm
LDFLAGS += $(LDLIBS)
OBJS := $(call sample_objs,$(SOURCES:.c=.o))
@@ -37,7 +51,10 @@ ifneq ($(SANITIZER),)
LDFLAGS := -fsanitize=$(SANITIZER) $(LDFLAGS)
endif
all: $(TARGET)
all: $(TARGET)$(EXE) $(NVAPI)
nvapi64.dll: fake_nvapi.c
$(CC) -shared -O1 -o $@ $<
%.o: %.c
$(CC) -c -o $@ $< $(CFLAGS)
@@ -46,14 +63,14 @@ $(SAMPLE_OBJDIR)/%.o: $$(call sample_src,$$*).c
@mkdir -p $(@D)
$(CC) -c -o $@ $< $(CFLAGS)
$(TARGET): $(OBJS)
$(TARGET)$(EXE): $(OBJS)
$(CC) -o $@ $^ $(LDFLAGS)
check: $(TARGET)
./$(TARGET)
check: all
$(RUNNER) ./$(TARGET)$(EXE)
clean:
rm -f $(TARGET) vulkan_driver_pacing_test.o
rm -f $(TARGET) $(TARGET).exe nvapi64.dll vulkan_driver_pacing_test.o
rm -rf $(SAMPLE_OBJDIR)
.PHONY: all check clean
@@ -0,0 +1,116 @@
/* A stand-in nvapi64.dll for the Windows run: nvapi_QueryInterface
* answers the four Reflex entry points gfx/common/d3d_driver_pacing.c
* resolves, and every call is logged for the test to read back
* through fake_nvapi_log(). */
#include <stdint.h>
#include <string.h>
#include <windows.h>
#define FAKE_NVAPI_MAX 256
#pragma pack(push, 8)
typedef struct
{
uint32_t version;
uint8_t low_latency_mode;
uint8_t low_latency_boost;
uint32_t minimum_interval_us;
uint8_t use_markers_to_optimize;
uint8_t use_min_queue_time;
uint8_t rsvd[30];
} sleep_mode_params_t;
typedef struct
{
uint32_t version;
uint64_t frame_id;
uint32_t marker_type;
uint64_t rsvd0;
uint8_t rsvd[56];
} marker_params_t;
#pragma pack(pop)
/* kind 0 the mode (a = mode | boost << 1, b = interval), 1 a sleep,
* 2 a marker (a = frame, b = marker) */
typedef struct
{
int kind;
void *device;
uint64_t a;
unsigned b;
} fake_nvapi_event_t;
static fake_nvapi_event_t events[FAKE_NVAPI_MAX];
static unsigned num_events;
static int sleep_fails;
static void ev(int kind, void *device, uint64_t a, unsigned b)
{
if (num_events < FAKE_NVAPI_MAX)
{
events[num_events].kind = kind;
events[num_events].device = device;
events[num_events].a = a;
events[num_events].b = b;
}
num_events++;
}
static int __cdecl fake_initialize(void) { return 0; }
static int __cdecl fake_set_sleep_mode(void *device,
sleep_mode_params_t *p)
{
if (!device || !p || (p->version >> 16) != 1
|| (p->version & 0xffff) != sizeof(*p))
return -5;
ev(0, device, p->low_latency_mode | (p->low_latency_boost << 1),
p->minimum_interval_us);
return 0;
}
static int __cdecl fake_sleep(void *device)
{
if (!device)
return -5;
ev(1, device, 0, 0);
return sleep_fails ? -1 : 0;
}
static int __cdecl fake_set_latency_marker(void *device, marker_params_t *p)
{
if (!device || !p || (p->version >> 16) != 1
|| (p->version & 0xffff) != sizeof(*p))
return -5;
ev(2, device, p->frame_id, p->marker_type);
return 0;
}
__declspec(dllexport) void *__cdecl nvapi_QueryInterface(unsigned int id)
{
switch (id)
{
case 0x0150e828: return (void*)fake_initialize;
case 0xac1ca9e0: return (void*)fake_set_sleep_mode;
case 0x852cd1d2: return (void*)fake_sleep;
case 0xd9984c05: return (void*)fake_set_latency_marker;
}
return NULL;
}
/* What was called since the last reset */
__declspec(dllexport) unsigned fake_nvapi_log(const fake_nvapi_event_t **out,
int reset)
{
unsigned n = num_events;
*out = events;
if (reset)
num_events = 0;
return n;
}
/* Sleeps from now on fail, or succeed again */
__declspec(dllexport) void fake_nvapi_fail_sleep(int fail)
{
sleep_fails = fail;
}
@@ -573,6 +573,196 @@ static void test_fallback(void)
"the runloop paces as before, off and on tries anew\n");
}
#ifdef VULKAN_DXGI_PACING
/* --- presented through DXGI: Reflex through NVAPI ------------------ */
#include "../../../gfx/common/win_vulkan_dxgi.h"
/* The presenter's D3D12 device, as far as COM's references go */
typedef struct fake_com fake_com_t;
struct fake_com_vtbl
{
void *query_interface;
unsigned long (__stdcall *add_ref)(fake_com_t *self);
unsigned long (__stdcall *release)(fake_com_t *self);
};
struct fake_com
{
const struct fake_com_vtbl *lpVtbl;
unsigned long refs;
};
static unsigned long __stdcall fake_add_ref(fake_com_t *self)
{ return ++self->refs; }
static unsigned long __stdcall fake_release(fake_com_t *self)
{ return --self->refs; }
static const struct fake_com_vtbl fake_com_vtbl = { NULL, fake_add_ref,
fake_release };
static fake_com_t fake_d3d12 = { &fake_com_vtbl, 1 };
/* The presenter itself is never driven here: only its device is */
ra_vulkan_dxgi_t *ra_vulkan_dxgi_create(VkInstance instance,
VkPhysicalDevice gpu, VkDevice device, VkQueue queue, unsigned family,
HWND window, unsigned width, unsigned height, VkFormat format,
void *queue_lock) { return NULL; }
int ra_vulkan_dxgi_resize(ra_vulkan_dxgi_t *p, unsigned width,
unsigned height, VkFormat format) { return 0; }
int ra_vulkan_dxgi_presentable(ra_vulkan_dxgi_t *p) { return 0; }
void ra_vulkan_dxgi_destroy(ra_vulkan_dxgi_t *p) { }
void ra_vulkan_dxgi_hdr_metadata(ra_vulkan_dxgi_t *p, float peak) { }
int64_t ra_vulkan_dxgi_present_time(ra_vulkan_dxgi_t *p) { return 0; }
VkImage ra_vulkan_dxgi_image(ra_vulkan_dxgi_t *p, unsigned index)
{ return VK_NULL_HANDLE; }
VkResult ra_vulkan_dxgi_acquire(ra_vulkan_dxgi_t *p, VkSemaphore semaphore,
VkFence fence, unsigned *index, unsigned interval)
{ return VK_ERROR_OUT_OF_DATE_KHR; }
VkResult ra_vulkan_dxgi_present(ra_vulkan_dxgi_t *p, unsigned index,
unsigned count, const VkSemaphore *wait, unsigned interval)
{ return VK_SUCCESS; }
void *ra_vulkan_dxgi_device(ra_vulkan_dxgi_t *p)
{ return p ? (void*)&fake_d3d12 : NULL; }
/* fake_nvapi.c's log, read through the DLL the backend loaded */
typedef struct
{
int kind;
void *device;
uint64_t a;
unsigned b;
} nv_event_t;
enum { NV_MODE, NV_SLEEP, NV_MARK };
typedef unsigned (*nv_log_t)(const nv_event_t **out, int reset);
typedef void (*nv_fail_t)(int fail);
static nv_log_t nv_log;
static nv_fail_t nv_fail;
static unsigned nv_count(int kind, const nv_event_t **last)
{
const nv_event_t *e;
unsigned i, n = nv_log(&e, 0), c = 0;
*last = NULL;
for (i = 0; i < n; i++)
if (e[i].kind == kind)
{
c++;
*last = &e[i];
}
return c;
}
static void nv_reset(void)
{
const nv_event_t *e;
nv_log(&e, 1);
}
static void test_dxgi(void)
{
const nv_event_t *e;
HMODULE dll = LoadLibraryA("nvapi64.dll");
void *presenter = (void*)(uintptr_t)0x5;
check(dll != NULL, "dxgi: the stand-in nvapi64.dll loads");
if (!dll)
return;
nv_log = (nv_log_t)GetProcAddress(dll, "fake_nvapi_log");
nv_fail = (nv_fail_t)GetProcAddress(dll, "fake_nvapi_fail_sleep");
if (!nv_log || !nv_fail)
return;
/* Vulkan's own backend is up, but nothing reaches it: the present
* is DXGI's, and the mode goes on its D3D12 device */
reset(true, 0x100);
vk.dxgi_presenter = presenter;
nv_reset();
check(vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_ON)
== VIDEO_DRIVER_PACING_REASON_NONE, "dxgi: in effect");
check(nv_count(NV_MODE, &e) == 1 && e->device == &fake_d3d12
&& e->a == 1, "dxgi: the mode on the presenter's D3D12 device");
check(fake_d3d12.refs == 2, "dxgi: the device held while it is used");
check(vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_BOOST)
== VIDEO_DRIVER_PACING_REASON_NONE
&& nv_count(NV_MODE, &e) == 2 && e->a == 3,
"dxgi: boost");
check(fake_d3d12.refs == 2, "dxgi: held once");
/* A frame: the presenter's markers on the device, the frame loop's
* sleep and input marker on it too */
nv_reset();
present_frame(1, 16667);
check(nv_count(NV_MODE, &e) == 1 && e->b == 16667,
"dxgi: the frame's interval goes to the sleep mode");
check(nv_count(NV_MARK, &e) == 5 && e->a == 1 && e->b == 5,
"dxgi: simulation end, submit and present markers on frame 1");
check(vulkan_low_latency_wait(&vk, 1, 2, 20000)
&& nv_count(NV_SLEEP, &e) == 1 && e->device == &fake_d3d12,
"dxgi: the release is NvAPI_D3D_Sleep on the device");
vulkan_low_latency_mark_main(&vk, 2,
VIDEO_DRIVER_PACING_MARK_INPUT_SAMPLE);
check(nv_count(NV_MARK, &e) == 6 && e->a == 2 && e->b == 6,
"dxgi: the frame loop's input marker");
check(count(EV_MODE) == 0 && count(EV_SLEEP) == 0
&& count(EV_MARK) == 0 && count(EV_WAIT) == 0,
"dxgi: nothing of VK_NV_low_latency2 is called");
/* A sleep NVAPI refuses: the frame loop falls back, the mode comes
* off at the presenter, and asking anew brings it back */
nv_fail(1);
check(!vulkan_low_latency_wait(&vk, 2, 3, 20000),
"dxgi: a failed sleep releases nothing");
nv_fail(0);
nv_reset();
check(vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_BOOST)
== VIDEO_DRIVER_PACING_REASON_FAILED
&& nv_count(NV_MODE, &e) == 1 && e->a == 0,
"dxgi: failed, and the mode taken off the device");
check(!vulkan_low_latency_wait(&vk, 2, 3, 20000)
&& nv_count(NV_SLEEP, &e) == 0, "dxgi: failed: no sleep");
check(vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_OFF)
== VIDEO_DRIVER_PACING_REASON_OFF
&& vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_ON)
== VIDEO_DRIVER_PACING_REASON_NONE,
"dxgi: off and on again: in effect");
/* Off: taken off, the frame loop not held */
nv_reset();
check(vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_OFF)
== VIDEO_DRIVER_PACING_REASON_OFF
&& nv_count(NV_MODE, &e) == 1 && e->a == 0,
"dxgi: off: the mode taken off the device");
check(!vulkan_low_latency_wait(&vk, 2, 3, 20000)
&& nv_count(NV_SLEEP, &e) == 0, "dxgi: off: no sleep");
/* Threaded: the presenter answers the frame loop's request */
threaded = true;
nv_reset();
check(vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_ON)
== VIDEO_DRIVER_PACING_REASON_PENDING && nv_count(NV_MODE, &e) == 0,
"dxgi threaded: pending until the presenter's frame");
present_frame(3, 0);
check(nv_count(NV_MODE, &e) >= 1 && e->a == 1,
"dxgi threaded: the presenter puts the mode on");
check(vulkan_low_latency_set(&vk, VIDEO_DRIVER_PACING_ON)
== VIDEO_DRIVER_PACING_REASON_NONE
&& vulkan_low_latency_wait(&vk, 3, 4, 20000)
&& nv_count(NV_SLEEP, &e) == 1,
"dxgi threaded: in effect, the frame loop sleeps on the device");
/* Native again: the device's mode comes off, Vulkan's own goes on */
vk.dxgi_presenter = NULL;
nv_reset();
num_events = 0;
present_frame(4, 0);
check(nv_count(NV_MODE, &e) == 1 && e->a == 0,
"dxgi gone: the mode taken off the D3D12 device");
check(count(EV_MODE) == 1 && last(EV_MODE)->b == 1,
"dxgi gone: VK_NV_low_latency2's mode on the swapchain");
threaded = false;
printf(" dxgi: through DXGI the mode, sleep and markers are NVAPI's "
"on the presenter's D3D12 device, held while used, and fall "
"back like the native path's\n");
}
#endif
int main(void)
{
printf("vulkan driver pacing backend:\n");
@@ -584,6 +774,9 @@ int main(void)
test_threaded();
test_anti_lag();
test_fallback();
#ifdef VULKAN_DXGI_PACING
test_dxgi();
#endif
if (failures)
{
printf("FAILED: %u check(s)\n", failures);
+1 -1
View File
@@ -106,4 +106,4 @@ S_UINT(video_driver_pacing, VIDEO_DRIVER_PACING,
"video_driver_pacing",
DEFAULT_VIDEO_DRIVER_PACING, SD_FLAG_NONE, SDESC_RANGE_MINMAX, CMD_EVENT_NONE, 0, 2, 1, 0, setting_action_ok_uint, setting_get_string_representation_uint_video_driver_pacing,
"Driver Pacing",
"Let the graphics driver schedule core frames, sampling input late and limiting queued work. Uses NVIDIA Reflex (VK_NV_low_latency2 on Vulkan, NVAPI on Direct3D 11/12) or AMD Anti-Lag (VK_AMD_anti_lag on Vulkan). Statistics and System Information report its status. 'On + Boost' holds GPU clocks up. Unavailable with Scanline Sync. With Threaded Video Display Pacing, the driver releases the frames at the content's rate. Frame Delay stands aside while active.")
"Let the graphics driver schedule core frames, sampling input late and limiting queued work. Uses NVIDIA Reflex (VK_NV_low_latency2 on Vulkan, NVAPI on Direct3D 11/12 and on Vulkan presenting through DXGI) or AMD Anti-Lag (VK_AMD_anti_lag on Vulkan). Statistics and System Information report its status. 'On + Boost' holds GPU clocks up. Unavailable with Scanline Sync. With Threaded Video Display Pacing, the driver releases the frames at the content's rate. Frame Delay stands aside while active.")