Compare commits

..

22 Commits

Author SHA1 Message Date
ReinUsesLisp
746dab407e vk_swapchain: Implement a swapchain manager 2019-03-29 00:00:51 -03:00
bunnei
f770c17d01 Merge pull request #2266 from FernandoS27/arbitration
Kernel: Fixes to Arbitration and SignalProcessWideKey Management
2019-03-28 21:42:24 -04:00
bunnei
b404fcdf14 Merge pull request #2265 from FernandoS27/multilevelqueue
Replace old Thread Queue for a new Multi Level Queue
2019-03-28 21:41:40 -04:00
bunnei
16dc3a1dd5 Merge pull request #2284 from lioncash/heap-alloc
kernel/vm_manager: Unify heap allocation/freeing functions
2019-03-28 17:56:49 -04:00
bunnei
76f024865d Merge pull request #2296 from lioncash/override
video_core: Add missing override specifiers
2019-03-28 17:54:51 -04:00
bunnei
a09d8cc8a2 Merge pull request #2295 from lioncash/typo
video_core/gpu: Amend typo in GPU member variable name
2019-03-28 17:54:20 -04:00
Fernando Sahmkow
db42bcb306 Fixes and corrections on formatting. 2019-03-27 14:49:43 -04:00
Fernando Sahmkow
f35e09fe0d Fixes to multilevelqueue's iterator. 2019-03-27 14:34:33 -04:00
Fernando Sahmkow
dde0814837 Use MultiLevelQueue instead of old ThreadQueueList 2019-03-27 14:34:32 -04:00
Fernando Sahmkow
9dbba9240b Add MultiLevelQueue Tests 2019-03-27 14:34:31 -04:00
Fernando Sahmkow
3bc815a5dc Implement intrinsics CountTrailingZeroes and test it. 2019-03-27 14:34:29 -04:00
Fernando Sahmkow
522957f9f3 Implement a MultiLevelQueue 2019-03-27 14:33:44 -04:00
Lioncash
bbe700359d video_core: Add missing override specifiers
Ensures that the signatures will always match with the base class.

Also silences a few compilation warnings.
2019-03-27 12:24:52 -04:00
Lioncash
1e92ba2785 kernel/vm_manager: Handle shrinking of the heap size within SetHeapSize()
One behavior that we weren't handling properly in our heap allocation
process was the ability for the heap to be shrunk down in size if a
larger size was previously requested.

This adds the basic behavior to do so and also gets rid of HeapFree, as
it's no longer necessary now that we have allocations and deallocations
going through the same API function.

While we're at it, fully document the behavior that this function
performs.
2019-03-24 17:08:30 -04:00
Lioncash
99a163478b kernel/vm_manager: Rename HeapAllocate to SetHeapSize
Makes it more obvious that this function is intending to stand in for
the actual supervisor call itself, and not acting as a general heap
allocation function.

Also the following change will merge the freeing behavior of HeapFree
into this function, so leaving it as HeapAllocate would be misleading.
2019-03-24 17:08:30 -04:00
Lioncash
abdb81ccaf kernel/vm_manager: Handle case of identical calls to HeapAllocate
In cases where HeapAllocate is called with the same size of the current
heap, we can simply do nothing and return successfully.

This avoids doing work where we otherwise don't have to. This is also
what the kernel itself does in this scenario.
2019-03-24 17:08:30 -04:00
Lioncash
9f63acac0f kernel/vm_manager: Remove unused class variables
Over time these have fallen out of use due to refactoring, so these can
be removed.
2019-03-24 17:08:30 -04:00
Lioncash
52980df1aa kernel/vm_manager: Remove unnecessary heap_used data member
This isn't required anymore, as all the kernel ever queries is the size
of the current heap, not the total usage of it.
2019-03-24 17:08:16 -04:00
Lioncash
586cab6172 kernel/vm_manager: Tidy up heap allocation code
Another holdover from citra that can be tossed out is the notion of the
heap needing to be allocated in different addresses. On the switch, the
base address of the heap will always be managed by the memory allocator
in the kernel, so this doesn't need to be specified in the function's
interface itself.

The heap on the switch is always allocated with read/write permissions,
so we don't need to add specifying the memory permissions as part of the
heap allocation itself either.

This also corrects the error code returned from within the function.
If the size of the heap is larger than the entire heap region, then the
kernel will report an out of memory condition.
2019-03-24 16:17:31 -04:00
Fernando Sahmkow
9c7319a4d4 Fix small bug that kept a thread as a condvar thread after being signalled. 2019-03-19 22:43:13 -04:00
Fernando Sahmkow
acbdfdae64 Add CondVar Thread State. 2019-03-19 20:32:47 -04:00
Fernando Sahmkow
774f139e65 Small fixes to address_arbiter to better match the IDB. 2019-03-19 20:32:46 -04:00
22 changed files with 897 additions and 98 deletions

View File

@@ -98,6 +98,7 @@ add_library(common STATIC
microprofile.h
microprofileui.h
misc.cpp
multi_level_queue.h
page_table.cpp
page_table.h
param_package.cpp

View File

@@ -58,4 +58,43 @@ inline u64 CountLeadingZeroes64(u64 value) {
return __builtin_clzll(value);
}
#endif
#ifdef _MSC_VER
inline u32 CountTrailingZeroes32(u32 value) {
unsigned long trailing_zero = 0;
if (_BitScanForward(&trailing_zero, value) != 0) {
return trailing_zero;
}
return 32;
}
inline u64 CountTrailingZeroes64(u64 value) {
unsigned long trailing_zero = 0;
if (_BitScanForward64(&trailing_zero, value) != 0) {
return trailing_zero;
}
return 64;
}
#else
inline u32 CountTrailingZeroes32(u32 value) {
if (value == 0) {
return 32;
}
return __builtin_ctz(value);
}
inline u64 CountTrailingZeroes64(u64 value) {
if (value == 0) {
return 64;
}
return __builtin_ctzll(value);
}
#endif
} // namespace Common

View File

@@ -0,0 +1,337 @@
// Copyright 2019 TuxSH
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <iterator>
#include <list>
#include <utility>
#include "common/bit_util.h"
#include "common/common_types.h"
namespace Common {
/**
* A MultiLevelQueue is a type of priority queue which has the following characteristics:
* - iteratable through each of its elements.
* - back can be obtained.
* - O(1) add, lookup (both front and back)
* - discrete priorities and a max of 64 priorities (limited domain)
* This type of priority queue is normaly used for managing threads within an scheduler
*/
template <typename T, std::size_t Depth>
class MultiLevelQueue {
public:
using value_type = T;
using reference = value_type&;
using const_reference = const value_type&;
using pointer = value_type*;
using const_pointer = const value_type*;
using difference_type = typename std::pointer_traits<pointer>::difference_type;
using size_type = std::size_t;
template <bool is_constant>
class iterator_impl {
public:
using iterator_category = std::bidirectional_iterator_tag;
using value_type = T;
using pointer = std::conditional_t<is_constant, T*, const T*>;
using reference = std::conditional_t<is_constant, const T&, T&>;
using difference_type = typename std::pointer_traits<pointer>::difference_type;
friend bool operator==(const iterator_impl& lhs, const iterator_impl& rhs) {
if (lhs.IsEnd() && rhs.IsEnd())
return true;
return std::tie(lhs.current_priority, lhs.it) == std::tie(rhs.current_priority, rhs.it);
}
friend bool operator!=(const iterator_impl& lhs, const iterator_impl& rhs) {
return !operator==(lhs, rhs);
}
reference operator*() const {
return *it;
}
pointer operator->() const {
return it.operator->();
}
iterator_impl& operator++() {
if (IsEnd()) {
return *this;
}
++it;
if (it == GetEndItForPrio()) {
u64 prios = mlq.used_priorities;
prios &= ~((1ULL << (current_priority + 1)) - 1);
if (prios == 0) {
current_priority = mlq.depth();
} else {
current_priority = CountTrailingZeroes64(prios);
it = GetBeginItForPrio();
}
}
return *this;
}
iterator_impl& operator--() {
if (IsEnd()) {
if (mlq.used_priorities != 0) {
current_priority = 63 - CountLeadingZeroes64(mlq.used_priorities);
it = GetEndItForPrio();
--it;
}
} else if (it == GetBeginItForPrio()) {
u64 prios = mlq.used_priorities;
prios &= (1ULL << current_priority) - 1;
if (prios != 0) {
current_priority = CountTrailingZeroes64(prios);
it = GetEndItForPrio();
--it;
}
} else {
--it;
}
return *this;
}
iterator_impl operator++(int) {
const iterator_impl v{*this};
++(*this);
return v;
}
iterator_impl operator--(int) {
const iterator_impl v{*this};
--(*this);
return v;
}
// allow implicit const->non-const
iterator_impl(const iterator_impl<false>& other)
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
iterator_impl(const iterator_impl<true>& other)
: mlq(other.mlq), it(other.it), current_priority(other.current_priority) {}
iterator_impl& operator=(const iterator_impl<false>& other) {
mlq = other.mlq;
it = other.it;
current_priority = other.current_priority;
return *this;
}
friend class iterator_impl<true>;
iterator_impl() = default;
private:
friend class MultiLevelQueue;
using container_ref =
std::conditional_t<is_constant, const MultiLevelQueue&, MultiLevelQueue&>;
using list_iterator = std::conditional_t<is_constant, typename std::list<T>::const_iterator,
typename std::list<T>::iterator>;
explicit iterator_impl(container_ref mlq, list_iterator it, u32 current_priority)
: mlq(mlq), it(it), current_priority(current_priority) {}
explicit iterator_impl(container_ref mlq, u32 current_priority)
: mlq(mlq), it(), current_priority(current_priority) {}
bool IsEnd() const {
return current_priority == mlq.depth();
}
list_iterator GetBeginItForPrio() const {
return mlq.levels[current_priority].begin();
}
list_iterator GetEndItForPrio() const {
return mlq.levels[current_priority].end();
}
container_ref mlq;
list_iterator it;
u32 current_priority;
};
using iterator = iterator_impl<false>;
using const_iterator = iterator_impl<true>;
void add(const T& element, u32 priority, bool send_back = true) {
if (send_back)
levels[priority].push_back(element);
else
levels[priority].push_front(element);
used_priorities |= 1ULL << priority;
}
void remove(const T& element, u32 priority) {
auto it = ListIterateTo(levels[priority], element);
if (it == levels[priority].end())
return;
levels[priority].erase(it);
if (levels[priority].empty()) {
used_priorities &= ~(1ULL << priority);
}
}
void adjust(const T& element, u32 old_priority, u32 new_priority, bool adjust_front = false) {
remove(element, old_priority);
add(element, new_priority, !adjust_front);
}
void adjust(const_iterator it, u32 old_priority, u32 new_priority, bool adjust_front = false) {
adjust(*it, old_priority, new_priority, adjust_front);
}
void transfer_to_front(const T& element, u32 priority, MultiLevelQueue& other) {
ListSplice(other.levels[priority], other.levels[priority].begin(), levels[priority],
ListIterateTo(levels[priority], element));
other.used_priorities |= 1ULL << priority;
if (levels[priority].empty()) {
used_priorities &= ~(1ULL << priority);
}
}
void transfer_to_front(const_iterator it, u32 priority, MultiLevelQueue& other) {
transfer_to_front(*it, priority, other);
}
void transfer_to_back(const T& element, u32 priority, MultiLevelQueue& other) {
ListSplice(other.levels[priority], other.levels[priority].end(), levels[priority],
ListIterateTo(levels[priority], element));
other.used_priorities |= 1ULL << priority;
if (levels[priority].empty()) {
used_priorities &= ~(1ULL << priority);
}
}
void transfer_to_back(const_iterator it, u32 priority, MultiLevelQueue& other) {
transfer_to_back(*it, priority, other);
}
void yield(u32 priority, std::size_t n = 1) {
ListShiftForward(levels[priority], n);
}
std::size_t depth() const {
return Depth;
}
std::size_t size(u32 priority) const {
return levels[priority].size();
}
std::size_t size() const {
u64 priorities = used_priorities;
std::size_t size = 0;
while (priorities != 0) {
const u64 current_priority = CountTrailingZeroes64(priorities);
size += levels[current_priority].size();
priorities &= ~(1ULL << current_priority);
}
return size;
}
bool empty() const {
return used_priorities == 0;
}
bool empty(u32 priority) const {
return (used_priorities & (1ULL << priority)) == 0;
}
u32 highest_priority_set(u32 max_priority = 0) const {
const u64 priorities =
max_priority == 0 ? used_priorities : (used_priorities & ~((1ULL << max_priority) - 1));
return priorities == 0 ? Depth : static_cast<u32>(CountTrailingZeroes64(priorities));
}
u32 lowest_priority_set(u32 min_priority = Depth - 1) const {
const u64 priorities = min_priority >= Depth - 1
? used_priorities
: (used_priorities & ((1ULL << (min_priority + 1)) - 1));
return priorities == 0 ? Depth : 63 - CountLeadingZeroes64(priorities);
}
const_iterator cbegin(u32 max_prio = 0) const {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? cend()
: const_iterator{*this, levels[priority].cbegin(), priority};
}
const_iterator begin(u32 max_prio = 0) const {
return cbegin(max_prio);
}
iterator begin(u32 max_prio = 0) {
const u32 priority = highest_priority_set(max_prio);
return priority == Depth ? end() : iterator{*this, levels[priority].begin(), priority};
}
const_iterator cend(u32 min_prio = Depth - 1) const {
return min_prio == Depth - 1 ? const_iterator{*this, Depth} : cbegin(min_prio + 1);
}
const_iterator end(u32 min_prio = Depth - 1) const {
return cend(min_prio);
}
iterator end(u32 min_prio = Depth - 1) {
return min_prio == Depth - 1 ? iterator{*this, Depth} : begin(min_prio + 1);
}
T& front(u32 max_priority = 0) {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
const T& front(u32 max_priority = 0) const {
const u32 priority = highest_priority_set(max_priority);
return levels[priority == Depth ? 0 : priority].front();
}
T back(u32 min_priority = Depth - 1) {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
const T& back(u32 min_priority = Depth - 1) const {
const u32 priority = lowest_priority_set(min_priority); // intended
return levels[priority == Depth ? 63 : priority].back();
}
private:
using const_list_iterator = typename std::list<T>::const_iterator;
static void ListShiftForward(std::list<T>& list, const std::size_t shift = 1) {
if (shift >= list.size()) {
return;
}
const auto begin_range = list.begin();
const auto end_range = std::next(begin_range, shift);
list.splice(list.end(), list, begin_range, end_range);
}
static void ListSplice(std::list<T>& in_list, const_list_iterator position,
std::list<T>& out_list, const_list_iterator element) {
in_list.splice(position, out_list, element);
}
static const_list_iterator ListIterateTo(const std::list<T>& list, const T& element) {
auto it = list.cbegin();
while (it != list.cend() && *it != element) {
++it;
}
return it;
}
std::array<std::list<T>, Depth> levels;
u64 used_priorities = 0;
};
} // namespace Common

View File

@@ -26,7 +26,7 @@ void WakeThreads(const std::vector<SharedPtr<Thread>>& waiting_threads, s32 num_
// them all.
std::size_t last = waiting_threads.size();
if (num_to_wake > 0) {
last = num_to_wake;
last = std::min(last, static_cast<std::size_t>(num_to_wake));
}
// Signal the waiting threads.
@@ -90,9 +90,9 @@ ResultCode AddressArbiter::ModifyByWaitingCountAndSignalToAddressIfEqual(VAddr a
// Determine the modified value depending on the waiting count.
s32 updated_value;
if (waiting_threads.empty()) {
updated_value = value - 1;
} else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
updated_value = value + 1;
} else if (num_to_wake <= 0 || waiting_threads.size() <= static_cast<u32>(num_to_wake)) {
updated_value = value - 1;
} else {
updated_value = value;
}

View File

@@ -62,7 +62,8 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_
if (thread->GetMutexWaitAddress() != 0 || thread->GetCondVarWaitAddress() != 0 ||
thread->GetWaitHandle() != 0) {
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex ||
thread->GetStatus() == ThreadStatus::WaitCondVar);
thread->SetMutexWaitAddress(0);
thread->SetCondVarWaitAddress(0);
thread->SetWaitHandle(0);

View File

@@ -30,7 +30,7 @@ Scheduler::~Scheduler() {
bool Scheduler::HaveReadyThreads() const {
std::lock_guard<std::mutex> lock(scheduler_mutex);
return ready_queue.get_first() != nullptr;
return !ready_queue.empty();
}
Thread* Scheduler::GetCurrentThread() const {
@@ -46,22 +46,27 @@ Thread* Scheduler::PopNextReadyThread() {
Thread* thread = GetCurrentThread();
if (thread && thread->GetStatus() == ThreadStatus::Running) {
if (ready_queue.empty()) {
return thread;
}
// We have to do better than the current thread.
// This call returns null when that's not possible.
next = ready_queue.pop_first_better(thread->GetPriority());
if (!next) {
// Otherwise just keep going with the current thread
next = ready_queue.front();
if (next == nullptr || next->GetPriority() >= thread->GetPriority()) {
next = thread;
}
} else {
next = ready_queue.pop_first();
if (ready_queue.empty()) {
return nullptr;
}
next = ready_queue.front();
}
return next;
}
void Scheduler::SwitchContext(Thread* new_thread) {
Thread* const previous_thread = GetCurrentThread();
Thread* previous_thread = GetCurrentThread();
Process* const previous_process = system.Kernel().CurrentProcess();
UpdateLastContextSwitchTime(previous_thread, previous_process);
@@ -75,7 +80,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
if (previous_thread->GetStatus() == ThreadStatus::Running) {
// This is only the case when a reschedule is triggered without the current thread
// yielding execution (i.e. an event triggered, system core time-sliced, etc)
ready_queue.push_front(previous_thread->GetPriority(), previous_thread);
ready_queue.add(previous_thread, previous_thread->GetPriority(), false);
previous_thread->SetStatus(ThreadStatus::Ready);
}
}
@@ -90,7 +95,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
current_thread = new_thread;
ready_queue.remove(new_thread->GetPriority(), new_thread);
ready_queue.remove(new_thread, new_thread->GetPriority());
new_thread->SetStatus(ThreadStatus::Running);
auto* const thread_owner_process = current_thread->GetOwnerProcess();
@@ -147,7 +152,6 @@ void Scheduler::AddThread(SharedPtr<Thread> thread, u32 priority) {
std::lock_guard<std::mutex> lock(scheduler_mutex);
thread_list.push_back(std::move(thread));
ready_queue.prepare(priority);
}
void Scheduler::RemoveThread(Thread* thread) {
@@ -161,33 +165,37 @@ void Scheduler::ScheduleThread(Thread* thread, u32 priority) {
std::lock_guard<std::mutex> lock(scheduler_mutex);
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
ready_queue.push_back(priority, thread);
ready_queue.add(thread, priority);
}
void Scheduler::UnscheduleThread(Thread* thread, u32 priority) {
std::lock_guard<std::mutex> lock(scheduler_mutex);
ASSERT(thread->GetStatus() == ThreadStatus::Ready);
ready_queue.remove(priority, thread);
ready_queue.remove(thread, priority);
}
void Scheduler::SetThreadPriority(Thread* thread, u32 priority) {
std::lock_guard<std::mutex> lock(scheduler_mutex);
if (thread->GetPriority() == priority) {
return;
}
// If thread was ready, adjust queues
if (thread->GetStatus() == ThreadStatus::Ready)
ready_queue.move(thread, thread->GetPriority(), priority);
else
ready_queue.prepare(priority);
ready_queue.adjust(thread, thread->GetPriority(), priority);
}
Thread* Scheduler::GetNextSuggestedThread(u32 core, u32 maximum_priority) const {
std::lock_guard<std::mutex> lock(scheduler_mutex);
const u32 mask = 1U << core;
return ready_queue.get_first_filter([mask, maximum_priority](Thread const* thread) {
return (thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority;
});
for (auto* thread : ready_queue) {
if ((thread->GetAffinityMask() & mask) != 0 && thread->GetPriority() < maximum_priority) {
return thread;
}
}
return nullptr;
}
void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {

View File

@@ -7,7 +7,7 @@
#include <mutex>
#include <vector>
#include "common/common_types.h"
#include "common/thread_queue_list.h"
#include "common/multi_level_queue.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/thread.h"
@@ -156,7 +156,7 @@ private:
std::vector<SharedPtr<Thread>> thread_list;
/// Lists only ready thread ids.
Common::ThreadQueueList<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
Common::MultiLevelQueue<Thread*, THREADPRIO_LOWEST + 1> ready_queue;
SharedPtr<Thread> current_thread = nullptr;

View File

@@ -175,11 +175,8 @@ static ResultCode SetHeapSize(VAddr* heap_addr, u64 heap_size) {
return ERR_INVALID_SIZE;
}
auto& vm_manager = Core::CurrentProcess()->VMManager();
const VAddr heap_base = vm_manager.GetHeapRegionBaseAddress();
const auto alloc_result =
vm_manager.HeapAllocate(heap_base, heap_size, VMAPermission::ReadWrite);
auto& vm_manager = Core::System::GetInstance().Kernel().CurrentProcess()->VMManager();
const auto alloc_result = vm_manager.SetHeapSize(heap_size);
if (alloc_result.Failed()) {
return alloc_result.Code();
}
@@ -809,7 +806,7 @@ static ResultCode GetInfo(u64* result, u64 info_id, u64 handle, u64 info_sub_id)
return RESULT_SUCCESS;
case GetInfoType::TotalHeapUsage:
*result = process->VMManager().GetTotalHeapUsage();
*result = process->VMManager().GetCurrentHeapSize();
return RESULT_SUCCESS;
case GetInfoType::IsVirtualAddressMemoryEnabled:
@@ -1356,7 +1353,7 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
current_thread->SetCondVarWaitAddress(condition_variable_addr);
current_thread->SetMutexWaitAddress(mutex_addr);
current_thread->SetWaitHandle(thread_handle);
current_thread->SetStatus(ThreadStatus::WaitMutex);
current_thread->SetStatus(ThreadStatus::WaitCondVar);
current_thread->InvalidateWakeupCallback();
current_thread->WakeAfterDelay(nano_seconds);
@@ -1400,10 +1397,10 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
// them all.
std::size_t last = waiting_threads.size();
if (target != -1)
last = target;
last = std::min(waiting_threads.size(), static_cast<std::size_t>(target));
// If there are no threads waiting on this condition variable, just exit
if (last > waiting_threads.size())
if (last == 0)
return RESULT_SUCCESS;
for (std::size_t index = 0; index < last; ++index) {
@@ -1411,6 +1408,9 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
ASSERT(thread->GetCondVarWaitAddress() == condition_variable_addr);
// liberate Cond Var Thread.
thread->SetCondVarWaitAddress(0);
std::size_t current_core = Core::System::GetInstance().CurrentCoreIndex();
auto& monitor = Core::System::GetInstance().Monitor();
@@ -1429,10 +1429,9 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
}
} while (!monitor.ExclusiveWrite32(current_core, thread->GetMutexWaitAddress(),
thread->GetWaitHandle()));
if (mutex_val == 0) {
// We were able to acquire the mutex, resume this thread.
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
thread->ResumeFromWait();
auto* const lock_owner = thread->GetLockOwner();
@@ -1442,8 +1441,8 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
thread->SetLockOwner(nullptr);
thread->SetMutexWaitAddress(0);
thread->SetCondVarWaitAddress(0);
thread->SetWaitHandle(0);
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
} else {
// Atomically signal that the mutex now has a waiting thread.
do {
@@ -1462,12 +1461,11 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
auto owner = handle_table.Get<Thread>(owner_handle);
ASSERT(owner);
ASSERT(thread->GetStatus() == ThreadStatus::WaitMutex);
ASSERT(thread->GetStatus() == ThreadStatus::WaitCondVar);
thread->InvalidateWakeupCallback();
thread->SetStatus(ThreadStatus::WaitMutex);
owner->AddMutexWaiter(thread);
Core::System::GetInstance().CpuCore(thread->GetProcessorID()).PrepareReschedule();
}
}

View File

@@ -105,6 +105,7 @@ void Thread::ResumeFromWait() {
case ThreadStatus::WaitSleep:
case ThreadStatus::WaitIPC:
case ThreadStatus::WaitMutex:
case ThreadStatus::WaitCondVar:
case ThreadStatus::WaitArb:
break;

View File

@@ -51,7 +51,8 @@ enum class ThreadStatus {
WaitIPC, ///< Waiting for the reply from an IPC request
WaitSynchAny, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
WaitSynchAll, ///< Waiting due to WaitSynchronizationN with wait_all = true
WaitMutex, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc
WaitMutex, ///< Waiting due to an ArbitrateLock svc
WaitCondVar, ///< Waiting due to an WaitProcessWideKey svc
WaitArb, ///< Waiting due to a SignalToAddress/WaitForAddress svc
Dormant, ///< Created but not yet made ready
Dead ///< Run to completion, or forcefully terminated

View File

@@ -256,57 +256,50 @@ ResultCode VMManager::ReprotectRange(VAddr target, u64 size, VMAPermission new_p
return RESULT_SUCCESS;
}
ResultVal<VAddr> VMManager::HeapAllocate(VAddr target, u64 size, VMAPermission perms) {
if (!IsWithinHeapRegion(target, size)) {
return ERR_INVALID_ADDRESS;
ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
if (size > GetHeapRegionSize()) {
return ERR_OUT_OF_MEMORY;
}
// No need to do any additional work if the heap is already the given size.
if (size == GetCurrentHeapSize()) {
return MakeResult(heap_region_base);
}
if (heap_memory == nullptr) {
// Initialize heap
heap_memory = std::make_shared<std::vector<u8>>();
heap_start = heap_end = target;
heap_memory = std::make_shared<std::vector<u8>>(size);
heap_end = heap_region_base + size;
} else {
UnmapRange(heap_start, heap_end - heap_start);
UnmapRange(heap_region_base, GetCurrentHeapSize());
}
// If necessary, expand backing vector to cover new heap extents.
if (target < heap_start) {
heap_memory->insert(begin(*heap_memory), heap_start - target, 0);
heap_start = target;
// If necessary, expand backing vector to cover new heap extents in
// the case of allocating. Otherwise, shrink the backing memory,
// if a smaller heap has been requested.
const u64 old_heap_size = GetCurrentHeapSize();
if (size > old_heap_size) {
const u64 alloc_size = size - old_heap_size;
heap_memory->insert(heap_memory->end(), alloc_size, 0);
RefreshMemoryBlockMappings(heap_memory.get());
} else if (size < old_heap_size) {
heap_memory->resize(size);
heap_memory->shrink_to_fit();
RefreshMemoryBlockMappings(heap_memory.get());
}
if (target + size > heap_end) {
heap_memory->insert(end(*heap_memory), (target + size) - heap_end, 0);
heap_end = target + size;
RefreshMemoryBlockMappings(heap_memory.get());
}
ASSERT(heap_end - heap_start == heap_memory->size());
CASCADE_RESULT(auto vma, MapMemoryBlock(target, heap_memory, target - heap_start, size,
MemoryState::Heap));
Reprotect(vma, perms);
heap_end = heap_region_base + size;
ASSERT(GetCurrentHeapSize() == heap_memory->size());
heap_used = size;
return MakeResult<VAddr>(heap_end - size);
}
ResultCode VMManager::HeapFree(VAddr target, u64 size) {
if (!IsWithinHeapRegion(target, size)) {
return ERR_INVALID_ADDRESS;
const auto mapping_result =
MapMemoryBlock(heap_region_base, heap_memory, 0, size, MemoryState::Heap);
if (mapping_result.Failed()) {
return mapping_result.Code();
}
if (size == 0) {
return RESULT_SUCCESS;
}
const ResultCode result = UnmapRange(target, size);
if (result.IsError()) {
return result;
}
heap_used -= size;
return RESULT_SUCCESS;
return MakeResult<VAddr>(heap_region_base);
}
MemoryInfo VMManager::QueryMemory(VAddr address) const {
@@ -598,6 +591,7 @@ void VMManager::InitializeMemoryRegionRanges(FileSys::ProgramAddressSpaceType ty
heap_region_base = map_region_end;
heap_region_end = heap_region_base + heap_region_size;
heap_end = heap_region_base;
new_map_region_base = heap_region_end;
new_map_region_end = new_map_region_base + new_map_region_size;
@@ -692,10 +686,6 @@ u64 VMManager::GetTotalMemoryUsage() const {
return 0xF8000000;
}
u64 VMManager::GetTotalHeapUsage() const {
return heap_used;
}
VAddr VMManager::GetAddressSpaceBaseAddress() const {
return address_space_base;
}
@@ -778,6 +768,10 @@ u64 VMManager::GetHeapRegionSize() const {
return heap_region_end - heap_region_base;
}
u64 VMManager::GetCurrentHeapSize() const {
return heap_end - heap_region_base;
}
bool VMManager::IsWithinHeapRegion(VAddr address, u64 size) const {
return IsInsideAddressRange(address, size, GetHeapRegionBaseAddress(),
GetHeapRegionEndAddress());

View File

@@ -380,11 +380,41 @@ public:
/// Changes the permissions of a range of addresses, splitting VMAs as necessary.
ResultCode ReprotectRange(VAddr target, u64 size, VMAPermission new_perms);
ResultVal<VAddr> HeapAllocate(VAddr target, u64 size, VMAPermission perms);
ResultCode HeapFree(VAddr target, u64 size);
ResultCode MirrorMemory(VAddr dst_addr, VAddr src_addr, u64 size, MemoryState state);
/// Attempts to allocate a heap with the given size.
///
/// @param size The size of the heap to allocate in bytes.
///
/// @note If a heap is currently allocated, and this is called
/// with a size that is equal to the size of the current heap,
/// then this function will do nothing and return the current
/// heap's starting address, as there's no need to perform
/// any additional heap allocation work.
///
/// @note If a heap is currently allocated, and this is called
/// with a size less than the current heap's size, then
/// this function will attempt to shrink the heap.
///
/// @note If a heap is currently allocated, and this is called
/// with a size larger than the current heap's size, then
/// this function will attempt to extend the size of the heap.
///
/// @returns A result indicating either success or failure.
/// <p>
/// If successful, this function will return a result
/// containing the starting address to the allocated heap.
/// <p>
/// If unsuccessful, this function will return a result
/// containing an error code.
///
/// @pre The given size must lie within the allowable heap
/// memory region managed by this VMManager instance.
/// Failure to abide by this will result in ERR_OUT_OF_MEMORY
/// being returned as the result.
///
ResultVal<VAddr> SetHeapSize(u64 size);
/// Queries the memory manager for information about the given address.
///
/// @param address The address to query the memory manager about for information.
@@ -418,9 +448,6 @@ public:
/// Gets the total memory usage, used by svcGetInfo
u64 GetTotalMemoryUsage() const;
/// Gets the total heap usage, used by svcGetInfo
u64 GetTotalHeapUsage() const;
/// Gets the address space base address
VAddr GetAddressSpaceBaseAddress() const;
@@ -469,6 +496,13 @@ public:
/// Gets the total size of the heap region in bytes.
u64 GetHeapRegionSize() const;
/// Gets the total size of the current heap in bytes.
///
/// @note This is the current allocated heap size, not the size
/// of the region it's allowed to exist within.
///
u64 GetCurrentHeapSize() const;
/// Determines whether or not the specified range is within the heap region.
bool IsWithinHeapRegion(VAddr address, u64 size) const;
@@ -617,9 +651,6 @@ private:
VAddr new_map_region_base = 0;
VAddr new_map_region_end = 0;
VAddr main_code_region_base = 0;
VAddr main_code_region_end = 0;
VAddr tls_io_region_base = 0;
VAddr tls_io_region_end = 0;
@@ -628,9 +659,9 @@ private:
// This makes deallocation and reallocation of holes fast and keeps process memory contiguous
// in the emulator address space, allowing Memory::GetPointer to be reasonably safe.
std::shared_ptr<std::vector<u8>> heap_memory;
// The left/right bounds of the address space covered by heap_memory.
VAddr heap_start = 0;
// The end of the currently allocated heap. This is not an inclusive
// end of the range. This is essentially 'base_address + current_size'.
VAddr heap_end = 0;
u64 heap_used = 0;
};
} // namespace Kernel

View File

@@ -1,5 +1,7 @@
add_executable(tests
common/bit_field.cpp
common/bit_utils.cpp
common/multi_level_queue.cpp
common/param_package.cpp
common/ring_buffer.cpp
core/arm/arm_test_common.cpp

View File

@@ -0,0 +1,23 @@
// Copyright 2017 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <catch2/catch.hpp>
#include <math.h>
#include "common/bit_util.h"
namespace Common {
TEST_CASE("BitUtils::CountTrailingZeroes", "[common]") {
REQUIRE(Common::CountTrailingZeroes32(0) == 32);
REQUIRE(Common::CountTrailingZeroes64(0) == 64);
REQUIRE(Common::CountTrailingZeroes32(9) == 0);
REQUIRE(Common::CountTrailingZeroes32(8) == 3);
REQUIRE(Common::CountTrailingZeroes32(0x801000) == 12);
REQUIRE(Common::CountTrailingZeroes64(9) == 0);
REQUIRE(Common::CountTrailingZeroes64(8) == 3);
REQUIRE(Common::CountTrailingZeroes64(0x801000) == 12);
REQUIRE(Common::CountTrailingZeroes64(0x801000000000UL) == 36);
}
} // namespace Common

View File

@@ -0,0 +1,55 @@
// Copyright 2019 Yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <catch2/catch.hpp>
#include <math.h>
#include "common/common_types.h"
#include "common/multi_level_queue.h"
namespace Common {
TEST_CASE("MultiLevelQueue", "[common]") {
std::array<f32, 8> values = {0.0, 5.0, 1.0, 9.0, 8.0, 2.0, 6.0, 7.0};
Common::MultiLevelQueue<f32, 64> mlq;
REQUIRE(mlq.empty());
mlq.add(values[2], 2);
mlq.add(values[7], 7);
mlq.add(values[3], 3);
mlq.add(values[4], 4);
mlq.add(values[0], 0);
mlq.add(values[5], 5);
mlq.add(values[6], 6);
mlq.add(values[1], 1);
u32 index = 0;
bool all_set = true;
for (auto& f : mlq) {
all_set &= (f == values[index]);
index++;
}
REQUIRE(all_set);
REQUIRE(!mlq.empty());
f32 v = 8.0;
mlq.add(v, 2);
v = -7.0;
mlq.add(v, 2, false);
REQUIRE(mlq.front(2) == -7.0);
mlq.yield(2);
REQUIRE(mlq.front(2) == values[2]);
REQUIRE(mlq.back(2) == -7.0);
REQUIRE(mlq.empty(8));
v = 10.0;
mlq.add(v, 8);
mlq.adjust(v, 8, 9);
REQUIRE(mlq.front(9) == v);
REQUIRE(mlq.empty(8));
REQUIRE(!mlq.empty(9));
mlq.adjust(values[0], 0, 9);
REQUIRE(mlq.highest_priority_set() == 1);
REQUIRE(mlq.lowest_priority_set() == 9);
mlq.remove(values[1], 1);
REQUIRE(mlq.highest_priority_set() == 2);
REQUIRE(mlq.empty(1));
}
} // namespace Common

View File

@@ -128,7 +128,9 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_scheduler.cpp
renderer_vulkan/vk_scheduler.h
renderer_vulkan/vk_stream_buffer.cpp
renderer_vulkan/vk_stream_buffer.h)
renderer_vulkan/vk_stream_buffer.h
renderer_vulkan/vk_swapchain.cpp
renderer_vulkan/vk_swapchain.h)
target_include_directories(video_core PRIVATE ../../externals/Vulkan-Headers/include)
target_compile_definitions(video_core PRIVATE HAS_VULKAN)

View File

@@ -538,12 +538,12 @@ private:
return nullptr;
}
void Register(const Surface& object) {
void Register(const Surface& object) override {
RasterizerCache<Surface>::Register(object);
}
/// Unregisters an object from the cache
void Unregister(const Surface& object) {
void Unregister(const Surface& object) override {
if (object->IsReinterpreted()) {
auto interval = GetReinterpretInterval(object);
reinterpreted_surfaces.erase(interval);

View File

@@ -21,7 +21,7 @@ public:
CommandBufferPool(const VKDevice& device)
: VKFencedPool(COMMAND_BUFFER_POOL_SIZE), device{device} {}
void Allocate(std::size_t begin, std::size_t end) {
void Allocate(std::size_t begin, std::size_t end) override {
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const u32 graphics_family = device.GetGraphicsFamily();

View File

@@ -97,7 +97,7 @@ private:
class VKFenceWatch final : public VKResource {
public:
explicit VKFenceWatch();
~VKFenceWatch();
~VKFenceWatch() override;
/// Waits for the fence to be released.
void Wait();

View File

@@ -0,0 +1,210 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <limits>
#include <vector>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/frontend/framebuffer_layout.h"
#include "video_core/renderer_vulkan/declarations.h"
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
namespace Vulkan {
namespace {
vk::SurfaceFormatKHR ChooseSwapSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& formats) {
if (formats.size() == 1 && formats[0].format == vk::Format::eUndefined) {
return {vk::Format::eB8G8R8A8Unorm, vk::ColorSpaceKHR::eSrgbNonlinear};
}
const auto& found = std::find_if(formats.begin(), formats.end(), [](const auto& format) {
return format.format == vk::Format::eB8G8R8A8Unorm &&
format.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear;
});
return found != formats.end() ? *found : formats[0];
}
vk::PresentModeKHR ChooseSwapPresentMode(const std::vector<vk::PresentModeKHR>& modes) {
// Mailbox doesn't lock the application like fifo (vsync), prefer it
const auto& found = std::find_if(modes.begin(), modes.end(), [](const auto& mode) {
return mode == vk::PresentModeKHR::eMailbox;
});
return found != modes.end() ? *found : vk::PresentModeKHR::eFifo;
}
vk::Extent2D ChooseSwapExtent(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width,
u32 height) {
constexpr auto undefined_size{std::numeric_limits<u32>::max()};
if (capabilities.currentExtent.width != undefined_size) {
return capabilities.currentExtent;
}
vk::Extent2D extent = {width, height};
extent.width = std::max(capabilities.minImageExtent.width,
std::min(capabilities.maxImageExtent.width, extent.width));
extent.height = std::max(capabilities.minImageExtent.height,
std::min(capabilities.maxImageExtent.height, extent.height));
return extent;
}
} // namespace
VKSwapchain::VKSwapchain(vk::SurfaceKHR surface, const VKDevice& device)
: surface{surface}, device{device} {}
VKSwapchain::~VKSwapchain() = default;
void VKSwapchain::Create(u32 width, u32 height) {
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const auto physical_device = device.GetPhysical();
const vk::SurfaceCapabilitiesKHR capabilities{
physical_device.getSurfaceCapabilitiesKHR(surface, dld)};
if (capabilities.maxImageExtent.width == 0 || capabilities.maxImageExtent.height == 0) {
return;
}
dev.waitIdle(dld);
Destroy();
CreateSwapchain(capabilities, width, height);
CreateSemaphores();
CreateImageViews();
fences.resize(image_count, nullptr);
}
void VKSwapchain::AcquireNextImage() {
const auto dev{device.GetLogical()};
const auto& dld{device.GetDispatchLoader()};
dev.acquireNextImageKHR(*swapchain, std::numeric_limits<u64>::max(),
*present_semaphores[frame_index], {}, &image_index, dld);
if (auto& fence = fences[image_index]; fence) {
fence->Wait();
fence->Release();
fence = nullptr;
}
}
bool VKSwapchain::Present(vk::Semaphore render_semaphore, VKFence& fence) {
const vk::Semaphore present_semaphore{*present_semaphores[frame_index]};
const std::array<vk::Semaphore, 2> semaphores{present_semaphore, render_semaphore};
const u32 wait_semaphore_count{render_semaphore ? 2U : 1U};
const auto& dld{device.GetDispatchLoader()};
const auto present_queue{device.GetPresentQueue()};
bool recreated = false;
const vk::PresentInfoKHR present_info(wait_semaphore_count, semaphores.data(), 1,
&swapchain.get(), &image_index, {});
switch (const auto result = present_queue.presentKHR(&present_info, dld); result) {
case vk::Result::eSuccess:
break;
case vk::Result::eErrorOutOfDateKHR:
if (current_width > 0 && current_height > 0) {
Create(current_width, current_height);
recreated = true;
}
break;
default:
LOG_CRITICAL(Render_Vulkan, "Vulkan failed to present swapchain due to {}!",
vk::to_string(result));
UNREACHABLE();
}
ASSERT(fences[image_index] == nullptr);
fences[image_index] = &fence;
frame_index = (frame_index + 1) % image_count;
return recreated;
}
bool VKSwapchain::HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const {
// TODO(Rodrigo): Handle framebuffer pixel format changes
return framebuffer.width != current_width || framebuffer.height != current_height;
}
void VKSwapchain::CreateSwapchain(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width,
u32 height) {
const auto dev{device.GetLogical()};
const auto& dld{device.GetDispatchLoader()};
const auto physical_device{device.GetPhysical()};
const std::vector<vk::SurfaceFormatKHR> formats{
physical_device.getSurfaceFormatsKHR(surface, dld)};
const std::vector<vk::PresentModeKHR> present_modes{
physical_device.getSurfacePresentModesKHR(surface, dld)};
const vk::SurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)};
const vk::PresentModeKHR present_mode{ChooseSwapPresentMode(present_modes)};
extent = ChooseSwapExtent(capabilities, width, height);
current_width = extent.width;
current_height = extent.height;
u32 requested_image_count{capabilities.minImageCount + 1};
if (capabilities.maxImageCount > 0 && requested_image_count > capabilities.maxImageCount) {
requested_image_count = capabilities.maxImageCount;
}
vk::SwapchainCreateInfoKHR swapchain_ci(
{}, surface, requested_image_count, surface_format.format, surface_format.colorSpace,
extent, 1, vk::ImageUsageFlagBits::eColorAttachment, {}, {}, {},
capabilities.currentTransform, vk::CompositeAlphaFlagBitsKHR::eOpaque, present_mode, false,
{});
const u32 graphics_family{device.GetGraphicsFamily()};
const u32 present_family{device.GetPresentFamily()};
const std::array<u32, 2> queue_indices{graphics_family, present_family};
if (graphics_family != present_family) {
swapchain_ci.imageSharingMode = vk::SharingMode::eConcurrent;
swapchain_ci.queueFamilyIndexCount = static_cast<u32>(queue_indices.size());
swapchain_ci.pQueueFamilyIndices = queue_indices.data();
} else {
swapchain_ci.imageSharingMode = vk::SharingMode::eExclusive;
}
swapchain = dev.createSwapchainKHRUnique(swapchain_ci, nullptr, dld);
images = dev.getSwapchainImagesKHR(*swapchain, dld);
image_count = static_cast<u32>(images.size());
image_format = surface_format.format;
}
void VKSwapchain::CreateSemaphores() {
const auto dev{device.GetLogical()};
const auto& dld{device.GetDispatchLoader()};
present_semaphores.resize(image_count);
for (std::size_t i = 0; i < image_count; i++) {
present_semaphores[i] = dev.createSemaphoreUnique({}, nullptr, dld);
}
}
void VKSwapchain::CreateImageViews() {
const auto dev{device.GetLogical()};
const auto& dld{device.GetDispatchLoader()};
image_views.resize(image_count);
for (std::size_t i = 0; i < image_count; i++) {
const vk::ImageViewCreateInfo image_view_ci({}, images[i], vk::ImageViewType::e2D,
image_format, {},
{vk::ImageAspectFlagBits::eColor, 0, 1, 0, 1});
image_views[i] = dev.createImageViewUnique(image_view_ci, nullptr, dld);
}
}
void VKSwapchain::Destroy() {
frame_index = 0;
present_semaphores.clear();
framebuffers.clear();
image_views.clear();
swapchain.reset();
}
} // namespace Vulkan

View File

@@ -0,0 +1,92 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <vector>
#include "common/common_types.h"
#include "video_core/renderer_vulkan/declarations.h"
namespace Layout {
struct FramebufferLayout;
}
namespace Vulkan {
class VKDevice;
class VKFence;
class VKSwapchain {
public:
explicit VKSwapchain(vk::SurfaceKHR surface, const VKDevice& device);
~VKSwapchain();
/// Creates (or recreates) the swapchain with a given size.
void Create(u32 width, u32 height);
/// Acquires the next image in the swapchain, waits as needed.
void AcquireNextImage();
/// Presents the rendered image to the swapchain. Returns true when the swapchains had to be
/// recreated. Takes responsability for the ownership of fence.
bool Present(vk::Semaphore render_semaphore, VKFence& fence);
/// Returns true when the framebuffer layout has changed.
bool HasFramebufferChanged(const Layout::FramebufferLayout& framebuffer) const;
const vk::Extent2D& GetSize() const {
return extent;
}
u32 GetImageCount() const {
return image_count;
}
u32 GetImageIndex() const {
return image_index;
}
vk::Image GetImageIndex(u32 index) const {
return images[index];
}
vk::ImageView GetImageViewIndex(u32 index) const {
return *image_views[index];
}
vk::Format GetImageFormat() const {
return image_format;
}
private:
void CreateSwapchain(const vk::SurfaceCapabilitiesKHR& capabilities, u32 width, u32 height);
void CreateSemaphores();
void CreateImageViews();
void Destroy();
const vk::SurfaceKHR surface;
const VKDevice& device;
UniqueSwapchainKHR swapchain;
u32 image_count{};
std::vector<vk::Image> images;
std::vector<UniqueImageView> image_views;
std::vector<UniqueFramebuffer> framebuffers;
std::vector<VKFence*> fences;
std::vector<UniqueSemaphore> present_semaphores;
u32 image_index{};
u32 frame_index{};
vk::Format image_format{};
vk::Extent2D extent{};
u32 current_width{};
u32 current_height{};
};
} // namespace Vulkan

View File

@@ -234,6 +234,9 @@ QString WaitTreeThread::GetText() const {
case Kernel::ThreadStatus::WaitMutex:
status = tr("waiting for mutex");
break;
case Kernel::ThreadStatus::WaitCondVar:
status = tr("waiting for condition variable");
break;
case Kernel::ThreadStatus::WaitArb:
status = tr("waiting for address arbiter");
break;
@@ -269,6 +272,7 @@ QColor WaitTreeThread::GetColor() const {
case Kernel::ThreadStatus::WaitSynchAll:
case Kernel::ThreadStatus::WaitSynchAny:
case Kernel::ThreadStatus::WaitMutex:
case Kernel::ThreadStatus::WaitCondVar:
case Kernel::ThreadStatus::WaitArb:
return QColor(Qt::GlobalColor::red);
case Kernel::ThreadStatus::Dormant: