mirror of
https://github.com/libretro/RetroArch.git
synced 2026-11-08 17:28:49 -10:00
dxgi: report presentation backpressure before running the core
An unthreaded core with vsync and audio sync disabled can submit frames faster than the shared presentation target is consumed. The bridge reported itself presentable while its current target was still pending, allowing seconds of GPU queue latency to accumulate. Report a busy target through presentable() so the runloop's existing no-surface pacing defers the next core frame. Query the completed fence value without waiting or consuming a frame-latency token. Preserve occlusion recovery and let a removed device reach the fallback path. Exercise pending target admission, recovery and device removal. Refresh the DXGI regression fixtures to match the current production functions.
This commit is contained in:
1 parent
6654c71e92
commit
c58d703a2e
6 files changed
+57
-14
No files matched your search
@@ -427,11 +427,25 @@ unsigned ra_dxgi_bridge_index(ra_dxgi_bridge_t *b, unsigned interval)
|
||||
int ra_dxgi_bridge_presentable(ra_dxgi_bridge_t *b)
|
||||
{
|
||||
HRESULT result;
|
||||
if (!b->occluded || b->failed) return 1;
|
||||
result = b->chain->lpVtbl->Present(b->chain, 0, DXGI_PRESENT_TEST);
|
||||
b->occluded = result == DXGI_STATUS_OCCLUDED;
|
||||
if (FAILED(result)) b->failed = 1;
|
||||
return !b->occluded;
|
||||
UINT64 completed;
|
||||
unsigned index;
|
||||
if (b->failed) return 1;
|
||||
if (b->occluded)
|
||||
{
|
||||
result = b->chain->lpVtbl->Present(b->chain, 0, DXGI_PRESENT_TEST);
|
||||
b->occluded = result == DXGI_STATUS_OCCLUDED;
|
||||
if (FAILED(result)) b->failed = 1;
|
||||
if (b->failed) return 1;
|
||||
if (b->occluded) return 0;
|
||||
}
|
||||
index = b->chain->lpVtbl->GetCurrentBackBufferIndex(b->chain);
|
||||
completed = b->done->lpVtbl->GetCompletedValue(b->done);
|
||||
if (completed == ~(UINT64)0)
|
||||
{
|
||||
b->failed = 1;
|
||||
return 1;
|
||||
}
|
||||
return completed >= b->last_use[index];
|
||||
}
|
||||
int ra_dxgi_bridge_failed(ra_dxgi_bridge_t *b) { return b->failed; }
|
||||
uint64_t ra_dxgi_bridge_last_use(ra_dxgi_bridge_t *b, unsigned index)
|
||||
|
||||
@@ -16,6 +16,7 @@ typedef unsigned VkPipelineStageFlags;
|
||||
#define VK_NULL_HANDLE 0
|
||||
struct fence_values {
|
||||
unsigned sType, waitSemaphoreValuesCount, signalSemaphoreValuesCount;
|
||||
const void *pNext;
|
||||
const uint64_t *pWaitSemaphoreValues, *pSignalSemaphoreValues;
|
||||
};
|
||||
typedef struct fence_values VkD3D12FenceSubmitInfoKHR;
|
||||
@@ -58,6 +59,7 @@ static VkResult ra_vulkan_dxgi_submit(ra_vulkan_dxgi_t *p,
|
||||
#define VK_COMMAND_BUFFER_LEVEL_PRIMARY 0
|
||||
typedef struct command_info {
|
||||
unsigned sType, commandPool, level, commandBufferCount;
|
||||
const void *pNext;
|
||||
} VkCommandBufferAllocateInfo;
|
||||
static unsigned allocations;
|
||||
static VkResult allocation_result;
|
||||
|
||||
@@ -8,7 +8,8 @@ import tempfile
|
||||
|
||||
root = Path(__file__).resolve().parents[3]
|
||||
source = (root / "gfx/common/win_dxgi_present.c").read_text()
|
||||
start = source.index("unsigned ra_dxgi_bridge_index(")
|
||||
source = source[source.index("#define CINTERFACE"):]
|
||||
start = source.rindex("unsigned ra_dxgi_bridge_index(")
|
||||
end = source.index("void ra_dxgi_bridge_hdr_metadata(", start)
|
||||
destroy_start = source.index("static int ra_dxgi_bridge_wait_idle(")
|
||||
destroy_end = source.index("static int ra_dxgi_bridge_create_buffers(", destroy_start)
|
||||
@@ -23,10 +24,13 @@ with tempfile.TemporaryDirectory(prefix="retroarch-dxgi-") as directory:
|
||||
acquire_start = vulkan.index("VkResult ra_vulkan_dxgi_acquire(")
|
||||
acquire_end = vulkan.index("VkResult ra_vulkan_dxgi_present(", acquire_start)
|
||||
(directory / "acquire.inc").write_text(vulkan[acquire_start:acquire_end])
|
||||
commands_start = vulkan.index(" memset(&command, 0, sizeof(command));")
|
||||
commands_end = vulkan.index(" memset(&begin, 0, sizeof(begin));", commands_start)
|
||||
(directory / "commands.inc").write_text(vulkan[commands_start:commands_end])
|
||||
wait_start = vulkan.index("static VkResult ra_vulkan_dxgi_wait_resize(")
|
||||
commands_start = vulkan.index(" command.sType")
|
||||
commands_end = vulkan.index(" for (j = 0; j < 2; j++)", commands_start)
|
||||
allocate_start = vulkan.index(" if (*buffer == VK_NULL_HANDLE)", commands_end)
|
||||
allocate_end = vulkan.index(" begin.sType", allocate_start)
|
||||
(directory / "commands.inc").write_text(
|
||||
vulkan[commands_start:commands_end] + vulkan[allocate_start:allocate_end])
|
||||
wait_start = vulkan.rindex("static VkResult ra_vulkan_dxgi_drain(")
|
||||
wait_end = vulkan.index("int ra_vulkan_dxgi_resize(", wait_start)
|
||||
(directory / "wait.inc").write_text(vulkan[wait_start:wait_end])
|
||||
frontend = (root / "gfx/common/vulkan_common.c").read_text()
|
||||
|
||||
@@ -60,7 +60,6 @@ static unsigned WaitForSingleObject(void *event, unsigned timeout)
|
||||
drained = 1; ++waits; return 0;
|
||||
}
|
||||
static int CloseHandle(void *handle) { assert(handle); return 1; }
|
||||
static int FreeLibrary(void *module) { assert(module); return 1; }
|
||||
#define DROP(p) do { if (p) { (p)->lpVtbl->Release(p); (p) = NULL; } } while (0)
|
||||
#include "destroy.inc"
|
||||
static HRESULT resize_buffers(object *p, unsigned count, unsigned width,
|
||||
|
||||
@@ -7,7 +7,7 @@ typedef int HRESULT;
|
||||
typedef unsigned DWORD;
|
||||
#define S_OK 0
|
||||
typedef struct { int64_t QuadPart; } LARGE_INTEGER;
|
||||
typedef struct { unsigned PresentCount, PresentRefreshCount, SyncRefreshCount; LARGE_INTEGER SyncQPCTime; } DXGI_FRAME_STATISTICS;
|
||||
typedef struct { unsigned PresentCount, PresentRefreshCount, SyncRefreshCount; LARGE_INTEGER SyncQPCTime, SyncGPUTime; } DXGI_FRAME_STATISTICS;
|
||||
typedef uint64_t UINT64;
|
||||
#define WAIT_OBJECT_0 0u
|
||||
#define WAIT_TIMEOUT 258u
|
||||
@@ -20,6 +20,7 @@ typedef uint64_t UINT64;
|
||||
|
||||
typedef struct queue ID3D12CommandQueue;
|
||||
typedef struct chain IDXGISwapChain3;
|
||||
typedef struct fence ID3D12Fence;
|
||||
typedef void ID3D12CommandList;
|
||||
struct queue_vtable {
|
||||
HRESULT (*Wait)(ID3D12CommandQueue *, void *, UINT64);
|
||||
@@ -34,10 +35,13 @@ struct chain_vtable {
|
||||
};
|
||||
struct queue { struct queue_vtable *lpVtbl; };
|
||||
struct chain { struct chain_vtable *lpVtbl; };
|
||||
struct fence_vtable { UINT64 (*GetCompletedValue)(ID3D12Fence *); };
|
||||
struct fence { struct fence_vtable *lpVtbl; };
|
||||
typedef struct ra_dxgi_bridge {
|
||||
ID3D12CommandQueue *queue;
|
||||
IDXGISwapChain3 *chain;
|
||||
void *ready, *done, *latency, *lists[2];
|
||||
void *ready, *latency, *lists[2];
|
||||
ID3D12Fence *done;
|
||||
unsigned current_index, present_count;
|
||||
int64_t present_submitted_qpc;
|
||||
int failed, tearing, occluded, resized;
|
||||
@@ -47,6 +51,9 @@ typedef struct ra_dxgi_bridge {
|
||||
static DWORD wait_result;
|
||||
static HRESULT probe_result, present_result, queue_result, signal_result;
|
||||
static unsigned waits, probes, copies, presents, order;
|
||||
static UINT64 completed;
|
||||
static UINT64 get_completed(ID3D12Fence *fence)
|
||||
{ (void)fence; return completed; }
|
||||
static unsigned expected_interval = 1, expected_flags;
|
||||
static HRESULT count_result, stats_result;
|
||||
static DXGI_FRAME_STATISTICS frame_stats;
|
||||
@@ -93,15 +100,31 @@ int main(void)
|
||||
{
|
||||
struct queue_vtable queue_methods = { queue_wait, copy, signal_done };
|
||||
struct chain_vtable chain_methods = { present, current, get_count, get_stats };
|
||||
struct fence_vtable fence_methods = { get_completed };
|
||||
ID3D12Fence fence;
|
||||
ID3D12CommandQueue queue;
|
||||
IDXGISwapChain3 chain;
|
||||
ra_dxgi_bridge_t bridge;
|
||||
queue.lpVtbl = &queue_methods; chain.lpVtbl = &chain_methods;
|
||||
memset(&bridge, 0, sizeof(bridge)); bridge.queue = &queue; bridge.chain = &chain;
|
||||
fence.lpVtbl = &fence_methods; bridge.done = &fence;
|
||||
assert(ra_dxgi_bridge_presentable(&bridge));
|
||||
assert(ra_dxgi_bridge_index(&bridge, 1) == 1 && waits == 1 && probes == 0);
|
||||
assert(ra_dxgi_bridge_present(&bridge, 7, 1));
|
||||
assert(order == 4 && copies == 1 && presents == 1 && waited == 7 && signalled == 1);
|
||||
assert(ra_dxgi_bridge_last_use(&bridge, 1) == 1 && bridge.sequence == 1);
|
||||
{
|
||||
unsigned i, before = waits;
|
||||
for (i = 0; i < 10000; i++)
|
||||
assert(!ra_dxgi_bridge_presentable(&bridge));
|
||||
assert(waits == before && presents == 1 && copies == 1);
|
||||
completed = 1;
|
||||
assert(ra_dxgi_bridge_presentable(&bridge));
|
||||
assert(waits == before);
|
||||
completed = ~(UINT64)0;
|
||||
assert(ra_dxgi_bridge_presentable(&bridge) && bridge.failed);
|
||||
bridge.failed = 0; completed = 1;
|
||||
}
|
||||
probe_result = DXGI_STATUS_OCCLUDED;
|
||||
assert(ra_dxgi_bridge_index(&bridge, 1) == ~0u && waits == 1);
|
||||
assert(!ra_dxgi_bridge_failed(&bridge));
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
#define VK_STRUCTURE_TYPE_FENCE_CREATE_INFO 8
|
||||
typedef int VkResult;
|
||||
typedef unsigned VkFence;
|
||||
typedef struct { unsigned sType, flags; } VkFenceCreateInfo;
|
||||
typedef struct { unsigned sType, flags; const void *pNext; } VkFenceCreateInfo;
|
||||
typedef unsigned slock_t;
|
||||
typedef struct { unsigned device, queue; VkFence resize_fence; int submitted; void *queue_lock; } ra_vulkan_dxgi_t;
|
||||
static unsigned locked;
|
||||
@@ -41,6 +41,7 @@ static VkResult vkResetFences(unsigned device, unsigned count, const VkFence *fe
|
||||
assert(device == 1 && count == 1 && *fence == 9 && step == 2);
|
||||
++resets; step = 0; return reset_result;
|
||||
}
|
||||
#define ra_vulkan_dxgi_drain ra_vulkan_dxgi_wait_resize
|
||||
#include "wait.inc"
|
||||
int main(void)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user