Compare commits
22 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
746dab407e | ||
|
|
f770c17d01 | ||
|
|
b404fcdf14 | ||
|
|
16dc3a1dd5 | ||
|
|
76f024865d | ||
|
|
a09d8cc8a2 | ||
|
|
db42bcb306 | ||
|
|
f35e09fe0d | ||
|
|
dde0814837 | ||
|
|
9dbba9240b | ||
|
|
3bc815a5dc | ||
|
|
522957f9f3 | ||
|
|
bbe700359d | ||
|
|
1e92ba2785 | ||
|
|
99a163478b | ||
|
|
abdb81ccaf | ||
|
|
9f63acac0f | ||
|
|
52980df1aa | ||
|
|
586cab6172 | ||
|
|
9c7319a4d4 | ||
|
|
acbdfdae64 | ||
|
|
774f139e65 |
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
337
src/common/multi_level_queue.h
Normal file
337
src/common/multi_level_queue.h
Normal 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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -105,6 +105,7 @@ void Thread::ResumeFromWait() {
|
||||
case ThreadStatus::WaitSleep:
|
||||
case ThreadStatus::WaitIPC:
|
||||
case ThreadStatus::WaitMutex:
|
||||
case ThreadStatus::WaitCondVar:
|
||||
case ThreadStatus::WaitArb:
|
||||
break;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
23
src/tests/common/bit_utils.cpp
Normal file
23
src/tests/common/bit_utils.cpp
Normal 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
|
||||
55
src/tests/common/multi_level_queue.cpp
Normal file
55
src/tests/common/multi_level_queue.cpp
Normal 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
|
||||
@@ -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)
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
210
src/video_core/renderer_vulkan/vk_swapchain.cpp
Normal file
210
src/video_core/renderer_vulkan/vk_swapchain.cpp
Normal 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
|
||||
92
src/video_core/renderer_vulkan/vk_swapchain.h
Normal file
92
src/video_core/renderer_vulkan/vk_swapchain.h
Normal 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
|
||||
@@ -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:
|
||||
|
||||
Reference in New Issue
Block a user