Compare commits
7 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46572d027d | ||
|
|
a70ed9c8ae | ||
|
|
013778aa21 | ||
|
|
be155f4d9d | ||
|
|
5fdfbfe25a | ||
|
|
b18ccf9399 | ||
|
|
e81a2080eb |
@@ -42,8 +42,6 @@ add_library(core STATIC
|
||||
hle/kernel/client_port.h
|
||||
hle/kernel/client_session.cpp
|
||||
hle/kernel/client_session.h
|
||||
hle/kernel/condition_variable.cpp
|
||||
hle/kernel/condition_variable.h
|
||||
hle/kernel/errors.h
|
||||
hle/kernel/event.cpp
|
||||
hle/kernel/event.h
|
||||
|
||||
@@ -12,13 +12,10 @@
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/gdbstub/gdbstub.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "core/hle/service/sm/controller.h"
|
||||
#include "core/hle/service/sm/sm.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/loader/loader.h"
|
||||
#include "core/memory_setup.h"
|
||||
@@ -29,8 +26,6 @@ namespace Core {
|
||||
|
||||
/*static*/ System System::s_instance;
|
||||
|
||||
System::~System() = default;
|
||||
|
||||
System::ResultStatus System::RunLoop(bool tight_loop) {
|
||||
status = ResultStatus::Success;
|
||||
if (!cpu_core) {
|
||||
@@ -172,12 +167,10 @@ System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) {
|
||||
|
||||
telemetry_session = std::make_unique<Core::TelemetrySession>();
|
||||
|
||||
service_manager = std::make_shared<Service::SM::ServiceManager>();
|
||||
|
||||
HW::Init();
|
||||
Kernel::Init(system_mode);
|
||||
scheduler = std::make_unique<Kernel::Scheduler>(cpu_core.get());
|
||||
Service::Init(service_manager);
|
||||
Service::Init();
|
||||
GDBStub::Init();
|
||||
|
||||
if (!VideoCore::Init(emu_window)) {
|
||||
@@ -207,26 +200,17 @@ void System::Shutdown() {
|
||||
VideoCore::Shutdown();
|
||||
GDBStub::Shutdown();
|
||||
Service::Shutdown();
|
||||
scheduler.reset();
|
||||
scheduler = nullptr;
|
||||
Kernel::Shutdown();
|
||||
HW::Shutdown();
|
||||
service_manager.reset();
|
||||
telemetry_session.reset();
|
||||
gpu_core.reset();
|
||||
cpu_core.reset();
|
||||
telemetry_session = nullptr;
|
||||
gpu_core = nullptr;
|
||||
cpu_core = nullptr;
|
||||
CoreTiming::Shutdown();
|
||||
|
||||
app_loader.reset();
|
||||
app_loader = nullptr;
|
||||
|
||||
LOG_DEBUG(Core, "Shutdown OK");
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& System::ServiceManager() {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
const Service::SM::ServiceManager& System::ServiceManager() const {
|
||||
return *service_manager;
|
||||
}
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -19,16 +19,10 @@
|
||||
class EmuWindow;
|
||||
class ARM_Interface;
|
||||
|
||||
namespace Service::SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
namespace Core {
|
||||
|
||||
class System {
|
||||
public:
|
||||
~System();
|
||||
|
||||
/**
|
||||
* Gets the instance of the System singleton class.
|
||||
* @returns Reference to the instance of the System singleton class.
|
||||
@@ -143,9 +137,6 @@ public:
|
||||
return *app_loader;
|
||||
}
|
||||
|
||||
Service::SM::ServiceManager& ServiceManager();
|
||||
const Service::SM::ServiceManager& ServiceManager() const;
|
||||
|
||||
void SetGPUDebugContext(std::shared_ptr<Tegra::DebugContext> context) {
|
||||
debug_context = std::move(context);
|
||||
}
|
||||
@@ -180,9 +171,6 @@ private:
|
||||
/// When true, signals that a reschedule should happen
|
||||
bool reschedule_pending{};
|
||||
|
||||
/// Service manager
|
||||
std::shared_ptr<Service::SM::ServiceManager> service_manager;
|
||||
|
||||
/// Telemetry session for this emulation session
|
||||
std::unique_ptr<Core::TelemetrySession> telemetry_session;
|
||||
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/kernel/condition_variable.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/object_address_table.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
ConditionVariable::ConditionVariable() {}
|
||||
ConditionVariable::~ConditionVariable() {}
|
||||
|
||||
ResultVal<SharedPtr<ConditionVariable>> ConditionVariable::Create(VAddr guest_addr,
|
||||
std::string name) {
|
||||
SharedPtr<ConditionVariable> condition_variable(new ConditionVariable);
|
||||
|
||||
condition_variable->name = std::move(name);
|
||||
condition_variable->guest_addr = guest_addr;
|
||||
condition_variable->mutex_addr = 0;
|
||||
|
||||
// Condition variables are referenced by guest address, so track this in the kernel
|
||||
g_object_address_table.Insert(guest_addr, condition_variable);
|
||||
|
||||
return MakeResult<SharedPtr<ConditionVariable>>(std::move(condition_variable));
|
||||
}
|
||||
|
||||
bool ConditionVariable::ShouldWait(Thread* thread) const {
|
||||
return GetAvailableCount() <= 0;
|
||||
}
|
||||
|
||||
void ConditionVariable::Acquire(Thread* thread) {
|
||||
if (GetAvailableCount() <= 0)
|
||||
return;
|
||||
|
||||
SetAvailableCount(GetAvailableCount() - 1);
|
||||
}
|
||||
|
||||
ResultCode ConditionVariable::Release(s32 target) {
|
||||
if (target == -1) {
|
||||
// When -1, wake up all waiting threads
|
||||
SetAvailableCount(static_cast<s32>(GetWaitingThreads().size()));
|
||||
WakeupAllWaitingThreads();
|
||||
} else {
|
||||
// Otherwise, wake up just a single thread
|
||||
SetAvailableCount(target);
|
||||
WakeupWaitingThread(GetHighestPriorityReadyThread());
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
s32 ConditionVariable::GetAvailableCount() const {
|
||||
return Memory::Read32(guest_addr);
|
||||
}
|
||||
|
||||
void ConditionVariable::SetAvailableCount(s32 value) const {
|
||||
Memory::Write32(guest_addr, value);
|
||||
}
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -1,63 +0,0 @@
|
||||
// Copyright 2018 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <queue>
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/wait_object.h"
|
||||
#include "core/hle/result.h"
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class ConditionVariable final : public WaitObject {
|
||||
public:
|
||||
/**
|
||||
* Creates a condition variable.
|
||||
* @param guest_addr Address of the object tracking the condition variable in guest memory. If
|
||||
* specified, this condition variable will update the guest object when its state changes.
|
||||
* @param name Optional name of condition variable.
|
||||
* @return The created condition variable.
|
||||
*/
|
||||
static ResultVal<SharedPtr<ConditionVariable>> Create(VAddr guest_addr,
|
||||
std::string name = "Unknown");
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "ConditionVariable";
|
||||
}
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
|
||||
static const HandleType HANDLE_TYPE = HandleType::ConditionVariable;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
s32 GetAvailableCount() const;
|
||||
void SetAvailableCount(s32 value) const;
|
||||
|
||||
std::string name; ///< Name of condition variable (optional)
|
||||
VAddr guest_addr; ///< Address of the guest condition variable value
|
||||
VAddr mutex_addr; ///< (optional) Address of guest mutex value associated with this condition
|
||||
///< variable, used for implementing events
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
/**
|
||||
* Releases a slot from a condition variable.
|
||||
* @param target The number of threads to wakeup, -1 is all.
|
||||
* @return ResultCode indicating if the operation succeeded.
|
||||
*/
|
||||
ResultCode Release(s32 target);
|
||||
|
||||
private:
|
||||
ConditionVariable();
|
||||
~ConditionVariable() override;
|
||||
};
|
||||
|
||||
} // namespace Kernel
|
||||
@@ -20,6 +20,7 @@ enum {
|
||||
MaxConnectionsReached = 52,
|
||||
|
||||
// Confirmed Switch OS error codes
|
||||
MisalignedAddress = 102,
|
||||
InvalidHandle = 114,
|
||||
Timeout = 117,
|
||||
SynchronizationCanceled = 118,
|
||||
|
||||
@@ -18,12 +18,10 @@ using Handle = u32;
|
||||
enum class HandleType : u32 {
|
||||
Unknown,
|
||||
Event,
|
||||
Mutex,
|
||||
SharedMemory,
|
||||
Thread,
|
||||
Process,
|
||||
AddressArbiter,
|
||||
ConditionVariable,
|
||||
Timer,
|
||||
ResourceLimit,
|
||||
CodeSet,
|
||||
@@ -63,9 +61,7 @@ public:
|
||||
bool IsWaitable() const {
|
||||
switch (GetHandleType()) {
|
||||
case HandleType::Event:
|
||||
case HandleType::Mutex:
|
||||
case HandleType::Thread:
|
||||
case HandleType::ConditionVariable:
|
||||
case HandleType::Timer:
|
||||
case HandleType::ServerPort:
|
||||
case HandleType::ServerSession:
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
#include "common/assert.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
@@ -15,124 +16,120 @@
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
void ReleaseThreadMutexes(Thread* thread) {
|
||||
for (auto& mtx : thread->held_mutexes) {
|
||||
mtx->SetHasWaiters(false);
|
||||
mtx->SetHoldingThread(nullptr);
|
||||
mtx->WakeupAllWaitingThreads();
|
||||
}
|
||||
thread->held_mutexes.clear();
|
||||
}
|
||||
/// Returns the number of threads that are waiting for a mutex, and the highest priority one among
|
||||
/// those.
|
||||
static std::pair<SharedPtr<Thread>, u32> GetHighestPriorityMutexWaitingThread(
|
||||
SharedPtr<Thread> current_thread, VAddr mutex_addr) {
|
||||
|
||||
Mutex::Mutex() {}
|
||||
Mutex::~Mutex() {}
|
||||
SharedPtr<Thread> highest_priority_thread;
|
||||
u32 num_waiters = 0;
|
||||
|
||||
SharedPtr<Mutex> Mutex::Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr,
|
||||
std::string name) {
|
||||
SharedPtr<Mutex> mutex(new Mutex);
|
||||
for (auto& thread : current_thread->wait_mutex_threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
continue;
|
||||
|
||||
mutex->guest_addr = guest_addr;
|
||||
mutex->name = std::move(name);
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
|
||||
// If mutex was initialized with a holding thread, acquire it by the holding thread
|
||||
if (holding_thread) {
|
||||
mutex->Acquire(holding_thread.get());
|
||||
++num_waiters;
|
||||
if (highest_priority_thread == nullptr ||
|
||||
thread->GetPriority() < highest_priority_thread->GetPriority()) {
|
||||
highest_priority_thread = thread;
|
||||
}
|
||||
}
|
||||
|
||||
// Mutexes are referenced by guest address, so track this in the kernel
|
||||
g_object_address_table.Insert(guest_addr, mutex);
|
||||
|
||||
return mutex;
|
||||
return {highest_priority_thread, num_waiters};
|
||||
}
|
||||
|
||||
bool Mutex::ShouldWait(Thread* thread) const {
|
||||
auto holding_thread = GetHoldingThread();
|
||||
return holding_thread != nullptr && thread != holding_thread;
|
||||
/// Update the mutex owner field of all threads waiting on the mutex to point to the new owner.
|
||||
static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_thread,
|
||||
SharedPtr<Thread> new_owner) {
|
||||
auto threads = current_thread->wait_mutex_threads;
|
||||
for (auto& thread : threads) {
|
||||
if (thread->mutex_wait_address != mutex_addr)
|
||||
continue;
|
||||
|
||||
ASSERT(thread->lock_owner == current_thread);
|
||||
current_thread->RemoveMutexWaiter(thread);
|
||||
if (new_owner != thread)
|
||||
new_owner->AddMutexWaiter(thread);
|
||||
}
|
||||
}
|
||||
|
||||
void Mutex::Acquire(Thread* thread) {
|
||||
ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
|
||||
ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
|
||||
}
|
||||
|
||||
priority = thread->current_priority;
|
||||
thread->held_mutexes.insert(this);
|
||||
SetHoldingThread(thread);
|
||||
thread->UpdatePriority();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
}
|
||||
SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
|
||||
SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
|
||||
|
||||
ResultCode Mutex::Release(Thread* thread) {
|
||||
auto holding_thread = GetHoldingThread();
|
||||
ASSERT(holding_thread);
|
||||
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
|
||||
// thread.
|
||||
ASSERT(requesting_thread == GetCurrentThread());
|
||||
|
||||
// We can only release the mutex if it's held by the calling thread.
|
||||
ASSERT(thread == holding_thread);
|
||||
u32 addr_value = Memory::Read32(address);
|
||||
|
||||
// If the mutex isn't being held, just return success.
|
||||
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
if (holding_thread == nullptr)
|
||||
return ERR_INVALID_HANDLE;
|
||||
|
||||
// Wait until the mutex is released
|
||||
GetCurrentThread()->mutex_wait_address = address;
|
||||
GetCurrentThread()->wait_handle = requesting_thread_handle;
|
||||
|
||||
GetCurrentThread()->status = THREADSTATUS_WAIT_MUTEX;
|
||||
GetCurrentThread()->wakeup_callback = nullptr;
|
||||
|
||||
// Update the lock holder thread's priority to prevent priority inversion.
|
||||
holding_thread->AddMutexWaiter(GetCurrentThread());
|
||||
|
||||
holding_thread->held_mutexes.erase(this);
|
||||
holding_thread->UpdatePriority();
|
||||
SetHoldingThread(nullptr);
|
||||
SetHasWaiters(!GetWaitingThreads().empty());
|
||||
WakeupAllWaitingThreads();
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
void Mutex::AddWaitingThread(SharedPtr<Thread> thread) {
|
||||
WaitObject::AddWaitingThread(thread);
|
||||
thread->pending_mutexes.insert(this);
|
||||
SetHasWaiters(true);
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Mutex::RemoveWaitingThread(Thread* thread) {
|
||||
WaitObject::RemoveWaitingThread(thread);
|
||||
thread->pending_mutexes.erase(this);
|
||||
if (!GetHasWaiters())
|
||||
SetHasWaiters(!GetWaitingThreads().empty());
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Mutex::UpdatePriority() {
|
||||
if (!GetHoldingThread())
|
||||
return;
|
||||
|
||||
u32 best_priority = THREADPRIO_LOWEST;
|
||||
for (auto& waiter : GetWaitingThreads()) {
|
||||
if (waiter->current_priority < best_priority)
|
||||
best_priority = waiter->current_priority;
|
||||
ResultCode Mutex::Release(VAddr address) {
|
||||
// The mutex address must be 4-byte aligned
|
||||
if ((address % sizeof(u32)) != 0) {
|
||||
return ResultCode(ErrorModule::Kernel, ErrCodes::MisalignedAddress);
|
||||
}
|
||||
|
||||
if (best_priority != priority) {
|
||||
priority = best_priority;
|
||||
GetHoldingThread()->UpdatePriority();
|
||||
auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
|
||||
|
||||
// There are no more threads waiting for the mutex, release it completely.
|
||||
if (thread == nullptr) {
|
||||
ASSERT(GetCurrentThread()->wait_mutex_threads.empty());
|
||||
Memory::Write32(address, 0);
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
}
|
||||
|
||||
Handle Mutex::GetOwnerHandle() const {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
return guest_state.holding_thread_handle;
|
||||
}
|
||||
// Transfer the ownership of the mutex from the previous owner to the new one.
|
||||
TransferMutexOwnership(address, GetCurrentThread(), thread);
|
||||
|
||||
SharedPtr<Thread> Mutex::GetHoldingThread() const {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
return g_handle_table.Get<Thread>(guest_state.holding_thread_handle);
|
||||
}
|
||||
u32 mutex_value = thread->wait_handle;
|
||||
|
||||
void Mutex::SetHoldingThread(SharedPtr<Thread> thread) {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
guest_state.holding_thread_handle.Assign(thread ? thread->guest_handle : 0);
|
||||
Memory::Write32(guest_addr, guest_state.raw);
|
||||
}
|
||||
if (num_waiters >= 2) {
|
||||
// Notify the guest that there are still some threads waiting for the mutex
|
||||
mutex_value |= Mutex::MutexHasWaitersFlag;
|
||||
}
|
||||
|
||||
bool Mutex::GetHasWaiters() const {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
return guest_state.has_waiters != 0;
|
||||
}
|
||||
// Grant the mutex to the next waiting thread and resume it.
|
||||
Memory::Write32(address, mutex_value);
|
||||
|
||||
void Mutex::SetHasWaiters(bool has_waiters) {
|
||||
GuestState guest_state{Memory::Read32(guest_addr)};
|
||||
guest_state.has_waiters.Assign(has_waiters ? 1 : 0);
|
||||
Memory::Write32(guest_addr, guest_state.raw);
|
||||
}
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -15,87 +15,23 @@ namespace Kernel {
|
||||
|
||||
class Thread;
|
||||
|
||||
class Mutex final : public WaitObject {
|
||||
class Mutex final {
|
||||
public:
|
||||
/**
|
||||
* Creates a mutex.
|
||||
* @param holding_thread Specifies a thread already holding the mutex. If not nullptr, this
|
||||
* thread will acquire the mutex.
|
||||
* @param guest_addr Address of the object tracking the mutex in guest memory. If specified,
|
||||
* this mutex will update the guest object when its state changes.
|
||||
* @param name Optional name of mutex
|
||||
* @return Pointer to new Mutex object
|
||||
*/
|
||||
static SharedPtr<Mutex> Create(SharedPtr<Kernel::Thread> holding_thread, VAddr guest_addr = 0,
|
||||
std::string name = "Unknown");
|
||||
/// Flag that indicates that a mutex still has threads waiting for it.
|
||||
static constexpr u32 MutexHasWaitersFlag = 0x40000000;
|
||||
/// Mask of the bits in a mutex address value that contain the mutex owner.
|
||||
static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
|
||||
|
||||
std::string GetTypeName() const override {
|
||||
return "Mutex";
|
||||
}
|
||||
std::string GetName() const override {
|
||||
return name;
|
||||
}
|
||||
/// Attempts to acquire a mutex at the specified address.
|
||||
static ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
|
||||
Handle requesting_thread_handle);
|
||||
|
||||
static const HandleType HANDLE_TYPE = HandleType::Mutex;
|
||||
HandleType GetHandleType() const override {
|
||||
return HANDLE_TYPE;
|
||||
}
|
||||
|
||||
u32 priority; ///< The priority of the mutex, used for priority inheritance.
|
||||
std::string name; ///< Name of mutex (optional)
|
||||
VAddr guest_addr; ///< Address of the guest mutex value
|
||||
|
||||
/**
|
||||
* Elevate the mutex priority to the best priority
|
||||
* among the priorities of all its waiting threads.
|
||||
*/
|
||||
void UpdatePriority();
|
||||
|
||||
bool ShouldWait(Thread* thread) const override;
|
||||
void Acquire(Thread* thread) override;
|
||||
|
||||
void AddWaitingThread(SharedPtr<Thread> thread) override;
|
||||
void RemoveWaitingThread(Thread* thread) override;
|
||||
|
||||
/**
|
||||
* Attempts to release the mutex from the specified thread.
|
||||
* @param thread Thread that wants to release the mutex.
|
||||
* @returns The result code of the operation.
|
||||
*/
|
||||
ResultCode Release(Thread* thread);
|
||||
|
||||
/// Gets the handle to the holding process stored in the guest state.
|
||||
Handle GetOwnerHandle() const;
|
||||
|
||||
/// Gets the Thread pointed to by the owner handle
|
||||
SharedPtr<Thread> GetHoldingThread() const;
|
||||
/// Sets the holding process handle in the guest state.
|
||||
void SetHoldingThread(SharedPtr<Thread> thread);
|
||||
|
||||
/// Returns the has_waiters bit in the guest state.
|
||||
bool GetHasWaiters() const;
|
||||
/// Sets the has_waiters bit in the guest state.
|
||||
void SetHasWaiters(bool has_waiters);
|
||||
/// Releases the mutex at the specified address.
|
||||
static ResultCode Release(VAddr address);
|
||||
|
||||
private:
|
||||
Mutex();
|
||||
~Mutex() override;
|
||||
|
||||
/// Object in guest memory used to track the mutex state
|
||||
union GuestState {
|
||||
u32_le raw;
|
||||
/// Handle of the thread that currently holds the mutex, 0 if available
|
||||
BitField<0, 30, u32_le> holding_thread_handle;
|
||||
/// 1 when there are threads waiting for this mutex, otherwise 0
|
||||
BitField<30, 1, u32_le> has_waiters;
|
||||
};
|
||||
static_assert(sizeof(GuestState) == 4, "GuestState size is incorrect");
|
||||
Mutex() = default;
|
||||
~Mutex() = default;
|
||||
};
|
||||
|
||||
/**
|
||||
* Releases all the mutexes held by the specified thread
|
||||
* @param thread Thread that is holding the mutexes
|
||||
*/
|
||||
void ReleaseThreadMutexes(Thread* thread);
|
||||
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -34,57 +34,57 @@ SharedPtr<ResourceLimit> ResourceLimit::GetForCategory(ResourceLimitCategory cat
|
||||
}
|
||||
}
|
||||
|
||||
s32 ResourceLimit::GetCurrentResourceValue(ResourceType resource) const {
|
||||
s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const {
|
||||
switch (resource) {
|
||||
case ResourceType::Commit:
|
||||
case COMMIT:
|
||||
return current_commit;
|
||||
case ResourceType::Thread:
|
||||
case THREAD:
|
||||
return current_threads;
|
||||
case ResourceType::Event:
|
||||
case EVENT:
|
||||
return current_events;
|
||||
case ResourceType::Mutex:
|
||||
case MUTEX:
|
||||
return current_mutexes;
|
||||
case ResourceType::Semaphore:
|
||||
case SEMAPHORE:
|
||||
return current_semaphores;
|
||||
case ResourceType::Timer:
|
||||
case TIMER:
|
||||
return current_timers;
|
||||
case ResourceType::SharedMemory:
|
||||
case SHARED_MEMORY:
|
||||
return current_shared_mems;
|
||||
case ResourceType::AddressArbiter:
|
||||
case ADDRESS_ARBITER:
|
||||
return current_address_arbiters;
|
||||
case ResourceType::CPUTime:
|
||||
case CPU_TIME:
|
||||
return current_cpu_time;
|
||||
default:
|
||||
LOG_ERROR(Kernel, "Unknown resource type=%08X", static_cast<u32>(resource));
|
||||
LOG_ERROR(Kernel, "Unknown resource type=%08X", resource);
|
||||
UNIMPLEMENTED();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
u32 ResourceLimit::GetMaxResourceValue(ResourceType resource) const {
|
||||
u32 ResourceLimit::GetMaxResourceValue(u32 resource) const {
|
||||
switch (resource) {
|
||||
case ResourceType::Priority:
|
||||
case PRIORITY:
|
||||
return max_priority;
|
||||
case ResourceType::Commit:
|
||||
case COMMIT:
|
||||
return max_commit;
|
||||
case ResourceType::Thread:
|
||||
case THREAD:
|
||||
return max_threads;
|
||||
case ResourceType::Event:
|
||||
case EVENT:
|
||||
return max_events;
|
||||
case ResourceType::Mutex:
|
||||
case MUTEX:
|
||||
return max_mutexes;
|
||||
case ResourceType::Semaphore:
|
||||
case SEMAPHORE:
|
||||
return max_semaphores;
|
||||
case ResourceType::Timer:
|
||||
case TIMER:
|
||||
return max_timers;
|
||||
case ResourceType::SharedMemory:
|
||||
case SHARED_MEMORY:
|
||||
return max_shared_mems;
|
||||
case ResourceType::AddressArbiter:
|
||||
case ADDRESS_ARBITER:
|
||||
return max_address_arbiters;
|
||||
case ResourceType::CPUTime:
|
||||
case CPU_TIME:
|
||||
return max_cpu_time;
|
||||
default:
|
||||
LOG_ERROR(Kernel, "Unknown resource type=%08X", static_cast<u32>(resource));
|
||||
LOG_ERROR(Kernel, "Unknown resource type=%08X", resource);
|
||||
UNIMPLEMENTED();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -16,17 +16,17 @@ enum class ResourceLimitCategory : u8 {
|
||||
OTHER = 3
|
||||
};
|
||||
|
||||
enum class ResourceType {
|
||||
Priority = 0,
|
||||
Commit = 1,
|
||||
Thread = 2,
|
||||
Event = 3,
|
||||
Mutex = 4,
|
||||
Semaphore = 5,
|
||||
Timer = 6,
|
||||
SharedMemory = 7,
|
||||
AddressArbiter = 8,
|
||||
CPUTime = 9,
|
||||
enum ResourceTypes {
|
||||
PRIORITY = 0,
|
||||
COMMIT = 1,
|
||||
THREAD = 2,
|
||||
EVENT = 3,
|
||||
MUTEX = 4,
|
||||
SEMAPHORE = 5,
|
||||
TIMER = 6,
|
||||
SHARED_MEMORY = 7,
|
||||
ADDRESS_ARBITER = 8,
|
||||
CPU_TIME = 9,
|
||||
};
|
||||
|
||||
class ResourceLimit final : public Object {
|
||||
@@ -60,14 +60,14 @@ public:
|
||||
* @param resource Requested resource type
|
||||
* @returns The current value of the resource type
|
||||
*/
|
||||
s32 GetCurrentResourceValue(ResourceType resource) const;
|
||||
s32 GetCurrentResourceValue(u32 resource) const;
|
||||
|
||||
/**
|
||||
* Gets the max value for the specified resource.
|
||||
* @param resource Requested resource type
|
||||
* @returns The max value of the resource type
|
||||
*/
|
||||
u32 GetMaxResourceValue(ResourceType resource) const;
|
||||
u32 GetMaxResourceValue(u32 resource) const;
|
||||
|
||||
/// Name of resource limit object.
|
||||
std::string name;
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/kernel/client_port.h"
|
||||
#include "core/hle/kernel/client_session.h"
|
||||
#include "core/hle/kernel/condition_variable.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
@@ -262,32 +261,14 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
|
||||
"requesting_current_thread_handle=0x%08X",
|
||||
holding_thread_handle, mutex_addr, requesting_thread_handle);
|
||||
|
||||
SharedPtr<Thread> holding_thread = g_handle_table.Get<Thread>(holding_thread_handle);
|
||||
SharedPtr<Thread> requesting_thread = g_handle_table.Get<Thread>(requesting_thread_handle);
|
||||
|
||||
ASSERT(requesting_thread);
|
||||
ASSERT(requesting_thread == GetCurrentThread());
|
||||
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
||||
if (!mutex) {
|
||||
// Create a new mutex for the specified address if one does not already exist
|
||||
mutex = Mutex::Create(holding_thread, mutex_addr);
|
||||
mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
|
||||
}
|
||||
|
||||
ASSERT(holding_thread == mutex->GetHoldingThread());
|
||||
|
||||
return WaitSynchronization1(mutex, requesting_thread.get());
|
||||
return Mutex::TryAcquire(mutex_addr, holding_thread_handle, requesting_thread_handle);
|
||||
}
|
||||
|
||||
/// Unlock a mutex
|
||||
static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
|
||||
LOG_TRACE(Kernel_SVC, "called mutex_addr=0x%llx", mutex_addr);
|
||||
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
||||
ASSERT(mutex);
|
||||
|
||||
return mutex->Release(GetCurrentThread());
|
||||
return Mutex::Release(mutex_addr);
|
||||
}
|
||||
|
||||
/// Break program execution
|
||||
@@ -407,16 +388,11 @@ static ResultCode SetThreadPriority(Handle handle, u32 priority) {
|
||||
// Note: The kernel uses the current process's resource limit instead of
|
||||
// the one from the thread owner's resource limit.
|
||||
SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
thread->SetPriority(priority);
|
||||
thread->UpdatePriority();
|
||||
|
||||
// Update the mutexes that this thread is waiting for
|
||||
for (auto& mutex : thread->pending_mutexes)
|
||||
mutex->UpdatePriority();
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
@@ -541,7 +517,7 @@ static ResultCode CreateThread(Handle* out_handle, VAddr entry_point, u64 arg, V
|
||||
}
|
||||
|
||||
SharedPtr<ResourceLimit>& resource_limit = Core::CurrentProcess()->resource_limit;
|
||||
if (resource_limit->GetMaxResourceValue(ResourceType::Priority) > priority) {
|
||||
if (resource_limit->GetMaxResourceValue(ResourceTypes::PRIORITY) > priority) {
|
||||
return ERR_NOT_AUTHORIZED;
|
||||
}
|
||||
|
||||
@@ -634,77 +610,20 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
|
||||
SharedPtr<Thread> thread = g_handle_table.Get<Thread>(thread_handle);
|
||||
ASSERT(thread);
|
||||
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(mutex_addr);
|
||||
if (!mutex) {
|
||||
// Create a new mutex for the specified address if one does not already exist
|
||||
mutex = Mutex::Create(thread, mutex_addr);
|
||||
mutex->name = Common::StringFromFormat("mutex-%llx", mutex_addr);
|
||||
}
|
||||
CASCADE_CODE(Mutex::Release(mutex_addr));
|
||||
|
||||
SharedPtr<ConditionVariable> condition_variable =
|
||||
g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
|
||||
if (!condition_variable) {
|
||||
// Create a new condition_variable for the specified address if one does not already exist
|
||||
condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
|
||||
condition_variable->name =
|
||||
Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
|
||||
}
|
||||
SharedPtr<Thread> current_thread = GetCurrentThread();
|
||||
current_thread->condvar_wait_address = condition_variable_addr;
|
||||
current_thread->mutex_wait_address = mutex_addr;
|
||||
current_thread->wait_handle = thread_handle;
|
||||
current_thread->status = THREADSTATUS_WAIT_MUTEX;
|
||||
current_thread->wakeup_callback = nullptr;
|
||||
|
||||
if (condition_variable->mutex_addr) {
|
||||
// Previously created the ConditionVariable using WaitProcessWideKeyAtomic, verify
|
||||
// everything is correct
|
||||
ASSERT(condition_variable->mutex_addr == mutex_addr);
|
||||
} else {
|
||||
// Previously created the ConditionVariable using SignalProcessWideKey, set the mutex
|
||||
// associated with it
|
||||
condition_variable->mutex_addr = mutex_addr;
|
||||
}
|
||||
current_thread->WakeAfterDelay(nano_seconds);
|
||||
|
||||
if (mutex->GetOwnerHandle()) {
|
||||
// Release the mutex if the current thread is holding it
|
||||
mutex->Release(thread.get());
|
||||
}
|
||||
|
||||
auto wakeup_callback = [mutex, nano_seconds](ThreadWakeupReason reason,
|
||||
SharedPtr<Thread> thread,
|
||||
SharedPtr<WaitObject> object, size_t index) {
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_SYNCH_ANY);
|
||||
|
||||
if (reason == ThreadWakeupReason::Timeout) {
|
||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
||||
return true;
|
||||
}
|
||||
|
||||
ASSERT(reason == ThreadWakeupReason::Signal);
|
||||
|
||||
// Now try to acquire the mutex and don't resume if it's not available.
|
||||
if (!mutex->ShouldWait(thread.get())) {
|
||||
mutex->Acquire(thread.get());
|
||||
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (nano_seconds == 0) {
|
||||
thread->SetWaitSynchronizationResult(RESULT_TIMEOUT);
|
||||
return true;
|
||||
}
|
||||
|
||||
thread->wait_objects = {mutex};
|
||||
mutex->AddWaitingThread(thread);
|
||||
thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
|
||||
|
||||
// Create an event to wake the thread up after the
|
||||
// specified nanosecond delay has passed
|
||||
thread->WakeAfterDelay(nano_seconds);
|
||||
thread->wakeup_callback = DefaultThreadWakeupCallback;
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
|
||||
return false;
|
||||
};
|
||||
CASCADE_CODE(
|
||||
WaitSynchronization1(condition_variable, thread.get(), nano_seconds, wakeup_callback));
|
||||
// Note: Deliberately don't attempt to inherit the lock owner's priority.
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
return RESULT_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -713,24 +632,53 @@ static ResultCode SignalProcessWideKey(VAddr condition_variable_addr, s32 target
|
||||
LOG_TRACE(Kernel_SVC, "called, condition_variable_addr=0x%llx, target=0x%08x",
|
||||
condition_variable_addr, target);
|
||||
|
||||
// Wakeup all or one thread - Any other value is unimplemented
|
||||
ASSERT(target == -1 || target == 1);
|
||||
u32 processed = 0;
|
||||
auto& thread_list = Core::System::GetInstance().Scheduler().GetThreadList();
|
||||
|
||||
SharedPtr<ConditionVariable> condition_variable =
|
||||
g_object_address_table.Get<ConditionVariable>(condition_variable_addr);
|
||||
if (!condition_variable) {
|
||||
// Create a new condition_variable for the specified address if one does not already exist
|
||||
condition_variable = ConditionVariable::Create(condition_variable_addr).Unwrap();
|
||||
condition_variable->name =
|
||||
Common::StringFromFormat("condition-variable-%llx", condition_variable_addr);
|
||||
}
|
||||
for (auto& thread : thread_list) {
|
||||
if (thread->condvar_wait_address != condition_variable_addr)
|
||||
continue;
|
||||
|
||||
CASCADE_CODE(condition_variable->Release(target));
|
||||
// Only process up to 'target' threads, unless 'target' is -1, in which case process
|
||||
// them all.
|
||||
if (target != -1 && processed >= target)
|
||||
break;
|
||||
|
||||
if (condition_variable->mutex_addr) {
|
||||
// If a mutex was created for this condition_variable, wait the current thread on it
|
||||
SharedPtr<Mutex> mutex = g_object_address_table.Get<Mutex>(condition_variable->mutex_addr);
|
||||
return WaitSynchronization1(mutex, GetCurrentThread());
|
||||
// If the mutex is not yet acquired, acquire it.
|
||||
u32 mutex_val = Memory::Read32(thread->mutex_wait_address);
|
||||
|
||||
if (mutex_val == 0) {
|
||||
// We were able to acquire the mutex, resume this thread.
|
||||
Memory::Write32(thread->mutex_wait_address, thread->wait_handle);
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
thread->ResumeFromWait();
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
if (lock_owner)
|
||||
lock_owner->RemoveMutexWaiter(thread);
|
||||
|
||||
thread->lock_owner = nullptr;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
} else {
|
||||
// Couldn't acquire the mutex, block the thread.
|
||||
Handle owner_handle = static_cast<Handle>(mutex_val & Mutex::MutexOwnerMask);
|
||||
auto owner = g_handle_table.Get<Thread>(owner_handle);
|
||||
ASSERT(owner);
|
||||
ASSERT(thread->status != THREADSTATUS_RUNNING);
|
||||
thread->status = THREADSTATUS_WAIT_MUTEX;
|
||||
thread->wakeup_callback = nullptr;
|
||||
|
||||
// Signal that the mutex now has a waiting thread.
|
||||
Memory::Write32(thread->mutex_wait_address, mutex_val | Mutex::MutexHasWaitersFlag);
|
||||
|
||||
owner->AddMutexWaiter(thread);
|
||||
|
||||
Core::System::GetInstance().PrepareReschedule();
|
||||
}
|
||||
|
||||
++processed;
|
||||
}
|
||||
|
||||
return RESULT_SUCCESS;
|
||||
|
||||
@@ -77,9 +77,6 @@ void Thread::Stop() {
|
||||
}
|
||||
wait_objects.clear();
|
||||
|
||||
// Release all the mutexes that this thread holds
|
||||
ReleaseThreadMutexes(this);
|
||||
|
||||
// Mark the TLS slot in the thread's page as free.
|
||||
u64 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
|
||||
u64 tls_slot =
|
||||
@@ -126,6 +123,19 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
|
||||
resume = thread->wakeup_callback(ThreadWakeupReason::Timeout, thread, nullptr, 0);
|
||||
}
|
||||
|
||||
if (thread->mutex_wait_address != 0 || thread->condvar_wait_address != 0 ||
|
||||
thread->wait_handle) {
|
||||
ASSERT(thread->status == THREADSTATUS_WAIT_MUTEX);
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
|
||||
auto lock_owner = thread->lock_owner;
|
||||
// Threads waking up by timeout from WaitProcessWideKey do not perform priority inheritance
|
||||
// and don't have a lock owner.
|
||||
ASSERT(lock_owner == nullptr);
|
||||
}
|
||||
|
||||
if (resume)
|
||||
thread->ResumeFromWait();
|
||||
}
|
||||
@@ -151,6 +161,7 @@ void Thread::ResumeFromWait() {
|
||||
case THREADSTATUS_WAIT_HLE_EVENT:
|
||||
case THREADSTATUS_WAIT_SLEEP:
|
||||
case THREADSTATUS_WAIT_IPC:
|
||||
case THREADSTATUS_WAIT_MUTEX:
|
||||
break;
|
||||
|
||||
case THREADSTATUS_READY:
|
||||
@@ -256,7 +267,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||
thread->last_running_ticks = CoreTiming::GetTicks();
|
||||
thread->processor_id = processor_id;
|
||||
thread->wait_objects.clear();
|
||||
thread->wait_address = 0;
|
||||
thread->mutex_wait_address = 0;
|
||||
thread->condvar_wait_address = 0;
|
||||
thread->wait_handle = 0;
|
||||
thread->name = std::move(name);
|
||||
thread->callback_handle = wakeup_callback_handle_table.Create(thread).Unwrap();
|
||||
thread->owner_process = owner_process;
|
||||
@@ -317,17 +330,8 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
|
||||
void Thread::SetPriority(u32 priority) {
|
||||
ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
|
||||
"Invalid priority value.");
|
||||
Core::System::GetInstance().Scheduler().SetThreadPriority(this, priority);
|
||||
nominal_priority = current_priority = priority;
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
u32 best_priority = nominal_priority;
|
||||
for (auto& mutex : held_mutexes) {
|
||||
if (mutex->priority < best_priority)
|
||||
best_priority = mutex->priority;
|
||||
}
|
||||
BoostPriority(best_priority);
|
||||
nominal_priority = priority;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::BoostPriority(u32 priority) {
|
||||
@@ -377,6 +381,38 @@ VAddr Thread::GetCommandBufferAddress() const {
|
||||
return GetTLSAddress() + CommandHeaderOffset;
|
||||
}
|
||||
|
||||
void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
thread->lock_owner = this;
|
||||
wait_mutex_threads.emplace_back(std::move(thread));
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||
boost::remove_erase(wait_mutex_threads, thread);
|
||||
thread->lock_owner = nullptr;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
// Find the highest priority among all the threads that are waiting for this thread's lock
|
||||
u32 new_priority = nominal_priority;
|
||||
for (const auto& thread : wait_mutex_threads) {
|
||||
if (thread->nominal_priority < new_priority)
|
||||
new_priority = thread->nominal_priority;
|
||||
}
|
||||
|
||||
if (new_priority == current_priority)
|
||||
return;
|
||||
|
||||
Core::System::GetInstance().Scheduler().SetThreadPriority(this, new_priority);
|
||||
|
||||
current_priority = new_priority;
|
||||
|
||||
// Recursively update the priority of the thread that depends on the priority of this one.
|
||||
if (lock_owner)
|
||||
lock_owner->UpdatePriority();
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
/**
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
enum ThreadPriority : u32 {
|
||||
THREADPRIO_HIGHEST = 0, ///< Highest thread priority
|
||||
THREADPRIO_USERLAND_MAX = 24, ///< Highest thread priority for userland apps
|
||||
THREADPRIO_DEFAULT = 48, ///< Default thread priority for userland apps
|
||||
THREADPRIO_DEFAULT = 44, ///< Default thread priority for userland apps
|
||||
THREADPRIO_LOWEST = 63, ///< Lowest thread priority
|
||||
};
|
||||
|
||||
@@ -43,6 +43,7 @@ enum ThreadStatus {
|
||||
THREADSTATUS_WAIT_IPC, ///< Waiting for the reply from an IPC request
|
||||
THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
|
||||
THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
|
||||
THREADSTATUS_WAIT_MUTEX, ///< Waiting due to an ArbitrateLock/WaitProcessWideKey svc
|
||||
THREADSTATUS_DORMANT, ///< Created but not yet made ready
|
||||
THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
|
||||
};
|
||||
@@ -54,7 +55,6 @@ enum class ThreadWakeupReason {
|
||||
|
||||
namespace Kernel {
|
||||
|
||||
class Mutex;
|
||||
class Process;
|
||||
|
||||
class Thread final : public WaitObject {
|
||||
@@ -103,18 +103,21 @@ public:
|
||||
*/
|
||||
void SetPriority(u32 priority);
|
||||
|
||||
/**
|
||||
* Boost's a thread's priority to the best priority among the thread's held mutexes.
|
||||
* This prevents priority inversion via priority inheritance.
|
||||
*/
|
||||
void UpdatePriority();
|
||||
|
||||
/**
|
||||
* Temporarily boosts the thread's priority until the next time it is scheduled
|
||||
* @param priority The new priority
|
||||
*/
|
||||
void BoostPriority(u32 priority);
|
||||
|
||||
/// Adds a thread to the list of threads that are waiting for a lock held by this thread.
|
||||
void AddMutexWaiter(SharedPtr<Thread> thread);
|
||||
|
||||
/// Removes a thread from the list of threads that are waiting for a lock held by this thread.
|
||||
void RemoveMutexWaiter(SharedPtr<Thread> thread);
|
||||
|
||||
/// Recalculates the current priority taking into account priority inheritance.
|
||||
void UpdatePriority();
|
||||
|
||||
/**
|
||||
* Gets the thread's thread ID
|
||||
* @return The thread's ID
|
||||
@@ -205,19 +208,22 @@ public:
|
||||
|
||||
VAddr tls_address; ///< Virtual address of the Thread Local Storage of the thread
|
||||
|
||||
/// Mutexes currently held by this thread, which will be released when it exits.
|
||||
boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
|
||||
|
||||
/// Mutexes that this thread is currently waiting for.
|
||||
boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes;
|
||||
|
||||
SharedPtr<Process> owner_process; ///< Process that owns this thread
|
||||
|
||||
/// Objects that the thread is waiting on, in the same order as they were
|
||||
// passed to WaitSynchronization1/N.
|
||||
std::vector<SharedPtr<WaitObject>> wait_objects;
|
||||
|
||||
VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
|
||||
/// List of threads that are waiting for a mutex that is held by this thread.
|
||||
std::vector<SharedPtr<Thread>> wait_mutex_threads;
|
||||
|
||||
/// Thread that owns the lock that this thread is waiting for.
|
||||
SharedPtr<Thread> lock_owner;
|
||||
|
||||
// If waiting on a ConditionVariable, this is the ConditionVariable address
|
||||
VAddr condvar_wait_address;
|
||||
VAddr mutex_wait_address; ///< If waiting on a Mutex, this is the mutex address
|
||||
Handle wait_handle; ///< The handle used to wait for the mutex.
|
||||
|
||||
std::string name;
|
||||
|
||||
|
||||
@@ -9,7 +9,8 @@
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/nvflinger/buffer_queue.h"
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
namespace Service {
|
||||
namespace NVFlinger {
|
||||
|
||||
BufferQueue::BufferQueue(u32 id, u64 layer_id) : id(id), layer_id(layer_id) {
|
||||
native_handle = Kernel::Event::Create(Kernel::ResetType::OneShot, "BufferQueue NativeHandle");
|
||||
@@ -110,4 +111,5 @@ void BufferQueue::SetBufferWaitEvent(Kernel::SharedPtr<Kernel::Event>&& wait_eve
|
||||
buffer_wait_event = std::move(wait_event);
|
||||
}
|
||||
|
||||
} // namespace Service::NVFlinger
|
||||
} // namespace NVFlinger
|
||||
} // namespace Service
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace CoreTiming {
|
||||
struct EventType;
|
||||
}
|
||||
|
||||
namespace Service::NVFlinger {
|
||||
namespace Service {
|
||||
namespace NVFlinger {
|
||||
|
||||
struct IGBPBuffer {
|
||||
u32_le magic;
|
||||
@@ -97,4 +98,5 @@ private:
|
||||
Kernel::SharedPtr<Kernel::Event> buffer_wait_event;
|
||||
};
|
||||
|
||||
} // namespace Service::NVFlinger
|
||||
} // namespace NVFlinger
|
||||
} // namespace Service
|
||||
|
||||
@@ -145,7 +145,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
|
||||
return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead);
|
||||
}
|
||||
case IPC::CommandType::Control: {
|
||||
Core::System::GetInstance().ServiceManager().InvokeControlRequest(context);
|
||||
SM::g_service_manager->InvokeControlRequest(context);
|
||||
break;
|
||||
}
|
||||
case IPC::CommandType::Request: {
|
||||
@@ -170,40 +170,42 @@ void AddNamedPort(std::string name, SharedPtr<ClientPort> port) {
|
||||
}
|
||||
|
||||
/// Initialize ServiceManager
|
||||
void Init(std::shared_ptr<SM::ServiceManager>& sm) {
|
||||
void Init() {
|
||||
// NVFlinger needs to be accessed by several services like Vi and AppletOE so we instantiate it
|
||||
// here and pass it into the respective InstallInterfaces functions.
|
||||
auto nv_flinger = std::make_shared<NVFlinger::NVFlinger>();
|
||||
|
||||
SM::ServiceManager::InstallInterfaces(sm);
|
||||
SM::g_service_manager = std::make_shared<SM::ServiceManager>();
|
||||
SM::ServiceManager::InstallInterfaces(SM::g_service_manager);
|
||||
|
||||
Account::InstallInterfaces(*sm);
|
||||
AM::InstallInterfaces(*sm, nv_flinger);
|
||||
AOC::InstallInterfaces(*sm);
|
||||
APM::InstallInterfaces(*sm);
|
||||
Audio::InstallInterfaces(*sm);
|
||||
Fatal::InstallInterfaces(*sm);
|
||||
FileSystem::InstallInterfaces(*sm);
|
||||
Friend::InstallInterfaces(*sm);
|
||||
HID::InstallInterfaces(*sm);
|
||||
LM::InstallInterfaces(*sm);
|
||||
NFP::InstallInterfaces(*sm);
|
||||
NIFM::InstallInterfaces(*sm);
|
||||
NS::InstallInterfaces(*sm);
|
||||
Nvidia::InstallInterfaces(*sm);
|
||||
PCTL::InstallInterfaces(*sm);
|
||||
Sockets::InstallInterfaces(*sm);
|
||||
SPL::InstallInterfaces(*sm);
|
||||
SSL::InstallInterfaces(*sm);
|
||||
Time::InstallInterfaces(*sm);
|
||||
VI::InstallInterfaces(*sm, nv_flinger);
|
||||
Set::InstallInterfaces(*sm);
|
||||
Account::InstallInterfaces(*SM::g_service_manager);
|
||||
AM::InstallInterfaces(*SM::g_service_manager, nv_flinger);
|
||||
AOC::InstallInterfaces(*SM::g_service_manager);
|
||||
APM::InstallInterfaces(*SM::g_service_manager);
|
||||
Audio::InstallInterfaces(*SM::g_service_manager);
|
||||
Fatal::InstallInterfaces(*SM::g_service_manager);
|
||||
FileSystem::InstallInterfaces(*SM::g_service_manager);
|
||||
Friend::InstallInterfaces(*SM::g_service_manager);
|
||||
HID::InstallInterfaces(*SM::g_service_manager);
|
||||
LM::InstallInterfaces(*SM::g_service_manager);
|
||||
NFP::InstallInterfaces(*SM::g_service_manager);
|
||||
NIFM::InstallInterfaces(*SM::g_service_manager);
|
||||
NS::InstallInterfaces(*SM::g_service_manager);
|
||||
Nvidia::InstallInterfaces(*SM::g_service_manager);
|
||||
PCTL::InstallInterfaces(*SM::g_service_manager);
|
||||
Sockets::InstallInterfaces(*SM::g_service_manager);
|
||||
SPL::InstallInterfaces(*SM::g_service_manager);
|
||||
SSL::InstallInterfaces(*SM::g_service_manager);
|
||||
Time::InstallInterfaces(*SM::g_service_manager);
|
||||
VI::InstallInterfaces(*SM::g_service_manager, nv_flinger);
|
||||
Set::InstallInterfaces(*SM::g_service_manager);
|
||||
|
||||
LOG_DEBUG(Service, "initialized OK");
|
||||
}
|
||||
|
||||
/// Shutdown ServiceManager
|
||||
void Shutdown() {
|
||||
SM::g_service_manager = nullptr;
|
||||
g_kernel_named_ports.clear();
|
||||
LOG_DEBUG(Service, "shutdown OK");
|
||||
}
|
||||
|
||||
@@ -178,7 +178,7 @@ private:
|
||||
};
|
||||
|
||||
/// Initialize ServiceManager
|
||||
void Init(std::shared_ptr<SM::ServiceManager>& sm);
|
||||
void Init();
|
||||
|
||||
/// Shutdown ServiceManager
|
||||
void Shutdown();
|
||||
|
||||
@@ -14,8 +14,6 @@
|
||||
|
||||
namespace Service::SM {
|
||||
|
||||
ServiceManager::~ServiceManager() = default;
|
||||
|
||||
void ServiceManager::InvokeControlRequest(Kernel::HLERequestContext& context) {
|
||||
controller_interface->InvokeRequest(context);
|
||||
}
|
||||
@@ -74,7 +72,7 @@ ResultVal<Kernel::SharedPtr<Kernel::ClientSession>> ServiceManager::ConnectToSer
|
||||
return client_port->Connect();
|
||||
}
|
||||
|
||||
SM::~SM() = default;
|
||||
std::shared_ptr<ServiceManager> g_service_manager;
|
||||
|
||||
/**
|
||||
* SM::Initialize service function
|
||||
|
||||
@@ -23,7 +23,7 @@ namespace Service::SM {
|
||||
class SM final : public ServiceFramework<SM> {
|
||||
public:
|
||||
SM(std::shared_ptr<ServiceManager> service_manager);
|
||||
~SM() override;
|
||||
~SM() = default;
|
||||
|
||||
private:
|
||||
void Initialize(Kernel::HLERequestContext& ctx);
|
||||
@@ -44,8 +44,6 @@ class ServiceManager {
|
||||
public:
|
||||
static void InstallInterfaces(std::shared_ptr<ServiceManager> self);
|
||||
|
||||
~ServiceManager();
|
||||
|
||||
ResultVal<Kernel::SharedPtr<Kernel::ServerPort>> RegisterService(std::string name,
|
||||
unsigned int max_sessions);
|
||||
ResultVal<Kernel::SharedPtr<Kernel::ClientPort>> GetServicePort(const std::string& name);
|
||||
@@ -61,4 +59,6 @@ private:
|
||||
std::unordered_map<std::string, Kernel::SharedPtr<Kernel::ClientPort>> registered_services;
|
||||
};
|
||||
|
||||
extern std::shared_ptr<ServiceManager> g_service_manager;
|
||||
|
||||
} // namespace Service::SM
|
||||
|
||||
@@ -582,7 +582,7 @@ public:
|
||||
{2203, nullptr, "SetLayerSize"},
|
||||
{2204, nullptr, "GetLayerZ"},
|
||||
{2205, &ISystemDisplayService::SetLayerZ, "SetLayerZ"},
|
||||
{2207, &ISystemDisplayService::SetLayerVisibility, "SetLayerVisibility"},
|
||||
{2207, nullptr, "SetLayerVisibility"},
|
||||
{2209, nullptr, "SetLayerAlpha"},
|
||||
{2312, nullptr, "CreateStrayLayer"},
|
||||
{2400, nullptr, "OpenIndirectLayer"},
|
||||
@@ -632,16 +632,6 @@ private:
|
||||
IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SetLayerVisibility(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u64 layer_id = rp.Pop<u64>();
|
||||
bool visibility = rp.Pop<bool>();
|
||||
IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x%x, visibility=%u", layer_id,
|
||||
visibility);
|
||||
}
|
||||
};
|
||||
|
||||
class IManagerDisplayService final : public ServiceFramework<IManagerDisplayService> {
|
||||
@@ -673,7 +663,7 @@ public:
|
||||
{4206, nullptr, "SetDefaultDisplay"},
|
||||
{6000, &IManagerDisplayService::AddToLayerStack, "AddToLayerStack"},
|
||||
{6001, nullptr, "RemoveFromLayerStack"},
|
||||
{6002, &IManagerDisplayService::SetLayerVisibility, "SetLayerVisibility"},
|
||||
{6002, nullptr, "SetLayerVisibility"},
|
||||
{6003, nullptr, "SetLayerConfig"},
|
||||
{6004, nullptr, "AttachLayerPresentationTracer"},
|
||||
{6005, nullptr, "DetachLayerPresentationTracer"},
|
||||
@@ -755,16 +745,6 @@ private:
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void SetLayerVisibility(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
u64 layer_id = rp.Pop<u64>();
|
||||
bool visibility = rp.Pop<bool>();
|
||||
IPC::ResponseBuilder rb = rp.MakeBuilder(2, 0, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
LOG_WARNING(Service_VI, "(STUBBED) called, layer_id=0x%x, visibility=%u", layer_id,
|
||||
visibility);
|
||||
}
|
||||
|
||||
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
|
||||
};
|
||||
|
||||
@@ -835,15 +815,15 @@ private:
|
||||
IPC::RequestParser rp{ctx};
|
||||
u64 display_id = rp.Pop<u64>();
|
||||
|
||||
IPC::ResponseBuilder rb = rp.MakeBuilder(6, 0, 0);
|
||||
IPC::ResponseBuilder rb = rp.MakeBuilder(4, 0, 0);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
if (Settings::values.use_docked_mode) {
|
||||
rb.Push(static_cast<u64>(DisplayResolution::DockedWidth));
|
||||
rb.Push(static_cast<u64>(DisplayResolution::DockedHeight));
|
||||
rb.Push(static_cast<u32>(DisplayResolution::DockedWidth));
|
||||
rb.Push(static_cast<u32>(DisplayResolution::DockedHeight));
|
||||
} else {
|
||||
rb.Push(static_cast<u64>(DisplayResolution::UndockedWidth));
|
||||
rb.Push(static_cast<u64>(DisplayResolution::UndockedHeight));
|
||||
rb.Push(static_cast<u32>(DisplayResolution::UndockedWidth));
|
||||
rb.Push(static_cast<u32>(DisplayResolution::UndockedHeight));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -137,7 +137,7 @@ ResultStatus AppLoader_NRO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
process->address_mappings = default_address_mappings;
|
||||
process->resource_limit =
|
||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
||||
process->Run(base_addr, 48, Memory::DEFAULT_STACK_SIZE);
|
||||
process->Run(base_addr, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||
|
||||
is_loaded = true;
|
||||
return ResultStatus::Success;
|
||||
|
||||
@@ -165,7 +165,7 @@ ResultStatus AppLoader_NSO::Load(Kernel::SharedPtr<Kernel::Process>& process) {
|
||||
process->address_mappings = default_address_mappings;
|
||||
process->resource_limit =
|
||||
Kernel::ResourceLimit::GetForCategory(Kernel::ResourceLimitCategory::APPLICATION);
|
||||
process->Run(Memory::PROCESS_IMAGE_VADDR, 48, Memory::DEFAULT_STACK_SIZE);
|
||||
process->Run(Memory::PROCESS_IMAGE_VADDR, THREADPRIO_DEFAULT, Memory::DEFAULT_STACK_SIZE);
|
||||
|
||||
is_loaded = true;
|
||||
return ResultStatus::Success;
|
||||
|
||||
@@ -147,36 +147,11 @@ void Maxwell3D::ProcessQueryGet() {
|
||||
// VAddr before writing.
|
||||
VAddr address = memory_manager.PhysicalToVirtualAddress(sequence_address);
|
||||
|
||||
// TODO(Subv): Support the other query units.
|
||||
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
|
||||
"Units other than CROP are unimplemented");
|
||||
ASSERT_MSG(regs.query.query_get.short_query,
|
||||
"Writing the entire query result structure is unimplemented");
|
||||
|
||||
u32 value = Memory::Read32(address);
|
||||
u32 result = 0;
|
||||
|
||||
// TODO(Subv): Support the other query variables
|
||||
switch (regs.query.query_get.select) {
|
||||
case Regs::QuerySelect::Zero:
|
||||
result = 0;
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented query select type %u",
|
||||
static_cast<u32>(regs.query.query_get.select.Value()));
|
||||
}
|
||||
|
||||
// TODO(Subv): Research and implement how query sync conditions work.
|
||||
|
||||
switch (regs.query.query_get.mode) {
|
||||
case Regs::QueryMode::Write:
|
||||
case Regs::QueryMode::Write2: {
|
||||
case Regs::QueryMode::Write: {
|
||||
// Write the current query sequence to the sequence address.
|
||||
u32 sequence = regs.query.query_sequence;
|
||||
Memory::Write32(address, sequence);
|
||||
|
||||
// TODO(Subv): Write the proper query response structure to the address when not using short
|
||||
// mode.
|
||||
break;
|
||||
}
|
||||
default:
|
||||
|
||||
@@ -46,29 +46,6 @@ public:
|
||||
enum class QueryMode : u32 {
|
||||
Write = 0,
|
||||
Sync = 1,
|
||||
// TODO(Subv): It is currently unknown what the difference between method 2 and method 0
|
||||
// is.
|
||||
Write2 = 2,
|
||||
};
|
||||
|
||||
enum class QueryUnit : u32 {
|
||||
VFetch = 1,
|
||||
VP = 2,
|
||||
Rast = 4,
|
||||
StrmOut = 5,
|
||||
GP = 6,
|
||||
ZCull = 7,
|
||||
Prop = 10,
|
||||
Crop = 15,
|
||||
};
|
||||
|
||||
enum class QuerySelect : u32 {
|
||||
Zero = 0,
|
||||
};
|
||||
|
||||
enum class QuerySyncCondition : u32 {
|
||||
NotEqual = 0,
|
||||
GreaterThan = 1,
|
||||
};
|
||||
|
||||
enum class ShaderProgram : u32 {
|
||||
@@ -499,10 +476,7 @@ public:
|
||||
u32 raw;
|
||||
BitField<0, 2, QueryMode> mode;
|
||||
BitField<4, 1, u32> fence;
|
||||
BitField<12, 4, QueryUnit> unit;
|
||||
BitField<16, 1, QuerySyncCondition> sync_cond;
|
||||
BitField<23, 5, QuerySelect> select;
|
||||
BitField<28, 1, u32> short_query;
|
||||
BitField<12, 4, u32> unit;
|
||||
} query_get;
|
||||
|
||||
GPUVAddr QueryAddress() const {
|
||||
|
||||
@@ -4,24 +4,15 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <bitset>
|
||||
#include <cstring>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/optional.hpp>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Tegra {
|
||||
namespace Shader {
|
||||
|
||||
struct Register {
|
||||
// Register 255 is special cased to always be 0
|
||||
static constexpr size_t ZeroIndex = 255;
|
||||
|
||||
constexpr Register() = default;
|
||||
|
||||
constexpr Register(u64 value) : value(value) {}
|
||||
@@ -95,12 +86,181 @@ union Uniform {
|
||||
BitField<34, 5, u64> index;
|
||||
};
|
||||
|
||||
union OpCode {
|
||||
enum class Id : u64 {
|
||||
TEXS = 0x6C,
|
||||
IPA = 0xE0,
|
||||
FMUL32_IMM = 0x1E,
|
||||
FFMA_IMM = 0x65,
|
||||
FFMA_CR = 0x93,
|
||||
FFMA_RC = 0xA3,
|
||||
FFMA_RR = 0xB3,
|
||||
|
||||
FADD_C = 0x98B,
|
||||
FMUL_C = 0x98D,
|
||||
MUFU = 0xA10,
|
||||
FADD_R = 0xB8B,
|
||||
FMUL_R = 0xB8D,
|
||||
LD_A = 0x1DFB,
|
||||
ST_A = 0x1DFE,
|
||||
|
||||
FSETP_R = 0x5BB,
|
||||
FSETP_C = 0x4BB,
|
||||
EXIT = 0xE30,
|
||||
KIL = 0xE33,
|
||||
|
||||
FMUL_IMM = 0x70D,
|
||||
FMUL_IMM_x = 0x72D,
|
||||
FADD_IMM = 0x70B,
|
||||
FADD_IMM_x = 0x72B,
|
||||
};
|
||||
|
||||
enum class Type {
|
||||
Trivial,
|
||||
Arithmetic,
|
||||
Ffma,
|
||||
Flow,
|
||||
Memory,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct Info {
|
||||
Type type;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
OpCode() = default;
|
||||
|
||||
constexpr OpCode(Id value) : value(static_cast<u64>(value)) {}
|
||||
|
||||
constexpr OpCode(u64 value) : value{value} {}
|
||||
|
||||
constexpr Id EffectiveOpCode() const {
|
||||
switch (op1) {
|
||||
case Id::TEXS:
|
||||
return op1;
|
||||
}
|
||||
|
||||
switch (op2) {
|
||||
case Id::IPA:
|
||||
case Id::FMUL32_IMM:
|
||||
return op2;
|
||||
}
|
||||
|
||||
switch (op3) {
|
||||
case Id::FFMA_IMM:
|
||||
case Id::FFMA_CR:
|
||||
case Id::FFMA_RC:
|
||||
case Id::FFMA_RR:
|
||||
return op3;
|
||||
}
|
||||
|
||||
switch (op4) {
|
||||
case Id::EXIT:
|
||||
case Id::FSETP_R:
|
||||
case Id::FSETP_C:
|
||||
case Id::KIL:
|
||||
return op4;
|
||||
}
|
||||
|
||||
switch (op5) {
|
||||
case Id::MUFU:
|
||||
case Id::LD_A:
|
||||
case Id::ST_A:
|
||||
case Id::FADD_R:
|
||||
case Id::FADD_C:
|
||||
case Id::FMUL_R:
|
||||
case Id::FMUL_C:
|
||||
return op5;
|
||||
|
||||
case Id::FMUL_IMM:
|
||||
case Id::FMUL_IMM_x:
|
||||
return Id::FMUL_IMM;
|
||||
|
||||
case Id::FADD_IMM:
|
||||
case Id::FADD_IMM_x:
|
||||
return Id::FADD_IMM;
|
||||
}
|
||||
|
||||
return static_cast<Id>(value);
|
||||
}
|
||||
|
||||
static const Info& GetInfo(const OpCode& opcode) {
|
||||
static const std::map<Id, Info> info_table{BuildInfoTable()};
|
||||
const auto& search{info_table.find(opcode.EffectiveOpCode())};
|
||||
if (search != info_table.end()) {
|
||||
return search->second;
|
||||
}
|
||||
|
||||
static const Info unknown{Type::Unknown, "UNK"};
|
||||
return unknown;
|
||||
}
|
||||
|
||||
constexpr operator Id() const {
|
||||
return static_cast<Id>(value);
|
||||
}
|
||||
|
||||
constexpr OpCode operator<<(size_t bits) const {
|
||||
return value << bits;
|
||||
}
|
||||
|
||||
constexpr OpCode operator>>(size_t bits) const {
|
||||
return value >> bits;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
constexpr u64 operator-(const T& oth) const {
|
||||
return value - oth;
|
||||
}
|
||||
|
||||
constexpr u64 operator&(const OpCode& oth) const {
|
||||
return value & oth.value;
|
||||
}
|
||||
|
||||
constexpr u64 operator~() const {
|
||||
return ~value;
|
||||
}
|
||||
|
||||
static std::map<Id, Info> BuildInfoTable() {
|
||||
std::map<Id, Info> info_table;
|
||||
info_table[Id::TEXS] = {Type::Memory, "texs"};
|
||||
info_table[Id::LD_A] = {Type::Memory, "ld_a"};
|
||||
info_table[Id::ST_A] = {Type::Memory, "st_a"};
|
||||
info_table[Id::MUFU] = {Type::Arithmetic, "mufu"};
|
||||
info_table[Id::FFMA_IMM] = {Type::Ffma, "ffma_imm"};
|
||||
info_table[Id::FFMA_CR] = {Type::Ffma, "ffma_cr"};
|
||||
info_table[Id::FFMA_RC] = {Type::Ffma, "ffma_rc"};
|
||||
info_table[Id::FFMA_RR] = {Type::Ffma, "ffma_rr"};
|
||||
info_table[Id::FADD_R] = {Type::Arithmetic, "fadd_r"};
|
||||
info_table[Id::FADD_C] = {Type::Arithmetic, "fadd_c"};
|
||||
info_table[Id::FADD_IMM] = {Type::Arithmetic, "fadd_imm"};
|
||||
info_table[Id::FMUL_R] = {Type::Arithmetic, "fmul_r"};
|
||||
info_table[Id::FMUL_C] = {Type::Arithmetic, "fmul_c"};
|
||||
info_table[Id::FMUL_IMM] = {Type::Arithmetic, "fmul_imm"};
|
||||
info_table[Id::FMUL32_IMM] = {Type::Arithmetic, "fmul32_imm"};
|
||||
info_table[Id::FSETP_C] = {Type::Arithmetic, "fsetp_c"};
|
||||
info_table[Id::FSETP_R] = {Type::Arithmetic, "fsetp_r"};
|
||||
info_table[Id::EXIT] = {Type::Trivial, "exit"};
|
||||
info_table[Id::IPA] = {Type::Trivial, "ipa"};
|
||||
info_table[Id::KIL] = {Type::Flow, "kil"};
|
||||
return info_table;
|
||||
}
|
||||
|
||||
BitField<57, 7, Id> op1;
|
||||
BitField<56, 8, Id> op2;
|
||||
BitField<55, 9, Id> op3;
|
||||
BitField<52, 12, Id> op4;
|
||||
BitField<51, 13, Id> op5;
|
||||
u64 value{};
|
||||
};
|
||||
static_assert(sizeof(OpCode) == 0x8, "Incorrect structure size");
|
||||
|
||||
} // namespace Shader
|
||||
} // namespace Tegra
|
||||
|
||||
namespace std {
|
||||
|
||||
// TODO(bunnei): The below is forbidden by the C++ standard, but works fine. See #330.
|
||||
// TODO(bunne): The below is forbidden by the C++ standard, but works fine. See #330.
|
||||
template <>
|
||||
struct make_unsigned<Tegra::Shader::Attribute> {
|
||||
using type = Tegra::Shader::Attribute;
|
||||
@@ -111,6 +271,11 @@ struct make_unsigned<Tegra::Shader::Register> {
|
||||
using type = Tegra::Shader::Register;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct make_unsigned<Tegra::Shader::OpCode> {
|
||||
using type = Tegra::Shader::OpCode;
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace Tegra {
|
||||
@@ -118,23 +283,7 @@ namespace Shader {
|
||||
|
||||
enum class Pred : u64 {
|
||||
UnusedIndex = 0x7,
|
||||
NeverExecute = 0xF,
|
||||
};
|
||||
|
||||
enum class PredCondition : u64 {
|
||||
LessThan = 1,
|
||||
Equal = 2,
|
||||
LessEqual = 3,
|
||||
GreaterThan = 4,
|
||||
NotEqual = 5,
|
||||
GreaterEqual = 6,
|
||||
// TODO(Subv): Other condition types
|
||||
};
|
||||
|
||||
enum class PredOperation : u64 {
|
||||
And = 0,
|
||||
Or = 1,
|
||||
Xor = 2,
|
||||
NeverExecute = 0xf,
|
||||
};
|
||||
|
||||
enum class SubOp : u64 {
|
||||
@@ -149,24 +298,18 @@ enum class SubOp : u64 {
|
||||
|
||||
union Instruction {
|
||||
Instruction& operator=(const Instruction& instr) {
|
||||
value = instr.value;
|
||||
hex = instr.hex;
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr Instruction(u64 value) : value{value} {}
|
||||
|
||||
OpCode opcode;
|
||||
BitField<0, 8, Register> gpr0;
|
||||
BitField<8, 8, Register> gpr8;
|
||||
union {
|
||||
BitField<16, 4, Pred> full_pred;
|
||||
BitField<16, 3, u64> pred_index;
|
||||
} pred;
|
||||
BitField<19, 1, u64> negate_pred;
|
||||
BitField<16, 4, Pred> pred;
|
||||
BitField<20, 8, Register> gpr20;
|
||||
BitField<20, 7, SubOp> sub_op;
|
||||
BitField<28, 8, Register> gpr28;
|
||||
BitField<39, 8, Register> gpr39;
|
||||
BitField<48, 16, u64> opcode;
|
||||
|
||||
union {
|
||||
BitField<20, 19, u64> imm20_19;
|
||||
@@ -200,20 +343,6 @@ union Instruction {
|
||||
BitField<49, 1, u64> negate_c;
|
||||
} ffma;
|
||||
|
||||
union {
|
||||
BitField<0, 3, u64> pred0;
|
||||
BitField<3, 3, u64> pred3;
|
||||
BitField<7, 1, u64> abs_a;
|
||||
BitField<39, 3, u64> pred39;
|
||||
BitField<42, 1, u64> neg_pred;
|
||||
BitField<43, 1, u64> neg_a;
|
||||
BitField<44, 1, u64> abs_b;
|
||||
BitField<45, 2, PredOperation> op;
|
||||
BitField<47, 1, u64> ftz;
|
||||
BitField<48, 4, PredCondition> cond;
|
||||
BitField<56, 1, u64> neg_b;
|
||||
} fsetp;
|
||||
|
||||
BitField<61, 1, u64> is_b_imm;
|
||||
BitField<60, 1, u64> is_b_gpr;
|
||||
BitField<59, 1, u64> is_c_gpr;
|
||||
@@ -222,218 +351,11 @@ union Instruction {
|
||||
Uniform uniform;
|
||||
Sampler sampler;
|
||||
|
||||
u64 value;
|
||||
u64 hex;
|
||||
};
|
||||
static_assert(sizeof(Instruction) == 0x8, "Incorrect structure size");
|
||||
static_assert(std::is_standard_layout<Instruction>::value,
|
||||
"Structure does not have standard layout");
|
||||
|
||||
class OpCode {
|
||||
public:
|
||||
enum class Id {
|
||||
KIL,
|
||||
LD_A,
|
||||
ST_A,
|
||||
TEXQ, // Texture Query
|
||||
TEXS, // Texture Fetch with scalar/non-vec4 source/destinations
|
||||
TLDS, // Texture Load with scalar/non-vec4 source/destinations
|
||||
EXIT,
|
||||
IPA,
|
||||
FFMA_IMM, // Fused Multiply and Add
|
||||
FFMA_CR,
|
||||
FFMA_RC,
|
||||
FFMA_RR,
|
||||
FADD_C,
|
||||
FADD_R,
|
||||
FADD_IMM,
|
||||
FMUL_C,
|
||||
FMUL_R,
|
||||
FMUL_IMM,
|
||||
FMUL32_IMM,
|
||||
MUFU, // Multi-Function Operator
|
||||
RRO, // Range Reduction Operator
|
||||
F2F_C,
|
||||
F2F_R,
|
||||
F2F_IMM,
|
||||
F2I_C,
|
||||
F2I_R,
|
||||
F2I_IMM,
|
||||
I2F_C,
|
||||
I2F_R,
|
||||
I2F_IMM,
|
||||
LOP32I,
|
||||
MOV_C,
|
||||
MOV_R,
|
||||
MOV_IMM,
|
||||
MOV32I,
|
||||
SHR_C,
|
||||
SHR_R,
|
||||
SHR_IMM,
|
||||
FSETP_C, // Set Predicate
|
||||
FSETP_R,
|
||||
FSETP_IMM,
|
||||
ISETP_C,
|
||||
ISETP_IMM,
|
||||
ISETP_R,
|
||||
};
|
||||
|
||||
enum class Type {
|
||||
Trivial,
|
||||
Arithmetic,
|
||||
Ffma,
|
||||
Flow,
|
||||
Memory,
|
||||
FloatPredicate,
|
||||
IntegerPredicate,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
class Matcher {
|
||||
public:
|
||||
Matcher(const char* const name, u16 mask, u16 expected, OpCode::Id id, OpCode::Type type)
|
||||
: name{name}, mask{mask}, expected{expected}, id{id}, type{type} {}
|
||||
|
||||
const char* GetName() const {
|
||||
return name;
|
||||
}
|
||||
|
||||
u16 GetMask() const {
|
||||
return mask;
|
||||
}
|
||||
|
||||
Id GetId() const {
|
||||
return id;
|
||||
}
|
||||
|
||||
Type GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to see if the given instruction is the instruction this matcher represents.
|
||||
* @param instruction The instruction to test
|
||||
* @returns true if the given instruction matches.
|
||||
*/
|
||||
bool Matches(u16 instruction) const {
|
||||
return (instruction & mask) == expected;
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name;
|
||||
u16 mask;
|
||||
u16 expected;
|
||||
Id id;
|
||||
Type type;
|
||||
};
|
||||
|
||||
static boost::optional<const Matcher&> Decode(Instruction instr) {
|
||||
static const auto table{GetDecodeTable()};
|
||||
|
||||
const auto matches_instruction = [instr](const auto& matcher) {
|
||||
return matcher.Matches(static_cast<u16>(instr.opcode));
|
||||
};
|
||||
|
||||
auto iter = std::find_if(table.begin(), table.end(), matches_instruction);
|
||||
return iter != table.end() ? boost::optional<const Matcher&>(*iter) : boost::none;
|
||||
}
|
||||
|
||||
private:
|
||||
struct Detail {
|
||||
private:
|
||||
static constexpr size_t opcode_bitsize = 16;
|
||||
|
||||
/**
|
||||
* Generates the mask and the expected value after masking from a given bitstring.
|
||||
* A '0' in a bitstring indicates that a zero must be present at that bit position.
|
||||
* A '1' in a bitstring indicates that a one must be present at that bit position.
|
||||
*/
|
||||
static auto GetMaskAndExpect(const char* const bitstring) {
|
||||
u16 mask = 0, expect = 0;
|
||||
for (size_t i = 0; i < opcode_bitsize; i++) {
|
||||
const size_t bit_position = opcode_bitsize - i - 1;
|
||||
switch (bitstring[i]) {
|
||||
case '0':
|
||||
mask |= 1 << bit_position;
|
||||
break;
|
||||
case '1':
|
||||
expect |= 1 << bit_position;
|
||||
mask |= 1 << bit_position;
|
||||
break;
|
||||
default:
|
||||
// Ignore
|
||||
break;
|
||||
}
|
||||
}
|
||||
return std::make_tuple(mask, expect);
|
||||
}
|
||||
|
||||
public:
|
||||
/// Creates a matcher that can match and parse instructions based on bitstring.
|
||||
static auto GetMatcher(const char* const bitstring, OpCode::Id op, OpCode::Type type,
|
||||
const char* const name) {
|
||||
const auto mask_expect = GetMaskAndExpect(bitstring);
|
||||
return Matcher(name, std::get<0>(mask_expect), std::get<1>(mask_expect), op, type);
|
||||
}
|
||||
};
|
||||
|
||||
static std::vector<Matcher> GetDecodeTable() {
|
||||
std::vector<Matcher> table = {
|
||||
#define INST(bitstring, op, type, name) Detail::GetMatcher(bitstring, op, type, name)
|
||||
INST("111000110011----", Id::KIL, Type::Flow, "KIL"),
|
||||
INST("1110111111011---", Id::LD_A, Type::Memory, "LD_A"),
|
||||
INST("1110111111110---", Id::ST_A, Type::Memory, "ST_A"),
|
||||
INST("1101111101001---", Id::TEXQ, Type::Memory, "TEXQ"),
|
||||
INST("1101100---------", Id::TEXS, Type::Memory, "TEXS"),
|
||||
INST("1101101---------", Id::TLDS, Type::Memory, "TLDS"),
|
||||
INST("111000110000----", Id::EXIT, Type::Trivial, "EXIT"),
|
||||
INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
|
||||
INST("001100101-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
|
||||
INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
|
||||
INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
|
||||
INST("010110011-------", Id::FFMA_RR, Type::Ffma, "FFMA_RR"),
|
||||
INST("0100110001011---", Id::FADD_C, Type::Arithmetic, "FADD_C"),
|
||||
INST("0101110001011---", Id::FADD_R, Type::Arithmetic, "FADD_R"),
|
||||
INST("0011100-01011---", Id::FADD_IMM, Type::Arithmetic, "FADD_IMM"),
|
||||
INST("0100110001101---", Id::FMUL_C, Type::Arithmetic, "FMUL_C"),
|
||||
INST("0101110001101---", Id::FMUL_R, Type::Arithmetic, "FMUL_R"),
|
||||
INST("0011100-01101---", Id::FMUL_IMM, Type::Arithmetic, "FMUL_IMM"),
|
||||
INST("00011110--------", Id::FMUL32_IMM, Type::Arithmetic, "FMUL32_IMM"),
|
||||
INST("0101000010000---", Id::MUFU, Type::Arithmetic, "MUFU"),
|
||||
INST("0101110010010---", Id::RRO, Type::Arithmetic, "RRO"),
|
||||
INST("0100110010101---", Id::F2F_C, Type::Arithmetic, "F2F_C"),
|
||||
INST("0101110010101---", Id::F2F_R, Type::Arithmetic, "F2F_R"),
|
||||
INST("0011100-10101---", Id::F2F_IMM, Type::Arithmetic, "F2F_IMM"),
|
||||
INST("0100110010110---", Id::F2I_C, Type::Arithmetic, "F2I_C"),
|
||||
INST("0101110010110---", Id::F2I_R, Type::Arithmetic, "F2I_R"),
|
||||
INST("0011100-10110---", Id::F2I_IMM, Type::Arithmetic, "F2I_IMM"),
|
||||
INST("0100110010111---", Id::I2F_C, Type::Arithmetic, "I2F_C"),
|
||||
INST("0101110010111---", Id::I2F_R, Type::Arithmetic, "I2F_R"),
|
||||
INST("0011100-10111---", Id::I2F_IMM, Type::Arithmetic, "I2F_IMM"),
|
||||
INST("000001----------", Id::LOP32I, Type::Arithmetic, "LOP32I"),
|
||||
INST("0100110010011---", Id::MOV_C, Type::Arithmetic, "MOV_C"),
|
||||
INST("0101110010011---", Id::MOV_R, Type::Arithmetic, "MOV_R"),
|
||||
INST("0011100-10011---", Id::MOV_IMM, Type::Arithmetic, "MOV_IMM"),
|
||||
INST("000000010000----", Id::MOV32I, Type::Arithmetic, "MOV32I"),
|
||||
INST("0100110000101---", Id::SHR_C, Type::Arithmetic, "SHR_C"),
|
||||
INST("0101110000101---", Id::SHR_R, Type::Arithmetic, "SHR_R"),
|
||||
INST("0011100-00101---", Id::SHR_IMM, Type::Arithmetic, "SHR_IMM"),
|
||||
INST("010010111011----", Id::FSETP_C, Type::FloatPredicate, "FSETP_C"),
|
||||
INST("010110111011----", Id::FSETP_R, Type::FloatPredicate, "FSETP_R"),
|
||||
INST("0011011-1011----", Id::FSETP_IMM, Type::FloatPredicate, "FSETP_IMM"),
|
||||
INST("010010110110----", Id::ISETP_C, Type::IntegerPredicate, "ISETP_C"),
|
||||
INST("010110110110----", Id::ISETP_R, Type::IntegerPredicate, "ISETP_R"),
|
||||
INST("0011011-0110----", Id::ISETP_IMM, Type::IntegerPredicate, "ISETP_IMM"),
|
||||
};
|
||||
#undef INST
|
||||
std::stable_sort(table.begin(), table.end(), [](const auto& a, const auto& b) {
|
||||
// If a matcher has more bits in its mask it is more specific, so it
|
||||
// should come first.
|
||||
return std::bitset<16>(a.GetMask()).count() > std::bitset<16>(b.GetMask()).count();
|
||||
});
|
||||
|
||||
return table;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace Shader
|
||||
} // namespace Tegra
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
@@ -6,10 +6,15 @@
|
||||
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <cstring>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <glad/glad.h>
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <cstring>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include <boost/optional.hpp>
|
||||
@@ -19,6 +20,7 @@
|
||||
#include "common/math_util.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "common/vector_math.h"
|
||||
#include "core/core.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -670,8 +672,7 @@ void CachedSurface::DownloadGLTexture(const MathUtil::Rectangle<u32>& rect, GLui
|
||||
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
|
||||
}
|
||||
|
||||
enum class MatchFlags {
|
||||
None = 0,
|
||||
enum MatchFlags {
|
||||
Invalid = 1, // Flag that can be applied to other match types, invalid matches require
|
||||
// validation before they can be used
|
||||
Exact = 1 << 1, // Surfaces perfectly match
|
||||
@@ -685,10 +686,6 @@ constexpr MatchFlags operator|(MatchFlags lhs, MatchFlags rhs) {
|
||||
return static_cast<MatchFlags>(static_cast<int>(lhs) | static_cast<int>(rhs));
|
||||
}
|
||||
|
||||
constexpr MatchFlags operator&(MatchFlags lhs, MatchFlags rhs) {
|
||||
return static_cast<MatchFlags>(static_cast<int>(lhs) & static_cast<int>(rhs));
|
||||
}
|
||||
|
||||
/// Get the best surface match (and its match type) for the given flags
|
||||
template <MatchFlags find_flags>
|
||||
Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params,
|
||||
@@ -706,15 +703,15 @@ Surface FindMatch(const SurfaceCache& surface_cache, const SurfaceParams& params
|
||||
: (params.res_scale <= surface->res_scale);
|
||||
// validity will be checked in GetCopyableInterval
|
||||
bool is_valid =
|
||||
(find_flags & MatchFlags::Copy) != MatchFlags::None
|
||||
find_flags & MatchFlags::Copy
|
||||
? true
|
||||
: surface->IsRegionValid(validate_interval.value_or(params.GetInterval()));
|
||||
|
||||
if ((find_flags & MatchFlags::Invalid) == MatchFlags::None && !is_valid)
|
||||
if (!(find_flags & MatchFlags::Invalid) && !is_valid)
|
||||
continue;
|
||||
|
||||
auto IsMatch_Helper = [&](auto check_type, auto match_fn) {
|
||||
if ((find_flags & check_type) == MatchFlags::None)
|
||||
if (!(find_flags & check_type))
|
||||
return;
|
||||
|
||||
bool matched;
|
||||
|
||||
@@ -14,13 +14,13 @@ class OGLTexture : private NonCopyable {
|
||||
public:
|
||||
OGLTexture() = default;
|
||||
|
||||
OGLTexture(OGLTexture&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
OGLTexture(OGLTexture&& o) : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLTexture() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLTexture& operator=(OGLTexture&& o) noexcept {
|
||||
OGLTexture& operator=(OGLTexture&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
@@ -49,13 +49,13 @@ class OGLSampler : private NonCopyable {
|
||||
public:
|
||||
OGLSampler() = default;
|
||||
|
||||
OGLSampler(OGLSampler&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
OGLSampler(OGLSampler&& o) : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLSampler() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLSampler& operator=(OGLSampler&& o) noexcept {
|
||||
OGLSampler& operator=(OGLSampler&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
@@ -84,13 +84,13 @@ class OGLShader : private NonCopyable {
|
||||
public:
|
||||
OGLShader() = default;
|
||||
|
||||
OGLShader(OGLShader&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
OGLShader(OGLShader&& o) : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLShader() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLShader& operator=(OGLShader&& o) noexcept {
|
||||
OGLShader& operator=(OGLShader&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
@@ -118,13 +118,13 @@ class OGLProgram : private NonCopyable {
|
||||
public:
|
||||
OGLProgram() = default;
|
||||
|
||||
OGLProgram(OGLProgram&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
OGLProgram(OGLProgram&& o) : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLProgram() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLProgram& operator=(OGLProgram&& o) noexcept {
|
||||
OGLProgram& operator=(OGLProgram&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
@@ -165,12 +165,13 @@ public:
|
||||
class OGLPipeline : private NonCopyable {
|
||||
public:
|
||||
OGLPipeline() = default;
|
||||
OGLPipeline(OGLPipeline&& o) noexcept : handle{std::exchange<GLuint>(o.handle, 0)} {}
|
||||
|
||||
OGLPipeline(OGLPipeline&& o) {
|
||||
handle = std::exchange<GLuint>(o.handle, 0);
|
||||
}
|
||||
~OGLPipeline() {
|
||||
Release();
|
||||
}
|
||||
OGLPipeline& operator=(OGLPipeline&& o) noexcept {
|
||||
OGLPipeline& operator=(OGLPipeline&& o) {
|
||||
handle = std::exchange<GLuint>(o.handle, 0);
|
||||
return *this;
|
||||
}
|
||||
@@ -198,13 +199,13 @@ class OGLBuffer : private NonCopyable {
|
||||
public:
|
||||
OGLBuffer() = default;
|
||||
|
||||
OGLBuffer(OGLBuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
OGLBuffer(OGLBuffer&& o) : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLBuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLBuffer& operator=(OGLBuffer&& o) noexcept {
|
||||
OGLBuffer& operator=(OGLBuffer&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
@@ -233,12 +234,12 @@ class OGLSync : private NonCopyable {
|
||||
public:
|
||||
OGLSync() = default;
|
||||
|
||||
OGLSync(OGLSync&& o) noexcept : handle(std::exchange(o.handle, nullptr)) {}
|
||||
OGLSync(OGLSync&& o) : handle(std::exchange(o.handle, nullptr)) {}
|
||||
|
||||
~OGLSync() {
|
||||
Release();
|
||||
}
|
||||
OGLSync& operator=(OGLSync&& o) noexcept {
|
||||
OGLSync& operator=(OGLSync&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, nullptr);
|
||||
return *this;
|
||||
@@ -266,13 +267,13 @@ class OGLVertexArray : private NonCopyable {
|
||||
public:
|
||||
OGLVertexArray() = default;
|
||||
|
||||
OGLVertexArray(OGLVertexArray&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
OGLVertexArray(OGLVertexArray&& o) : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLVertexArray() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLVertexArray& operator=(OGLVertexArray&& o) noexcept {
|
||||
OGLVertexArray& operator=(OGLVertexArray&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
@@ -301,13 +302,13 @@ class OGLFramebuffer : private NonCopyable {
|
||||
public:
|
||||
OGLFramebuffer() = default;
|
||||
|
||||
OGLFramebuffer(OGLFramebuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {}
|
||||
OGLFramebuffer(OGLFramebuffer&& o) : handle(std::exchange(o.handle, 0)) {}
|
||||
|
||||
~OGLFramebuffer() {
|
||||
Release();
|
||||
}
|
||||
|
||||
OGLFramebuffer& operator=(OGLFramebuffer&& o) noexcept {
|
||||
OGLFramebuffer& operator=(OGLFramebuffer&& o) {
|
||||
Release();
|
||||
handle = std::exchange(o.handle, 0);
|
||||
return *this;
|
||||
|
||||
@@ -97,12 +97,11 @@ private:
|
||||
return exit_method;
|
||||
|
||||
for (u32 offset = begin; offset != end && offset != PROGRAM_END; ++offset) {
|
||||
if (const auto opcode = OpCode::Decode({program_code[offset]})) {
|
||||
switch (opcode->GetId()) {
|
||||
case OpCode::Id::EXIT: {
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
}
|
||||
}
|
||||
const Instruction instr = {program_code[offset]};
|
||||
switch (instr.opcode.EffectiveOpCode()) {
|
||||
case OpCode::Id::EXIT: {
|
||||
return exit_method = ExitMethod::AlwaysEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
return exit_method = ExitMethod::AlwaysReturn;
|
||||
@@ -221,8 +220,6 @@ private:
|
||||
|
||||
/// Generates code representing a temporary (GPR) register.
|
||||
std::string GetRegister(const Register& reg, unsigned elem = 0) {
|
||||
if (reg == Register::ZeroIndex)
|
||||
return "0";
|
||||
if (stage == Maxwell3D::Regs::ShaderStage::Fragment && reg < 4) {
|
||||
// GPRs 0-3 are output color for the fragment shader
|
||||
return std::string{"color."} + "rgba"[(reg + elem) & 3];
|
||||
@@ -279,52 +276,6 @@ private:
|
||||
shader.AddLine(dest + " = " + src + ";");
|
||||
}
|
||||
|
||||
/*
|
||||
* Writes code that assigns a predicate boolean variable.
|
||||
* @param pred The id of the predicate to write to.
|
||||
* @param value The expression value to assign to the predicate.
|
||||
*/
|
||||
void SetPredicate(u64 pred, const std::string& value) {
|
||||
using Tegra::Shader::Pred;
|
||||
// Can't assign to the constant predicate.
|
||||
ASSERT(pred != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string variable = 'p' + std::to_string(pred);
|
||||
shader.AddLine(variable + " = " + value + ';');
|
||||
declr_predicates.insert(std::move(variable));
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns the condition to use in the 'if' for a predicated instruction.
|
||||
* @param instr Instruction to generate the if condition for.
|
||||
* @returns string containing the predicate condition.
|
||||
*/
|
||||
std::string GetPredicateCondition(Instruction instr) const {
|
||||
using Tegra::Shader::Pred;
|
||||
ASSERT(instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
std::string variable =
|
||||
'p' + std::to_string(static_cast<u64>(instr.pred.pred_index.Value()));
|
||||
|
||||
if (instr.negate_pred) {
|
||||
return "!(" + variable + ')';
|
||||
}
|
||||
|
||||
return variable;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether the instruction at the specified offset is a 'sched' instruction.
|
||||
* Sched instructions always appear before a sequence of 3 instructions.
|
||||
*/
|
||||
bool IsSchedInstruction(u32 offset) const {
|
||||
// sched instructions appear once every 4 instructions.
|
||||
static constexpr size_t SchedPeriod = 4;
|
||||
u32 absolute_offset = offset - main_offset;
|
||||
|
||||
return (absolute_offset % SchedPeriod) == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compiles a single instruction from Tegra to GLSL.
|
||||
* @param offset the offset of the Tegra shader instruction.
|
||||
@@ -332,33 +283,11 @@ private:
|
||||
* + 1. If the current instruction always terminates the program, returns PROGRAM_END.
|
||||
*/
|
||||
u32 CompileInstr(u32 offset) {
|
||||
// Ignore sched instructions when generating code.
|
||||
if (IsSchedInstruction(offset)) {
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
const Instruction instr = {program_code[offset]};
|
||||
const auto opcode = OpCode::Decode(instr);
|
||||
|
||||
// Decoding failure
|
||||
if (!opcode) {
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {0:x}", instr.value);
|
||||
UNREACHABLE();
|
||||
}
|
||||
shader.AddLine("// " + std::to_string(offset) + ": " + OpCode::GetInfo(instr.opcode).name);
|
||||
|
||||
shader.AddLine("// " + std::to_string(offset) + ": " + opcode->GetName());
|
||||
|
||||
using Tegra::Shader::Pred;
|
||||
ASSERT_MSG(instr.pred.full_pred != Pred::NeverExecute,
|
||||
"NeverExecute predicate not implemented");
|
||||
|
||||
if (instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) {
|
||||
shader.AddLine("if (" + GetPredicateCondition(instr) + ')');
|
||||
shader.AddLine('{');
|
||||
++shader.scope;
|
||||
}
|
||||
|
||||
switch (opcode->GetType()) {
|
||||
switch (OpCode::GetInfo(instr.opcode).type) {
|
||||
case OpCode::Type::Arithmetic: {
|
||||
std::string dest = GetRegister(instr.gpr0);
|
||||
std::string op_a = instr.alu.negate_a ? "-" : "";
|
||||
@@ -383,7 +312,7 @@ private:
|
||||
op_b = "abs(" + op_b + ")";
|
||||
}
|
||||
|
||||
switch (opcode->GetId()) {
|
||||
switch (instr.opcode.EffectiveOpCode()) {
|
||||
case OpCode::Id::FMUL_C:
|
||||
case OpCode::Id::FMUL_R:
|
||||
case OpCode::Id::FMUL_IMM: {
|
||||
@@ -425,18 +354,16 @@ private:
|
||||
SetDest(0, dest, "min(" + op_a + "," + op_b + ")", 1, 1, instr.alu.abs_d);
|
||||
break;
|
||||
default:
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {0:x}",
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled MUFU sub op: {}",
|
||||
static_cast<unsigned>(instr.sub_op.Value()));
|
||||
UNREACHABLE();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::RRO: {
|
||||
NGLOG_DEBUG(HW_GPU, "Skipping RRO instruction");
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {}", opcode->GetName());
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled arithmetic instruction: {} ({}): {}",
|
||||
static_cast<unsigned>(instr.opcode.EffectiveOpCode()),
|
||||
OpCode::GetInfo(instr.opcode).name, instr.hex);
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
@@ -448,7 +375,7 @@ private:
|
||||
std::string op_b = instr.ffma.negate_b ? "-" : "";
|
||||
std::string op_c = instr.ffma.negate_c ? "-" : "";
|
||||
|
||||
switch (opcode->GetId()) {
|
||||
switch (instr.opcode.EffectiveOpCode()) {
|
||||
case OpCode::Id::FFMA_CR: {
|
||||
op_b += GetUniform(instr.uniform);
|
||||
op_c += GetRegister(instr.gpr39);
|
||||
@@ -470,7 +397,9 @@ private:
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {}", opcode->GetName());
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled FFMA instruction: {} ({}): {}",
|
||||
static_cast<unsigned>(instr.opcode.EffectiveOpCode()),
|
||||
OpCode::GetInfo(instr.opcode).name, instr.hex);
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
@@ -482,7 +411,7 @@ private:
|
||||
std::string gpr0 = GetRegister(instr.gpr0);
|
||||
const Attribute::Index attribute = instr.attribute.fmt20.index;
|
||||
|
||||
switch (opcode->GetId()) {
|
||||
switch (instr.opcode.EffectiveOpCode()) {
|
||||
case OpCode::Id::LD_A: {
|
||||
ASSERT_MSG(instr.attribute.fmt20.size == 0, "untested");
|
||||
SetDest(instr.attribute.fmt20.element, gpr0, GetInputAttribute(attribute), 1, 4);
|
||||
@@ -513,76 +442,22 @@ private:
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {}", opcode->GetName());
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled memory instruction: {} ({}): {}",
|
||||
static_cast<unsigned>(instr.opcode.EffectiveOpCode()),
|
||||
OpCode::GetInfo(instr.opcode).name, instr.hex);
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case OpCode::Type::FloatPredicate: {
|
||||
std::string op_a = instr.fsetp.neg_a ? "-" : "";
|
||||
op_a += GetRegister(instr.gpr8);
|
||||
|
||||
if (instr.fsetp.abs_a) {
|
||||
op_a = "abs(" + op_a + ')';
|
||||
}
|
||||
|
||||
std::string op_b{};
|
||||
|
||||
if (instr.is_b_imm) {
|
||||
if (instr.fsetp.neg_b) {
|
||||
// Only the immediate version of fsetp has a neg_b bit.
|
||||
op_b += '-';
|
||||
}
|
||||
op_b += '(' + GetImmediate19(instr) + ')';
|
||||
} else {
|
||||
if (instr.is_b_gpr) {
|
||||
op_b += GetRegister(instr.gpr20);
|
||||
} else {
|
||||
op_b += GetUniform(instr.uniform);
|
||||
}
|
||||
}
|
||||
|
||||
if (instr.fsetp.abs_b) {
|
||||
op_b = "abs(" + op_b + ')';
|
||||
}
|
||||
|
||||
using Tegra::Shader::Pred;
|
||||
ASSERT_MSG(instr.fsetp.pred0 == static_cast<u64>(Pred::UnusedIndex) &&
|
||||
instr.fsetp.pred39 == static_cast<u64>(Pred::UnusedIndex),
|
||||
"Compound predicates are not implemented");
|
||||
|
||||
// We can't use the constant predicate as destination.
|
||||
ASSERT(instr.fsetp.pred3 != static_cast<u64>(Pred::UnusedIndex));
|
||||
|
||||
using Tegra::Shader::PredCondition;
|
||||
switch (instr.fsetp.cond) {
|
||||
case PredCondition::LessThan:
|
||||
SetPredicate(instr.fsetp.pred3, '(' + op_a + ") < (" + op_b + ')');
|
||||
break;
|
||||
case PredCondition::Equal:
|
||||
SetPredicate(instr.fsetp.pred3, '(' + op_a + ") == (" + op_b + ')');
|
||||
break;
|
||||
default:
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled predicate condition: {} (a: {}, b: {})",
|
||||
static_cast<unsigned>(instr.fsetp.cond.Value()), op_a, op_b);
|
||||
UNREACHABLE();
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
switch (opcode->GetId()) {
|
||||
switch (instr.opcode.EffectiveOpCode()) {
|
||||
case OpCode::Id::EXIT: {
|
||||
ASSERT_MSG(instr.pred.pred_index == static_cast<u64>(Pred::UnusedIndex),
|
||||
"Predicated exits not implemented");
|
||||
shader.AddLine("return true;");
|
||||
offset = PROGRAM_END - 1;
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::KIL: {
|
||||
shader.AddLine("discard;");
|
||||
break;
|
||||
}
|
||||
case OpCode::Id::IPA: {
|
||||
const auto& attribute = instr.attribute.fmt28;
|
||||
std::string dest = GetRegister(instr.gpr0);
|
||||
@@ -590,7 +465,9 @@ private:
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
|
||||
NGLOG_CRITICAL(HW_GPU, "Unhandled instruction: {} ({}): {}",
|
||||
static_cast<unsigned>(instr.opcode.EffectiveOpCode()),
|
||||
OpCode::GetInfo(instr.opcode).name, instr.hex);
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
@@ -599,12 +476,6 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
// Close the predicate condition scope.
|
||||
if (instr.pred.pred_index != static_cast<u64>(Pred::UnusedIndex)) {
|
||||
--shader.scope;
|
||||
shader.AddLine('}');
|
||||
}
|
||||
|
||||
return offset + 1;
|
||||
}
|
||||
|
||||
@@ -734,12 +605,6 @@ private:
|
||||
declarations.AddNewLine();
|
||||
++const_buffer_layout;
|
||||
}
|
||||
|
||||
declarations.AddNewLine();
|
||||
for (const auto& pred : declr_predicates) {
|
||||
declarations.AddLine("bool " + pred + " = false;");
|
||||
}
|
||||
declarations.AddNewLine();
|
||||
}
|
||||
|
||||
private:
|
||||
@@ -753,7 +618,6 @@ private:
|
||||
|
||||
// Declarations
|
||||
std::set<std::string> declr_register;
|
||||
std::set<std::string> declr_predicates;
|
||||
std::set<Attribute::Index> declr_input_attribute;
|
||||
std::set<Attribute::Index> declr_output_attribute;
|
||||
std::array<ConstBufferEntry, Maxwell3D::Regs::MaxConstBuffers> declr_const_buffers;
|
||||
|
||||
@@ -9,10 +9,13 @@
|
||||
#include <memory>
|
||||
#include <glad/glad.h>
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/hw/hw.h"
|
||||
#include "core/hw/lcd.h"
|
||||
#include "core/memory.h"
|
||||
#include "core/settings.h"
|
||||
#include "core/tracer/recorder.h"
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#include "yuzu/util/util.h"
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/condition_variable.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
#include "core/hle/kernel/handle_table.h"
|
||||
#include "core/hle/kernel/mutex.h"
|
||||
#include "core/hle/kernel/thread.h"
|
||||
#include "core/hle/kernel/timer.h"
|
||||
@@ -67,6 +67,29 @@ QString WaitTreeText::GetText() const {
|
||||
return text;
|
||||
}
|
||||
|
||||
WaitTreeMutexInfo::WaitTreeMutexInfo(VAddr mutex_address) : mutex_address(mutex_address) {
|
||||
mutex_value = Memory::Read32(mutex_address);
|
||||
owner_handle = static_cast<Kernel::Handle>(mutex_value & Kernel::Mutex::MutexOwnerMask);
|
||||
owner = Kernel::g_handle_table.Get<Kernel::Thread>(owner_handle);
|
||||
}
|
||||
|
||||
QString WaitTreeMutexInfo::GetText() const {
|
||||
return tr("waiting for mutex 0x%1").arg(mutex_address, 16, 16, QLatin1Char('0'));
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexInfo::GetChildren() const {
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> list;
|
||||
|
||||
bool has_waiters = (mutex_value & Kernel::Mutex::MutexHasWaitersFlag) != 0;
|
||||
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("has waiters: %1").arg(has_waiters)));
|
||||
list.push_back(std::make_unique<WaitTreeText>(
|
||||
tr("owner handle: 0x%1").arg(owner_handle, 8, 16, QLatin1Char('0'))));
|
||||
if (owner != nullptr)
|
||||
list.push_back(std::make_unique<WaitTreeThread>(*owner));
|
||||
return list;
|
||||
}
|
||||
|
||||
WaitTreeWaitObject::WaitTreeWaitObject(const Kernel::WaitObject& o) : object(o) {}
|
||||
|
||||
bool WaitTreeExpandableItem::IsExpandable() const {
|
||||
@@ -84,11 +107,6 @@ std::unique_ptr<WaitTreeWaitObject> WaitTreeWaitObject::make(const Kernel::WaitO
|
||||
switch (object.GetHandleType()) {
|
||||
case Kernel::HandleType::Event:
|
||||
return std::make_unique<WaitTreeEvent>(static_cast<const Kernel::Event&>(object));
|
||||
case Kernel::HandleType::Mutex:
|
||||
return std::make_unique<WaitTreeMutex>(static_cast<const Kernel::Mutex&>(object));
|
||||
case Kernel::HandleType::ConditionVariable:
|
||||
return std::make_unique<WaitTreeConditionVariable>(
|
||||
static_cast<const Kernel::ConditionVariable&>(object));
|
||||
case Kernel::HandleType::Timer:
|
||||
return std::make_unique<WaitTreeTimer>(static_cast<const Kernel::Timer&>(object));
|
||||
case Kernel::HandleType::Thread:
|
||||
@@ -160,6 +178,9 @@ QString WaitTreeThread::GetText() const {
|
||||
case THREADSTATUS_WAIT_SYNCH_ANY:
|
||||
status = tr("waiting for objects");
|
||||
break;
|
||||
case THREADSTATUS_WAIT_MUTEX:
|
||||
status = tr("waiting for mutex");
|
||||
break;
|
||||
case THREADSTATUS_DORMANT:
|
||||
status = tr("dormant");
|
||||
break;
|
||||
@@ -186,6 +207,7 @@ QColor WaitTreeThread::GetColor() const {
|
||||
return QColor(Qt::GlobalColor::darkYellow);
|
||||
case THREADSTATUS_WAIT_SYNCH_ALL:
|
||||
case THREADSTATUS_WAIT_SYNCH_ANY:
|
||||
case THREADSTATUS_WAIT_MUTEX:
|
||||
return QColor(Qt::GlobalColor::red);
|
||||
case THREADSTATUS_DORMANT:
|
||||
return QColor(Qt::GlobalColor::darkCyan);
|
||||
@@ -225,11 +247,11 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
|
||||
list.push_back(std::make_unique<WaitTreeText>(
|
||||
tr("last running ticks = %1").arg(thread.last_running_ticks)));
|
||||
|
||||
if (thread.held_mutexes.empty()) {
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("not holding mutex")));
|
||||
} else {
|
||||
list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
|
||||
}
|
||||
if (thread.mutex_wait_address != 0)
|
||||
list.push_back(std::make_unique<WaitTreeMutexInfo>(thread.mutex_wait_address));
|
||||
else
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("not waiting for mutex")));
|
||||
|
||||
if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY ||
|
||||
thread.status == THREADSTATUS_WAIT_SYNCH_ALL) {
|
||||
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
|
||||
@@ -250,33 +272,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeEvent::GetChildren() const {
|
||||
return list;
|
||||
}
|
||||
|
||||
WaitTreeMutex::WaitTreeMutex(const Kernel::Mutex& object) : WaitTreeWaitObject(object) {}
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutex::GetChildren() const {
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
|
||||
|
||||
const auto& mutex = static_cast<const Kernel::Mutex&>(object);
|
||||
if (mutex.GetHasWaiters()) {
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("locked by thread:")));
|
||||
list.push_back(std::make_unique<WaitTreeThread>(*mutex.GetHoldingThread()));
|
||||
} else {
|
||||
list.push_back(std::make_unique<WaitTreeText>(tr("free")));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
WaitTreeConditionVariable::WaitTreeConditionVariable(const Kernel::ConditionVariable& object)
|
||||
: WaitTreeWaitObject(object) {}
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeConditionVariable::GetChildren() const {
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> list(WaitTreeWaitObject::GetChildren());
|
||||
|
||||
const auto& condition_variable = static_cast<const Kernel::ConditionVariable&>(object);
|
||||
list.push_back(std::make_unique<WaitTreeText>(
|
||||
tr("available count = %1").arg(condition_variable.GetAvailableCount())));
|
||||
return list;
|
||||
}
|
||||
|
||||
WaitTreeTimer::WaitTreeTimer(const Kernel::Timer& object) : WaitTreeWaitObject(object) {}
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
|
||||
@@ -293,21 +288,6 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeTimer::GetChildren() const {
|
||||
return list;
|
||||
}
|
||||
|
||||
WaitTreeMutexList::WaitTreeMutexList(
|
||||
const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list)
|
||||
: mutex_list(list) {}
|
||||
|
||||
QString WaitTreeMutexList::GetText() const {
|
||||
return tr("holding mutexes");
|
||||
}
|
||||
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeMutexList::GetChildren() const {
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> list(mutex_list.size());
|
||||
std::transform(mutex_list.begin(), mutex_list.end(), list.begin(),
|
||||
[](const auto& t) { return std::make_unique<WaitTreeMutex>(*t); });
|
||||
return list;
|
||||
}
|
||||
|
||||
WaitTreeThreadList::WaitTreeThreadList(const std::vector<Kernel::SharedPtr<Kernel::Thread>>& list)
|
||||
: thread_list(list) {}
|
||||
|
||||
|
||||
@@ -16,8 +16,6 @@ class EmuThread;
|
||||
namespace Kernel {
|
||||
class WaitObject;
|
||||
class Event;
|
||||
class Mutex;
|
||||
class ConditionVariable;
|
||||
class Thread;
|
||||
class Timer;
|
||||
} // namespace Kernel
|
||||
@@ -61,6 +59,20 @@ public:
|
||||
bool IsExpandable() const override;
|
||||
};
|
||||
|
||||
class WaitTreeMutexInfo : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeMutexInfo(VAddr mutex_address);
|
||||
QString GetText() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
|
||||
private:
|
||||
VAddr mutex_address;
|
||||
u32 mutex_value;
|
||||
Kernel::Handle owner_handle;
|
||||
Kernel::SharedPtr<Kernel::Thread> owner;
|
||||
};
|
||||
|
||||
class WaitTreeWaitObject : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -104,20 +116,6 @@ public:
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
};
|
||||
|
||||
class WaitTreeMutex : public WaitTreeWaitObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeMutex(const Kernel::Mutex& object);
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
};
|
||||
|
||||
class WaitTreeConditionVariable : public WaitTreeWaitObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeConditionVariable(const Kernel::ConditionVariable& object);
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
};
|
||||
|
||||
class WaitTreeTimer : public WaitTreeWaitObject {
|
||||
Q_OBJECT
|
||||
public:
|
||||
@@ -125,19 +123,6 @@ public:
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
};
|
||||
|
||||
class WaitTreeMutexList : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit WaitTreeMutexList(
|
||||
const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& list);
|
||||
|
||||
QString GetText() const override;
|
||||
std::vector<std::unique_ptr<WaitTreeItem>> GetChildren() const override;
|
||||
|
||||
private:
|
||||
const boost::container::flat_set<Kernel::SharedPtr<Kernel::Mutex>>& mutex_list;
|
||||
};
|
||||
|
||||
class WaitTreeThreadList : public WaitTreeExpandableItem {
|
||||
Q_OBJECT
|
||||
public:
|
||||
|
||||
@@ -56,28 +56,7 @@ void EmuWindow_SDL2::OnResize() {
|
||||
UpdateCurrentFramebufferLayout(width, height);
|
||||
}
|
||||
|
||||
void EmuWindow_SDL2::Fullscreen() {
|
||||
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
NGLOG_ERROR(Frontend, "Fullscreening failed: {}", SDL_GetError());
|
||||
|
||||
// Try a different fullscreening method
|
||||
NGLOG_INFO(Frontend, "Attempting to use borderless fullscreen...");
|
||||
if (SDL_SetWindowFullscreen(render_window, SDL_WINDOW_FULLSCREEN_DESKTOP) == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
NGLOG_ERROR(Frontend, "Borderless fullscreening failed: {}", SDL_GetError());
|
||||
|
||||
// Fallback algorithm: Maximise window.
|
||||
// Works on all systems (unless something is seriously wrong), so no fallback for this one.
|
||||
NGLOG_INFO(Frontend, "Falling back on a maximised window...");
|
||||
SDL_MaximizeWindow(render_window);
|
||||
}
|
||||
|
||||
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
EmuWindow_SDL2::EmuWindow_SDL2() {
|
||||
InputCommon::Init();
|
||||
|
||||
SDL_SetMainReady();
|
||||
@@ -111,10 +90,6 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
if (fullscreen) {
|
||||
Fullscreen();
|
||||
}
|
||||
|
||||
gl_context = SDL_GL_CreateContext(render_window);
|
||||
|
||||
if (gl_context == nullptr) {
|
||||
|
||||
@@ -12,7 +12,7 @@ struct SDL_Window;
|
||||
|
||||
class EmuWindow_SDL2 : public EmuWindow {
|
||||
public:
|
||||
explicit EmuWindow_SDL2(bool fullscreen);
|
||||
EmuWindow_SDL2();
|
||||
~EmuWindow_SDL2();
|
||||
|
||||
/// Swap buffers to display the next frame
|
||||
@@ -43,9 +43,6 @@ private:
|
||||
/// Called by PollEvents when any event that may cause the window to be resized occurs
|
||||
void OnResize();
|
||||
|
||||
/// Called when user passes the fullscreen parameter flag
|
||||
void Fullscreen();
|
||||
|
||||
/// Called when a configuration change affects the minimal size of the window
|
||||
void OnMinimalClientAreaChangeRequest(
|
||||
const std::pair<unsigned, unsigned>& minimal_size) override;
|
||||
|
||||
@@ -50,7 +50,6 @@ static void PrintHelp(const char* argv0) {
|
||||
std::cout << "Usage: " << argv0
|
||||
<< " [options] <filename>\n"
|
||||
"-g, --gdbport=NUMBER Enable gdb stub on port NUMBER\n"
|
||||
"-f, --fullscreen Start in fullscreen mode\n"
|
||||
"-h, --help Display this help and exit\n"
|
||||
"-v, --version Output version information and exit\n";
|
||||
}
|
||||
@@ -77,18 +76,15 @@ int main(int argc, char** argv) {
|
||||
#endif
|
||||
std::string filepath;
|
||||
|
||||
bool fullscreen = false;
|
||||
|
||||
static struct option long_options[] = {
|
||||
{"gdbport", required_argument, 0, 'g'},
|
||||
{"fullscreen", no_argument, 0, 'f'},
|
||||
{"help", no_argument, 0, 'h'},
|
||||
{"version", no_argument, 0, 'v'},
|
||||
{0, 0, 0, 0},
|
||||
};
|
||||
|
||||
while (optind < argc) {
|
||||
char arg = getopt_long(argc, argv, "g:fhv", long_options, &option_index);
|
||||
char arg = getopt_long(argc, argv, "g:hv", long_options, &option_index);
|
||||
if (arg != -1) {
|
||||
switch (arg) {
|
||||
case 'g':
|
||||
@@ -102,10 +98,6 @@ int main(int argc, char** argv) {
|
||||
exit(1);
|
||||
}
|
||||
break;
|
||||
case 'f':
|
||||
fullscreen = true;
|
||||
NGLOG_INFO(Frontend, "Starting in fullscreen mode...");
|
||||
break;
|
||||
case 'h':
|
||||
PrintHelp(argv[0]);
|
||||
return 0;
|
||||
@@ -145,7 +137,7 @@ int main(int argc, char** argv) {
|
||||
Settings::values.use_gdbstub = use_gdbstub;
|
||||
Settings::Apply();
|
||||
|
||||
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>(fullscreen)};
|
||||
std::unique_ptr<EmuWindow_SDL2> emu_window{std::make_unique<EmuWindow_SDL2>()};
|
||||
|
||||
Core::System& system{Core::System::GetInstance()};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user