Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Default MaximumFrameLatency increased #812

Merged
merged 1 commit into from
Feb 9, 2025

Conversation

Spodi
Copy link
Contributor

@Spodi Spodi commented Feb 7, 2025

Defaults jitter fix (MaximumFrameLatency) to be on (2), instead of off (1), if nothing is set by a port. Keeping it at 1 has a lower latency, but I think it's more sensible to get a smoother experience by default.

Defaults jitter fix to be on, instead of off, if nothing is set by a port.
@briaguya-ai
Copy link
Collaborator

Digging into this a bit https://github.com/search?q=repo%3AKenix3%2Flibultraship%20maximum_frame_latency&type=code

"apply"

static void apply_maximum_frame_latency(bool first) {
DXGI_SWAP_CHAIN_DESC swap_desc = {};
dxgi.swap_chain->GetDesc(&swap_desc);
ComPtr<IDXGISwapChain2> swap_chain2;
if ((swap_desc.Flags & DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT) &&
dxgi.swap_chain->QueryInterface(__uuidof(IDXGISwapChain2), &swap_chain2) == S_OK) {
ThrowIfFailed(swap_chain2->SetMaximumFrameLatency(dxgi.maximum_frame_latency));
if (first) {
dxgi.waitable_object = swap_chain2->GetFrameLatencyWaitableObject();
WaitForSingleObject(dxgi.waitable_object, INFINITE);
}
} else {
ComPtr<IDXGIDevice1> device1;
ThrowIfFailed(dxgi.swap_chain->GetDevice(__uuidof(IDXGIDevice1), &device1));
ThrowIfFailed(device1->SetMaximumFrameLatency(dxgi.maximum_frame_latency));
}
dxgi.applied_maximum_frame_latency = dxgi.maximum_frame_latency;
}

the line being changed in this PR

dxgi.maximum_frame_latency = 1;

a bunch of logic in swap buffers end

static void gfx_dxgi_swap_buffers_end() {
LARGE_INTEGER t0, t1, t2;
QueryPerformanceCounter(&t0);
QueryPerformanceCounter(&t1);
if (dxgi.applied_maximum_frame_latency > dxgi.maximum_frame_latency) {
// If latency is decreased, you have to wait the same amout of times as the old latency was set to
int times_to_wait = dxgi.applied_maximum_frame_latency;
int latency = dxgi.maximum_frame_latency;
dxgi.maximum_frame_latency = 1;
apply_maximum_frame_latency(false);
if (dxgi.waitable_object != nullptr) {
while (times_to_wait > 0) {
WaitForSingleObject(dxgi.waitable_object, INFINITE);
times_to_wait--;
}
}
dxgi.maximum_frame_latency = latency;
apply_maximum_frame_latency(false);
return; // Make sure we don't wait a second time on the waitable object, since that would hang the program
} else if (dxgi.applied_maximum_frame_latency != dxgi.maximum_frame_latency) {
apply_maximum_frame_latency(false);
}
if (!dxgi.dropped_frame) {
if (dxgi.waitable_object != nullptr) {
WaitForSingleObject(dxgi.waitable_object, INFINITE);
}
// else TODO: maybe sleep until some estimated time the frame will be shown to reduce lag
}
DXGI_FRAME_STATISTICS stats;
dxgi.swap_chain->GetFrameStatistics(&stats);
QueryPerformanceCounter(&t2);
dxgi.zero_latency = dxgi.pending_frame_stats.rbegin()->first == stats.PresentCount;
// printf(L"done %I64u gpu:%d wait:%d freed:%I64u frame:%u %u monitor:%u t:%I64u\n", (unsigned long
// long)(t0.QuadPart - dxgi.qpc_init), (int)(t1.QuadPart - t0.QuadPart), (int)(t2.QuadPart - t0.QuadPart), (unsigned
// long long)(t2.QuadPart - dxgi.qpc_init), dxgi.pending_frame_stats.rbegin()->first, stats.PresentCount,
// stats.SyncRefreshCount, (unsigned long long)(stats.SyncQPCTime.QuadPart - dxgi.qpc_init));
}

what is called when ports set this using Fast3dWindow::SetMaximumFrameLatency

static void gfx_dxgi_set_maximum_frame_latency(int latency) {
dxgi.maximum_frame_latency = latency;
}

a call to apply when creating swapchain

void gfx_dxgi_create_swap_chain(IUnknown* device, std::function<void()>&& before_destroy_fn) {
bool win8 = IsWindows8OrGreater(); // DXGI_SCALING_NONE is only supported on Win8 and beyond
bool dxgi_13 = dxgi.CreateDXGIFactory2 != nullptr; // DXGI 1.3 introduced waitable object
DXGI_SWAP_CHAIN_DESC1 swap_chain_desc = {};
swap_chain_desc.BufferCount = 3;
swap_chain_desc.Width = 0;
swap_chain_desc.Height = 0;
swap_chain_desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swap_chain_desc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swap_chain_desc.Scaling = win8 ? DXGI_SCALING_NONE : DXGI_SCALING_STRETCH;
swap_chain_desc.SwapEffect =
dxgi.dxgi1_4 ? DXGI_SWAP_EFFECT_FLIP_DISCARD : // Introduced in DXGI 1.4 and Windows 10
DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL; // Apparently flip sequential was also backported to Win 7 Platform Update
swap_chain_desc.Flags = dxgi_13 ? DXGI_SWAP_CHAIN_FLAG_FRAME_LATENCY_WAITABLE_OBJECT : 0;
if (dxgi.tearing_support) {
swap_chain_desc.Flags |= DXGI_SWAP_CHAIN_FLAG_ALLOW_TEARING; // Now we can use DXGI_PRESENT_ALLOW_TEARING
}
swap_chain_desc.SampleDesc.Count = 1;
ThrowIfFailed(
dxgi.factory->CreateSwapChainForHwnd(device, dxgi.h_wnd, &swap_chain_desc, nullptr, nullptr, &dxgi.swap_chain));
ThrowIfFailed(dxgi.factory->MakeWindowAssociation(dxgi.h_wnd, DXGI_MWA_NO_ALT_ENTER));
apply_maximum_frame_latency(true);
ThrowIfFailed(dxgi.swap_chain->GetDesc1(&swap_chain_desc));
dxgi.swap_chain_device = device;
dxgi.before_destroy_swap_chain_fn = std::move(before_destroy_fn);
}

there seems to be a lot here - if nothing else

dxgi.maximum_frame_latency = 1;

probably needs to be updated to 2 as well, but any explanation of why so much logic is needed for what seems like something we should just set and forget would be really helpful

@briaguya-ai
Copy link
Collaborator

after rereading this

dxgi.maximum_frame_latency = 1;

probably needs to be updated to 2 as well, but any explanation of why so much logic is needed for what seems like something we should just set and forget would be really helpful

it seems that spot can stay 1

breaking down

if (dxgi.applied_maximum_frame_latency > dxgi.maximum_frame_latency) {
// If latency is decreased, you have to wait the same amout of times as the old latency was set to
int times_to_wait = dxgi.applied_maximum_frame_latency;
int latency = dxgi.maximum_frame_latency;
dxgi.maximum_frame_latency = 1;
apply_maximum_frame_latency(false);
if (dxgi.waitable_object != nullptr) {
while (times_to_wait > 0) {
WaitForSingleObject(dxgi.waitable_object, INFINITE);
times_to_wait--;
}
}
dxgi.maximum_frame_latency = latency;
apply_maximum_frame_latency(false);
return; // Make sure we don't wait a second time on the waitable object, since that would hang the program
} else if (dxgi.applied_maximum_frame_latency != dxgi.maximum_frame_latency) {

current latency value is stored in local var

if (dxgi.applied_maximum_frame_latency > dxgi.maximum_frame_latency) {
// If latency is decreased, you have to wait the same amout of times as the old latency was set to
int times_to_wait = dxgi.applied_maximum_frame_latency;
int latency = dxgi.maximum_frame_latency;

it's set to 1 to "run out" the existing latency frames or something?

dxgi.maximum_frame_latency = 1;
apply_maximum_frame_latency(false);
if (dxgi.waitable_object != nullptr) {
while (times_to_wait > 0) {
WaitForSingleObject(dxgi.waitable_object, INFINITE);
times_to_wait--;
}
}

it is set back to the new applied latency value (which was stored in the local var)

dxgi.maximum_frame_latency = latency;
apply_maximum_frame_latency(false);
return; // Make sure we don't wait a second time on the waitable object, since that would hang the program

Copy link
Collaborator

@briaguya-ai briaguya-ai left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i still feel the ability to change this at runtime is adding a ton of complexity we likely don't need, but from a "minimal change maximum impact" perspective this seems like a good one

@Spodi
Copy link
Contributor Author

Spodi commented Feb 7, 2025

if (dxgi.applied_maximum_frame_latency > dxgi.maximum_frame_latency) {
// If latency is decreased, you have to wait the same amout of times as the old latency was set to
int times_to_wait = dxgi.applied_maximum_frame_latency;
int latency = dxgi.maximum_frame_latency;

it's set to 1 to "run out" the existing latency frames or something?

dxgi.maximum_frame_latency = 1;
apply_maximum_frame_latency(false);
if (dxgi.waitable_object != nullptr) {
while (times_to_wait > 0) {
WaitForSingleObject(dxgi.waitable_object, INFINITE);
times_to_wait--;
}
}

it is set back to the new applied latency value (which was stored in the local var)

dxgi.maximum_frame_latency = latency;
apply_maximum_frame_latency(false);
return; // Make sure we don't wait a second time on the waitable object, since that would hang the program

Yes, setting it to 1 and than waiting in a loop is to get the latency down again. This is the "undocumented stuff" I talked about. It's never mentioned in the official docs, that you can't just set it at runtime and latency will stay up instead.
In fact the previous aproach here was to create a completely new swapchain. Even the one that wrote the old approach was seemingly supprised, that latency won't go down automatically (according to the comments).
Creating a new swapchain had the problem of confusing ReShade, SK and whatever else is out there. And sometimes also did strange things on its own, when the jitter fix option was changed too fast and therefore multiple swapchains where created in a quick succesion.

@briaguya-ai briaguya-ai merged commit 74f1c35 into Kenix3:main Feb 9, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants