diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 32b138cc6..5b76f897a 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -1312,15 +1312,16 @@ void KernelState::EmulateCPInterruptDPC(uint32_t interrupt_callback, } void KernelState::InitializeProcess(X_KPROCESS* process, uint32_t type, - char unk_18, char unk_19, char unk_1A) { + char priority_class, char default_priority, + char max_dynamic_priority) { uint32_t guest_kprocess = memory()->HostToGuestVirtual(process); uint32_t thread_list_guest_ptr = guest_kprocess + offsetof(X_KPROCESS, thread_list); - process->unk_18 = unk_18; - process->unk_19 = unk_19; - process->unk_1A = unk_1A; + process->process_priority_class = priority_class; + process->default_thread_priority = default_priority; + process->max_dynamic_priority = max_dynamic_priority; util::XeInitializeListHead(&process->thread_list, thread_list_guest_ptr); process->quantum = 60; // doubt any guest code uses this ptr, which i think probably has something to @@ -1328,7 +1329,7 @@ void KernelState::InitializeProcess(X_KPROCESS* process, uint32_t type, process->clrdataa_masked_ptr = 0; // clrdataa_ & ~(1U << 31); process->thread_count = 0; - process->unk_1B = 0x06; + process->disable_quantum_decay = 0x06; process->kernel_stack_size = 16 * 1024; process->tls_slot_size = 0x80; diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index fb2bf33b8..0b5248943 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -66,10 +66,10 @@ struct X_KPROCESS { // so it sets this ptr to 0x1C0000 xe::be clrdataa_masked_ptr; xe::be thread_count; - uint8_t unk_18; - uint8_t unk_19; - uint8_t unk_1A; - uint8_t unk_1B; + uint8_t process_priority_class; + uint8_t default_thread_priority; + uint8_t max_dynamic_priority; + uint8_t disable_quantum_decay; xe::be kernel_stack_size; xe::be tls_static_data_address; xe::be tls_data_size; @@ -332,8 +332,9 @@ class KernelState { private: void LoadKernelModule(object_ref kernel_module); - void InitializeProcess(X_KPROCESS* process, uint32_t type, char unk_18, - char unk_19, char unk_1A); + void InitializeProcess(X_KPROCESS* process, uint32_t type, + char priority_class, char default_priority, + char max_dynamic_priority); void SetProcessTLSVars(X_KPROCESS* process, int num_slots, int tls_data_size, int tls_static_data_address); void InitializeKernelGuestGlobals(); diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc index f524b49ab..f4cfac6ee 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_threading.cc @@ -707,8 +707,7 @@ uint32_t xeKeReleaseSemaphore(X_KSEMAPHORE* semaphore_ptr, uint32_t increment, return 0; } - // TODO(benvanik): increment thread priority? - // TODO(benvanik): wait? + sem->set_priority_increment(increment); int32_t previous_count = 0; [[maybe_unused]] bool success = diff --git a/src/xenia/kernel/xevent.cc b/src/xenia/kernel/xevent.cc index bf1176af8..b583bf732 100644 --- a/src/xenia/kernel/xevent.cc +++ b/src/xenia/kernel/xevent.cc @@ -58,11 +58,13 @@ void XEvent::InitializeNative(void* native_ptr, X_DISPATCH_HEADER* header) { } int32_t XEvent::Set(uint32_t priority_increment, bool wait) { + set_priority_increment(priority_increment); event_->Set(); return 1; } int32_t XEvent::Pulse(uint32_t priority_increment, bool wait) { + set_priority_increment(priority_increment); event_->Pulse(); return 1; } diff --git a/src/xenia/kernel/xmutant.cc b/src/xenia/kernel/xmutant.cc index 58e379730..42da93d67 100644 --- a/src/xenia/kernel/xmutant.cc +++ b/src/xenia/kernel/xmutant.cc @@ -45,6 +45,8 @@ X_STATUS XMutant::ReleaseMutant(uint32_t priority_increment, bool abandon, owning_thread_ = nullptr; } + set_priority_increment(priority_increment); + // TODO(benvanik): abandoning. assert_false(abandon); if (mutant_->Release()) { diff --git a/src/xenia/kernel/xobject.cc b/src/xenia/kernel/xobject.cc index 83d2ec8ae..3c501509c 100644 --- a/src/xenia/kernel/xobject.cc +++ b/src/xenia/kernel/xobject.cc @@ -207,10 +207,9 @@ X_STATUS XObject::Wait(uint32_t wait_reason, uint32_t processor_mode, switch (result) { case xe::threading::WaitResult::kSuccess: case xe::threading::WaitResult::kUserCallback: { - // Thread actually blocked and woke — reset priority to base. auto current_thread = XThread::GetCurrentThread(); if (current_thread) { - current_thread->ResetQuantum(); + current_thread->BoostOnWake(priority_increment()); } if (result == xe::threading::WaitResult::kSuccess) { WaitCallback(); @@ -245,7 +244,7 @@ X_STATUS XObject::SignalAndWait(XObject* signal_object, XObject* wait_object, case xe::threading::WaitResult::kUserCallback: { auto current_thread = XThread::GetCurrentThread(); if (current_thread) { - current_thread->ResetQuantum(); + current_thread->BoostOnWake(wait_object->priority_increment()); } if (result == xe::threading::WaitResult::kSuccess) { wait_object->WaitCallback(); @@ -280,12 +279,14 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects, : std::chrono::milliseconds::max(); X_STATUS status; + uint32_t boost_increment = 0; if (wait_type) { auto result = xe::threading::WaitAny(wait_handles, count, alertable ? true : false, timeout_ms); switch (result.first) { case xe::threading::WaitResult::kSuccess: objects[result.second]->WaitCallback(); + boost_increment = objects[result.second]->priority_increment(); status = X_STATUS(result.second); break; case xe::threading::WaitResult::kUserCallback: @@ -310,6 +311,10 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects, case xe::threading::WaitResult::kSuccess: for (uint32_t i = 0; i < count; i++) { objects[i]->WaitCallback(); + // Use the largest increment among the signaled objects. + if (objects[i]->priority_increment() > boost_increment) { + boost_increment = objects[i]->priority_increment(); + } } status = X_STATUS_SUCCESS; break; @@ -328,12 +333,13 @@ X_STATUS XObject::WaitMultiple(uint32_t count, XObject** objects, } } - // Only reset quantum if the thread actually blocked (not on timeout/failure). + // Apply priority boost if the thread actually blocked (not on + // timeout/failure). if (status != X_STATUS_TIMEOUT && status != X_STATUS_UNSUCCESSFUL && status != X_STATUS_ABANDONED_WAIT_0) { auto current_thread = XThread::GetCurrentThread(); if (current_thread) { - current_thread->ResetQuantum(); + current_thread->BoostOnWake(boost_increment); } } return status; diff --git a/src/xenia/kernel/xobject.h b/src/xenia/kernel/xobject.h index 77fbf0870..da67be4c0 100644 --- a/src/xenia/kernel/xobject.h +++ b/src/xenia/kernel/xobject.h @@ -226,6 +226,12 @@ class XObject { void* native_ptr, int32_t as_type = -1, bool already_locked = false); + // Priority increment stored by the most recent signal operation + // (KeSetEvent, KeReleaseSemaphore, etc.). Read by the waiter on wake + // to apply a priority boost matching real Xenon scheduler behavior. + uint32_t priority_increment() const { return priority_increment_; } + void set_priority_increment(uint32_t inc) { priority_increment_ = inc; } + protected: bool SaveObject(ByteStream* stream); bool RestoreObject(ByteStream* stream); @@ -253,6 +259,8 @@ class XObject { KernelState* kernel_state_; + uint32_t priority_increment_ = 0; + // Host objects are persisted through resets/etc. bool host_object_ = false; diff --git a/src/xenia/kernel/xthread.cc b/src/xenia/kernel/xthread.cc index 49fd30d21..cc7d90c2e 100644 --- a/src/xenia/kernel/xthread.cc +++ b/src/xenia/kernel/xthread.cc @@ -214,6 +214,19 @@ void XThread::InitializeGuestObject() { guest_thread->apc_lists[0].Initialize(memory()); guest_thread->apc_lists[1].Initialize(memory()); + guest_thread->process_priority_class = process->process_priority_class; + auto base_prio = process->default_thread_priority; + guest_thread->base_priority_copy = base_prio; + guest_thread->base_priority = base_prio; + guest_thread->priority = base_prio; + guest_thread->max_dynamic_priority = process->max_dynamic_priority; + guest_thread->quantum = process->quantum; + + // Sync the host-side priority tracking to match the guest defaults. + // Games may later override these via KeSetPriorityThread. + priority_ = base_prio; + base_priority_ = base_prio; + guest_thread->a_prcb_ptr = kpcrb; guest_thread->another_prcb_ptr = kpcrb; @@ -706,7 +719,7 @@ void XThread::SetPriority(int32_t increment) { void XThread::CheckQuantumAndDecay() { if (cvars::ignore_thread_priorities) return; - // Real-time threads (priority >= 18) don't decay on Xenon. + // Real-time threads (current priority >= 0x12) don't decay on Xenon. if (priority_ >= 18) return; uint64_t now = Clock::QueryHostUptimeMillis(); @@ -719,14 +732,15 @@ void XThread::CheckQuantumAndDecay() { constexpr uint64_t kQuantumPeriodMs = 20; if (elapsed < kQuantumPeriodMs) return; - // TODO(has207): The real kernel also subtracts the accumulated priority - // boost (boost_accumulator in X_KTHREAD) during decay: - // new_prio = priority - boost_accumulator - 1 - // We don't implement priority boosting on wait completion yet, so - // the boost accumulator is always effectively 0. When boost-on-wake - // is added, the accumulator needs to be drained here as well. int32_t decay_steps = static_cast(elapsed / kQuantumPeriodMs); - int32_t new_priority = priority_ - decay_steps; + // On the first decay step, drain the accumulated priority boost as well. + // The real kernel computes: new_prio = priority - boost_accumulator - 1 + // then zeroes the accumulator. Additional decay steps (if the timer + // callback was late) each subtract 1 more. + int32_t total_decay = boost_amount_ + decay_steps; + boost_amount_ = 0; + + int32_t new_priority = priority_ - total_decay; if (new_priority < base_priority_) { new_priority = base_priority_; } @@ -740,16 +754,57 @@ void XThread::CheckQuantumAndDecay() { quantum_start_ms_ = now; } -void XThread::ResetQuantum() { +void XThread::BoostOnWake(int32_t increment) { if (cvars::ignore_thread_priorities) return; - if (priority_ != base_priority_) { - priority_ = base_priority_; - if (is_guest_thread()) { - guest_object()->priority = - static_cast(base_priority_); - } - thread_->set_priority(GuestPriorityToHost(base_priority_)); + + // Real-time threads (priority >= 0x12) just get their quantum reset. + if (priority_ >= 18) { + boost_amount_ = 0; + quantum_start_ms_ = Clock::QueryHostUptimeMillis(); + return; } + + // Match the real kernel (xeEnqueueThreadPostWait): + // - Only apply boost if there is no pending decay (priority_decrement == 0) + // AND boost is not disabled on this thread. + // - Boosted priority = base + increment, clamped to max_priority_cap. + // - Only boost UP — never lower priority below its current value. + bool apply_boost = false; + if (increment > 0 && is_guest_thread()) { + auto* kthread = guest_object(); + if (kthread->priority_decrement == 0 && !kthread->boost_disabled) { + apply_boost = true; + } + } else if (increment > 0) { + // Host threads (non-guest): apply boost unconditionally. + apply_boost = true; + } + + if (apply_boost) { + int32_t boosted = base_priority_ + increment; + // Clamp to the per-thread max dynamic priority cap. + // For title threads this is 17 (just below real-time threshold). + int32_t max_cap = 17; + if (is_guest_thread()) { + uint8_t guest_cap = guest_object()->max_dynamic_priority; + if (guest_cap > 0) { + max_cap = guest_cap; + } + } + if (boosted > max_cap) { + boosted = max_cap; + } + // Only boost UP, never lower. + if (boosted > priority_) { + priority_ = boosted; + boost_amount_ = priority_ - base_priority_; + if (is_guest_thread()) { + guest_object()->priority = static_cast(priority_); + } + thread_->set_priority(GuestPriorityToHost(priority_)); + } + } + quantum_start_ms_ = Clock::QueryHostUptimeMillis(); } diff --git a/src/xenia/kernel/xthread.h b/src/xenia/kernel/xthread.h index a2fc02020..527e549c5 100644 --- a/src/xenia/kernel/xthread.h +++ b/src/xenia/kernel/xthread.h @@ -298,9 +298,9 @@ struct X_KTHREAD { uint8_t unk_A5[0xB]; // 0xA5 int32_t apc_disable_count; // 0xB0 xe::be quantum; // 0xB4 - uint8_t unk_B8; // 0xB8 - uint8_t unk_B9; // 0xB9 - uint8_t unk_BA; // 0xBA + uint8_t saturation_increment; // 0xB8 + uint8_t base_priority; // 0xB9 + uint8_t priority_decrement; // 0xBA uint8_t boost_disabled; // 0xBB uint8_t suspend_count; // 0xBC uint8_t was_preempted; // 0xBD @@ -310,9 +310,9 @@ struct X_KTHREAD { // all TypedGuestPointer a_prcb_ptr; // 0xC0 TypedGuestPointer another_prcb_ptr; // 0xC4 - uint8_t unk_C8; // 0xC8 - uint8_t unk_C9; // 0xC9 - uint8_t unk_CA; // 0xCA + uint8_t process_priority_class; // 0xC8 + uint8_t base_priority_copy; // 0xC9 + uint8_t max_dynamic_priority; // 0xCA uint8_t unk_CB; // 0xCB X_KSPINLOCK timer_list_lock; // 0xCC xe::be stack_alloc_base; // 0xD0 @@ -424,14 +424,19 @@ class XThread : public XObject, public cpu::Thread { // Called periodically (~20ms) by KernelState's timestamp timer to simulate // the Xenon scheduler's quantum-based priority decay for non-real-time - // threads (priority < 18). Threads that run for longer than one quantum - // (~20ms) have their effective priority decayed toward the base, which - // causes them to drop into lower host priority buckets and prevents - // starvation. + // threads (base_priority < 18). Threads that run for longer than one + // quantum (~20ms) have their effective priority decayed toward the base, + // which causes them to drop into lower host priority buckets and prevents + // starvation. On the first decay step the accumulated priority boost is + // also drained. void CheckQuantumAndDecay(); - // Resets effective priority back to base and restarts the quantum timer. - // Called when a thread wakes from a kernel wait. - void ResetQuantum(); + // Called when a thread wakes from a kernel wait. Applies a priority + // boost of |increment| above base_priority (matching the Xenon kernel's + // unwait-boost behavior) and restarts the quantum timer. The boost is + // drained on the next quantum expiry via CheckQuantumAndDecay(). + // If increment is 0 or the thread has boost disabled, the priority is + // simply restored to base_priority. + void BoostOnWake(int32_t increment); // Xbox thread IDs: // 0 - core 0, thread 0 - user @@ -504,6 +509,7 @@ class XThread : public XObject, public cpu::Thread { int32_t priority_ = 0; // current effective priority (may be decayed) int32_t base_priority_ = 0; // priority floor — decay never goes below this + int32_t boost_amount_ = 0; // accumulated priority boost above base uint64_t quantum_start_ms_ = 0; // host uptime (ms) when quantum last reset #if !XE_PLATFORM_WIN32