mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 02:08:22 -10:00
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:
1 parent
9a4f3815bd
commit
7234101942
17 files changed
+998
-371
No files matched your search
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(¶ms, 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, ¶ms) != 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(¶ms, 0, sizeof(params));
|
||||
params.version = NVAPI_LATENCY_MARKER_PARAMS_VER;
|
||||
params.frame_id = frame_id;
|
||||
params.marker_type = marker;
|
||||
nvapi_set_latency_marker(device, ¶ms);
|
||||
}
|
||||
|
||||
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(¶ms, 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, ¶ms) == 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
@@ -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
|
||||
@@ -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(¶ms, 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, ¶ms) != 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(¶ms, 0, sizeof(params));
|
||||
params.version = NVAPI_LATENCY_MARKER_PARAMS_VER;
|
||||
params.frame_id = frame_id;
|
||||
params.marker_type = marker;
|
||||
nvapi_set_latency_marker(device, ¶ms);
|
||||
}
|
||||
|
||||
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(¶ms, 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, ¶ms) == 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
|
||||
@@ -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
@@ -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
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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.")
|
||||
Reference in new issue
Block a user