Merge pull request #5208 from bunnei/service-threads

Service threads
master
bunnei 2020-12-30 22:06:05 +07:00 committed by GitHub
commit 25d607f5f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 770 additions and 1001 deletions

@ -11,7 +11,6 @@
#include "audio_core/info_updater.h" #include "audio_core/info_updater.h"
#include "audio_core/voice_context.h" #include "audio_core/voice_context.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/hle/kernel/writable_event.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/settings.h" #include "core/settings.h"
@ -71,10 +70,9 @@ namespace {
namespace AudioCore { namespace AudioCore {
AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
AudioCommon::AudioRendererParameter params, AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event_, Stream::ReleaseCallback&& release_callback,
std::size_t instance_number) std::size_t instance_number)
: worker_params{params}, buffer_event{buffer_event_}, : worker_params{params}, memory_pool_info(params.effect_count + params.voice_count * 4),
memory_pool_info(params.effect_count + params.voice_count * 4),
voice_context(params.voice_count), effect_context(params.effect_count), mix_context(), voice_context(params.voice_count), effect_context(params.effect_count), mix_context(),
sink_context(params.sink_count), splitter_context(), sink_context(params.sink_count), splitter_context(),
voices(params.voice_count), memory{memory_}, voices(params.voice_count), memory{memory_},
@ -85,10 +83,9 @@ AudioRenderer::AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory
params.num_splitter_send_channels); params.num_splitter_send_channels);
mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count); mix_context.Initialize(behavior_info, params.submix_count + 1, params.effect_count);
audio_out = std::make_unique<AudioCore::AudioOut>(); audio_out = std::make_unique<AudioCore::AudioOut>();
stream = stream = audio_out->OpenStream(
audio_out->OpenStream(core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS, core_timing, params.sample_rate, AudioCommon::STREAM_NUM_CHANNELS,
fmt::format("AudioRenderer-Instance{}", instance_number), fmt::format("AudioRenderer-Instance{}", instance_number), std::move(release_callback));
[=]() { buffer_event_->Signal(); });
audio_out->StartStream(stream); audio_out->StartStream(stream);
QueueMixedBuffer(0); QueueMixedBuffer(0);

@ -27,10 +27,6 @@ namespace Core::Timing {
class CoreTiming; class CoreTiming;
} }
namespace Kernel {
class WritableEvent;
}
namespace Core::Memory { namespace Core::Memory {
class Memory; class Memory;
} }
@ -44,8 +40,7 @@ class AudioRenderer {
public: public:
AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_, AudioRenderer(Core::Timing::CoreTiming& core_timing, Core::Memory::Memory& memory_,
AudioCommon::AudioRendererParameter params, AudioCommon::AudioRendererParameter params,
std::shared_ptr<Kernel::WritableEvent> buffer_event_, Stream::ReleaseCallback&& release_callback, std::size_t instance_number);
std::size_t instance_number);
~AudioRenderer(); ~AudioRenderer();
[[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params, [[nodiscard]] ResultCode UpdateAudioRenderer(const std::vector<u8>& input_params,
@ -61,7 +56,6 @@ private:
BehaviorInfo behavior_info{}; BehaviorInfo behavior_info{};
AudioCommon::AudioRendererParameter worker_params; AudioCommon::AudioRendererParameter worker_params;
std::shared_ptr<Kernel::WritableEvent> buffer_event;
std::vector<ServerMemoryPoolInfo> memory_pool_info; std::vector<ServerMemoryPoolInfo> memory_pool_info;
VoiceContext voice_context; VoiceContext voice_context;
EffectContext effect_context; EffectContext effect_context;

@ -130,7 +130,11 @@ bool Stream::ContainsBuffer([[maybe_unused]] Buffer::Tag tag) const {
std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) { std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers(std::size_t max_count) {
std::vector<Buffer::Tag> tags; std::vector<Buffer::Tag> tags;
for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) { for (std::size_t count = 0; count < max_count && !released_buffers.empty(); ++count) {
tags.push_back(released_buffers.front()->GetTag()); if (released_buffers.front()) {
tags.push_back(released_buffers.front()->GetTag());
} else {
ASSERT_MSG(false, "Invalid tag in released_buffers!");
}
released_buffers.pop(); released_buffers.pop();
} }
return tags; return tags;
@ -140,7 +144,11 @@ std::vector<Buffer::Tag> Stream::GetTagsAndReleaseBuffers() {
std::vector<Buffer::Tag> tags; std::vector<Buffer::Tag> tags;
tags.reserve(released_buffers.size()); tags.reserve(released_buffers.size());
while (!released_buffers.empty()) { while (!released_buffers.empty()) {
tags.push_back(released_buffers.front()->GetTag()); if (released_buffers.front()) {
tags.push_back(released_buffers.front()->GetTag());
} else {
ASSERT_MSG(false, "Invalid tag in released_buffers!");
}
released_buffers.pop(); released_buffers.pop();
} }
return tags; return tags;

@ -162,6 +162,8 @@ add_library(common STATIC
thread.cpp thread.cpp
thread.h thread.h
thread_queue_list.h thread_queue_list.h
thread_worker.cpp
thread_worker.h
threadsafe_queue.h threadsafe_queue.h
time_zone.cpp time_zone.cpp
time_zone.h time_zone.h

@ -0,0 +1,58 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/thread.h"
#include "common/thread_worker.h"
namespace Common {
ThreadWorker::ThreadWorker(std::size_t num_workers, const std::string& name) {
for (std::size_t i = 0; i < num_workers; ++i)
threads.emplace_back([this, thread_name{std::string{name}}] {
Common::SetCurrentThreadName(thread_name.c_str());
// Wait for first request
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return stop || !requests.empty(); });
}
while (true) {
std::function<void()> task;
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return stop || !requests.empty(); });
if (stop || requests.empty()) {
return;
}
task = std::move(requests.front());
requests.pop();
}
task();
}
});
}
ThreadWorker::~ThreadWorker() {
{
std::unique_lock lock{queue_mutex};
stop = true;
}
condition.notify_all();
for (std::thread& thread : threads) {
thread.join();
}
}
void ThreadWorker::QueueWork(std::function<void()>&& work) {
{
std::unique_lock lock{queue_mutex};
requests.emplace(work);
}
condition.notify_one();
}
} // namespace Common

@ -0,0 +1,30 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <functional>
#include <mutex>
#include <string>
#include <vector>
#include <queue>
namespace Common {
class ThreadWorker final {
public:
explicit ThreadWorker(std::size_t num_workers, const std::string& name);
~ThreadWorker();
void QueueWork(std::function<void()>&& work);
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> requests;
std::mutex queue_mutex;
std::condition_variable condition;
std::atomic_bool stop{};
};
} // namespace Common

@ -202,6 +202,8 @@ add_library(core STATIC
hle/kernel/server_port.h hle/kernel/server_port.h
hle/kernel/server_session.cpp hle/kernel/server_session.cpp
hle/kernel/server_session.h hle/kernel/server_session.h
hle/kernel/service_thread.cpp
hle/kernel/service_thread.h
hle/kernel/session.cpp hle/kernel/session.cpp
hle/kernel/session.h hle/kernel/session.h
hle/kernel/shared_memory.cpp hle/kernel/shared_memory.cpp
@ -500,7 +502,6 @@ add_library(core STATIC
hle/service/sm/controller.h hle/service/sm/controller.h
hle/service/sm/sm.cpp hle/service/sm/sm.cpp
hle/service/sm/sm.h hle/service/sm/sm.h
hle/service/sockets/blocking_worker.h
hle/service/sockets/bsd.cpp hle/service/sockets/bsd.cpp
hle/service/sockets/bsd.h hle/service/sockets/bsd.h
hle/service/sockets/ethc.cpp hle/service/sockets/ethc.cpp

@ -159,7 +159,7 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>(); device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue(); is_multicore = Settings::values.use_multi_core.GetValue();
is_async_gpu = is_multicore || Settings::values.use_asynchronous_gpu_emulation.GetValue(); is_async_gpu = Settings::values.use_asynchronous_gpu_emulation.GetValue();
kernel.SetMulticore(is_multicore); kernel.SetMulticore(is_multicore);
cpu_manager.SetMulticore(is_multicore); cpu_manager.SetMulticore(is_multicore);
@ -307,7 +307,6 @@ struct System::Impl {
service_manager.reset(); service_manager.reset();
cheat_engine.reset(); cheat_engine.reset();
telemetry_session.reset(); telemetry_session.reset();
device_memory.reset();
// Close all CPU/threading state // Close all CPU/threading state
cpu_manager.Shutdown(); cpu_manager.Shutdown();

@ -46,43 +46,6 @@ void SessionRequestHandler::ClientDisconnected(
boost::range::remove_erase(connected_sessions, server_session); boost::range::remove_erase(connected_sessions, server_session);
} }
std::shared_ptr<WritableEvent> HLERequestContext::SleepClientThread(
const std::string& reason, u64 timeout, WakeupCallback&& callback,
std::shared_ptr<WritableEvent> writable_event) {
// Put the client thread to sleep until the wait event is signaled or the timeout expires.
if (!writable_event) {
// Create event if not provided
const auto pair = WritableEvent::CreateEventPair(kernel, "HLE Pause Event: " + reason);
writable_event = pair.writable;
}
Handle event_handle = InvalidHandle;
{
KScopedSchedulerLockAndSleep lock(kernel, event_handle, thread.get(), timeout);
thread->SetHLECallback(
[context = *this, callback](std::shared_ptr<Thread> thread) mutable -> bool {
ThreadWakeupReason reason = thread->GetSignalingResult() == RESULT_TIMEOUT
? ThreadWakeupReason::Timeout
: ThreadWakeupReason::Signal;
callback(thread, context, reason);
context.WriteToOutgoingCommandBuffer(*thread);
return true;
});
const auto readable_event{writable_event->GetReadableEvent()};
writable_event->Clear();
thread->SetHLESyncObject(readable_event.get());
thread->SetStatus(ThreadStatus::WaitHLEEvent);
thread->SetSynchronizationResults(nullptr, RESULT_TIMEOUT);
readable_event->AddWaitingThread(thread);
}
thread->SetHLETimeEvent(event_handle);
is_thread_waiting = true;
return writable_event;
}
HLERequestContext::HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory, HLERequestContext::HLERequestContext(KernelCore& kernel, Core::Memory::Memory& memory,
std::shared_ptr<ServerSession> server_session, std::shared_ptr<ServerSession> server_session,
std::shared_ptr<Thread> thread) std::shared_ptr<Thread> thread)

@ -129,23 +129,6 @@ public:
using WakeupCallback = std::function<void( using WakeupCallback = std::function<void(
std::shared_ptr<Thread> thread, HLERequestContext& context, ThreadWakeupReason reason)>; std::shared_ptr<Thread> thread, HLERequestContext& context, ThreadWakeupReason reason)>;
/**
* Puts the specified guest thread to sleep until the returned event is signaled or until the
* specified timeout expires.
* @param reason Reason for pausing the thread, to be used for debugging purposes.
* @param timeout Timeout in nanoseconds after which the thread will be awoken and the callback
* invoked with a Timeout reason.
* @param callback Callback to be invoked when the thread is resumed. This callback must write
* the entire command response once again, regardless of the state of it before this function
* was called.
* @param writable_event Event to use to wake up the thread. If unspecified, an event will be
* created.
* @returns Event that when signaled will resume the thread and call the callback function.
*/
std::shared_ptr<WritableEvent> SleepClientThread(
const std::string& reason, u64 timeout, WakeupCallback&& callback,
std::shared_ptr<WritableEvent> writable_event = nullptr);
/// Populates this context with data from the requesting process/thread. /// Populates this context with data from the requesting process/thread.
ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table, ResultCode PopulateFromIncomingCommandBuffer(const HandleTable& handle_table,
u32_le* src_cmdbuf); u32_le* src_cmdbuf);

@ -8,13 +8,14 @@
#include <functional> #include <functional>
#include <memory> #include <memory>
#include <thread> #include <thread>
#include <unordered_map> #include <unordered_set>
#include <utility> #include <utility>
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/thread.h" #include "common/thread.h"
#include "common/thread_worker.h"
#include "core/arm/arm_interface.h" #include "core/arm/arm_interface.h"
#include "core/arm/cpu_interrupt_handler.h" #include "core/arm/cpu_interrupt_handler.h"
#include "core/arm/exclusive_monitor.h" #include "core/arm/exclusive_monitor.h"
@ -35,6 +36,7 @@
#include "core/hle/kernel/physical_core.h" #include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h" #include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h" #include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/service_thread.h"
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
#include "core/hle/kernel/synchronization.h" #include "core/hle/kernel/synchronization.h"
#include "core/hle/kernel/thread.h" #include "core/hle/kernel/thread.h"
@ -60,6 +62,8 @@ struct KernelCore::Impl {
RegisterHostThread(); RegisterHostThread();
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel); global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
service_thread_manager =
std::make_unique<Common::ThreadWorker>(1, "yuzu:ServiceThreadManager");
InitializePhysicalCores(); InitializePhysicalCores();
InitializeSystemResourceLimit(kernel); InitializeSystemResourceLimit(kernel);
@ -76,6 +80,12 @@ struct KernelCore::Impl {
} }
void Shutdown() { void Shutdown() {
process_list.clear();
// Ensures all service threads gracefully shutdown
service_thread_manager.reset();
service_threads.clear();
next_object_id = 0; next_object_id = 0;
next_kernel_process_id = Process::InitialKIPIDMin; next_kernel_process_id = Process::InitialKIPIDMin;
next_user_process_id = Process::ProcessIDMin; next_user_process_id = Process::ProcessIDMin;
@ -89,8 +99,6 @@ struct KernelCore::Impl {
cores.clear(); cores.clear();
process_list.clear();
current_process = nullptr; current_process = nullptr;
system_resource_limit = nullptr; system_resource_limit = nullptr;
@ -103,10 +111,8 @@ struct KernelCore::Impl {
exclusive_monitor.reset(); exclusive_monitor.reset();
num_host_threads = 0; // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
std::fill(register_host_thread_keys.begin(), register_host_thread_keys.end(), next_host_thread_id = Core::Hardware::NUM_CPU_CORES;
std::thread::id{});
std::fill(register_host_thread_values.begin(), register_host_thread_values.end(), 0);
} }
void InitializePhysicalCores() { void InitializePhysicalCores() {
@ -186,52 +192,46 @@ struct KernelCore::Impl {
} }
} }
/// Creates a new host thread ID, should only be called by GetHostThreadId
u32 AllocateHostThreadId(std::optional<std::size_t> core_id) {
if (core_id) {
// The first for slots are reserved for CPU core threads
ASSERT(*core_id < Core::Hardware::NUM_CPU_CORES);
return static_cast<u32>(*core_id);
} else {
return next_host_thread_id++;
}
}
/// Gets the host thread ID for the caller, allocating a new one if this is the first time
u32 GetHostThreadId(std::optional<std::size_t> core_id = std::nullopt) {
const thread_local auto host_thread_id{AllocateHostThreadId(core_id)};
return host_thread_id;
}
/// Registers a CPU core thread by allocating a host thread ID for it
void RegisterCoreThread(std::size_t core_id) { void RegisterCoreThread(std::size_t core_id) {
const std::thread::id this_id = std::this_thread::get_id(); ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
const auto this_id = GetHostThreadId(core_id);
if (!is_multicore) { if (!is_multicore) {
single_core_thread_id = this_id; single_core_thread_id = this_id;
} }
const auto end =
register_host_thread_keys.begin() + static_cast<ptrdiff_t>(num_host_threads);
const auto it = std::find(register_host_thread_keys.begin(), end, this_id);
ASSERT(core_id < Core::Hardware::NUM_CPU_CORES);
ASSERT(it == end);
InsertHostThread(static_cast<u32>(core_id));
} }
/// Registers a new host thread by allocating a host thread ID for it
void RegisterHostThread() { void RegisterHostThread() {
const std::thread::id this_id = std::this_thread::get_id(); [[maybe_unused]] const auto this_id = GetHostThreadId();
const auto end =
register_host_thread_keys.begin() + static_cast<ptrdiff_t>(num_host_threads);
const auto it = std::find(register_host_thread_keys.begin(), end, this_id);
if (it == end) {
InsertHostThread(registered_thread_ids++);
}
} }
void InsertHostThread(u32 value) { [[nodiscard]] u32 GetCurrentHostThreadID() {
const size_t index = num_host_threads++; const auto this_id = GetHostThreadId();
ASSERT_MSG(index < NUM_REGISTRABLE_HOST_THREADS, "Too many host threads");
register_host_thread_values[index] = value;
register_host_thread_keys[index] = std::this_thread::get_id();
}
[[nodiscard]] u32 GetCurrentHostThreadID() const {
const std::thread::id this_id = std::this_thread::get_id();
if (!is_multicore && single_core_thread_id == this_id) { if (!is_multicore && single_core_thread_id == this_id) {
return static_cast<u32>(system.GetCpuManager().CurrentCore()); return static_cast<u32>(system.GetCpuManager().CurrentCore());
} }
const auto end = return this_id;
register_host_thread_keys.begin() + static_cast<ptrdiff_t>(num_host_threads);
const auto it = std::find(register_host_thread_keys.begin(), end, this_id);
if (it == end) {
return Core::INVALID_HOST_THREAD_ID;
}
return register_host_thread_values[static_cast<size_t>(
std::distance(register_host_thread_keys.begin(), it))];
} }
Core::EmuThreadHandle GetCurrentEmuThreadID() const { [[nodiscard]] Core::EmuThreadHandle GetCurrentEmuThreadID() {
Core::EmuThreadHandle result = Core::EmuThreadHandle::InvalidHandle(); Core::EmuThreadHandle result = Core::EmuThreadHandle::InvalidHandle();
result.host_handle = GetCurrentHostThreadID(); result.host_handle = GetCurrentHostThreadID();
if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) { if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) {
@ -325,15 +325,8 @@ struct KernelCore::Impl {
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor; std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
std::vector<Kernel::PhysicalCore> cores; std::vector<Kernel::PhysicalCore> cores;
// 0-3 IDs represent core threads, >3 represent others // Next host thead ID to use, 0-3 IDs represent core threads, >3 represent others
std::atomic<u32> registered_thread_ids{Core::Hardware::NUM_CPU_CORES}; std::atomic<u32> next_host_thread_id{Core::Hardware::NUM_CPU_CORES};
// Number of host threads is a relatively high number to avoid overflowing
static constexpr size_t NUM_REGISTRABLE_HOST_THREADS = 64;
std::atomic<size_t> num_host_threads{0};
std::array<std::atomic<std::thread::id>, NUM_REGISTRABLE_HOST_THREADS>
register_host_thread_keys{};
std::array<std::atomic<u32>, NUM_REGISTRABLE_HOST_THREADS> register_host_thread_values{};
// Kernel memory management // Kernel memory management
std::unique_ptr<Memory::MemoryManager> memory_manager; std::unique_ptr<Memory::MemoryManager> memory_manager;
@ -345,12 +338,19 @@ struct KernelCore::Impl {
std::shared_ptr<Kernel::SharedMemory> irs_shared_mem; std::shared_ptr<Kernel::SharedMemory> irs_shared_mem;
std::shared_ptr<Kernel::SharedMemory> time_shared_mem; std::shared_ptr<Kernel::SharedMemory> time_shared_mem;
// Threads used for services
std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads;
// Service threads are managed by a worker thread, so that a calling service thread can queue up
// the release of itself
std::unique_ptr<Common::ThreadWorker> service_thread_manager;
std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{}; std::array<std::shared_ptr<Thread>, Core::Hardware::NUM_CPU_CORES> suspend_threads{};
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{}; std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{}; std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
bool is_multicore{}; bool is_multicore{};
std::thread::id single_core_thread_id{}; u32 single_core_thread_id{};
std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{}; std::array<u64, Core::Hardware::NUM_CPU_CORES> svc_ticks{};
@ -639,4 +639,19 @@ void KernelCore::ExitSVCProfile() {
MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]); MicroProfileLeave(MICROPROFILE_TOKEN(Kernel_SVC), impl->svc_ticks[core]);
} }
std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {
auto service_thread = std::make_shared<Kernel::ServiceThread>(*this, 1, name);
impl->service_thread_manager->QueueWork(
[this, service_thread] { impl->service_threads.emplace(service_thread); });
return service_thread;
}
void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
impl->service_thread_manager->QueueWork([this, service_thread] {
if (auto strong_ptr = service_thread.lock()) {
impl->service_threads.erase(strong_ptr);
}
});
}
} // namespace Kernel } // namespace Kernel

@ -42,6 +42,7 @@ class Process;
class ResourceLimit; class ResourceLimit;
class KScheduler; class KScheduler;
class SharedMemory; class SharedMemory;
class ServiceThread;
class Synchronization; class Synchronization;
class Thread; class Thread;
class TimeManager; class TimeManager;
@ -227,6 +228,22 @@ public:
void ExitSVCProfile(); void ExitSVCProfile();
/**
* Creates an HLE service thread, which are used to execute service routines asynchronously.
* While these are allocated per ServerSession, these need to be owned and managed outside of
* ServerSession to avoid a circular dependency.
* @param name String name for the ServerSession creating this thread, used for debug purposes.
* @returns The a weak pointer newly created service thread.
*/
std::weak_ptr<Kernel::ServiceThread> CreateServiceThread(const std::string& name);
/**
* Releases a HLE service thread, instructing KernelCore to free it. This should be called when
* the ServerSession associated with the thread is destroyed.
* @param service_thread Service thread to release.
*/
void ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread);
private: private:
friend class Object; friend class Object;
friend class Process; friend class Process;

@ -25,19 +25,19 @@
namespace Kernel { namespace Kernel {
ServerSession::ServerSession(KernelCore& kernel) : SynchronizationObject{kernel} {} ServerSession::ServerSession(KernelCore& kernel) : SynchronizationObject{kernel} {}
ServerSession::~ServerSession() = default;
ServerSession::~ServerSession() {
kernel.ReleaseServiceThread(service_thread);
}
ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel, ResultVal<std::shared_ptr<ServerSession>> ServerSession::Create(KernelCore& kernel,
std::shared_ptr<Session> parent, std::shared_ptr<Session> parent,
std::string name) { std::string name) {
std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)}; std::shared_ptr<ServerSession> session{std::make_shared<ServerSession>(kernel)};
session->request_event =
Core::Timing::CreateEvent(name, [session](std::uintptr_t, std::chrono::nanoseconds) {
session->CompleteSyncRequest();
});
session->name = std::move(name); session->name = std::move(name);
session->parent = std::move(parent); session->parent = std::move(parent);
session->service_thread = kernel.CreateServiceThread(session->name);
return MakeResult(std::move(session)); return MakeResult(std::move(session));
} }
@ -142,16 +142,16 @@ ResultCode ServerSession::QueueSyncRequest(std::shared_ptr<Thread> thread,
std::make_shared<HLERequestContext>(kernel, memory, SharedFrom(this), std::move(thread)); std::make_shared<HLERequestContext>(kernel, memory, SharedFrom(this), std::move(thread));
context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
request_queue.Push(std::move(context));
if (auto strong_ptr = service_thread.lock()) {
strong_ptr->QueueSyncRequest(*this, std::move(context));
return RESULT_SUCCESS;
}
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }
ResultCode ServerSession::CompleteSyncRequest() { ResultCode ServerSession::CompleteSyncRequest(HLERequestContext& context) {
ASSERT(!request_queue.Empty());
auto& context = *request_queue.Front();
ResultCode result = RESULT_SUCCESS; ResultCode result = RESULT_SUCCESS;
// If the session has been converted to a domain, handle the domain request // If the session has been converted to a domain, handle the domain request
if (IsDomain() && context.HasDomainMessageHeader()) { if (IsDomain() && context.HasDomainMessageHeader()) {
@ -177,18 +177,13 @@ ResultCode ServerSession::CompleteSyncRequest() {
} }
} }
request_queue.Pop();
return result; return result;
} }
ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread, ResultCode ServerSession::HandleSyncRequest(std::shared_ptr<Thread> thread,
Core::Memory::Memory& memory, Core::Memory::Memory& memory,
Core::Timing::CoreTiming& core_timing) { Core::Timing::CoreTiming& core_timing) {
const ResultCode result = QueueSyncRequest(std::move(thread), memory); return QueueSyncRequest(std::move(thread), memory);
const auto delay = std::chrono::nanoseconds{kernel.IsMulticore() ? 0 : 20000};
core_timing.ScheduleEvent(delay, request_event, {});
return result;
} }
} // namespace Kernel } // namespace Kernel

@ -10,6 +10,7 @@
#include <vector> #include <vector>
#include "common/threadsafe_queue.h" #include "common/threadsafe_queue.h"
#include "core/hle/kernel/service_thread.h"
#include "core/hle/kernel/synchronization_object.h" #include "core/hle/kernel/synchronization_object.h"
#include "core/hle/result.h" #include "core/hle/result.h"
@ -43,6 +44,8 @@ class Thread;
* TLS buffer and control is transferred back to it. * TLS buffer and control is transferred back to it.
*/ */
class ServerSession final : public SynchronizationObject { class ServerSession final : public SynchronizationObject {
friend class ServiceThread;
public: public:
explicit ServerSession(KernelCore& kernel); explicit ServerSession(KernelCore& kernel);
~ServerSession() override; ~ServerSession() override;
@ -132,7 +135,7 @@ private:
ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory); ResultCode QueueSyncRequest(std::shared_ptr<Thread> thread, Core::Memory::Memory& memory);
/// Completes a sync request from the emulated application. /// Completes a sync request from the emulated application.
ResultCode CompleteSyncRequest(); ResultCode CompleteSyncRequest(HLERequestContext& context);
/// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an /// Handles a SyncRequest to a domain, forwarding the request to the proper object or closing an
/// object handle. /// object handle.
@ -163,11 +166,8 @@ private:
/// The name of this session (optional) /// The name of this session (optional)
std::string name; std::string name;
/// Core timing event used to schedule the service request at some point in the future /// Thread to dispatch service requests
std::shared_ptr<Core::Timing::EventType> request_event; std::weak_ptr<ServiceThread> service_thread;
/// Queue of scheduled service requests
Common::MPSCQueue<std::shared_ptr<Kernel::HLERequestContext>> request_queue;
}; };
} // namespace Kernel } // namespace Kernel

@ -0,0 +1,110 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <condition_variable>
#include <functional>
#include <mutex>
#include <thread>
#include <vector>
#include <queue>
#include "common/assert.h"
#include "common/scope_exit.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/server_session.h"
#include "core/hle/kernel/service_thread.h"
#include "core/hle/lock.h"
#include "video_core/renderer_base.h"
namespace Kernel {
class ServiceThread::Impl final {
public:
explicit Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name);
~Impl();
void QueueSyncRequest(ServerSession& session, std::shared_ptr<HLERequestContext>&& context);
private:
std::vector<std::thread> threads;
std::queue<std::function<void()>> requests;
std::mutex queue_mutex;
std::condition_variable condition;
const std::string service_name;
bool stop{};
};
ServiceThread::Impl::Impl(KernelCore& kernel, std::size_t num_threads, const std::string& name)
: service_name{name} {
for (std::size_t i = 0; i < num_threads; ++i)
threads.emplace_back([this, &kernel] {
Common::SetCurrentThreadName(std::string{"yuzu:HleService:" + service_name}.c_str());
// Wait for first request before trying to acquire a render context
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return stop || !requests.empty(); });
}
kernel.RegisterHostThread();
while (true) {
std::function<void()> task;
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return stop || !requests.empty(); });
if (stop || requests.empty()) {
return;
}
task = std::move(requests.front());
requests.pop();
}
task();
}
});
}
void ServiceThread::Impl::QueueSyncRequest(ServerSession& session,
std::shared_ptr<HLERequestContext>&& context) {
{
std::unique_lock lock{queue_mutex};
// ServerSession owns the service thread, so we cannot caption a strong pointer here in the
// event that the ServerSession is terminated.
std::weak_ptr<ServerSession> weak_ptr{SharedFrom(&session)};
requests.emplace([weak_ptr, context{std::move(context)}]() {
if (auto strong_ptr = weak_ptr.lock()) {
strong_ptr->CompleteSyncRequest(*context);
}
});
}
condition.notify_one();
}
ServiceThread::Impl::~Impl() {
{
std::unique_lock lock{queue_mutex};
stop = true;
}
condition.notify_all();
for (std::thread& thread : threads) {
thread.join();
}
}
ServiceThread::ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name)
: impl{std::make_unique<Impl>(kernel, num_threads, name)} {}
ServiceThread::~ServiceThread() = default;
void ServiceThread::QueueSyncRequest(ServerSession& session,
std::shared_ptr<HLERequestContext>&& context) {
impl->QueueSyncRequest(session, std::move(context));
}
} // namespace Kernel

@ -0,0 +1,28 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <memory>
#include <string>
namespace Kernel {
class HLERequestContext;
class KernelCore;
class ServerSession;
class ServiceThread final {
public:
explicit ServiceThread(KernelCore& kernel, std::size_t num_threads, const std::string& name);
~ServiceThread();
void QueueSyncRequest(ServerSession& session, std::shared_ptr<HLERequestContext>&& context);
private:
class Impl;
std::unique_ptr<Impl> impl;
};
} // namespace Kernel

@ -70,8 +70,10 @@ public:
Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioOutBufferReleased"); Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioOutBufferReleased");
stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate, stream = audio_core.OpenStream(system.CoreTiming(), audio_params.sample_rate,
audio_params.channel_count, std::move(unique_name), audio_params.channel_count, std::move(unique_name), [this] {
[this] { buffer_event.writable->Signal(); }); const auto guard = LockService();
buffer_event.writable->Signal();
});
} }
private: private:

@ -49,16 +49,16 @@ public:
system_event = system_event =
Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioRenderer:SystemEvent"); Kernel::WritableEvent::CreateEventPair(system.Kernel(), "IAudioRenderer:SystemEvent");
renderer = std::make_unique<AudioCore::AudioRenderer>(system.CoreTiming(), system.Memory(), renderer = std::make_unique<AudioCore::AudioRenderer>(
audren_params, system_event.writable, system.CoreTiming(), system.Memory(), audren_params,
instance_number); [this]() {
const auto guard = LockService();
system_event.writable->Signal();
},
instance_number);
} }
private: private:
void UpdateAudioCallback() {
system_event.writable->Signal();
}
void GetSampleRate(Kernel::HLERequestContext& ctx) { void GetSampleRate(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Audio, "called"); LOG_DEBUG(Service_Audio, "called");

@ -78,11 +78,13 @@ IAppletResource::IAppletResource(Core::System& system_)
pad_update_event = Core::Timing::CreateEvent( pad_update_event = Core::Timing::CreateEvent(
"HID::UpdatePadCallback", "HID::UpdatePadCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
const auto guard = LockService();
UpdateControllers(user_data, ns_late); UpdateControllers(user_data, ns_late);
}); });
motion_update_event = Core::Timing::CreateEvent( motion_update_event = Core::Timing::CreateEvent(
"HID::MotionPadCallback", "HID::MotionPadCallback",
[this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) { [this](std::uintptr_t user_data, std::chrono::nanoseconds ns_late) {
const auto guard = LockService();
UpdateMotion(user_data, ns_late); UpdateMotion(user_data, ns_late);
}); });

@ -31,8 +31,8 @@ public:
* @param output A buffer where the output data will be written to. * @param output A buffer where the output data will be written to.
* @returns The result code of the ioctl. * @returns The result code of the ioctl.
*/ */
virtual NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, virtual NvResult Ioctl1(Ioctl command, const std::vector<u8>& input,
IoctlCtrl& ctrl) = 0; std::vector<u8>& output) = 0;
/** /**
* Handles an ioctl2 request. * Handles an ioctl2 request.
@ -43,8 +43,7 @@ public:
* @returns The result code of the ioctl. * @returns The result code of the ioctl.
*/ */
virtual NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, virtual NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) = 0;
IoctlCtrl& ctrl) = 0;
/** /**
* Handles an ioctl3 request. * Handles an ioctl3 request.
@ -55,7 +54,7 @@ public:
* @returns The result code of the ioctl. * @returns The result code of the ioctl.
*/ */
virtual NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, virtual NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) = 0; std::vector<u8>& inline_output) = 0;
protected: protected:
Core::System& system; Core::System& system;

@ -18,21 +18,20 @@ nvdisp_disp0::nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_de
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvdisp_disp0 ::~nvdisp_disp0() = default; nvdisp_disp0 ::~nvdisp_disp0() = default;
NvResult nvdisp_disp0::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvdisp_disp0::Ioctl1(Ioctl command, const std::vector<u8>& input,
IoctlCtrl& ctrl) { std::vector<u8>& output) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvdisp_disp0::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvdisp_disp0::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvdisp_disp0::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvdisp_disp0::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_output) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }

@ -20,13 +20,11 @@ public:
explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); explicit nvdisp_disp0(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvdisp_disp0() override; ~nvdisp_disp0() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
/// Performs a screen flip, drawing the buffer pointed to by the handle. /// Performs a screen flip, drawing the buffer pointed to by the handle.
void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride, void flip(u32 buffer_handle, u32 offset, u32 format, u32 width, u32 height, u32 stride,

@ -21,8 +21,8 @@ nvhost_as_gpu::nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_
: nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {} : nvdevice(system), nvmap_dev(std::move(nvmap_dev)) {}
nvhost_as_gpu::~nvhost_as_gpu() = default; nvhost_as_gpu::~nvhost_as_gpu() = default;
NvResult nvhost_as_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_as_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input,
IoctlCtrl& ctrl) { std::vector<u8>& output) {
switch (command.group) { switch (command.group) {
case 'A': case 'A':
switch (command.cmd) { switch (command.cmd) {
@ -55,14 +55,13 @@ NvResult nvhost_as_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std:
} }
NvResult nvhost_as_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvhost_as_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvhost_as_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_as_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_output) {
switch (command.group) { switch (command.group) {
case 'A': case 'A':
switch (command.cmd) { switch (command.cmd) {

@ -30,13 +30,11 @@ public:
explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); explicit nvhost_as_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_as_gpu() override; ~nvhost_as_gpu() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
private: private:
class BufferMap final { class BufferMap final {

@ -20,8 +20,7 @@ nvhost_ctrl::nvhost_ctrl(Core::System& system, EventInterface& events_interface,
: nvdevice(system), events_interface{events_interface}, syncpoint_manager{syncpoint_manager} {} : nvdevice(system), events_interface{events_interface}, syncpoint_manager{syncpoint_manager} {}
nvhost_ctrl::~nvhost_ctrl() = default; nvhost_ctrl::~nvhost_ctrl() = default;
NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
switch (command.group) { switch (command.group) {
case 0x0: case 0x0:
switch (command.cmd) { switch (command.cmd) {
@ -30,9 +29,9 @@ NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector<u8>& input, std::v
case 0x1c: case 0x1c:
return IocCtrlClearEventWait(input, output); return IocCtrlClearEventWait(input, output);
case 0x1d: case 0x1d:
return IocCtrlEventWait(input, output, false, ctrl); return IocCtrlEventWait(input, output, false);
case 0x1e: case 0x1e:
return IocCtrlEventWait(input, output, true, ctrl); return IocCtrlEventWait(input, output, true);
case 0x1f: case 0x1f:
return IocCtrlEventRegister(input, output); return IocCtrlEventRegister(input, output);
case 0x20: case 0x20:
@ -48,14 +47,13 @@ NvResult nvhost_ctrl::Ioctl1(Ioctl command, const std::vector<u8>& input, std::v
} }
NvResult nvhost_ctrl::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvhost_ctrl::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvhost_ctrl::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_ctrl::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_outpu) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
@ -69,7 +67,7 @@ NvResult nvhost_ctrl::NvOsGetConfigU32(const std::vector<u8>& input, std::vector
} }
NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output,
bool is_async, IoctlCtrl& ctrl) { bool is_async) {
IocCtrlEventWaitParams params{}; IocCtrlEventWaitParams params{};
std::memcpy(&params, input.data(), sizeof(params)); std::memcpy(&params, input.data(), sizeof(params));
LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}", LOG_DEBUG(Service_NVDRV, "syncpt_id={}, threshold={}, timeout={}, is_async={}",
@ -141,12 +139,6 @@ NvResult nvhost_ctrl::IocCtrlEventWait(const std::vector<u8>& input, std::vector
params.value |= event_id; params.value |= event_id;
event.event.writable->Clear(); event.event.writable->Clear();
gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value); gpu.RegisterSyncptInterrupt(params.syncpt_id, target_value);
if (!is_async && ctrl.fresh_call) {
ctrl.must_delay = true;
ctrl.timeout = params.timeout;
ctrl.event_id = event_id;
return NvResult::Timeout;
}
std::memcpy(output.data(), &params, sizeof(params)); std::memcpy(output.data(), &params, sizeof(params));
return NvResult::Timeout; return NvResult::Timeout;
} }

@ -18,13 +18,11 @@ public:
SyncpointManager& syncpoint_manager); SyncpointManager& syncpoint_manager);
~nvhost_ctrl() override; ~nvhost_ctrl() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
private: private:
struct IocSyncptReadParams { struct IocSyncptReadParams {
@ -123,8 +121,7 @@ private:
static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size"); static_assert(sizeof(IocCtrlEventKill) == 8, "IocCtrlEventKill is incorrect size");
NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output); NvResult NvOsGetConfigU32(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async, NvResult IocCtrlEventWait(const std::vector<u8>& input, std::vector<u8>& output, bool is_async);
IoctlCtrl& ctrl);
NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output); NvResult IocCtrlEventRegister(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output); NvResult IocCtrlEventUnregister(const std::vector<u8>& input, std::vector<u8>& output);
NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output); NvResult IocCtrlClearEventWait(const std::vector<u8>& input, std::vector<u8>& output);

@ -16,7 +16,7 @@ nvhost_ctrl_gpu::nvhost_ctrl_gpu(Core::System& system) : nvdevice(system) {}
nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default; nvhost_ctrl_gpu::~nvhost_ctrl_gpu() = default;
NvResult nvhost_ctrl_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, NvResult nvhost_ctrl_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output, IoctlCtrl& ctrl) { std::vector<u8>& output) {
switch (command.group) { switch (command.group) {
case 'G': case 'G':
switch (command.cmd) { switch (command.cmd) {
@ -48,15 +48,13 @@ NvResult nvhost_ctrl_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input,
} }
NvResult nvhost_ctrl_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvhost_ctrl_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvhost_ctrl_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, NvResult nvhost_ctrl_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output, std::vector<u8>& inline_output, std::vector<u8>& output, std::vector<u8>& inline_output) {
IoctlCtrl& ctrl) {
switch (command.group) { switch (command.group) {
case 'G': case 'G':
switch (command.cmd) { switch (command.cmd) {

@ -16,13 +16,11 @@ public:
explicit nvhost_ctrl_gpu(Core::System& system); explicit nvhost_ctrl_gpu(Core::System& system);
~nvhost_ctrl_gpu() override; ~nvhost_ctrl_gpu() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
private: private:
struct IoctlGpuCharacteristics { struct IoctlGpuCharacteristics {

@ -23,8 +23,7 @@ nvhost_gpu::nvhost_gpu(Core::System& system, std::shared_ptr<nvmap> nvmap_dev,
nvhost_gpu::~nvhost_gpu() = default; nvhost_gpu::~nvhost_gpu() = default;
NvResult nvhost_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
switch (command.group) { switch (command.group) {
case 0x0: case 0x0:
switch (command.cmd) { switch (command.cmd) {
@ -76,8 +75,7 @@ NvResult nvhost_gpu::Ioctl1(Ioctl command, const std::vector<u8>& input, std::ve
}; };
NvResult nvhost_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvhost_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
switch (command.group) { switch (command.group) {
case 'H': case 'H':
switch (command.cmd) { switch (command.cmd) {
@ -91,7 +89,7 @@ NvResult nvhost_gpu::Ioctl2(Ioctl command, const std::vector<u8>& input,
} }
NvResult nvhost_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_gpu::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_output) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }

@ -26,13 +26,11 @@ public:
SyncpointManager& syncpoint_manager); SyncpointManager& syncpoint_manager);
~nvhost_gpu() override; ~nvhost_gpu() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
private: private:
enum class CtxObjects : u32_le { enum class CtxObjects : u32_le {

@ -15,8 +15,8 @@ nvhost_nvdec::nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_de
: nvhost_nvdec_common(system, std::move(nvmap_dev)) {} : nvhost_nvdec_common(system, std::move(nvmap_dev)) {}
nvhost_nvdec::~nvhost_nvdec() = default; nvhost_nvdec::~nvhost_nvdec() = default;
NvResult nvhost_nvdec::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_nvdec::Ioctl1(Ioctl command, const std::vector<u8>& input,
IoctlCtrl& ctrl) { std::vector<u8>& output) {
switch (command.group) { switch (command.group) {
case 0x0: case 0x0:
switch (command.cmd) { switch (command.cmd) {
@ -58,14 +58,13 @@ NvResult nvhost_nvdec::Ioctl1(Ioctl command, const std::vector<u8>& input, std::
} }
NvResult nvhost_nvdec::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvhost_nvdec::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvhost_nvdec::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_nvdec::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_output) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }

@ -14,13 +14,11 @@ public:
explicit nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); explicit nvhost_nvdec(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_nvdec() override; ~nvhost_nvdec() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
}; };
} // namespace Service::Nvidia::Devices } // namespace Service::Nvidia::Devices

@ -13,8 +13,8 @@ namespace Service::Nvidia::Devices {
nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {} nvhost_nvjpg::nvhost_nvjpg(Core::System& system) : nvdevice(system) {}
nvhost_nvjpg::~nvhost_nvjpg() = default; nvhost_nvjpg::~nvhost_nvjpg() = default;
NvResult nvhost_nvjpg::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_nvjpg::Ioctl1(Ioctl command, const std::vector<u8>& input,
IoctlCtrl& ctrl) { std::vector<u8>& output) {
switch (command.group) { switch (command.group) {
case 'H': case 'H':
switch (command.cmd) { switch (command.cmd) {
@ -33,14 +33,13 @@ NvResult nvhost_nvjpg::Ioctl1(Ioctl command, const std::vector<u8>& input, std::
} }
NvResult nvhost_nvjpg::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvhost_nvjpg::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvhost_nvjpg::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_nvjpg::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_output) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }

@ -16,13 +16,11 @@ public:
explicit nvhost_nvjpg(Core::System& system); explicit nvhost_nvjpg(Core::System& system);
~nvhost_nvjpg() override; ~nvhost_nvjpg() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
private: private:
struct IoctlSetNvmapFD { struct IoctlSetNvmapFD {

@ -15,8 +15,7 @@ nvhost_vic::nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev)
nvhost_vic::~nvhost_vic() = default; nvhost_vic::~nvhost_vic() = default;
NvResult nvhost_vic::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_vic::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
switch (command.group) { switch (command.group) {
case 0x0: case 0x0:
switch (command.cmd) { switch (command.cmd) {
@ -51,14 +50,13 @@ NvResult nvhost_vic::Ioctl1(Ioctl command, const std::vector<u8>& input, std::ve
} }
NvResult nvhost_vic::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvhost_vic::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvhost_vic::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvhost_vic::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_output) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }

@ -14,12 +14,10 @@ public:
explicit nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev); explicit nvhost_vic(Core::System& system, std::shared_ptr<nvmap> nvmap_dev);
~nvhost_vic(); ~nvhost_vic();
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
}; };
} // namespace Service::Nvidia::Devices } // namespace Service::Nvidia::Devices

@ -19,8 +19,7 @@ nvmap::nvmap(Core::System& system) : nvdevice(system) {
nvmap::~nvmap() = default; nvmap::~nvmap() = default;
NvResult nvmap::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvmap::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
switch (command.group) { switch (command.group) {
case 0x1: case 0x1:
switch (command.cmd) { switch (command.cmd) {
@ -49,14 +48,13 @@ NvResult nvmap::Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<
} }
NvResult nvmap::Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult nvmap::Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
NvResult nvmap::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult nvmap::Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& inline_output) {
UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw); UNIMPLEMENTED_MSG("Unimplemented ioctl={:08X}", command.raw);
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }

@ -19,13 +19,11 @@ public:
explicit nvmap(Core::System& system); explicit nvmap(Core::System& system);
~nvmap() override; ~nvmap() override;
NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl1(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl2(Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) override;
IoctlCtrl& ctrl) override;
NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output, NvResult Ioctl3(Ioctl command, const std::vector<u8>& input, std::vector<u8>& output,
std::vector<u8>& inline_output, IoctlCtrl& ctrl) override; std::vector<u8>& inline_output) override;
/// Returns the allocated address of an nvmap object given its handle. /// Returns the allocated address of an nvmap object given its handle.
VAddr GetObjectAddress(u32 handle) const; VAddr GetObjectAddress(u32 handle) const;

@ -61,32 +61,9 @@ void NVDRV::Ioctl1(Kernel::HLERequestContext& ctx) {
std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0)); std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
const auto input_buffer = ctx.ReadBuffer(0); const auto input_buffer = ctx.ReadBuffer(0);
IoctlCtrl ctrl{}; const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer);
if (command.is_out != 0) {
const auto nv_result = nvdrv->Ioctl1(fd, command, input_buffer, output_buffer, ctrl); ctx.WriteBuffer(output_buffer);
if (ctrl.must_delay) {
ctrl.fresh_call = false;
ctx.SleepClientThread(
"NVServices::DelayedResponse", ctrl.timeout,
[=, this](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx_,
Kernel::ThreadWakeupReason reason) {
IoctlCtrl ctrl2{ctrl};
std::vector<u8> tmp_output = output_buffer;
const auto nv_result2 = nvdrv->Ioctl1(fd, command, input_buffer, tmp_output, ctrl2);
if (command.is_out != 0) {
ctx.WriteBuffer(tmp_output);
}
IPC::ResponseBuilder rb{ctx_, 3};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(nv_result2);
},
nvdrv->GetEventWriteable(ctrl.event_id));
} else {
if (command.is_out != 0) {
ctx.WriteBuffer(output_buffer);
}
} }
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};
@ -110,36 +87,8 @@ void NVDRV::Ioctl2(Kernel::HLERequestContext& ctx) {
const auto input_inlined_buffer = ctx.ReadBuffer(1); const auto input_inlined_buffer = ctx.ReadBuffer(1);
std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0)); std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
IoctlCtrl ctrl{};
const auto nv_result = const auto nv_result =
nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer, ctrl); nvdrv->Ioctl2(fd, command, input_buffer, input_inlined_buffer, output_buffer);
if (ctrl.must_delay) {
ctrl.fresh_call = false;
ctx.SleepClientThread(
"NVServices::DelayedResponse", ctrl.timeout,
[=, this](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx_,
Kernel::ThreadWakeupReason reason) {
IoctlCtrl ctrl2{ctrl};
std::vector<u8> tmp_output = output_buffer;
const auto nv_result2 = nvdrv->Ioctl2(fd, command, input_buffer,
input_inlined_buffer, tmp_output, ctrl2);
if (command.is_out != 0) {
ctx.WriteBuffer(tmp_output);
}
IPC::ResponseBuilder rb{ctx_, 3};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(nv_result2);
},
nvdrv->GetEventWriteable(ctrl.event_id));
} else {
if (command.is_out != 0) {
ctx.WriteBuffer(output_buffer);
}
}
if (command.is_out != 0) { if (command.is_out != 0) {
ctx.WriteBuffer(output_buffer); ctx.WriteBuffer(output_buffer);
} }
@ -165,36 +114,11 @@ void NVDRV::Ioctl3(Kernel::HLERequestContext& ctx) {
std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0)); std::vector<u8> output_buffer(ctx.GetWriteBufferSize(0));
std::vector<u8> output_buffer_inline(ctx.GetWriteBufferSize(1)); std::vector<u8> output_buffer_inline(ctx.GetWriteBufferSize(1));
IoctlCtrl ctrl{};
const auto nv_result = const auto nv_result =
nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline, ctrl); nvdrv->Ioctl3(fd, command, input_buffer, output_buffer, output_buffer_inline);
if (ctrl.must_delay) { if (command.is_out != 0) {
ctrl.fresh_call = false; ctx.WriteBuffer(output_buffer, 0);
ctx.SleepClientThread( ctx.WriteBuffer(output_buffer_inline, 1);
"NVServices::DelayedResponse", ctrl.timeout,
[=, this](std::shared_ptr<Kernel::Thread> thread, Kernel::HLERequestContext& ctx_,
Kernel::ThreadWakeupReason reason) {
IoctlCtrl ctrl2{ctrl};
std::vector<u8> tmp_output = output_buffer;
std::vector<u8> tmp_output2 = output_buffer;
const auto nv_result2 =
nvdrv->Ioctl3(fd, command, input_buffer, tmp_output, tmp_output2, ctrl2);
if (command.is_out != 0) {
ctx.WriteBuffer(tmp_output, 0);
ctx.WriteBuffer(tmp_output2, 1);
}
IPC::ResponseBuilder rb{ctx_, 3};
rb.Push(RESULT_SUCCESS);
rb.PushEnum(nv_result2);
},
nvdrv->GetEventWriteable(ctrl.event_id));
} else {
if (command.is_out != 0) {
ctx.WriteBuffer(output_buffer, 0);
ctx.WriteBuffer(output_buffer_inline, 1);
}
} }
IPC::ResponseBuilder rb{ctx, 3}; IPC::ResponseBuilder rb{ctx, 3};

@ -97,15 +97,4 @@ union Ioctl {
BitField<31, 1, u32> is_out; BitField<31, 1, u32> is_out;
}; };
struct IoctlCtrl {
// First call done to the servioce for services that call itself again after a call.
bool fresh_call{true};
// Tells the Ioctl Wrapper that it must delay the IPC response and send the thread to sleep
bool must_delay{};
// Timeout for the delay
s64 timeout{};
// NV Event Id
s32 event_id{-1};
};
} // namespace Service::Nvidia } // namespace Service::Nvidia

@ -91,7 +91,7 @@ DeviceFD Module::Open(const std::string& device_name) {
} }
NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output, IoctlCtrl& ctrl) { std::vector<u8>& output) {
if (fd < 0) { if (fd < 0) {
LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
return NvResult::InvalidState; return NvResult::InvalidState;
@ -104,12 +104,11 @@ NvResult Module::Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
return itr->second->Ioctl1(command, input, output, ctrl); return itr->second->Ioctl1(command, input, output);
} }
NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, const std::vector<u8>& inline_input, std::vector<u8>& output) {
IoctlCtrl& ctrl) {
if (fd < 0) { if (fd < 0) {
LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
return NvResult::InvalidState; return NvResult::InvalidState;
@ -122,11 +121,11 @@ NvResult Module::Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
return itr->second->Ioctl2(command, input, inline_input, output, ctrl); return itr->second->Ioctl2(command, input, inline_input, output);
} }
NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output, std::vector<u8>& inline_output, IoctlCtrl& ctrl) { std::vector<u8>& output, std::vector<u8>& inline_output) {
if (fd < 0) { if (fd < 0) {
LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd); LOG_ERROR(Service_NVDRV, "Invalid DeviceFD={}!", fd);
return NvResult::InvalidState; return NvResult::InvalidState;
@ -139,7 +138,7 @@ NvResult Module::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input
return NvResult::NotImplemented; return NvResult::NotImplemented;
} }
return itr->second->Ioctl3(command, input, output, inline_output, ctrl); return itr->second->Ioctl3(command, input, output, inline_output);
} }
NvResult Module::Close(DeviceFD fd) { NvResult Module::Close(DeviceFD fd) {

@ -119,13 +119,13 @@ public:
/// Sends an ioctl command to the specified file descriptor. /// Sends an ioctl command to the specified file descriptor.
NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input, NvResult Ioctl1(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output, IoctlCtrl& ctrl); std::vector<u8>& output);
NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input, NvResult Ioctl2(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
const std::vector<u8>& inline_input, std::vector<u8>& output, IoctlCtrl& ctrl); const std::vector<u8>& inline_input, std::vector<u8>& output);
NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input, NvResult Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& input,
std::vector<u8>& output, std::vector<u8>& inline_output, IoctlCtrl& ctrl); std::vector<u8>& output, std::vector<u8>& inline_output);
/// Closes a device file descriptor and returns operation success. /// Closes a device file descriptor and returns operation success.
NvResult Close(DeviceFD fd); NvResult Close(DeviceFD fd);

@ -25,7 +25,12 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
ASSERT(slot < buffer_slots); ASSERT(slot < buffer_slots);
LOG_WARNING(Service, "Adding graphics buffer {}", slot); LOG_WARNING(Service, "Adding graphics buffer {}", slot);
free_buffers.push_back(slot); {
std::unique_lock lock{queue_mutex};
free_buffers.push_back(slot);
}
condition.notify_one();
buffers[slot] = { buffers[slot] = {
.slot = slot, .slot = slot,
.status = Buffer::Status::Free, .status = Buffer::Status::Free,
@ -41,10 +46,20 @@ void BufferQueue::SetPreallocatedBuffer(u32 slot, const IGBPBuffer& igbp_buffer)
std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width, std::optional<std::pair<u32, Service::Nvidia::MultiFence*>> BufferQueue::DequeueBuffer(u32 width,
u32 height) { u32 height) {
// Wait for first request before trying to dequeue
{
std::unique_lock lock{queue_mutex};
condition.wait(lock, [this] { return !free_buffers.empty() || !is_connect; });
}
if (free_buffers.empty()) { if (!is_connect) {
// Buffer was disconnected while the thread was blocked, this is most likely due to
// emulation being stopped
return std::nullopt; return std::nullopt;
} }
std::unique_lock lock{queue_mutex};
auto f_itr = free_buffers.begin(); auto f_itr = free_buffers.begin();
auto slot = buffers.size(); auto slot = buffers.size();
@ -97,7 +112,11 @@ void BufferQueue::CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& mult
buffers[slot].multi_fence = multi_fence; buffers[slot].multi_fence = multi_fence;
buffers[slot].swap_interval = 0; buffers[slot].swap_interval = 0;
free_buffers.push_back(slot); {
std::unique_lock lock{queue_mutex};
free_buffers.push_back(slot);
}
condition.notify_one();
buffer_wait_event.writable->Signal(); buffer_wait_event.writable->Signal();
} }
@ -127,15 +146,28 @@ void BufferQueue::ReleaseBuffer(u32 slot) {
ASSERT(buffers[slot].slot == slot); ASSERT(buffers[slot].slot == slot);
buffers[slot].status = Buffer::Status::Free; buffers[slot].status = Buffer::Status::Free;
free_buffers.push_back(slot); {
std::unique_lock lock{queue_mutex};
free_buffers.push_back(slot);
}
condition.notify_one();
buffer_wait_event.writable->Signal(); buffer_wait_event.writable->Signal();
} }
void BufferQueue::Connect() {
queue_sequence.clear();
id = 1;
layer_id = 1;
is_connect = true;
}
void BufferQueue::Disconnect() { void BufferQueue::Disconnect() {
buffers.fill({}); buffers.fill({});
queue_sequence.clear(); queue_sequence.clear();
buffer_wait_event.writable->Signal(); buffer_wait_event.writable->Signal();
is_connect = false;
condition.notify_one();
} }
u32 BufferQueue::Query(QueryType type) { u32 BufferQueue::Query(QueryType type) {

@ -4,7 +4,9 @@
#pragma once #pragma once
#include <condition_variable>
#include <list> #include <list>
#include <mutex>
#include <optional> #include <optional>
#include <vector> #include <vector>
@ -99,6 +101,7 @@ public:
void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence); void CancelBuffer(u32 slot, const Service::Nvidia::MultiFence& multi_fence);
std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer(); std::optional<std::reference_wrapper<const Buffer>> AcquireBuffer();
void ReleaseBuffer(u32 slot); void ReleaseBuffer(u32 slot);
void Connect();
void Disconnect(); void Disconnect();
u32 Query(QueryType type); u32 Query(QueryType type);
@ -106,18 +109,28 @@ public:
return id; return id;
} }
bool IsConnected() const {
return is_connect;
}
std::shared_ptr<Kernel::WritableEvent> GetWritableBufferWaitEvent() const; std::shared_ptr<Kernel::WritableEvent> GetWritableBufferWaitEvent() const;
std::shared_ptr<Kernel::ReadableEvent> GetBufferWaitEvent() const; std::shared_ptr<Kernel::ReadableEvent> GetBufferWaitEvent() const;
private: private:
u32 id; BufferQueue(const BufferQueue&) = delete;
u64 layer_id;
u32 id{};
u64 layer_id{};
std::atomic_bool is_connect{};
std::list<u32> free_buffers; std::list<u32> free_buffers;
std::array<Buffer, buffer_slots> buffers; std::array<Buffer, buffer_slots> buffers;
std::list<u32> queue_sequence; std::list<u32> queue_sequence;
Kernel::EventPair buffer_wait_event; Kernel::EventPair buffer_wait_event;
std::mutex queue_mutex;
std::condition_variable condition;
}; };
} // namespace Service::NVFlinger } // namespace Service::NVFlinger

@ -88,6 +88,10 @@ NVFlinger::NVFlinger(Core::System& system) : system(system) {
} }
NVFlinger::~NVFlinger() { NVFlinger::~NVFlinger() {
for (auto& buffer_queue : buffer_queues) {
buffer_queue->Disconnect();
}
if (system.IsMulticore()) { if (system.IsMulticore()) {
is_running = false; is_running = false;
wait_event->Set(); wait_event->Set();
@ -104,6 +108,8 @@ void NVFlinger::SetNVDrvInstance(std::shared_ptr<Nvidia::Module> instance) {
} }
std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) { std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
const auto guard = Lock();
LOG_DEBUG(Service, "Opening \"{}\" display", name); LOG_DEBUG(Service, "Opening \"{}\" display", name);
// TODO(Subv): Currently we only support the Default display. // TODO(Subv): Currently we only support the Default display.
@ -121,6 +127,7 @@ std::optional<u64> NVFlinger::OpenDisplay(std::string_view name) {
} }
std::optional<u64> NVFlinger::CreateLayer(u64 display_id) { std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
const auto guard = Lock();
auto* const display = FindDisplay(display_id); auto* const display = FindDisplay(display_id);
if (display == nullptr) { if (display == nullptr) {
@ -129,18 +136,22 @@ std::optional<u64> NVFlinger::CreateLayer(u64 display_id) {
const u64 layer_id = next_layer_id++; const u64 layer_id = next_layer_id++;
const u32 buffer_queue_id = next_buffer_queue_id++; const u32 buffer_queue_id = next_buffer_queue_id++;
buffer_queues.emplace_back(system.Kernel(), buffer_queue_id, layer_id); buffer_queues.emplace_back(
display->CreateLayer(layer_id, buffer_queues.back()); std::make_unique<BufferQueue>(system.Kernel(), buffer_queue_id, layer_id));
display->CreateLayer(layer_id, *buffer_queues.back());
return layer_id; return layer_id;
} }
void NVFlinger::CloseLayer(u64 layer_id) { void NVFlinger::CloseLayer(u64 layer_id) {
const auto guard = Lock();
for (auto& display : displays) { for (auto& display : displays) {
display.CloseLayer(layer_id); display.CloseLayer(layer_id);
} }
} }
std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) const { std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) const {
const auto guard = Lock();
const auto* const layer = FindLayer(display_id, layer_id); const auto* const layer = FindLayer(display_id, layer_id);
if (layer == nullptr) { if (layer == nullptr) {
@ -151,6 +162,7 @@ std::optional<u32> NVFlinger::FindBufferQueueId(u64 display_id, u64 layer_id) co
} }
std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id) const { std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id) const {
const auto guard = Lock();
auto* const display = FindDisplay(display_id); auto* const display = FindDisplay(display_id);
if (display == nullptr) { if (display == nullptr) {
@ -160,20 +172,16 @@ std::shared_ptr<Kernel::ReadableEvent> NVFlinger::FindVsyncEvent(u64 display_id)
return display->GetVSyncEvent(); return display->GetVSyncEvent();
} }
BufferQueue& NVFlinger::FindBufferQueue(u32 id) { BufferQueue* NVFlinger::FindBufferQueue(u32 id) {
const auto guard = Lock();
const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(), const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
[id](const auto& queue) { return queue.GetId() == id; }); [id](const auto& queue) { return queue->GetId() == id; });
ASSERT(itr != buffer_queues.end()); if (itr == buffer_queues.end()) {
return *itr; return nullptr;
} }
const BufferQueue& NVFlinger::FindBufferQueue(u32 id) const { return itr->get();
const auto itr = std::find_if(buffer_queues.begin(), buffer_queues.end(),
[id](const auto& queue) { return queue.GetId() == id; });
ASSERT(itr != buffer_queues.end());
return *itr;
} }
VI::Display* NVFlinger::FindDisplay(u64 display_id) { VI::Display* NVFlinger::FindDisplay(u64 display_id) {

@ -75,10 +75,7 @@ public:
[[nodiscard]] std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const; [[nodiscard]] std::shared_ptr<Kernel::ReadableEvent> FindVsyncEvent(u64 display_id) const;
/// Obtains a buffer queue identified by the ID. /// Obtains a buffer queue identified by the ID.
[[nodiscard]] BufferQueue& FindBufferQueue(u32 id); [[nodiscard]] BufferQueue* FindBufferQueue(u32 id);
/// Obtains a buffer queue identified by the ID.
[[nodiscard]] const BufferQueue& FindBufferQueue(u32 id) const;
/// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when /// Performs a composition request to the emulated nvidia GPU and triggers the vsync events when
/// finished. /// finished.
@ -86,11 +83,11 @@ public:
[[nodiscard]] s64 GetNextTicks() const; [[nodiscard]] s64 GetNextTicks() const;
private:
[[nodiscard]] std::unique_lock<std::mutex> Lock() const { [[nodiscard]] std::unique_lock<std::mutex> Lock() const {
return std::unique_lock{*guard}; return std::unique_lock{*guard};
} }
private:
/// Finds the display identified by the specified ID. /// Finds the display identified by the specified ID.
[[nodiscard]] VI::Display* FindDisplay(u64 display_id); [[nodiscard]] VI::Display* FindDisplay(u64 display_id);
@ -110,7 +107,7 @@ private:
std::shared_ptr<Nvidia::Module> nvdrv; std::shared_ptr<Nvidia::Module> nvdrv;
std::vector<VI::Display> displays; std::vector<VI::Display> displays;
std::vector<BufferQueue> buffer_queues; std::vector<std::unique_ptr<BufferQueue>> buffer_queues;
/// Id to use for the next layer that is created, this counter is shared among all displays. /// Id to use for the next layer that is created, this counter is shared among all displays.
u64 next_layer_id = 1; u64 next_layer_id = 1;

@ -95,9 +95,14 @@ ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* se
: system{system_}, service_name{service_name_}, max_sessions{max_sessions_}, : system{system_}, service_name{service_name_}, max_sessions{max_sessions_},
handler_invoker{handler_invoker_} {} handler_invoker{handler_invoker_} {}
ServiceFrameworkBase::~ServiceFrameworkBase() = default; ServiceFrameworkBase::~ServiceFrameworkBase() {
// Wait for other threads to release access before destroying
const auto guard = LockService();
}
void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) { void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) {
const auto guard = LockService();
ASSERT(!port_installed); ASSERT(!port_installed);
auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap(); auto port = service_manager.RegisterService(service_name, max_sessions).Unwrap();
@ -106,6 +111,8 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager)
} }
void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) { void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) {
const auto guard = LockService();
ASSERT(!port_installed); ASSERT(!port_installed);
auto [server_port, client_port] = auto [server_port, client_port] =
@ -115,17 +122,6 @@ void ServiceFrameworkBase::InstallAsNamedPort(Kernel::KernelCore& kernel) {
port_installed = true; port_installed = true;
} }
std::shared_ptr<Kernel::ClientPort> ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) {
ASSERT(!port_installed);
auto [server_port, client_port] =
Kernel::ServerPort::CreatePortPair(kernel, max_sessions, service_name);
auto port = MakeResult(std::move(server_port)).Unwrap();
port->SetHleHandler(shared_from_this());
port_installed = true;
return client_port;
}
void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) { void ServiceFrameworkBase::RegisterHandlersBase(const FunctionInfoBase* functions, std::size_t n) {
handlers.reserve(handlers.size() + n); handlers.reserve(handlers.size() + n);
for (std::size_t i = 0; i < n; ++i) { for (std::size_t i = 0; i < n; ++i) {
@ -164,6 +160,8 @@ void ServiceFrameworkBase::InvokeRequest(Kernel::HLERequestContext& ctx) {
} }
ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) { ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& context) {
const auto guard = LockService();
switch (context.GetCommandType()) { switch (context.GetCommandType()) {
case IPC::CommandType::Close: { case IPC::CommandType::Close: {
IPC::ResponseBuilder rb{context, 2}; IPC::ResponseBuilder rb{context, 2};
@ -184,7 +182,11 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
UNIMPLEMENTED_MSG("command_type={}", context.GetCommandType()); UNIMPLEMENTED_MSG("command_type={}", context.GetCommandType());
} }
context.WriteToOutgoingCommandBuffer(context.GetThread()); // If emulation was shutdown, we are closing service threads, do not write the response back to
// memory that may be shutting down as well.
if (system.IsPoweredOn()) {
context.WriteToOutgoingCommandBuffer(context.GetThread());
}
return RESULT_SUCCESS; return RESULT_SUCCESS;
} }

@ -5,9 +5,11 @@
#pragma once #pragma once
#include <cstddef> #include <cstddef>
#include <mutex>
#include <string> #include <string>
#include <boost/container/flat_map.hpp> #include <boost/container/flat_map.hpp>
#include "common/common_types.h" #include "common/common_types.h"
#include "common/spin_lock.h"
#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h" #include "core/hle/kernel/object.h"
@ -68,11 +70,9 @@ public:
void InstallAsService(SM::ServiceManager& service_manager); void InstallAsService(SM::ServiceManager& service_manager);
/// Creates a port pair and registers it on the kernel's global port registry. /// Creates a port pair and registers it on the kernel's global port registry.
void InstallAsNamedPort(Kernel::KernelCore& kernel); void InstallAsNamedPort(Kernel::KernelCore& kernel);
/// Creates and returns an unregistered port for the service. /// Invokes a service request routine.
std::shared_ptr<Kernel::ClientPort> CreatePort(Kernel::KernelCore& kernel);
void InvokeRequest(Kernel::HLERequestContext& ctx); void InvokeRequest(Kernel::HLERequestContext& ctx);
/// Handles a synchronization request for the service.
ResultCode HandleSyncRequest(Kernel::HLERequestContext& context) override; ResultCode HandleSyncRequest(Kernel::HLERequestContext& context) override;
protected: protected:
@ -80,6 +80,11 @@ protected:
template <typename Self> template <typename Self>
using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&); using HandlerFnP = void (Self::*)(Kernel::HLERequestContext&);
/// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
[[nodiscard]] std::scoped_lock<Common::SpinLock> LockService() {
return std::scoped_lock{lock_service};
}
/// System context that the service operates under. /// System context that the service operates under.
Core::System& system; Core::System& system;
@ -115,6 +120,9 @@ private:
/// Function used to safely up-cast pointers to the derived class before invoking a handler. /// Function used to safely up-cast pointers to the derived class before invoking a handler.
InvokerFn* handler_invoker; InvokerFn* handler_invoker;
boost::container::flat_map<u32, FunctionInfoBase> handlers; boost::container::flat_map<u32, FunctionInfoBase> handlers;
/// Used to gain exclusive access to the service members, e.g. from CoreTiming thread.
Common::SpinLock lock_service;
}; };
/** /**

@ -1,161 +0,0 @@
// Copyright 2020 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <memory>
#include <string>
#include <string_view>
#include <thread>
#include <variant>
#include <vector>
#include <fmt/format.h>
#include "common/assert.h"
#include "common/microprofile.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/kernel/writable_event.h"
namespace Service::Sockets {
/**
* Worker abstraction to execute blocking calls on host without blocking the guest thread
*
* @tparam Service Service where the work is executed
* @tparam Types Types of work to execute
*/
template <class Service, class... Types>
class BlockingWorker {
using This = BlockingWorker<Service, Types...>;
using WorkVariant = std::variant<std::monostate, Types...>;
public:
/// Create a new worker
static std::unique_ptr<This> Create(Core::System& system, Service* service,
std::string_view name) {
return std::unique_ptr<This>(new This(system, service, name));
}
~BlockingWorker() {
while (!is_available.load(std::memory_order_relaxed)) {
// Busy wait until work is finished
std::this_thread::yield();
}
// Monostate means to exit the thread
work = std::monostate{};
work_event.Set();
thread.join();
}
/**
* Try to capture the worker to send work after a success
* @returns True when the worker has been successfully captured
*/
bool TryCapture() {
bool expected = true;
return is_available.compare_exchange_weak(expected, false, std::memory_order_relaxed,
std::memory_order_relaxed);
}
/**
* Send work to this worker abstraction
* @see TryCapture must be called before attempting to call this function
*/
template <class Work>
void SendWork(Work new_work) {
ASSERT_MSG(!is_available, "Trying to send work on a worker that's not captured");
work = std::move(new_work);
work_event.Set();
}
/// Generate a callback for @see SleepClientThread
template <class Work>
auto Callback() {
return [this](std::shared_ptr<Kernel::Thread>, Kernel::HLERequestContext& ctx,
Kernel::ThreadWakeupReason reason) {
ASSERT(reason == Kernel::ThreadWakeupReason::Signal);
std::get<Work>(work).Response(ctx);
is_available.store(true);
};
}
/// Get kernel event that will be signalled by the worker when the host operation finishes
std::shared_ptr<Kernel::WritableEvent> KernelEvent() const {
return kernel_event;
}
private:
explicit BlockingWorker(Core::System& system, Service* service, std::string_view name) {
auto pair = Kernel::WritableEvent::CreateEventPair(system.Kernel(), std::string(name));
kernel_event = std::move(pair.writable);
thread = std::thread([this, &system, service, name] { Run(system, service, name); });
}
void Run(Core::System& system, Service* service, std::string_view name) {
system.RegisterHostThread();
const std::string thread_name = fmt::format("yuzu:{}", name);
MicroProfileOnThreadCreate(thread_name.c_str());
Common::SetCurrentThreadName(thread_name.c_str());
bool keep_running = true;
while (keep_running) {
work_event.Wait();
const auto visit_fn = [service, &keep_running]<typename T>(T&& w) {
if constexpr (std::is_same_v<std::decay_t<T>, std::monostate>) {
keep_running = false;
} else {
w.Execute(service);
}
};
std::visit(visit_fn, work);
kernel_event->Signal();
}
}
std::thread thread;
WorkVariant work;
Common::Event work_event;
std::shared_ptr<Kernel::WritableEvent> kernel_event;
std::atomic_bool is_available{true};
};
template <class Service, class... Types>
class BlockingWorkerPool {
using Worker = BlockingWorker<Service, Types...>;
public:
explicit BlockingWorkerPool(Core::System& system_, Service* service_)
: system{system_}, service{service_} {}
/// Returns a captured worker thread, creating new ones if necessary
Worker* CaptureWorker() {
for (auto& worker : workers) {
if (worker->TryCapture()) {
return worker.get();
}
}
auto new_worker = Worker::Create(system, service, fmt::format("BSD:{}", workers.size()));
[[maybe_unused]] const bool success = new_worker->TryCapture();
ASSERT(success);
return workers.emplace_back(std::move(new_worker)).get();
}
private:
Core::System& system;
Service* const service;
std::vector<std::unique_ptr<Worker>> workers;
};
} // namespace Service::Sockets

@ -178,13 +178,12 @@ void BSD::Poll(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout); LOG_DEBUG(Service, "called. nfds={} timeout={}", nfds, timeout);
ExecuteWork(ctx, "BSD:Poll", timeout != 0, ExecuteWork(ctx, PollWork{
PollWork{ .nfds = nfds,
.nfds = nfds, .timeout = timeout,
.timeout = timeout, .read_buffer = ctx.ReadBuffer(),
.read_buffer = ctx.ReadBuffer(), .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
.write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()), });
});
} }
void BSD::Accept(Kernel::HLERequestContext& ctx) { void BSD::Accept(Kernel::HLERequestContext& ctx) {
@ -193,11 +192,10 @@ void BSD::Accept(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={}", fd); LOG_DEBUG(Service, "called. fd={}", fd);
ExecuteWork(ctx, "BSD:Accept", IsBlockingSocket(fd), ExecuteWork(ctx, AcceptWork{
AcceptWork{ .fd = fd,
.fd = fd, .write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()),
.write_buffer = std::vector<u8>(ctx.GetWriteBufferSize()), });
});
} }
void BSD::Bind(Kernel::HLERequestContext& ctx) { void BSD::Bind(Kernel::HLERequestContext& ctx) {
@ -215,11 +213,10 @@ void BSD::Connect(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize()); LOG_DEBUG(Service, "called. fd={} addrlen={}", fd, ctx.GetReadBufferSize());
ExecuteWork(ctx, "BSD:Connect", IsBlockingSocket(fd), ExecuteWork(ctx, ConnectWork{
ConnectWork{ .fd = fd,
.fd = fd, .addr = ctx.ReadBuffer(),
.addr = ctx.ReadBuffer(), });
});
} }
void BSD::GetPeerName(Kernel::HLERequestContext& ctx) { void BSD::GetPeerName(Kernel::HLERequestContext& ctx) {
@ -327,12 +324,11 @@ void BSD::Recv(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize()); LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetWriteBufferSize());
ExecuteWork(ctx, "BSD:Recv", IsBlockingSocket(fd), ExecuteWork(ctx, RecvWork{
RecvWork{ .fd = fd,
.fd = fd, .flags = flags,
.flags = flags, .message = std::vector<u8>(ctx.GetWriteBufferSize()),
.message = std::vector<u8>(ctx.GetWriteBufferSize()), });
});
} }
void BSD::RecvFrom(Kernel::HLERequestContext& ctx) { void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
@ -344,13 +340,12 @@ void BSD::RecvFrom(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags, LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={} addrlen={}", fd, flags,
ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1)); ctx.GetWriteBufferSize(0), ctx.GetWriteBufferSize(1));
ExecuteWork(ctx, "BSD:RecvFrom", IsBlockingSocket(fd), ExecuteWork(ctx, RecvFromWork{
RecvFromWork{ .fd = fd,
.fd = fd, .flags = flags,
.flags = flags, .message = std::vector<u8>(ctx.GetWriteBufferSize(0)),
.message = std::vector<u8>(ctx.GetWriteBufferSize(0)), .addr = std::vector<u8>(ctx.GetWriteBufferSize(1)),
.addr = std::vector<u8>(ctx.GetWriteBufferSize(1)), });
});
} }
void BSD::Send(Kernel::HLERequestContext& ctx) { void BSD::Send(Kernel::HLERequestContext& ctx) {
@ -361,12 +356,11 @@ void BSD::Send(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize()); LOG_DEBUG(Service, "called. fd={} flags=0x{:x} len={}", fd, flags, ctx.GetReadBufferSize());
ExecuteWork(ctx, "BSD:Send", IsBlockingSocket(fd), ExecuteWork(ctx, SendWork{
SendWork{ .fd = fd,
.fd = fd, .flags = flags,
.flags = flags, .message = ctx.ReadBuffer(),
.message = ctx.ReadBuffer(), });
});
} }
void BSD::SendTo(Kernel::HLERequestContext& ctx) { void BSD::SendTo(Kernel::HLERequestContext& ctx) {
@ -377,13 +371,12 @@ void BSD::SendTo(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags, LOG_DEBUG(Service, "called. fd={} flags=0x{} len={} addrlen={}", fd, flags,
ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1)); ctx.GetReadBufferSize(0), ctx.GetReadBufferSize(1));
ExecuteWork(ctx, "BSD:SendTo", IsBlockingSocket(fd), ExecuteWork(ctx, SendToWork{
SendToWork{ .fd = fd,
.fd = fd, .flags = flags,
.flags = flags, .message = ctx.ReadBuffer(0),
.message = ctx.ReadBuffer(0), .addr = ctx.ReadBuffer(1),
.addr = ctx.ReadBuffer(1), });
});
} }
void BSD::Write(Kernel::HLERequestContext& ctx) { void BSD::Write(Kernel::HLERequestContext& ctx) {
@ -392,12 +385,11 @@ void BSD::Write(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize()); LOG_DEBUG(Service, "called. fd={} len={}", fd, ctx.GetReadBufferSize());
ExecuteWork(ctx, "BSD:Write", IsBlockingSocket(fd), ExecuteWork(ctx, SendWork{
SendWork{ .fd = fd,
.fd = fd, .flags = 0,
.flags = 0, .message = ctx.ReadBuffer(),
.message = ctx.ReadBuffer(), });
});
} }
void BSD::Close(Kernel::HLERequestContext& ctx) { void BSD::Close(Kernel::HLERequestContext& ctx) {
@ -410,24 +402,9 @@ void BSD::Close(Kernel::HLERequestContext& ctx) {
} }
template <typename Work> template <typename Work>
void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, void BSD::ExecuteWork(Kernel::HLERequestContext& ctx, Work work) {
bool is_blocking, Work work) { work.Execute(this);
if (!is_blocking) {
work.Execute(this);
work.Response(ctx);
return;
}
// Signal a dummy response to make IPC validation happy
// This will be overwritten by the SleepClientThread callback
work.Response(ctx); work.Response(ctx);
auto worker = worker_pool.CaptureWorker();
ctx.SleepClientThread(std::string(sleep_reason), std::numeric_limits<u64>::max(),
worker->Callback<Work>(), worker->KernelEvent());
worker->SendWork(std::move(work));
} }
std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) { std::pair<s32, Errno> BSD::SocketImpl(Domain domain, Type type, Protocol protocol) {
@ -807,18 +784,6 @@ bool BSD::IsFileDescriptorValid(s32 fd) const noexcept {
return true; return true;
} }
bool BSD::IsBlockingSocket(s32 fd) const noexcept {
// Inform invalid sockets as non-blocking
// This way we avoid using a worker thread as it will fail without blocking host
if (fd > static_cast<s32>(MAX_FD) || fd < 0) {
return false;
}
if (!file_descriptors[fd]) {
return false;
}
return (file_descriptors[fd]->flags & FLAG_O_NONBLOCK) != 0;
}
void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept { void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept {
IPC::ResponseBuilder rb{ctx, 4}; IPC::ResponseBuilder rb{ctx, 4};
@ -827,8 +792,7 @@ void BSD::BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) co
rb.PushEnum(bsd_errno); rb.PushEnum(bsd_errno);
} }
BSD::BSD(Core::System& system_, const char* name) BSD::BSD(Core::System& system_, const char* name) : ServiceFramework{system_, name} {
: ServiceFramework{system_, name}, worker_pool{system_, this} {
// clang-format off // clang-format off
static const FunctionInfo functions[] = { static const FunctionInfo functions[] = {
{0, &BSD::RegisterClient, "RegisterClient"}, {0, &BSD::RegisterClient, "RegisterClient"},

@ -11,7 +11,6 @@
#include "common/common_types.h" #include "common/common_types.h"
#include "core/hle/kernel/hle_ipc.h" #include "core/hle/kernel/hle_ipc.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
#include "core/hle/service/sockets/blocking_worker.h"
#include "core/hle/service/sockets/sockets.h" #include "core/hle/service/sockets/sockets.h"
namespace Core { namespace Core {
@ -138,8 +137,7 @@ private:
void Close(Kernel::HLERequestContext& ctx); void Close(Kernel::HLERequestContext& ctx);
template <typename Work> template <typename Work>
void ExecuteWork(Kernel::HLERequestContext& ctx, std::string_view sleep_reason, void ExecuteWork(Kernel::HLERequestContext& ctx, Work work);
bool is_blocking, Work work);
std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol); std::pair<s32, Errno> SocketImpl(Domain domain, Type type, Protocol protocol);
std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer, std::pair<s32, Errno> PollImpl(std::vector<u8>& write_buffer, std::vector<u8> read_buffer,
@ -163,15 +161,10 @@ private:
s32 FindFreeFileDescriptorHandle() noexcept; s32 FindFreeFileDescriptorHandle() noexcept;
bool IsFileDescriptorValid(s32 fd) const noexcept; bool IsFileDescriptorValid(s32 fd) const noexcept;
bool IsBlockingSocket(s32 fd) const noexcept;
void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept; void BuildErrnoResponse(Kernel::HLERequestContext& ctx, Errno bsd_errno) const noexcept;
std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors; std::array<std::optional<FileDescriptor>, MAX_FD> file_descriptors;
BlockingWorkerPool<BSD, PollWork, AcceptWork, ConnectWork, RecvWork, RecvFromWork, SendWork,
SendToWork>
worker_pool;
}; };
class BSDCFG final : public ServiceFramework<BSDCFG> { class BSDCFG final : public ServiceFramework<BSDCFG> {

@ -536,8 +536,7 @@ private:
LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id, LOG_DEBUG(Service_VI, "called. id=0x{:08X} transaction={:X}, flags=0x{:08X}", id,
transaction, flags); transaction, flags);
const auto guard = nv_flinger.Lock(); auto& buffer_queue = *nv_flinger.FindBufferQueue(id);
auto& buffer_queue = nv_flinger.FindBufferQueue(id);
switch (transaction) { switch (transaction) {
case TransactionId::Connect: { case TransactionId::Connect: {
@ -547,6 +546,9 @@ private:
Settings::values.resolution_factor.GetValue()), Settings::values.resolution_factor.GetValue()),
static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) * static_cast<u32>(static_cast<u32>(DisplayResolution::UndockedHeight) *
Settings::values.resolution_factor.GetValue())}; Settings::values.resolution_factor.GetValue())};
buffer_queue.Connect();
ctx.WriteBuffer(response.Serialize()); ctx.WriteBuffer(response.Serialize());
break; break;
} }
@ -563,40 +565,25 @@ private:
IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()}; IGBPDequeueBufferRequestParcel request{ctx.ReadBuffer()};
const u32 width{request.data.width}; const u32 width{request.data.width};
const u32 height{request.data.height}; const u32 height{request.data.height};
auto result = buffer_queue.DequeueBuffer(width, height);
if (result) { do {
// Buffer is available if (auto result = buffer_queue.DequeueBuffer(width, height); result) {
IGBPDequeueBufferResponseParcel response{result->first, *result->second}; // Buffer is available
ctx.WriteBuffer(response.Serialize()); IGBPDequeueBufferResponseParcel response{result->first, *result->second};
} else { ctx.WriteBuffer(response.Serialize());
// Wait the current thread until a buffer becomes available break;
ctx.SleepClientThread( }
"IHOSBinderDriver::DequeueBuffer", UINT64_MAX, } while (buffer_queue.IsConnected());
[=, this](std::shared_ptr<Kernel::Thread> thread,
Kernel::HLERequestContext& ctx, Kernel::ThreadWakeupReason reason) {
// Repeat TransactParcel DequeueBuffer when a buffer is available
const auto guard = nv_flinger.Lock();
auto& buffer_queue = nv_flinger.FindBufferQueue(id);
auto result = buffer_queue.DequeueBuffer(width, height);
ASSERT_MSG(result != std::nullopt, "Could not dequeue buffer.");
IGBPDequeueBufferResponseParcel response{result->first, *result->second};
ctx.WriteBuffer(response.Serialize());
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
},
buffer_queue.GetWritableBufferWaitEvent());
}
break; break;
} }
case TransactionId::RequestBuffer: { case TransactionId::RequestBuffer: {
IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()}; IGBPRequestBufferRequestParcel request{ctx.ReadBuffer()};
auto& buffer = buffer_queue.RequestBuffer(request.slot); auto& buffer = buffer_queue.RequestBuffer(request.slot);
IGBPRequestBufferResponseParcel response{buffer}; IGBPRequestBufferResponseParcel response{buffer};
ctx.WriteBuffer(response.Serialize()); ctx.WriteBuffer(response.Serialize());
break; break;
} }
case TransactionId::QueueBuffer: { case TransactionId::QueueBuffer: {
@ -682,7 +669,7 @@ private:
LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown); LOG_WARNING(Service_VI, "(STUBBED) called id={}, unknown={:08X}", id, unknown);
const auto& buffer_queue = nv_flinger.FindBufferQueue(id); const auto& buffer_queue = *nv_flinger.FindBufferQueue(id);
// TODO(Subv): Find out what this actually is. // TODO(Subv): Find out what this actually is.
IPC::ResponseBuilder rb{ctx, 2, 1}; IPC::ResponseBuilder rb{ctx, 2, 1};

@ -148,9 +148,4 @@ void RestoreGlobalState(bool is_powered_on) {
values.motion_enabled.SetGlobal(true); values.motion_enabled.SetGlobal(true);
} }
void Sanitize() {
values.use_asynchronous_gpu_emulation.SetValue(
values.use_asynchronous_gpu_emulation.GetValue() || values.use_multi_core.GetValue());
}
} // namespace Settings } // namespace Settings

@ -257,7 +257,4 @@ void LogSettings();
// Restore the global state of all applicable settings in the Values struct // Restore the global state of all applicable settings in the Values struct
void RestoreGlobalState(bool is_powered_on); void RestoreGlobalState(bool is_powered_on);
// Fixes settings that are known to cause issues with the emulator
void Sanitize();
} // namespace Settings } // namespace Settings

@ -48,6 +48,7 @@ add_library(video_core STATIC
engines/shader_bytecode.h engines/shader_bytecode.h
engines/shader_header.h engines/shader_header.h
engines/shader_type.h engines/shader_type.h
framebuffer_config.h
macro/macro.cpp macro/macro.cpp
macro/macro.h macro/macro.h
macro/macro_hle.cpp macro/macro_hle.cpp
@ -59,10 +60,6 @@ add_library(video_core STATIC
fence_manager.h fence_manager.h
gpu.cpp gpu.cpp
gpu.h gpu.h
gpu_asynch.cpp
gpu_asynch.h
gpu_synch.cpp
gpu_synch.h
gpu_thread.cpp gpu_thread.cpp
gpu_thread.h gpu_thread.h
guest_driver.cpp guest_driver.cpp

@ -0,0 +1,31 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Tegra {
/**
* Struct describing framebuffer configuration
*/
struct FramebufferConfig {
enum class PixelFormat : u32 {
A8B8G8R8_UNORM = 1,
RGB565_UNORM = 4,
B8G8R8A8_UNORM = 5,
};
VAddr address{};
u32 offset{};
u32 width{};
u32 height{};
u32 stride{};
PixelFormat pixel_format{};
using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
TransformFlags transform_flags{};
Common::Rectangle<int> crop_rect;
};
} // namespace Tegra

@ -10,6 +10,7 @@
#include "core/core_timing.h" #include "core/core_timing.h"
#include "core/core_timing_util.h" #include "core/core_timing_util.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
#include "core/hardware_interrupt_manager.h"
#include "core/memory.h" #include "core/memory.h"
#include "core/settings.h" #include "core/settings.h"
#include "video_core/engines/fermi_2d.h" #include "video_core/engines/fermi_2d.h"
@ -36,7 +37,8 @@ GPU::GPU(Core::System& system_, bool is_async_, bool use_nvdec_)
kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)}, kepler_compute{std::make_unique<Engines::KeplerCompute>(system, *memory_manager)},
maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)}, maxwell_dma{std::make_unique<Engines::MaxwellDMA>(system, *memory_manager)},
kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)}, kepler_memory{std::make_unique<Engines::KeplerMemory>(system, *memory_manager)},
shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_} {} shader_notify{std::make_unique<VideoCore::ShaderNotify>()}, is_async{is_async_},
gpu_thread{system_, is_async_} {}
GPU::~GPU() = default; GPU::~GPU() = default;
@ -198,10 +200,6 @@ void GPU::SyncGuestHost() {
renderer->Rasterizer().SyncGuestHost(); renderer->Rasterizer().SyncGuestHost();
} }
void GPU::OnCommandListEnd() {
renderer->Rasterizer().ReleaseFences();
}
enum class GpuSemaphoreOperation { enum class GpuSemaphoreOperation {
AcquireEqual = 0x1, AcquireEqual = 0x1,
WriteLong = 0x2, WriteLong = 0x2,
@ -461,4 +459,75 @@ void GPU::ProcessSemaphoreAcquire() {
} }
} }
void GPU::Start() {
gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher, *cdma_pusher);
cpu_context = renderer->GetRenderWindow().CreateSharedContext();
cpu_context->MakeCurrent();
}
void GPU::ObtainContext() {
cpu_context->MakeCurrent();
}
void GPU::ReleaseContext() {
cpu_context->DoneCurrent();
}
void GPU::PushGPUEntries(Tegra::CommandList&& entries) {
gpu_thread.SubmitList(std::move(entries));
}
void GPU::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
if (!use_nvdec) {
return;
}
// This condition fires when a video stream ends, clear all intermediary data
if (entries[0].raw == 0xDEADB33F) {
cdma_pusher.reset();
return;
}
if (!cdma_pusher) {
cdma_pusher = std::make_unique<Tegra::CDmaPusher>(*this);
}
// SubmitCommandBuffer would make the nvdec operations async, this is not currently working
// TODO(ameerj): RE proper async nvdec operation
// gpu_thread.SubmitCommandBuffer(std::move(entries));
cdma_pusher->Push(std::move(entries));
cdma_pusher->DispatchCalls();
}
void GPU::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
gpu_thread.SwapBuffers(framebuffer);
}
void GPU::FlushRegion(VAddr addr, u64 size) {
gpu_thread.FlushRegion(addr, size);
}
void GPU::InvalidateRegion(VAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
}
void GPU::FlushAndInvalidateRegion(VAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size);
}
void GPU::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const {
auto& interrupt_manager = system.InterruptManager();
interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
}
void GPU::WaitIdle() const {
gpu_thread.WaitIdle();
}
void GPU::OnCommandListEnd() {
if (is_async) {
// This command only applies to asynchronous GPU mode
gpu_thread.OnCommandListEnd();
}
}
} // namespace Tegra } // namespace Tegra

@ -15,6 +15,8 @@
#include "core/hle/service/nvflinger/buffer_queue.h" #include "core/hle/service/nvflinger/buffer_queue.h"
#include "video_core/cdma_pusher.h" #include "video_core/cdma_pusher.h"
#include "video_core/dma_pusher.h" #include "video_core/dma_pusher.h"
#include "video_core/framebuffer_config.h"
#include "video_core/gpu_thread.h"
using CacheAddr = std::uintptr_t; using CacheAddr = std::uintptr_t;
[[nodiscard]] inline CacheAddr ToCacheAddr(const void* host_ptr) { [[nodiscard]] inline CacheAddr ToCacheAddr(const void* host_ptr) {
@ -101,28 +103,6 @@ enum class DepthFormat : u32 {
struct CommandListHeader; struct CommandListHeader;
class DebugContext; class DebugContext;
/**
* Struct describing framebuffer configuration
*/
struct FramebufferConfig {
enum class PixelFormat : u32 {
A8B8G8R8_UNORM = 1,
RGB565_UNORM = 4,
B8G8R8A8_UNORM = 5,
};
VAddr address;
u32 offset;
u32 width;
u32 height;
u32 stride;
PixelFormat pixel_format;
using TransformFlags = Service::NVFlinger::BufferQueue::BufferTransformFlags;
TransformFlags transform_flags;
Common::Rectangle<int> crop_rect;
};
namespace Engines { namespace Engines {
class Fermi2D; class Fermi2D;
class Maxwell3D; class Maxwell3D;
@ -141,7 +121,7 @@ enum class EngineID {
class MemoryManager; class MemoryManager;
class GPU { class GPU final {
public: public:
struct MethodCall { struct MethodCall {
u32 method{}; u32 method{};
@ -159,7 +139,7 @@ public:
}; };
explicit GPU(Core::System& system_, bool is_async_, bool use_nvdec_); explicit GPU(Core::System& system_, bool is_async_, bool use_nvdec_);
virtual ~GPU(); ~GPU();
/// Binds a renderer to the GPU. /// Binds a renderer to the GPU.
void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer); void BindRenderer(std::unique_ptr<VideoCore::RendererBase> renderer);
@ -176,7 +156,7 @@ public:
/// Synchronizes CPU writes with Host GPU memory. /// Synchronizes CPU writes with Host GPU memory.
void SyncGuestHost(); void SyncGuestHost();
/// Signal the ending of command list. /// Signal the ending of command list.
virtual void OnCommandListEnd(); void OnCommandListEnd();
/// Request a host GPU memory flush from the CPU. /// Request a host GPU memory flush from the CPU.
[[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size); [[nodiscard]] u64 RequestFlush(VAddr addr, std::size_t size);
@ -240,7 +220,7 @@ public:
} }
// Waits for the GPU to finish working // Waits for the GPU to finish working
virtual void WaitIdle() const = 0; void WaitIdle() const;
/// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame. /// Allows the CPU/NvFlinger to wait on the GPU before presenting a frame.
void WaitFence(u32 syncpoint_id, u32 value); void WaitFence(u32 syncpoint_id, u32 value);
@ -330,34 +310,34 @@ public:
/// Performs any additional setup necessary in order to begin GPU emulation. /// Performs any additional setup necessary in order to begin GPU emulation.
/// This can be used to launch any necessary threads and register any necessary /// This can be used to launch any necessary threads and register any necessary
/// core timing events. /// core timing events.
virtual void Start() = 0; void Start();
/// Obtain the CPU Context /// Obtain the CPU Context
virtual void ObtainContext() = 0; void ObtainContext();
/// Release the CPU Context /// Release the CPU Context
virtual void ReleaseContext() = 0; void ReleaseContext();
/// Push GPU command entries to be processed /// Push GPU command entries to be processed
virtual void PushGPUEntries(Tegra::CommandList&& entries) = 0; void PushGPUEntries(Tegra::CommandList&& entries);
/// Push GPU command buffer entries to be processed /// Push GPU command buffer entries to be processed
virtual void PushCommandBuffer(Tegra::ChCommandHeaderList& entries) = 0; void PushCommandBuffer(Tegra::ChCommandHeaderList& entries);
/// Swap buffers (render frame) /// Swap buffers (render frame)
virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
virtual void FlushRegion(VAddr addr, u64 size) = 0; void FlushRegion(VAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated /// Notify rasterizer that any caches of the specified region should be invalidated
virtual void InvalidateRegion(VAddr addr, u64 size) = 0; void InvalidateRegion(VAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated /// Notify rasterizer that any caches of the specified region should be flushed and invalidated
virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0; void FlushAndInvalidateRegion(VAddr addr, u64 size);
protected: protected:
virtual void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const = 0; void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const;
private: private:
void ProcessBindMethod(const MethodCall& method_call); void ProcessBindMethod(const MethodCall& method_call);
@ -427,6 +407,9 @@ private:
std::mutex flush_request_mutex; std::mutex flush_request_mutex;
const bool is_async; const bool is_async;
VideoCommon::GPUThread::ThreadManager gpu_thread;
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
}; };
#define ASSERT_REG_POSITION(field_name, position) \ #define ASSERT_REG_POSITION(field_name, position) \

@ -1,86 +0,0 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hardware_interrupt_manager.h"
#include "video_core/gpu_asynch.h"
#include "video_core/gpu_thread.h"
#include "video_core/renderer_base.h"
namespace VideoCommon {
GPUAsynch::GPUAsynch(Core::System& system_, bool use_nvdec_)
: GPU{system_, true, use_nvdec_}, gpu_thread{system_} {}
GPUAsynch::~GPUAsynch() = default;
void GPUAsynch::Start() {
gpu_thread.StartThread(*renderer, renderer->Context(), *dma_pusher, *cdma_pusher);
cpu_context = renderer->GetRenderWindow().CreateSharedContext();
cpu_context->MakeCurrent();
}
void GPUAsynch::ObtainContext() {
cpu_context->MakeCurrent();
}
void GPUAsynch::ReleaseContext() {
cpu_context->DoneCurrent();
}
void GPUAsynch::PushGPUEntries(Tegra::CommandList&& entries) {
gpu_thread.SubmitList(std::move(entries));
}
void GPUAsynch::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
if (!use_nvdec) {
return;
}
// This condition fires when a video stream ends, clear all intermediary data
if (entries[0].raw == 0xDEADB33F) {
cdma_pusher.reset();
return;
}
if (!cdma_pusher) {
cdma_pusher = std::make_unique<Tegra::CDmaPusher>(*this);
}
// SubmitCommandBuffer would make the nvdec operations async, this is not currently working
// TODO(ameerj): RE proper async nvdec operation
// gpu_thread.SubmitCommandBuffer(std::move(entries));
cdma_pusher->Push(std::move(entries));
cdma_pusher->DispatchCalls();
}
void GPUAsynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
gpu_thread.SwapBuffers(framebuffer);
}
void GPUAsynch::FlushRegion(VAddr addr, u64 size) {
gpu_thread.FlushRegion(addr, size);
}
void GPUAsynch::InvalidateRegion(VAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
}
void GPUAsynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size);
}
void GPUAsynch::TriggerCpuInterrupt(const u32 syncpoint_id, const u32 value) const {
auto& interrupt_manager = system.InterruptManager();
interrupt_manager.GPUInterruptSyncpt(syncpoint_id, value);
}
void GPUAsynch::WaitIdle() const {
gpu_thread.WaitIdle();
}
void GPUAsynch::OnCommandListEnd() {
gpu_thread.OnCommandListEnd();
}
} // namespace VideoCommon

@ -1,47 +0,0 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/gpu.h"
#include "video_core/gpu_thread.h"
namespace Core::Frontend {
class GraphicsContext;
}
namespace VideoCore {
class RendererBase;
} // namespace VideoCore
namespace VideoCommon {
/// Implementation of GPU interface that runs the GPU asynchronously
class GPUAsynch final : public Tegra::GPU {
public:
explicit GPUAsynch(Core::System& system_, bool use_nvdec_);
~GPUAsynch() override;
void Start() override;
void ObtainContext() override;
void ReleaseContext() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
void PushCommandBuffer(Tegra::ChCommandHeaderList& entries) override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
void FlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
void WaitIdle() const override;
void OnCommandListEnd() override;
protected:
void TriggerCpuInterrupt(u32 syncpoint_id, u32 value) const override;
private:
GPUThread::ThreadManager gpu_thread;
std::unique_ptr<Core::Frontend::GraphicsContext> cpu_context;
};
} // namespace VideoCommon

@ -1,61 +0,0 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "video_core/gpu_synch.h"
#include "video_core/renderer_base.h"
namespace VideoCommon {
GPUSynch::GPUSynch(Core::System& system_, bool use_nvdec_) : GPU{system_, false, use_nvdec_} {}
GPUSynch::~GPUSynch() = default;
void GPUSynch::Start() {}
void GPUSynch::ObtainContext() {
renderer->Context().MakeCurrent();
}
void GPUSynch::ReleaseContext() {
renderer->Context().DoneCurrent();
}
void GPUSynch::PushGPUEntries(Tegra::CommandList&& entries) {
dma_pusher->Push(std::move(entries));
dma_pusher->DispatchCalls();
}
void GPUSynch::PushCommandBuffer(Tegra::ChCommandHeaderList& entries) {
if (!use_nvdec) {
return;
}
// This condition fires when a video stream ends, clears all intermediary data
if (entries[0].raw == 0xDEADB33F) {
cdma_pusher.reset();
return;
}
if (!cdma_pusher) {
cdma_pusher = std::make_unique<Tegra::CDmaPusher>(*this);
}
cdma_pusher->Push(std::move(entries));
cdma_pusher->DispatchCalls();
}
void GPUSynch::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
renderer->SwapBuffers(framebuffer);
}
void GPUSynch::FlushRegion(VAddr addr, u64 size) {
renderer->Rasterizer().FlushRegion(addr, size);
}
void GPUSynch::InvalidateRegion(VAddr addr, u64 size) {
renderer->Rasterizer().InvalidateRegion(addr, size);
}
void GPUSynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
renderer->Rasterizer().FlushAndInvalidateRegion(addr, size);
}
} // namespace VideoCommon

@ -1,41 +0,0 @@
// Copyright 2019 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "video_core/gpu.h"
namespace Core::Frontend {
class GraphicsContext;
}
namespace VideoCore {
class RendererBase;
} // namespace VideoCore
namespace VideoCommon {
/// Implementation of GPU interface that runs the GPU synchronously
class GPUSynch final : public Tegra::GPU {
public:
explicit GPUSynch(Core::System& system_, bool use_nvdec_);
~GPUSynch() override;
void Start() override;
void ObtainContext() override;
void ReleaseContext() override;
void PushGPUEntries(Tegra::CommandList&& entries) override;
void PushCommandBuffer(Tegra::ChCommandHeaderList& entries) override;
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override;
void FlushRegion(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size) override;
void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
void WaitIdle() const override {}
protected:
void TriggerCpuInterrupt([[maybe_unused]] u32 syncpoint_id,
[[maybe_unused]] u32 value) const override {}
};
} // namespace VideoCommon

@ -4,6 +4,7 @@
#include "common/assert.h" #include "common/assert.h"
#include "common/microprofile.h" #include "common/microprofile.h"
#include "common/scope_exit.h"
#include "common/thread.h" #include "common/thread.h"
#include "core/core.h" #include "core/core.h"
#include "core/frontend/emu_window.h" #include "core/frontend/emu_window.h"
@ -21,6 +22,8 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer,
SynchState& state, Tegra::CDmaPusher& cdma_pusher) { SynchState& state, Tegra::CDmaPusher& cdma_pusher) {
std::string name = "yuzu:GPU"; std::string name = "yuzu:GPU";
MicroProfileOnThreadCreate(name.c_str()); MicroProfileOnThreadCreate(name.c_str());
SCOPE_EXIT({ MicroProfileOnThreadExit(); });
Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadName(name.c_str());
Common::SetCurrentThreadPriority(Common::ThreadPriority::High); Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
system.RegisterHostThread(); system.RegisterHostThread();
@ -65,7 +68,8 @@ static void RunThread(Core::System& system, VideoCore::RendererBase& renderer,
} }
} }
ThreadManager::ThreadManager(Core::System& system_) : system{system_} {} ThreadManager::ThreadManager(Core::System& system_, bool is_async_)
: system{system_}, is_async{is_async_} {}
ThreadManager::~ThreadManager() { ThreadManager::~ThreadManager() {
if (!thread.joinable()) { if (!thread.joinable()) {
@ -97,19 +101,30 @@ void ThreadManager::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
} }
void ThreadManager::FlushRegion(VAddr addr, u64 size) { void ThreadManager::FlushRegion(VAddr addr, u64 size) {
if (!Settings::IsGPULevelHigh()) { if (!is_async) {
// Always flush with synchronous GPU mode
PushCommand(FlushRegionCommand(addr, size)); PushCommand(FlushRegionCommand(addr, size));
return; return;
} }
if (!Settings::IsGPULevelExtreme()) {
return; // Asynchronous GPU mode
} switch (Settings::values.gpu_accuracy.GetValue()) {
if (system.Renderer().Rasterizer().MustFlushRegion(addr, size)) { case Settings::GPUAccuracy::Normal:
PushCommand(FlushRegionCommand(addr, size));
break;
case Settings::GPUAccuracy::High:
// TODO(bunnei): Is this right? Preserving existing behavior for now
break;
case Settings::GPUAccuracy::Extreme: {
auto& gpu = system.GPU(); auto& gpu = system.GPU();
u64 fence = gpu.RequestFlush(addr, size); u64 fence = gpu.RequestFlush(addr, size);
PushCommand(GPUTickCommand()); PushCommand(GPUTickCommand());
while (fence > gpu.CurrentFlushRequestFence()) { while (fence > gpu.CurrentFlushRequestFence()) {
} }
break;
}
default:
UNIMPLEMENTED_MSG("Unsupported gpu_accuracy {}", Settings::values.gpu_accuracy.GetValue());
} }
} }
@ -123,7 +138,8 @@ void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
} }
void ThreadManager::WaitIdle() const { void ThreadManager::WaitIdle() const {
while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed)) { while (state.last_fence > state.signaled_fence.load(std::memory_order_relaxed) &&
system.IsPoweredOn()) {
} }
} }
@ -134,6 +150,12 @@ void ThreadManager::OnCommandListEnd() {
u64 ThreadManager::PushCommand(CommandData&& command_data) { u64 ThreadManager::PushCommand(CommandData&& command_data) {
const u64 fence{++state.last_fence}; const u64 fence{++state.last_fence};
state.queue.Push(CommandDataContainer(std::move(command_data), fence)); state.queue.Push(CommandDataContainer(std::move(command_data), fence));
if (!is_async) {
// In synchronous GPU mode, block the caller until the command has executed
WaitIdle();
}
return fence; return fence;
} }

@ -10,8 +10,9 @@
#include <optional> #include <optional>
#include <thread> #include <thread>
#include <variant> #include <variant>
#include "common/threadsafe_queue.h" #include "common/threadsafe_queue.h"
#include "video_core/gpu.h" #include "video_core/framebuffer_config.h"
namespace Tegra { namespace Tegra {
struct FramebufferConfig; struct FramebufferConfig;
@ -25,6 +26,10 @@ class GraphicsContext;
class System; class System;
} // namespace Core } // namespace Core
namespace VideoCore {
class RendererBase;
} // namespace VideoCore
namespace VideoCommon::GPUThread { namespace VideoCommon::GPUThread {
/// Command to signal to the GPU thread that processing has ended /// Command to signal to the GPU thread that processing has ended
@ -112,7 +117,7 @@ struct SynchState final {
/// Class used to manage the GPU thread /// Class used to manage the GPU thread
class ThreadManager final { class ThreadManager final {
public: public:
explicit ThreadManager(Core::System& system_); explicit ThreadManager(Core::System& system_, bool is_async_);
~ThreadManager(); ~ThreadManager();
/// Creates and starts the GPU thread. /// Creates and starts the GPU thread.
@ -150,6 +155,7 @@ private:
Core::System& system; Core::System& system;
std::thread thread; std::thread thread;
std::thread::id thread_id; std::thread::id thread_id;
const bool is_async;
}; };
} // namespace VideoCommon::GPUThread } // namespace VideoCommon::GPUThread

@ -7,8 +7,6 @@
#include "common/logging/log.h" #include "common/logging/log.h"
#include "core/core.h" #include "core/core.h"
#include "core/settings.h" #include "core/settings.h"
#include "video_core/gpu_asynch.h"
#include "video_core/gpu_synch.h"
#include "video_core/renderer_base.h" #include "video_core/renderer_base.h"
#include "video_core/renderer_opengl/renderer_opengl.h" #include "video_core/renderer_opengl/renderer_opengl.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h" #include "video_core/renderer_vulkan/renderer_vulkan.h"
@ -39,13 +37,9 @@ std::unique_ptr<VideoCore::RendererBase> CreateRenderer(
namespace VideoCore { namespace VideoCore {
std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) { std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system) {
std::unique_ptr<Tegra::GPU> gpu;
const bool use_nvdec = Settings::values.use_nvdec_emulation.GetValue(); const bool use_nvdec = Settings::values.use_nvdec_emulation.GetValue();
if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) { std::unique_ptr<Tegra::GPU> gpu = std::make_unique<Tegra::GPU>(
gpu = std::make_unique<VideoCommon::GPUAsynch>(system, use_nvdec); system, Settings::values.use_asynchronous_gpu_emulation.GetValue(), use_nvdec);
} else {
gpu = std::make_unique<VideoCommon::GPUSynch>(system, use_nvdec);
}
auto context = emu_window.CreateSharedContext(); auto context = emu_window.CreateSharedContext();
const auto scope = context->Acquire(); const auto scope = context->Acquire();

@ -1589,14 +1589,12 @@ void Config::WriteSettingGlobal(const QString& name, const QVariant& value, bool
void Config::Reload() { void Config::Reload() {
ReadValues(); ReadValues();
Settings::Sanitize();
// To apply default value changes // To apply default value changes
SaveValues(); SaveValues();
Settings::Apply(Core::System::GetInstance()); Settings::Apply(Core::System::GetInstance());
} }
void Config::Save() { void Config::Save() {
Settings::Sanitize();
SaveValues(); SaveValues();
} }

@ -580,9 +580,8 @@ void GMainWindow::InitializeWidgets() {
if (emulation_running) { if (emulation_running) {
return; return;
} }
const bool is_async = !Settings::values.use_asynchronous_gpu_emulation.GetValue() || Settings::values.use_asynchronous_gpu_emulation.SetValue(
Settings::values.use_multi_core.GetValue(); !Settings::values.use_asynchronous_gpu_emulation.GetValue());
Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
Settings::Apply(Core::System::GetInstance()); Settings::Apply(Core::System::GetInstance());
}); });
@ -599,16 +598,13 @@ void GMainWindow::InitializeWidgets() {
return; return;
} }
Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue()); Settings::values.use_multi_core.SetValue(!Settings::values.use_multi_core.GetValue());
const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
Settings::values.use_multi_core.GetValue();
Settings::values.use_asynchronous_gpu_emulation.SetValue(is_async);
async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
Settings::Apply(Core::System::GetInstance()); Settings::Apply(Core::System::GetInstance());
}); });
multicore_status_button->setText(tr("MULTICORE")); multicore_status_button->setText(tr("MULTICORE"));
multicore_status_button->setCheckable(true); multicore_status_button->setCheckable(true);
multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
statusBar()->insertPermanentWidget(0, multicore_status_button); statusBar()->insertPermanentWidget(0, multicore_status_button);
statusBar()->insertPermanentWidget(0, async_status_button); statusBar()->insertPermanentWidget(0, async_status_button);
@ -2533,9 +2529,6 @@ void GMainWindow::UpdateStatusBar() {
void GMainWindow::UpdateStatusButtons() { void GMainWindow::UpdateStatusButtons() {
dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue()); dock_status_button->setChecked(Settings::values.use_docked_mode.GetValue());
multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue()); multicore_status_button->setChecked(Settings::values.use_multi_core.GetValue());
Settings::values.use_asynchronous_gpu_emulation.SetValue(
Settings::values.use_asynchronous_gpu_emulation.GetValue() ||
Settings::values.use_multi_core.GetValue());
async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue()); async_status_button->setChecked(Settings::values.use_asynchronous_gpu_emulation.GetValue());
renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() == renderer_status_button->setChecked(Settings::values.renderer_backend.GetValue() ==
Settings::RendererBackend::Vulkan); Settings::RendererBackend::Vulkan);