Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions runtime/kernel.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "hle.h"
#include "kernel.h"
#include "platform.h"
#include "prof.h"

// ------------------------------------------------------------------ results
Expand Down Expand Up @@ -394,6 +395,7 @@ void rt_unwind_point(void) {
static void *thread_main(void *arg) {
KThread *t = arg;
t_cur = t;
if (t->id == 1) plat_thread_elevate();
{
char pn[32];
snprintf(pn, sizeof pn, "guest-%x", t->handle);
Expand Down
2 changes: 2 additions & 0 deletions runtime/nvn/objects.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "../framepace.h"
#include "../game_sites.h"
#include "../kernel.h"
#include "../platform.h"
#include "../prof.h"
#include "../perfmon.h"
#include "nvn.h"
Expand Down Expand Up @@ -224,6 +225,7 @@ static void qop_run(const QOp *o) {
static void *queue_thread(void *arg) {
(void)arg;
prof_thread_name("nvn-queue");
plat_thread_elevate();
kernel_sample_host_thread();
extern void control_prof_thread(const char *name); // runtime/control_prof.c (`threads`)
control_prof_thread("render");
Expand Down
1 change: 1 addition & 0 deletions runtime/nvn/vulkan/record.c
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,7 @@ static uint64_t spin_for_work(uint64_t t) { // the published end once it moves
static void *run(void *arg) {
(void)arg;
prof_thread_name("vk-record");
plat_thread_elevate();
extern void control_prof_thread(const char *name); // runtime/control_prof.c (`threads`)
control_prof_thread("recorder");
uint64_t t = 0;
Expand Down
1 change: 1 addition & 0 deletions runtime/platform.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ uint64_t plat_now_ns(void); // monotonic
void plat_sleep_until_ns(uint64_t t); // precise wait for plat_now_ns() >= t
void plat_wait_until_ns(uint64_t t); // wait for plat_now_ns() >= t without spinning (the OS timer's precision)
void plat_thread_realtime(uint64_t period_ns); // periodic timing thread (vsync): don't coalesce its timers
void plat_thread_elevate(void); // latency-critical thread (game main, render, recorder): favor P-cores on hybrid architectures
int plat_cpu_count(void); // logical processors this process may run on (its affinity, after plat_cpu_placement)
void plat_cpu_placement(void); // (game start) one logical processor per core where plat_one_per_core_default says
// Whether the game runs on one logical processor per core when SAVAGEWIND_ONE_PER_CORE doesn't say (cores: physical
Expand Down
2 changes: 2 additions & 0 deletions runtime/platform/linux.c
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ void plat_thread_realtime(uint64_t period_ns) {
setpriority(PRIO_PROCESS, (id_t)gettid(), -10); // (per thread on Linux; fails quietly without CAP_SYS_NICE)
}

void plat_thread_elevate(void) {}

// ---- process
// stdout / stderr go to savagewind.log in the data folder (runtime/paths.h; rt_paths_init runs later, so the same rule
// is applied here). Processes started by this one (the start screen re-executing itself for the game) append to the
Expand Down
2 changes: 2 additions & 0 deletions runtime/platform/macos.c
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,8 @@ void plat_thread_realtime(uint64_t period_ns) {
THREAD_TIME_CONSTRAINT_POLICY_COUNT);
}

void plat_thread_elevate(void) {}

const char *plat_log_path(void) { return ""; } // (the log goes to stderr)

bool plat_exe_path(char *buf, size_t size) {
Expand Down
30 changes: 23 additions & 7 deletions runtime/platform/windows.c
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,10 @@ void plat_thread_realtime(uint64_t period_ns) {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL);
}

void plat_thread_elevate(void) {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_ABOVE_NORMAL);
}

// ---- process
// Release builds are GUI-subsystem executables (no console window): stdout / stderr go to savagewind.log next to the
// exe, and a fatal error shows a message box (plat_fatal_notice) instead of vanishing. Processes started by this one
Expand Down Expand Up @@ -433,9 +437,11 @@ void plat_init(void) {
// never share a core with another busy thread of the game. With fewer cores than the game's busy threads (main, two job
// workers, render, recorder, audio, file loading) they queue for one instead: by default with SMT and 4 or more
// physical cores (plat_one_per_core_default). SAVAGEWIND_ONE_PER_CORE=1: always, 0: never. Called when the game
// starts, so what the start screen runs (the game code build) keeps every logical processor. Under Wine / Proton the
// Linux kernel schedules the threads, and there the Steam Deck measured one processor per core as a loss
// (platform.h): the default keeps every logical processor, as the Linux build does.
// starts, so what the start screen runs (the game code build) keeps every logical processor. On hybrid architectures
// (Intel 12th+ Gen, Core Ultra, heterogeneous P/E cores) keeping all logical processors avoids starving P-core SMT
// execution slots and forcing critical threads onto low-clock E-cores. Under Wine / Proton the Linux kernel schedules
// the threads, and there the Steam Deck measured one processor per core as a loss (platform.h): the default keeps
// every logical processor, as the Linux build does.
void plat_cpu_placement(void) {
DWORD_PTR all, sys, one = 0;
if (!GetProcessAffinityMask(GetCurrentProcess(), &all, &sys)) return;
Expand All @@ -445,18 +451,28 @@ void plat_cpu_placement(void) {
DWORD len = 0;
GetLogicalProcessorInformationEx(RelationProcessorCore, NULL, &len);
char *buf = len ? malloc(len) : NULL;
BYTE min_eff = 255, max_eff = 0;
if (buf && GetLogicalProcessorInformationEx(RelationProcessorCore, (void *)buf, &len))
for (DWORD o = 0; o < len;) {
SYSTEM_LOGICAL_PROCESSOR_INFORMATION_EX *e = (void *)(buf + o);
DWORD_PTR m = e->Processor.GroupMask[0].Group == 0 ? e->Processor.GroupMask[0].Mask & all : 0;
if (m) cores++, one |= m & -m; // (the core's first logical processor)
if (m) {
cores++;
one |= m & -m; // (the core's first logical processor)
BYTE eff = e->Processor.EfficiencyClass;
if (eff < min_eff) min_eff = eff;
if (eff > max_eff) max_eff = eff;
}
o += e->Size;
}
free(buf);
bool hybrid = (cores > 0 && min_eff != max_eff);
const char *x = getenv("SAVAGEWIND_ONE_PER_CORE");
bool on = one && (x ? atoi(x) != 0 : !wine && plat_one_per_core_default(cores, logical));
fprintf(stderr, "[cpu] %d cores, %d logical processors: %s%s\n", cores, logical,
on ? "one logical processor per core" : "all logical processors", wine && !x ? " (Wine)" : "");
bool on = one && (x ? atoi(x) != 0 : !wine && !hybrid && plat_one_per_core_default(cores, logical));
fprintf(stderr, "[cpu] %d cores%s, %d logical processors: %s%s\n", cores,
hybrid ? " (hybrid P/E)" : "", logical,
on ? "one logical processor per core" : "all logical processors",
wine && !x ? " (Wine)" : hybrid && !x ? " (hybrid architecture)" : "");
if (on) SetProcessAffinityMask(GetCurrentProcess(), one);
}

Expand Down
Loading