diff options
72 files changed, 1402 insertions, 636 deletions
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 26612e692..54be7dc0c 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -131,8 +131,8 @@ add_library(core STATIC frontend/framebuffer_layout.cpp frontend/framebuffer_layout.h frontend/input.h - frontend/scope_acquire_window_context.cpp - frontend/scope_acquire_window_context.h + frontend/scope_acquire_context.cpp + frontend/scope_acquire_context.h gdbstub/gdbstub.cpp gdbstub/gdbstub.h hardware_interrupt_manager.cpp @@ -187,6 +187,8 @@ add_library(core STATIC hle/kernel/synchronization.h hle/kernel/thread.cpp hle/kernel/thread.h + hle/kernel/time_manager.cpp + hle/kernel/time_manager.h hle/kernel/transfer_memory.cpp hle/kernel/transfer_memory.h hle/kernel/vm_manager.cpp diff --git a/src/core/core.cpp b/src/core/core.cpp index 0eb0c0dca..a82faf127 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -24,6 +24,7 @@ #include "core/file_sys/sdmc_factory.h" #include "core/file_sys/vfs_concat.h" #include "core/file_sys/vfs_real.h" +#include "core/frontend/scope_acquire_context.h" #include "core/gdbstub/gdbstub.h" #include "core/hardware_interrupt_manager.h" #include "core/hle/kernel/client_port.h" @@ -184,6 +185,8 @@ struct System::Impl { ResultStatus Load(System& system, Frontend::EmuWindow& emu_window, const std::string& filepath) { + Core::Frontend::ScopeAcquireContext acquire_context{emu_window}; + app_loader = Loader::GetLoader(GetGameFileFromPath(virtual_filesystem, filepath)); if (!app_loader) { LOG_CRITICAL(Core, "Failed to obtain loader for {}!", filepath); @@ -707,4 +710,12 @@ const Service::SM::ServiceManager& System::ServiceManager() const { return *impl->service_manager; } +void System::RegisterCoreThread(std::size_t id) { + impl->kernel.RegisterCoreThread(id); +} + +void System::RegisterHostThread() { + impl->kernel.RegisterHostThread(); +} + } // namespace Core diff --git a/src/core/core.h b/src/core/core.h index e69d68fcf..8d862a8e6 100644 --- a/src/core/core.h +++ b/src/core/core.h @@ -360,6 +360,12 @@ public: const CurrentBuildProcessID& GetCurrentProcessBuildID() const; + /// Register a host thread as an emulated CPU Core. + void RegisterCoreThread(std::size_t id); + + /// Register a host thread as an auxiliary thread. + void RegisterHostThread(); + private: System(); diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 3376eedc5..5eb87fb63 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h @@ -26,9 +26,6 @@ public: /// Releases (dunno if this is the "right" word) the context from the caller thread virtual void DoneCurrent() = 0; - - /// Swap buffers to display the next frame - virtual void SwapBuffers() = 0; }; /** diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h index 1d39c1faf..e9d0a40d3 100644 --- a/src/core/frontend/framebuffer_layout.h +++ b/src/core/frontend/framebuffer_layout.h @@ -29,6 +29,7 @@ enum class AspectRatio { struct FramebufferLayout { u32 width{ScreenUndocked::Width}; u32 height{ScreenUndocked::Height}; + bool is_srgb{}; Common::Rectangle<u32> screen; diff --git a/src/core/frontend/scope_acquire_context.cpp b/src/core/frontend/scope_acquire_context.cpp new file mode 100644 index 000000000..878c3157c --- /dev/null +++ b/src/core/frontend/scope_acquire_context.cpp @@ -0,0 +1,18 @@ +// Copyright 2019 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/emu_window.h" +#include "core/frontend/scope_acquire_context.h" + +namespace Core::Frontend { + +ScopeAcquireContext::ScopeAcquireContext(Core::Frontend::GraphicsContext& context) + : context{context} { + context.MakeCurrent(); +} +ScopeAcquireContext::~ScopeAcquireContext() { + context.DoneCurrent(); +} + +} // namespace Core::Frontend diff --git a/src/core/frontend/scope_acquire_window_context.h b/src/core/frontend/scope_acquire_context.h index 2d9f6e825..7a65c0623 100644 --- a/src/core/frontend/scope_acquire_window_context.h +++ b/src/core/frontend/scope_acquire_context.h @@ -8,16 +8,16 @@ namespace Core::Frontend { -class EmuWindow; +class GraphicsContext; /// Helper class to acquire/release window context within a given scope -class ScopeAcquireWindowContext : NonCopyable { +class ScopeAcquireContext : NonCopyable { public: - explicit ScopeAcquireWindowContext(Core::Frontend::EmuWindow& window); - ~ScopeAcquireWindowContext(); + explicit ScopeAcquireContext(Core::Frontend::GraphicsContext& context); + ~ScopeAcquireContext(); private: - Core::Frontend::EmuWindow& emu_window; + Core::Frontend::GraphicsContext& context; }; } // namespace Core::Frontend diff --git a/src/core/frontend/scope_acquire_window_context.cpp b/src/core/frontend/scope_acquire_window_context.cpp deleted file mode 100644 index 3663dad17..000000000 --- a/src/core/frontend/scope_acquire_window_context.cpp +++ /dev/null @@ -1,18 +0,0 @@ -// Copyright 2019 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "core/frontend/emu_window.h" -#include "core/frontend/scope_acquire_window_context.h" - -namespace Core::Frontend { - -ScopeAcquireWindowContext::ScopeAcquireWindowContext(Core::Frontend::EmuWindow& emu_window_) - : emu_window{emu_window_} { - emu_window.MakeCurrent(); -} -ScopeAcquireWindowContext::~ScopeAcquireWindowContext() { - emu_window.DoneCurrent(); -} - -} // namespace Core::Frontend diff --git a/src/core/hardware_properties.h b/src/core/hardware_properties.h index 213461b6a..b04e046ed 100644 --- a/src/core/hardware_properties.h +++ b/src/core/hardware_properties.h @@ -20,6 +20,8 @@ constexpr u32 NUM_CPU_CORES = 4; // Number of CPU Cores } // namespace Hardware +constexpr u32 INVALID_HOST_THREAD_ID = 0xFFFFFFFF; + struct EmuThreadHandle { u32 host_handle; u32 guest_handle; diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 4eb1d8703..9232f4d7e 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp @@ -3,9 +3,12 @@ // Refer to the license.txt file included. #include <atomic> +#include <bitset> #include <functional> #include <memory> #include <mutex> +#include <thread> +#include <unordered_map> #include <utility> #include "common/assert.h" @@ -15,6 +18,7 @@ #include "core/core.h" #include "core/core_timing.h" #include "core/core_timing_util.h" +#include "core/hardware_properties.h" #include "core/hle/kernel/client_port.h" #include "core/hle/kernel/errors.h" #include "core/hle/kernel/handle_table.h" @@ -25,6 +29,7 @@ #include "core/hle/kernel/scheduler.h" #include "core/hle/kernel/synchronization.h" #include "core/hle/kernel/thread.h" +#include "core/hle/kernel/time_manager.h" #include "core/hle/lock.h" #include "core/hle/result.h" #include "core/memory.h" @@ -44,7 +49,7 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_ std::lock_guard lock{HLE::g_hle_lock}; std::shared_ptr<Thread> thread = - system.Kernel().RetrieveThreadFromWakeupCallbackHandleTable(proper_handle); + system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); if (thread == nullptr) { LOG_CRITICAL(Kernel, "Callback fired for invalid thread {:08X}", proper_handle); return; @@ -97,8 +102,8 @@ static void ThreadWakeupCallback(u64 thread_handle, [[maybe_unused]] s64 cycles_ } struct KernelCore::Impl { - explicit Impl(Core::System& system) - : system{system}, global_scheduler{system}, synchronization{system} {} + explicit Impl(Core::System& system, KernelCore& kernel) + : system{system}, global_scheduler{kernel}, synchronization{system}, time_manager{system} {} void Initialize(KernelCore& kernel) { Shutdown(); @@ -120,7 +125,7 @@ struct KernelCore::Impl { system_resource_limit = nullptr; - thread_wakeup_callback_handle_table.Clear(); + global_handle_table.Clear(); thread_wakeup_event_type = nullptr; preemption_event = nullptr; @@ -138,8 +143,8 @@ struct KernelCore::Impl { void InitializePhysicalCores() { exclusive_monitor = - Core::MakeExclusiveMonitor(system.Memory(), global_scheduler.CpuCoresCount()); - for (std::size_t i = 0; i < global_scheduler.CpuCoresCount(); i++) { + Core::MakeExclusiveMonitor(system.Memory(), Core::Hardware::NUM_CPU_CORES); + for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { cores.emplace_back(system, i, *exclusive_monitor); } } @@ -184,6 +189,50 @@ struct KernelCore::Impl { system.Memory().SetCurrentPageTable(*process); } + void RegisterCoreThread(std::size_t core_id) { + std::unique_lock lock{register_thread_mutex}; + const std::thread::id this_id = std::this_thread::get_id(); + const auto it = host_thread_ids.find(this_id); + ASSERT(core_id < Core::Hardware::NUM_CPU_CORES); + ASSERT(it == host_thread_ids.end()); + ASSERT(!registered_core_threads[core_id]); + host_thread_ids[this_id] = static_cast<u32>(core_id); + registered_core_threads.set(core_id); + } + + void RegisterHostThread() { + std::unique_lock lock{register_thread_mutex}; + const std::thread::id this_id = std::this_thread::get_id(); + const auto it = host_thread_ids.find(this_id); + ASSERT(it == host_thread_ids.end()); + host_thread_ids[this_id] = registered_thread_ids++; + } + + u32 GetCurrentHostThreadID() const { + const std::thread::id this_id = std::this_thread::get_id(); + const auto it = host_thread_ids.find(this_id); + if (it == host_thread_ids.end()) { + return Core::INVALID_HOST_THREAD_ID; + } + return it->second; + } + + Core::EmuThreadHandle GetCurrentEmuThreadID() const { + Core::EmuThreadHandle result = Core::EmuThreadHandle::InvalidHandle(); + result.host_handle = GetCurrentHostThreadID(); + if (result.host_handle >= Core::Hardware::NUM_CPU_CORES) { + return result; + } + const Kernel::Scheduler& sched = cores[result.host_handle].Scheduler(); + const Kernel::Thread* current = sched.GetCurrentThread(); + if (current != nullptr) { + result.guest_handle = current->GetGlobalHandle(); + } else { + result.guest_handle = InvalidHandle; + } + return result; + } + std::atomic<u32> next_object_id{0}; std::atomic<u64> next_kernel_process_id{Process::InitialKIPIDMin}; std::atomic<u64> next_user_process_id{Process::ProcessIDMin}; @@ -194,15 +243,16 @@ struct KernelCore::Impl { Process* current_process = nullptr; Kernel::GlobalScheduler global_scheduler; Kernel::Synchronization synchronization; + Kernel::TimeManager time_manager; std::shared_ptr<ResourceLimit> system_resource_limit; std::shared_ptr<Core::Timing::EventType> thread_wakeup_event_type; std::shared_ptr<Core::Timing::EventType> preemption_event; - // TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, - // allowing us to simply use a pool index or similar. - Kernel::HandleTable thread_wakeup_callback_handle_table; + // This is the kernel's handle table or supervisor handle table which + // stores all the objects in place. + Kernel::HandleTable global_handle_table; /// Map of named ports managed by the kernel, which can be retrieved using /// the ConnectToPort SVC. @@ -211,11 +261,17 @@ struct KernelCore::Impl { std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor; std::vector<Kernel::PhysicalCore> cores; + // 0-3 IDs represent core threads, >3 represent others + std::unordered_map<std::thread::id, u32> host_thread_ids; + u32 registered_thread_ids{Core::Hardware::NUM_CPU_CORES}; + std::bitset<Core::Hardware::NUM_CPU_CORES> registered_core_threads; + std::mutex register_thread_mutex; + // System context Core::System& system; }; -KernelCore::KernelCore(Core::System& system) : impl{std::make_unique<Impl>(system)} {} +KernelCore::KernelCore(Core::System& system) : impl{std::make_unique<Impl>(system, *this)} {} KernelCore::~KernelCore() { Shutdown(); } @@ -232,9 +288,8 @@ std::shared_ptr<ResourceLimit> KernelCore::GetSystemResourceLimit() const { return impl->system_resource_limit; } -std::shared_ptr<Thread> KernelCore::RetrieveThreadFromWakeupCallbackHandleTable( - Handle handle) const { - return impl->thread_wakeup_callback_handle_table.Get<Thread>(handle); +std::shared_ptr<Thread> KernelCore::RetrieveThreadFromGlobalHandleTable(Handle handle) const { + return impl->global_handle_table.Get<Thread>(handle); } void KernelCore::AppendNewProcess(std::shared_ptr<Process> process) { @@ -265,6 +320,14 @@ const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const { return impl->global_scheduler; } +Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) { + return impl->cores[id].Scheduler(); +} + +const Kernel::Scheduler& KernelCore::Scheduler(std::size_t id) const { + return impl->cores[id].Scheduler(); +} + Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) { return impl->cores[id]; } @@ -281,6 +344,14 @@ const Kernel::Synchronization& KernelCore::Synchronization() const { return impl->synchronization; } +Kernel::TimeManager& KernelCore::TimeManager() { + return impl->time_manager; +} + +const Kernel::TimeManager& KernelCore::TimeManager() const { + return impl->time_manager; +} + Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() { return *impl->exclusive_monitor; } @@ -338,12 +409,28 @@ const std::shared_ptr<Core::Timing::EventType>& KernelCore::ThreadWakeupCallback return impl->thread_wakeup_event_type; } -Kernel::HandleTable& KernelCore::ThreadWakeupCallbackHandleTable() { - return impl->thread_wakeup_callback_handle_table; +Kernel::HandleTable& KernelCore::GlobalHandleTable() { + return impl->global_handle_table; +} + +const Kernel::HandleTable& KernelCore::GlobalHandleTable() const { + return impl->global_handle_table; +} + +void KernelCore::RegisterCoreThread(std::size_t core_id) { + impl->RegisterCoreThread(core_id); +} + +void KernelCore::RegisterHostThread() { + impl->RegisterHostThread(); +} + +u32 KernelCore::GetCurrentHostThreadID() const { + return impl->GetCurrentHostThreadID(); } -const Kernel::HandleTable& KernelCore::ThreadWakeupCallbackHandleTable() const { - return impl->thread_wakeup_callback_handle_table; +Core::EmuThreadHandle KernelCore::GetCurrentEmuThreadID() const { + return impl->GetCurrentEmuThreadID(); } } // namespace Kernel diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h index 1eede3063..c4f78ab71 100644 --- a/src/core/hle/kernel/kernel.h +++ b/src/core/hle/kernel/kernel.h @@ -11,6 +11,7 @@ #include "core/hle/kernel/object.h" namespace Core { +struct EmuThreadHandle; class ExclusiveMonitor; class System; } // namespace Core @@ -29,8 +30,10 @@ class HandleTable; class PhysicalCore; class Process; class ResourceLimit; +class Scheduler; class Synchronization; class Thread; +class TimeManager; /// Represents a single instance of the kernel. class KernelCore { @@ -64,7 +67,7 @@ public: std::shared_ptr<ResourceLimit> GetSystemResourceLimit() const; /// Retrieves a shared pointer to a Thread instance within the thread wakeup handle table. - std::shared_ptr<Thread> RetrieveThreadFromWakeupCallbackHandleTable(Handle handle) const; + std::shared_ptr<Thread> RetrieveThreadFromGlobalHandleTable(Handle handle) const; /// Adds the given shared pointer to an internal list of active processes. void AppendNewProcess(std::shared_ptr<Process> process); @@ -87,6 +90,12 @@ public: /// Gets the sole instance of the global scheduler const Kernel::GlobalScheduler& GlobalScheduler() const; + /// Gets the sole instance of the Scheduler assoviated with cpu core 'id' + Kernel::Scheduler& Scheduler(std::size_t id); + + /// Gets the sole instance of the Scheduler assoviated with cpu core 'id' + const Kernel::Scheduler& Scheduler(std::size_t id) const; + /// Gets the an instance of the respective physical CPU core. Kernel::PhysicalCore& PhysicalCore(std::size_t id); @@ -99,6 +108,12 @@ public: /// Gets the an instance of the Synchronization Interface. const Kernel::Synchronization& Synchronization() const; + /// Gets the an instance of the TimeManager Interface. + Kernel::TimeManager& TimeManager(); + + /// Gets the an instance of the TimeManager Interface. + const Kernel::TimeManager& TimeManager() const; + /// Stops execution of 'id' core, in order to reschedule a new thread. void PrepareReschedule(std::size_t id); @@ -120,6 +135,18 @@ public: /// Determines whether or not the given port is a valid named port. bool IsValidNamedPort(NamedPortTable::const_iterator port) const; + /// Gets the current host_thread/guest_thread handle. + Core::EmuThreadHandle GetCurrentEmuThreadID() const; + + /// Gets the current host_thread handle. + u32 GetCurrentHostThreadID() const; + + /// Register the current thread as a CPU Core Thread. + void RegisterCoreThread(std::size_t core_id); + + /// Register the current thread as a non CPU core thread. + void RegisterHostThread(); + private: friend class Object; friend class Process; @@ -140,11 +167,11 @@ private: /// Retrieves the event type used for thread wakeup callbacks. const std::shared_ptr<Core::Timing::EventType>& ThreadWakeupCallbackEventType() const; - /// Provides a reference to the thread wakeup callback handle table. - Kernel::HandleTable& ThreadWakeupCallbackHandleTable(); + /// Provides a reference to the global handle table. + Kernel::HandleTable& GlobalHandleTable(); - /// Provides a const reference to the thread wakeup callback handle table. - const Kernel::HandleTable& ThreadWakeupCallbackHandleTable() const; + /// Provides a const reference to the global handle table. + const Kernel::HandleTable& GlobalHandleTable() const; struct Impl; std::unique_ptr<Impl> impl; diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp index 86f1421bf..c65f82fb7 100644 --- a/src/core/hle/kernel/scheduler.cpp +++ b/src/core/hle/kernel/scheduler.cpp @@ -18,10 +18,11 @@ #include "core/hle/kernel/kernel.h" #include "core/hle/kernel/process.h" #include "core/hle/kernel/scheduler.h" +#include "core/hle/kernel/time_manager.h" namespace Kernel { -GlobalScheduler::GlobalScheduler(Core::System& system) : system{system} {} +GlobalScheduler::GlobalScheduler(KernelCore& kernel) : kernel{kernel} {} GlobalScheduler::~GlobalScheduler() = default; @@ -35,7 +36,7 @@ void GlobalScheduler::RemoveThread(std::shared_ptr<Thread> thread) { } void GlobalScheduler::UnloadThread(std::size_t core) { - Scheduler& sched = system.Scheduler(core); + Scheduler& sched = kernel.Scheduler(core); sched.UnloadThread(); } @@ -50,7 +51,7 @@ void GlobalScheduler::SelectThread(std::size_t core) { sched.is_context_switch_pending = sched.selected_thread != sched.current_thread; std::atomic_thread_fence(std::memory_order_seq_cst); }; - Scheduler& sched = system.Scheduler(core); + Scheduler& sched = kernel.Scheduler(core); Thread* current_thread = nullptr; // Step 1: Get top thread in schedule queue. current_thread = scheduled_queue[core].empty() ? nullptr : scheduled_queue[core].front(); @@ -356,6 +357,32 @@ void GlobalScheduler::Shutdown() { thread_list.clear(); } +void GlobalScheduler::Lock() { + Core::EmuThreadHandle current_thread = kernel.GetCurrentEmuThreadID(); + if (current_thread == current_owner) { + ++scope_lock; + } else { + inner_lock.lock(); + current_owner = current_thread; + ASSERT(current_owner != Core::EmuThreadHandle::InvalidHandle()); + scope_lock = 1; + } +} + +void GlobalScheduler::Unlock() { + if (--scope_lock != 0) { + ASSERT(scope_lock > 0); + return; + } + for (std::size_t i = 0; i < Core::Hardware::NUM_CPU_CORES; i++) { + SelectThread(i); + } + current_owner = Core::EmuThreadHandle::InvalidHandle(); + scope_lock = 1; + inner_lock.unlock(); + // TODO(Blinkhawk): Setup the interrupts and change context on current core. +} + Scheduler::Scheduler(Core::System& system, Core::ARM_Interface& cpu_core, std::size_t core_id) : system(system), cpu_core(cpu_core), core_id(core_id) {} @@ -485,4 +512,27 @@ void Scheduler::Shutdown() { selected_thread = nullptr; } +SchedulerLock::SchedulerLock(KernelCore& kernel) : kernel{kernel} { + kernel.GlobalScheduler().Lock(); +} + +SchedulerLock::~SchedulerLock() { + kernel.GlobalScheduler().Unlock(); +} + +SchedulerLockAndSleep::SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, + Thread* time_task, s64 nanoseconds) + : SchedulerLock{kernel}, event_handle{event_handle}, time_task{time_task}, nanoseconds{ + nanoseconds} { + event_handle = InvalidHandle; +} + +SchedulerLockAndSleep::~SchedulerLockAndSleep() { + if (sleep_cancelled) { + return; + } + auto& time_manager = kernel.TimeManager(); + time_manager.ScheduleTimeEvent(event_handle, time_task, nanoseconds); +} + } // namespace Kernel diff --git a/src/core/hle/kernel/scheduler.h b/src/core/hle/kernel/scheduler.h index 96db049cb..1c93a838c 100644 --- a/src/core/hle/kernel/scheduler.h +++ b/src/core/hle/kernel/scheduler.h @@ -6,6 +6,7 @@ #include <atomic> #include <memory> +#include <mutex> #include <vector> #include "common/common_types.h" @@ -20,11 +21,13 @@ class System; namespace Kernel { +class KernelCore; class Process; +class SchedulerLock; class GlobalScheduler final { public: - explicit GlobalScheduler(Core::System& system); + explicit GlobalScheduler(KernelCore& kernel); ~GlobalScheduler(); /// Adds a new thread to the scheduler @@ -138,6 +141,14 @@ public: void Shutdown(); private: + friend class SchedulerLock; + + /// Lock the scheduler to the current thread. + void Lock(); + + /// Unlocks the scheduler, reselects threads, interrupts cores for rescheduling + /// and reschedules current core if needed. + void Unlock(); /** * Transfers a thread into an specific core. If the destination_core is -1 * it will be unscheduled from its source code and added into its suggested @@ -158,9 +169,14 @@ private: // ordered from Core 0 to Core 3. std::array<u32, Core::Hardware::NUM_CPU_CORES> preemption_priorities = {59, 59, 59, 62}; + /// Scheduler lock mechanisms. + std::mutex inner_lock{}; // TODO(Blinkhawk): Replace for a SpinLock + std::atomic<s64> scope_lock{}; + Core::EmuThreadHandle current_owner{Core::EmuThreadHandle::InvalidHandle()}; + /// Lists all thread ids that aren't deleted/etc. std::vector<std::shared_ptr<Thread>> thread_list; - Core::System& system; + KernelCore& kernel; }; class Scheduler final { @@ -227,4 +243,30 @@ private: bool is_context_switch_pending = false; }; +class SchedulerLock { +public: + explicit SchedulerLock(KernelCore& kernel); + ~SchedulerLock(); + +protected: + KernelCore& kernel; +}; + +class SchedulerLockAndSleep : public SchedulerLock { +public: + explicit SchedulerLockAndSleep(KernelCore& kernel, Handle& event_handle, Thread* time_task, + s64 nanoseconds); + ~SchedulerLockAndSleep(); + + void CancelSleep() { + sleep_cancelled = true; + } + +private: + Handle& event_handle; + Thread* time_task; + s64 nanoseconds; + bool sleep_cancelled{}; +}; + } // namespace Kernel diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index ae5f2c8bd..bf850e0b2 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp @@ -46,9 +46,9 @@ Thread::~Thread() = default; void Thread::Stop() { // Cancel any outstanding wakeup events for this thread Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), - callback_handle); - kernel.ThreadWakeupCallbackHandleTable().Close(callback_handle); - callback_handle = 0; + global_handle); + kernel.GlobalHandleTable().Close(global_handle); + global_handle = 0; SetStatus(ThreadStatus::Dead); Signal(); @@ -73,12 +73,12 @@ void Thread::WakeAfterDelay(s64 nanoseconds) { // thread-safe version of ScheduleEvent. const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds}); Core::System::GetInstance().CoreTiming().ScheduleEvent( - cycles, kernel.ThreadWakeupCallbackEventType(), callback_handle); + cycles, kernel.ThreadWakeupCallbackEventType(), global_handle); } void Thread::CancelWakeupTimer() { Core::System::GetInstance().CoreTiming().UnscheduleEvent(kernel.ThreadWakeupCallbackEventType(), - callback_handle); + global_handle); } void Thread::ResumeFromWait() { @@ -190,7 +190,7 @@ ResultVal<std::shared_ptr<Thread>> Thread::Create(KernelCore& kernel, std::strin thread->condvar_wait_address = 0; thread->wait_handle = 0; thread->name = std::move(name); - thread->callback_handle = kernel.ThreadWakeupCallbackHandleTable().Create(thread).Unwrap(); + thread->global_handle = kernel.GlobalHandleTable().Create(thread).Unwrap(); thread->owner_process = &owner_process; auto& scheduler = kernel.GlobalScheduler(); scheduler.AddThread(thread); diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h index 7a4916318..129e7858a 100644 --- a/src/core/hle/kernel/thread.h +++ b/src/core/hle/kernel/thread.h @@ -453,6 +453,10 @@ public: is_sync_cancelled = value; } + Handle GetGlobalHandle() const { + return global_handle; + } + private: void SetSchedulingStatus(ThreadSchedStatus new_status); void SetCurrentPriority(u32 new_priority); @@ -514,7 +518,7 @@ private: VAddr arb_wait_address{0}; /// Handle used as userdata to reference this object when inserting into the CoreTiming queue. - Handle callback_handle = 0; + Handle global_handle = 0; /// Callback that will be invoked when the thread is resumed from a waiting state. If the thread /// was waiting via WaitSynchronization then the object will be the last object that became diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp new file mode 100644 index 000000000..21b290468 --- /dev/null +++ b/src/core/hle/kernel/time_manager.cpp @@ -0,0 +1,44 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/assert.h" +#include "core/core.h" +#include "core/core_timing.h" +#include "core/core_timing_util.h" +#include "core/hle/kernel/handle_table.h" +#include "core/hle/kernel/kernel.h" +#include "core/hle/kernel/thread.h" +#include "core/hle/kernel/time_manager.h" + +namespace Kernel { + +TimeManager::TimeManager(Core::System& system) : system{system} { + time_manager_event_type = Core::Timing::CreateEvent( + "Kernel::TimeManagerCallback", [this](u64 thread_handle, [[maybe_unused]] s64 cycles_late) { + Handle proper_handle = static_cast<Handle>(thread_handle); + std::shared_ptr<Thread> thread = + this->system.Kernel().RetrieveThreadFromGlobalHandleTable(proper_handle); + thread->ResumeFromWait(); + }); +} + +void TimeManager::ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds) { + if (nanoseconds > 0) { + ASSERT(timetask); + event_handle = timetask->GetGlobalHandle(); + const s64 cycles = Core::Timing::nsToCycles(std::chrono::nanoseconds{nanoseconds}); + system.CoreTiming().ScheduleEvent(cycles, time_manager_event_type, event_handle); + } else { + event_handle = InvalidHandle; + } +} + +void TimeManager::UnscheduleTimeEvent(Handle event_handle) { + if (event_handle == InvalidHandle) { + return; + } + system.CoreTiming().UnscheduleEvent(time_manager_event_type, event_handle); +} + +} // namespace Kernel diff --git a/src/core/hle/kernel/time_manager.h b/src/core/hle/kernel/time_manager.h new file mode 100644 index 000000000..eaec486d1 --- /dev/null +++ b/src/core/hle/kernel/time_manager.h @@ -0,0 +1,43 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> + +#include "core/hle/kernel/object.h" + +namespace Core { +class System; +} // namespace Core + +namespace Core::Timing { +struct EventType; +} // namespace Core::Timing + +namespace Kernel { + +class Thread; + +/** + * The `TimeManager` takes care of scheduling time events on threads and executes their TimeUp + * method when the event is triggered. + */ +class TimeManager { +public: + explicit TimeManager(Core::System& system); + + /// Schedule a time event on `timetask` thread that will expire in 'nanoseconds' + /// returns a non-invalid handle in `event_handle` if correctly scheduled + void ScheduleTimeEvent(Handle& event_handle, Thread* timetask, s64 nanoseconds); + + /// Unschedule an existing time event + void UnscheduleTimeEvent(Handle event_handle); + +private: + Core::System& system; + std::shared_ptr<Core::Timing::EventType> time_manager_event_type; +}; + +} // namespace Kernel diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp index cc978713b..d1bf13c89 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -607,7 +607,7 @@ ICommonStateGetter::ICommonStateGetter(Core::System& system, {40, nullptr, "GetCradleFwVersion"}, {50, nullptr, "IsVrModeEnabled"}, {51, nullptr, "SetVrModeEnabled"}, - {52, nullptr, "SwitchLcdBacklight"}, + {52, &ICommonStateGetter::SetLcdBacklighOffEnabled, "SetLcdBacklighOffEnabled"}, {53, nullptr, "BeginVrModeEx"}, {54, nullptr, "EndVrModeEx"}, {55, nullptr, "IsInControllerFirmwareUpdateSection"}, @@ -636,7 +636,6 @@ void ICommonStateGetter::GetBootMode(Kernel::HLERequestContext& ctx) { IPC::ResponseBuilder rb{ctx, 3}; rb.Push(RESULT_SUCCESS); - rb.Push<u8>(static_cast<u8>(Service::PM::SystemBootMode::Normal)); // Normal boot mode } @@ -660,6 +659,7 @@ void ICommonStateGetter::ReceiveMessage(Kernel::HLERequestContext& ctx) { rb.PushEnum<AppletMessageQueue::AppletMessage>(message); return; } + rb.Push(RESULT_SUCCESS); rb.PushEnum<AppletMessageQueue::AppletMessage>(message); } @@ -672,6 +672,17 @@ void ICommonStateGetter::GetCurrentFocusState(Kernel::HLERequestContext& ctx) { rb.Push(static_cast<u8>(FocusState::InFocus)); } +void ICommonStateGetter::SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx) { + IPC::RequestParser rp{ctx}; + const auto is_lcd_backlight_off_enabled = rp.Pop<bool>(); + + LOG_WARNING(Service_AM, "(STUBBED) called. is_lcd_backlight_off_enabled={}", + is_lcd_backlight_off_enabled); + + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(RESULT_SUCCESS); +} + void ICommonStateGetter::GetDefaultDisplayResolutionChangeEvent(Kernel::HLERequestContext& ctx) { LOG_DEBUG(Service_AM, "called"); diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h index 0b9a4332d..0843de781 100644 --- a/src/core/hle/service/am/am.h +++ b/src/core/hle/service/am/am.h @@ -182,6 +182,7 @@ private: void GetOperationMode(Kernel::HLERequestContext& ctx); void GetPerformanceMode(Kernel::HLERequestContext& ctx); void GetBootMode(Kernel::HLERequestContext& ctx); + void SetLcdBacklighOffEnabled(Kernel::HLERequestContext& ctx); void GetDefaultDisplayResolution(Kernel::HLERequestContext& ctx); void SetCpuBoostMode(Kernel::HLERequestContext& ctx); diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 15c09f04c..c1e32b28c 100644 --- a/src/core/hle/service/hid/controllers/npad.cpp +++ b/src/core/hle/service/hid/controllers/npad.cpp @@ -287,13 +287,13 @@ void Controller_NPad::RequestPadStateUpdate(u32 npad_id) { analog_state[static_cast<std::size_t>(JoystickId::Joystick_Left)]->GetAnalogDirectionStatus( Input::AnalogDirection::DOWN)); - pad_state.r_stick_up.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); - pad_state.r_stick_left.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); pad_state.r_stick_right.Assign( analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] - ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); + ->GetAnalogDirectionStatus(Input::AnalogDirection::RIGHT)); + pad_state.r_stick_left.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::LEFT)); + pad_state.r_stick_up.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] + ->GetAnalogDirectionStatus(Input::AnalogDirection::UP)); pad_state.r_stick_down.Assign(analog_state[static_cast<std::size_t>(JoystickId::Joystick_Right)] ->GetAnalogDirectionStatus(Input::AnalogDirection::DOWN)); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index d1fc94060..7c0303684 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -94,6 +94,7 @@ void LogSettings() { LogSetting("Renderer_UseAccurateGpuEmulation", Settings::values.use_accurate_gpu_emulation); LogSetting("Renderer_UseAsynchronousGpuEmulation", Settings::values.use_asynchronous_gpu_emulation); + LogSetting("Renderer_UseVsync", Settings::values.use_vsync); LogSetting("Audio_OutputEngine", Settings::values.sink_id); LogSetting("Audio_EnableAudioStretching", Settings::values.enable_audio_stretching); LogSetting("Audio_OutputDevice", Settings::values.audio_device_id); diff --git a/src/core/settings.h b/src/core/settings.h index f837d3fbc..15b691342 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -435,6 +435,7 @@ struct Values { bool use_disk_shader_cache; bool use_accurate_gpu_emulation; bool use_asynchronous_gpu_emulation; + bool use_vsync; bool force_30fps_mode; float bg_red; diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index 0e72d31cd..0f3685d1c 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp @@ -188,6 +188,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader) { Settings::values.use_accurate_gpu_emulation); AddField(field_type, "Renderer_UseAsynchronousGpuEmulation", Settings::values.use_asynchronous_gpu_emulation); + AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync); AddField(field_type, "System_UseDockedMode", Settings::values.use_docked_mode); } diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp index e1a260762..6cabdaa3c 100755 --- a/src/input_common/analog_from_button.cpp +++ b/src/input_common/analog_from_button.cpp @@ -34,6 +34,20 @@ public: y * coef * (x == 0 ? 1.0f : SQRT_HALF)); } + bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { + switch (direction) { + case Input::AnalogDirection::RIGHT: + return right->GetStatus(); + case Input::AnalogDirection::LEFT: + return left->GetStatus(); + case Input::AnalogDirection::UP: + return up->GetStatus(); + case Input::AnalogDirection::DOWN: + return down->GetStatus(); + } + return false; + } + private: Button up; Button down; diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h index 26939be3f..6ea7cc6a5 100644 --- a/src/video_core/engines/maxwell_3d.h +++ b/src/video_core/engines/maxwell_3d.h @@ -542,7 +542,7 @@ public: BitField<12, 1, InvMemoryLayout> type; } memory_layout; union { - BitField<0, 16, u32> array_mode; + BitField<0, 16, u32> layers; BitField<16, 1, u32> volume; }; u32 layer_stride; @@ -800,8 +800,12 @@ public: u32 zeta_width; u32 zeta_height; + union { + BitField<0, 16, u32> zeta_layers; + BitField<16, 1, u32> zeta_volume; + }; - INSERT_UNION_PADDING_WORDS(0x27); + INSERT_UNION_PADDING_WORDS(0x26); u32 depth_test_enable; @@ -1507,6 +1511,7 @@ ASSERT_REG_POSITION(vertex_attrib_format, 0x458); ASSERT_REG_POSITION(rt_control, 0x487); ASSERT_REG_POSITION(zeta_width, 0x48a); ASSERT_REG_POSITION(zeta_height, 0x48b); +ASSERT_REG_POSITION(zeta_layers, 0x48c); ASSERT_REG_POSITION(depth_test_enable, 0x4B3); ASSERT_REG_POSITION(independent_blend_enable, 0x4B9); ASSERT_REG_POSITION(depth_write_enabled, 0x4BA); diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 7d7137109..e8f763ce9 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -140,71 +140,6 @@ void GPU::FlushCommands() { renderer.Rasterizer().FlushCommands(); } -u32 RenderTargetBytesPerPixel(RenderTargetFormat format) { - ASSERT(format != RenderTargetFormat::NONE); - - switch (format) { - case RenderTargetFormat::RGBA32_FLOAT: - case RenderTargetFormat::RGBA32_UINT: - return 16; - case RenderTargetFormat::RGBA16_UINT: - case RenderTargetFormat::RGBA16_UNORM: - case RenderTargetFormat::RGBA16_FLOAT: - case RenderTargetFormat::RGBX16_FLOAT: - case RenderTargetFormat::RG32_FLOAT: - case RenderTargetFormat::RG32_UINT: - return 8; - case RenderTargetFormat::RGBA8_UNORM: - case RenderTargetFormat::RGBA8_SNORM: - case RenderTargetFormat::RGBA8_SRGB: - case RenderTargetFormat::RGBA8_UINT: - case RenderTargetFormat::RGB10_A2_UNORM: - case RenderTargetFormat::BGRA8_UNORM: - case RenderTargetFormat::BGRA8_SRGB: - case RenderTargetFormat::RG16_UNORM: - case RenderTargetFormat::RG16_SNORM: - case RenderTargetFormat::RG16_UINT: - case RenderTargetFormat::RG16_SINT: - case RenderTargetFormat::RG16_FLOAT: - case RenderTargetFormat::R32_FLOAT: - case RenderTargetFormat::R11G11B10_FLOAT: - case RenderTargetFormat::R32_UINT: - return 4; - case RenderTargetFormat::R16_UNORM: - case RenderTargetFormat::R16_SNORM: - case RenderTargetFormat::R16_UINT: - case RenderTargetFormat::R16_SINT: - case RenderTargetFormat::R16_FLOAT: - case RenderTargetFormat::RG8_UNORM: - case RenderTargetFormat::RG8_SNORM: - return 2; - case RenderTargetFormat::R8_UNORM: - case RenderTargetFormat::R8_UINT: - return 1; - default: - UNIMPLEMENTED_MSG("Unimplemented render target format {}", static_cast<u32>(format)); - return 1; - } -} - -u32 DepthFormatBytesPerPixel(DepthFormat format) { - switch (format) { - case DepthFormat::Z32_S8_X24_FLOAT: - return 8; - case DepthFormat::Z32_FLOAT: - case DepthFormat::S8_Z24_UNORM: - case DepthFormat::Z24_X8_UNORM: - case DepthFormat::Z24_S8_UNORM: - case DepthFormat::Z24_C8_UNORM: - return 4; - case DepthFormat::Z16_UNORM: - return 2; - default: - UNIMPLEMENTED_MSG("Unimplemented Depth format {}", static_cast<u32>(format)); - return 1; - } -} - // Note that, traditionally, methods are treated as 4-byte addressable locations, and hence // their numbers are written down multiplied by 4 in Docs. Here we are not multiply by 4. // So the values you see in docs might be multiplied by 4. diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 07727210c..ba8c9d665 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -57,6 +57,7 @@ enum class RenderTargetFormat : u32 { RG16_UINT = 0xDD, RG16_FLOAT = 0xDE, R11G11B10_FLOAT = 0xE0, + R32_SINT = 0xE3, R32_UINT = 0xE4, R32_FLOAT = 0xE5, B5G6R5_UNORM = 0xE8, @@ -82,12 +83,6 @@ enum class DepthFormat : u32 { Z32_S8_X24_FLOAT = 0x19, }; -/// Returns the number of bytes per pixel of each rendertarget format. -u32 RenderTargetBytesPerPixel(RenderTargetFormat format); - -/// Returns the number of bytes per pixel of each depth format. -u32 DepthFormatBytesPerPixel(DepthFormat format); - struct CommandListHeader; class DebugContext; diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 2cdf1aa7f..b1088af3d 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -5,7 +5,7 @@ #include "common/assert.h" #include "common/microprofile.h" #include "core/core.h" -#include "core/frontend/scope_acquire_window_context.h" +#include "core/frontend/scope_acquire_context.h" #include "video_core/dma_pusher.h" #include "video_core/gpu.h" #include "video_core/gpu_thread.h" @@ -27,7 +27,7 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p return; } - Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()}; + Core::Frontend::ScopeAcquireContext acquire_context{renderer.GetRenderWindow()}; CommandDataContainer next; while (state.is_running) { diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp index 2f2fe6859..f2c83266e 100644 --- a/src/video_core/morton.cpp +++ b/src/video_core/morton.cpp @@ -85,6 +85,7 @@ static constexpr ConversionArray morton_to_linear_fns = { MortonCopy<true, PixelFormat::RG32UI>, MortonCopy<true, PixelFormat::RGBX16F>, MortonCopy<true, PixelFormat::R32UI>, + MortonCopy<true, PixelFormat::R32I>, MortonCopy<true, PixelFormat::ASTC_2D_8X8>, MortonCopy<true, PixelFormat::ASTC_2D_8X5>, MortonCopy<true, PixelFormat::ASTC_2D_5X4>, @@ -166,6 +167,7 @@ static constexpr ConversionArray linear_to_morton_fns = { MortonCopy<false, PixelFormat::RG32UI>, MortonCopy<false, PixelFormat::RGBX16F>, MortonCopy<false, PixelFormat::R32UI>, + MortonCopy<false, PixelFormat::R32I>, nullptr, nullptr, nullptr, diff --git a/src/video_core/renderer_base.h b/src/video_core/renderer_base.h index af1bebc4f..5ec99a126 100644 --- a/src/video_core/renderer_base.h +++ b/src/video_core/renderer_base.h @@ -35,15 +35,19 @@ public: explicit RendererBase(Core::Frontend::EmuWindow& window); virtual ~RendererBase(); - /// Swap buffers (render frame) - virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; - /// Initialize the renderer virtual bool Init() = 0; /// Shutdown the renderer virtual void ShutDown() = 0; + /// Finalize rendering the guest frame and draw into the presentation texture + virtual void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) = 0; + + /// Draws the latest frame to the window waiting timeout_ms for a frame to arrive (Renderer + /// specific implementation) + virtual void TryPresent(int timeout_ms) = 0; + // Getter/setter functions: // ------------------------ diff --git a/src/video_core/renderer_opengl/gl_resource_manager.cpp b/src/video_core/renderer_opengl/gl_resource_manager.cpp index f0ddfb276..c0aee770f 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.cpp +++ b/src/video_core/renderer_opengl/gl_resource_manager.cpp @@ -15,6 +15,24 @@ MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_R namespace OpenGL { +void OGLRenderbuffer::Create() { + if (handle != 0) + return; + + MICROPROFILE_SCOPE(OpenGL_ResourceCreation); + glGenRenderbuffers(1, &handle); +} + +void OGLRenderbuffer::Release() { + if (handle == 0) + return; + + MICROPROFILE_SCOPE(OpenGL_ResourceDeletion); + glDeleteRenderbuffers(1, &handle); + OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply(); + handle = 0; +} + void OGLTexture::Create(GLenum target) { if (handle != 0) return; diff --git a/src/video_core/renderer_opengl/gl_resource_manager.h b/src/video_core/renderer_opengl/gl_resource_manager.h index 514d1d165..995a4e45e 100644 --- a/src/video_core/renderer_opengl/gl_resource_manager.h +++ b/src/video_core/renderer_opengl/gl_resource_manager.h @@ -11,6 +11,31 @@ namespace OpenGL { +class OGLRenderbuffer : private NonCopyable { +public: + OGLRenderbuffer() = default; + + OGLRenderbuffer(OGLRenderbuffer&& o) noexcept : handle(std::exchange(o.handle, 0)) {} + + ~OGLRenderbuffer() { + Release(); + } + + OGLRenderbuffer& operator=(OGLRenderbuffer&& o) noexcept { + Release(); + handle = std::exchange(o.handle, 0); + return *this; + } + + /// Creates a new internal OpenGL resource and stores the handle + void Create(); + + /// Deletes the internal OpenGL resource + void Release(); + + GLuint handle = 0; +}; + class OGLTexture : private NonCopyable { public: OGLTexture() = default; diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp index ab1f7983c..7d3bc1a1f 100644 --- a/src/video_core/renderer_opengl/gl_state.cpp +++ b/src/video_core/renderer_opengl/gl_state.cpp @@ -423,6 +423,13 @@ void OpenGLState::ApplyClipControl() { } } +void OpenGLState::ApplyRenderBuffer() { + if (cur_state.renderbuffer != renderbuffer) { + cur_state.renderbuffer = renderbuffer; + glBindRenderbuffer(GL_RENDERBUFFER, renderbuffer); + } +} + void OpenGLState::ApplyTextures() { const std::size_t size = std::size(textures); for (std::size_t i = 0; i < size; ++i) { @@ -478,6 +485,7 @@ void OpenGLState::Apply() { ApplyPolygonOffset(); ApplyAlphaTest(); ApplyClipControl(); + ApplyRenderBuffer(); } void OpenGLState::EmulateViewportWithScissor() { @@ -551,4 +559,11 @@ OpenGLState& OpenGLState::ResetFramebuffer(GLuint handle) { return *this; } +OpenGLState& OpenGLState::ResetRenderbuffer(GLuint handle) { + if (renderbuffer == handle) { + renderbuffer = 0; + } + return *this; +} + } // namespace OpenGL diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h index 4953eeda2..bce662f2c 100644 --- a/src/video_core/renderer_opengl/gl_state.h +++ b/src/video_core/renderer_opengl/gl_state.h @@ -158,6 +158,8 @@ public: GLenum depth_mode = GL_NEGATIVE_ONE_TO_ONE; } clip_control; + GLuint renderbuffer{}; // GL_RENDERBUFFER_BINDING + OpenGLState(); /// Get the currently active OpenGL state @@ -196,6 +198,7 @@ public: void ApplyPolygonOffset(); void ApplyAlphaTest(); void ApplyClipControl(); + void ApplyRenderBuffer(); /// Resets any references to the given resource OpenGLState& UnbindTexture(GLuint handle); @@ -204,6 +207,7 @@ public: OpenGLState& ResetPipeline(GLuint handle); OpenGLState& ResetVertexArray(GLuint handle); OpenGLState& ResetFramebuffer(GLuint handle); + OpenGLState& ResetRenderbuffer(GLuint handle); /// Viewport does not affects glClearBuffer so emulate viewport using scissor test void EmulateViewportWithScissor(); diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index d4b81cd87..cf934b0d8 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -87,6 +87,7 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> tex_format {GL_RG32UI, GL_RG_INTEGER, GL_UNSIGNED_INT, false}, // RG32UI {GL_RGB16F, GL_RGBA, GL_HALF_FLOAT, false}, // RGBX16F {GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT, false}, // R32UI + {GL_R32I, GL_RED_INTEGER, GL_INT, false}, // R32I {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_8X8 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_8X5 {GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE, false}, // ASTC_2D_5X4 @@ -260,6 +261,13 @@ CachedSurface::~CachedSurface() = default; void CachedSurface::DownloadTexture(std::vector<u8>& staging_buffer) { MICROPROFILE_SCOPE(OpenGL_Texture_Download); + if (params.IsBuffer()) { + glGetNamedBufferSubData(texture_buffer.handle, 0, + static_cast<GLsizeiptr>(params.GetHostSizeInBytes()), + staging_buffer.data()); + return; + } + SCOPE_EXIT({ glPixelStorei(GL_PACK_ROW_LENGTH, 0); }); for (u32 level = 0; level < params.emulated_levels; ++level) { @@ -398,24 +406,36 @@ CachedSurfaceView::CachedSurfaceView(CachedSurface& surface, const ViewParams& p CachedSurfaceView::~CachedSurfaceView() = default; void CachedSurfaceView::Attach(GLenum attachment, GLenum target) const { - ASSERT(params.num_layers == 1 && params.num_levels == 1); + ASSERT(params.num_levels == 1); - const auto& owner_params = surface.GetSurfaceParams(); + const GLuint texture = surface.GetTexture(); + if (params.num_layers > 1) { + // Layered framebuffer attachments + UNIMPLEMENTED_IF(params.base_layer != 0); + + switch (params.target) { + case SurfaceTarget::Texture2DArray: + glFramebufferTexture(target, attachment, texture, params.base_level); + break; + default: + UNIMPLEMENTED(); + } + return; + } - switch (owner_params.target) { + const GLenum view_target = surface.GetTarget(); + switch (surface.GetSurfaceParams().target) { case SurfaceTarget::Texture1D: - glFramebufferTexture1D(target, attachment, surface.GetTarget(), surface.GetTexture(), - params.base_level); + glFramebufferTexture1D(target, attachment, view_target, texture, params.base_level); break; case SurfaceTarget::Texture2D: - glFramebufferTexture2D(target, attachment, surface.GetTarget(), surface.GetTexture(), - params.base_level); + glFramebufferTexture2D(target, attachment, view_target, texture, params.base_level); break; case SurfaceTarget::Texture1DArray: case SurfaceTarget::Texture2DArray: case SurfaceTarget::TextureCubemap: case SurfaceTarget::TextureCubeArray: - glFramebufferTextureLayer(target, attachment, surface.GetTexture(), params.base_level, + glFramebufferTextureLayer(target, attachment, texture, params.base_level, params.base_layer); break; default: diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index bba16afaf..a4340b502 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp @@ -9,11 +9,11 @@ #include <glad/glad.h> #include "common/assert.h" #include "common/logging/log.h" +#include "common/microprofile.h" #include "common/telemetry.h" #include "core/core.h" #include "core/core_timing.h" #include "core/frontend/emu_window.h" -#include "core/frontend/scope_acquire_window_context.h" #include "core/memory.h" #include "core/perf_stats.h" #include "core/settings.h" @@ -24,6 +24,144 @@ namespace OpenGL { +// If the size of this is too small, it ends up creating a soft cap on FPS as the renderer will have +// to wait on available presentation frames. +constexpr std::size_t SWAP_CHAIN_SIZE = 3; + +struct Frame { + u32 width{}; /// Width of the frame (to detect resize) + u32 height{}; /// Height of the frame + bool color_reloaded{}; /// Texture attachment was recreated (ie: resized) + OpenGL::OGLRenderbuffer color{}; /// Buffer shared between the render/present FBO + OpenGL::OGLFramebuffer render{}; /// FBO created on the render thread + OpenGL::OGLFramebuffer present{}; /// FBO created on the present thread + GLsync render_fence{}; /// Fence created on the render thread + GLsync present_fence{}; /// Fence created on the presentation thread + bool is_srgb{}; /// Framebuffer is sRGB or RGB +}; + +/** + * For smooth Vsync rendering, we want to always present the latest frame that the core generates, + * but also make sure that rendering happens at the pace that the frontend dictates. This is a + * helper class that the renderer uses to sync frames between the render thread and the presentation + * thread + */ +class FrameMailbox { +public: + std::mutex swap_chain_lock; + std::condition_variable present_cv; + std::array<Frame, SWAP_CHAIN_SIZE> swap_chain{}; + std::queue<Frame*> free_queue; + std::deque<Frame*> present_queue; + Frame* previous_frame{}; + + FrameMailbox() { + for (auto& frame : swap_chain) { + free_queue.push(&frame); + } + } + + ~FrameMailbox() { + // lock the mutex and clear out the present and free_queues and notify any people who are + // blocked to prevent deadlock on shutdown + std::scoped_lock lock{swap_chain_lock}; + std::queue<Frame*>().swap(free_queue); + present_queue.clear(); + present_cv.notify_all(); + } + + void ReloadPresentFrame(Frame* frame, u32 height, u32 width) { + frame->present.Release(); + frame->present.Create(); + GLint previous_draw_fbo{}; + glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &previous_draw_fbo); + glBindFramebuffer(GL_FRAMEBUFFER, frame->present.handle); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, + frame->color.handle); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + LOG_CRITICAL(Render_OpenGL, "Failed to recreate present FBO!"); + } + glBindFramebuffer(GL_DRAW_FRAMEBUFFER, previous_draw_fbo); + frame->color_reloaded = false; + } + + void ReloadRenderFrame(Frame* frame, u32 width, u32 height) { + OpenGLState prev_state = OpenGLState::GetCurState(); + OpenGLState state = OpenGLState::GetCurState(); + + // Recreate the color texture attachment + frame->color.Release(); + frame->color.Create(); + state.renderbuffer = frame->color.handle; + state.Apply(); + glRenderbufferStorage(GL_RENDERBUFFER, frame->is_srgb ? GL_SRGB8 : GL_RGB8, width, height); + + // Recreate the FBO for the render target + frame->render.Release(); + frame->render.Create(); + state.draw.read_framebuffer = frame->render.handle; + state.draw.draw_framebuffer = frame->render.handle; + state.Apply(); + glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, + frame->color.handle); + if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) { + LOG_CRITICAL(Render_OpenGL, "Failed to recreate render FBO!"); + } + prev_state.Apply(); + frame->width = width; + frame->height = height; + frame->color_reloaded = true; + } + + Frame* GetRenderFrame() { + std::unique_lock lock{swap_chain_lock}; + + // If theres no free frames, we will reuse the oldest render frame + if (free_queue.empty()) { + auto frame = present_queue.back(); + present_queue.pop_back(); + return frame; + } + + Frame* frame = free_queue.front(); + free_queue.pop(); + return frame; + } + + void ReleaseRenderFrame(Frame* frame) { + std::unique_lock lock{swap_chain_lock}; + present_queue.push_front(frame); + present_cv.notify_one(); + } + + Frame* TryGetPresentFrame(int timeout_ms) { + std::unique_lock lock{swap_chain_lock}; + // wait for new entries in the present_queue + present_cv.wait_for(lock, std::chrono::milliseconds(timeout_ms), + [&] { return !present_queue.empty(); }); + if (present_queue.empty()) { + // timed out waiting for a frame to draw so return the previous frame + return previous_frame; + } + + // free the previous frame and add it back to the free queue + if (previous_frame) { + free_queue.push(previous_frame); + } + + // the newest entries are pushed to the front of the queue + Frame* frame = present_queue.front(); + present_queue.pop_front(); + // remove all old entries from the present queue and move them back to the free_queue + for (auto f : present_queue) { + free_queue.push(f); + } + present_queue.clear(); + previous_frame = frame; + return frame; + } +}; + namespace { constexpr char vertex_shader[] = R"( @@ -158,21 +296,91 @@ void APIENTRY DebugHandler(GLenum source, GLenum type, GLuint id, GLenum severit } // Anonymous namespace RendererOpenGL::RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system) - : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system} {} + : VideoCore::RendererBase{emu_window}, emu_window{emu_window}, system{system}, + frame_mailbox{std::make_unique<FrameMailbox>()} {} RendererOpenGL::~RendererOpenGL() = default; +MICROPROFILE_DEFINE(OpenGL_RenderFrame, "OpenGL", "Render Frame", MP_RGB(128, 128, 64)); +MICROPROFILE_DEFINE(OpenGL_WaitPresent, "OpenGL", "Wait For Present", MP_RGB(128, 128, 128)); + void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { + render_window.PollEvents(); + + if (!framebuffer) { + return; + } + // Maintain the rasterizer's state as a priority OpenGLState prev_state = OpenGLState::GetCurState(); state.AllDirty(); state.Apply(); + PrepareRendertarget(framebuffer); + RenderScreenshot(); + + Frame* frame; + { + MICROPROFILE_SCOPE(OpenGL_WaitPresent); + + frame = frame_mailbox->GetRenderFrame(); + + // Clean up sync objects before drawing + + // INTEL driver workaround. We can't delete the previous render sync object until we are + // sure that the presentation is done + if (frame->present_fence) { + glClientWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + } + + // delete the draw fence if the frame wasn't presented + if (frame->render_fence) { + glDeleteSync(frame->render_fence); + frame->render_fence = 0; + } + + // wait for the presentation to be done + if (frame->present_fence) { + glWaitSync(frame->present_fence, 0, GL_TIMEOUT_IGNORED); + glDeleteSync(frame->present_fence); + frame->present_fence = 0; + } + } + + { + MICROPROFILE_SCOPE(OpenGL_RenderFrame); + const auto& layout = render_window.GetFramebufferLayout(); + + // Recreate the frame if the size of the window has changed + if (layout.width != frame->width || layout.height != frame->height || + screen_info.display_srgb != frame->is_srgb) { + LOG_DEBUG(Render_OpenGL, "Reloading render frame"); + frame->is_srgb = screen_info.display_srgb; + frame_mailbox->ReloadRenderFrame(frame, layout.width, layout.height); + } + state.draw.draw_framebuffer = frame->render.handle; + state.Apply(); + DrawScreen(layout); + // Create a fence for the frontend to wait on and swap this frame to OffTex + frame->render_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + frame_mailbox->ReleaseRenderFrame(frame); + m_current_frame++; + rasterizer->TickFrame(); + } + + // Restore the rasterizer state + prev_state.AllDirty(); + prev_state.Apply(); +} + +void RendererOpenGL::PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer) { if (framebuffer) { // If framebuffer is provided, reload it from memory to a texture if (screen_info.texture.width != static_cast<GLsizei>(framebuffer->width) || screen_info.texture.height != static_cast<GLsizei>(framebuffer->height) || - screen_info.texture.pixel_format != framebuffer->pixel_format) { + screen_info.texture.pixel_format != framebuffer->pixel_format || + gl_framebuffer_data.empty()) { // Reallocate texture if the framebuffer size has changed. // This is expected to not happen very often and hence should not be a // performance problem. @@ -181,22 +389,7 @@ void RendererOpenGL::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { // Load the framebuffer from memory, draw it to the screen, and swap buffers LoadFBToScreenInfo(*framebuffer); - - if (renderer_settings.screenshot_requested) - CaptureScreenshot(); - - DrawScreen(render_window.GetFramebufferLayout()); - - rasterizer->TickFrame(); - - render_window.SwapBuffers(); } - - render_window.PollEvents(); - - // Restore the rasterizer state - prev_state.AllDirty(); - prev_state.Apply(); } void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer) { @@ -418,13 +611,48 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { DrawScreenTriangles(screen_info, static_cast<float>(screen.left), static_cast<float>(screen.top), static_cast<float>(screen.GetWidth()), static_cast<float>(screen.GetHeight())); +} - m_current_frame++; +void RendererOpenGL::TryPresent(int timeout_ms) { + const auto& layout = render_window.GetFramebufferLayout(); + auto frame = frame_mailbox->TryGetPresentFrame(timeout_ms); + if (!frame) { + LOG_DEBUG(Render_OpenGL, "TryGetPresentFrame returned no frame to present"); + return; + } + + // Clearing before a full overwrite of a fbo can signal to drivers that they can avoid a + // readback since we won't be doing any blending + glClear(GL_COLOR_BUFFER_BIT); + + // Recreate the presentation FBO if the color attachment was changed + if (frame->color_reloaded) { + LOG_DEBUG(Render_OpenGL, "Reloading present frame"); + frame_mailbox->ReloadPresentFrame(frame, layout.width, layout.height); + } + glWaitSync(frame->render_fence, 0, GL_TIMEOUT_IGNORED); + // INTEL workaround. + // Normally we could just delete the draw fence here, but due to driver bugs, we can just delete + // it on the emulation thread without too much penalty + // glDeleteSync(frame.render_sync); + // frame.render_sync = 0; + + glBindFramebuffer(GL_READ_FRAMEBUFFER, frame->present.handle); + glBlitFramebuffer(0, 0, frame->width, frame->height, 0, 0, layout.width, layout.height, + GL_COLOR_BUFFER_BIT, GL_LINEAR); + + // Insert fence for the main thread to block on + frame->present_fence = glFenceSync(GL_SYNC_GPU_COMMANDS_COMPLETE, 0); + glFlush(); + + glBindFramebuffer(GL_READ_FRAMEBUFFER, 0); } -void RendererOpenGL::UpdateFramerate() {} +void RendererOpenGL::RenderScreenshot() { + if (!renderer_settings.screenshot_requested) { + return; + } -void RendererOpenGL::CaptureScreenshot() { // Draw the current frame to the screenshot framebuffer screenshot_framebuffer.Create(); GLuint old_read_fb = state.draw.read_framebuffer; @@ -459,8 +687,6 @@ void RendererOpenGL::CaptureScreenshot() { } bool RendererOpenGL::Init() { - Core::Frontend::ScopeAcquireWindowContext acquire_context{render_window}; - if (GLAD_GL_KHR_debug) { glEnable(GL_DEBUG_OUTPUT); glDebugMessageCallback(DebugHandler, nullptr); diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index b56328a7f..d45e69cbc 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h @@ -44,19 +44,23 @@ struct ScreenInfo { TextureInfo texture; }; +struct PresentationTexture { + u32 width = 0; + u32 height = 0; + OGLTexture texture; +}; + +class FrameMailbox; + class RendererOpenGL final : public VideoCore::RendererBase { public: explicit RendererOpenGL(Core::Frontend::EmuWindow& emu_window, Core::System& system); ~RendererOpenGL() override; - /// Swap buffers (render frame) - void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; - - /// Initialize the renderer bool Init() override; - - /// Shutdown the renderer void ShutDown() override; + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; + void TryPresent(int timeout_ms) override; private: /// Initializes the OpenGL state and creates persistent objects. @@ -74,10 +78,7 @@ private: void DrawScreenTriangles(const ScreenInfo& screen_info, float x, float y, float w, float h); - /// Updates the framerate. - void UpdateFramerate(); - - void CaptureScreenshot(); + void RenderScreenshot(); /// Loads framebuffer from emulated memory into the active OpenGL texture. void LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuffer); @@ -87,6 +88,8 @@ private: void LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color_b, u8 color_a, const TextureInfo& texture); + void PrepareRendertarget(const Tegra::FramebufferConfig* framebuffer); + Core::Frontend::EmuWindow& emu_window; Core::System& system; @@ -107,6 +110,9 @@ private: /// Used for transforming the framebuffer orientation Tegra::FramebufferConfig::TransformFlags framebuffer_transform_flags; Common::Rectangle<int> framebuffer_crop_rect; + + /// Frame presentation mailbox + std::unique_ptr<FrameMailbox> frame_mailbox; }; } // namespace OpenGL diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp index 5403c3ab7..ef66dd141 100644 --- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp +++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp @@ -159,6 +159,7 @@ struct FormatTuple { {vk::Format::eR32G32Uint, Attachable | Storage}, // RG32UI {vk::Format::eUndefined, {}}, // RGBX16F {vk::Format::eR32Uint, Attachable | Storage}, // R32UI + {vk::Format::eR32Sint, Attachable | Storage}, // R32I {vk::Format::eAstc8x8UnormBlock, {}}, // ASTC_2D_8X8 {vk::Format::eUndefined, {}}, // ASTC_2D_8X5 {vk::Format::eUndefined, {}}, // ASTC_2D_5X4 diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp index d5032b432..ddc62bc97 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp +++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp @@ -106,8 +106,14 @@ RendererVulkan::~RendererVulkan() { } void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { + render_window.PollEvents(); + + if (!framebuffer) { + return; + } + const auto& layout = render_window.GetFramebufferLayout(); - if (framebuffer && layout.width > 0 && layout.height > 0 && render_window.IsShown()) { + if (layout.width > 0 && layout.height > 0 && render_window.IsShown()) { const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset; const bool use_accelerated = rasterizer->AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride); @@ -128,13 +134,16 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) { blit_screen->Recreate(); } - render_window.SwapBuffers(); rasterizer->TickFrame(); } render_window.PollEvents(); } +void RendererVulkan::TryPresent(int /*timeout_ms*/) { + // TODO (bunnei): ImplementMe +} + bool RendererVulkan::Init() { PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr{}; render_window.RetrieveVulkanHandlers(&vkGetInstanceProcAddr, &instance, &surface); @@ -262,4 +271,4 @@ void RendererVulkan::Report() const { telemetry_session.AddField(field, "GPU_Vulkan_Extensions", extensions); } -} // namespace Vulkan
\ No newline at end of file +} // namespace Vulkan diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h index a472c5dc9..f513397f0 100644 --- a/src/video_core/renderer_vulkan/renderer_vulkan.h +++ b/src/video_core/renderer_vulkan/renderer_vulkan.h @@ -36,14 +36,10 @@ public: explicit RendererVulkan(Core::Frontend::EmuWindow& window, Core::System& system); ~RendererVulkan() override; - /// Swap buffers (render frame) - void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; - - /// Initialize the renderer bool Init() override; - - /// Shutdown the renderer void ShutDown() override; + void SwapBuffers(const Tegra::FramebufferConfig* framebuffer) override; + void TryPresent(int timeout_ms) override; private: std::optional<vk::DebugUtilsMessengerEXT> CreateDebugCallback( diff --git a/src/video_core/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp index d1da4f9d3..886bde3b9 100644 --- a/src/video_core/renderer_vulkan/vk_device.cpp +++ b/src/video_core/renderer_vulkan/vk_device.cpp @@ -523,6 +523,7 @@ std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperti vk::Format::eB10G11R11UfloatPack32, vk::Format::eR32Sfloat, vk::Format::eR32Uint, + vk::Format::eR32Sint, vk::Format::eR16Sfloat, vk::Format::eR16G16B16A16Sfloat, vk::Format::eB8G8R8A8Unorm, diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index 31c078f6a..3bf86da87 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -611,33 +611,34 @@ bool RasterizerVulkan::WalkAttachmentOverlaps(const CachedSurfaceView& attachmen std::tuple<vk::Framebuffer, vk::Extent2D> RasterizerVulkan::ConfigureFramebuffers( vk::RenderPass renderpass) { FramebufferCacheKey key{renderpass, std::numeric_limits<u32>::max(), - std::numeric_limits<u32>::max()}; + std::numeric_limits<u32>::max(), std::numeric_limits<u32>::max()}; - const auto MarkAsModifiedAndPush = [&](const View& view) { - if (view == nullptr) { + const auto try_push = [&](const View& view) { + if (!view) { return false; } key.views.push_back(view->GetHandle()); key.width = std::min(key.width, view->GetWidth()); key.height = std::min(key.height, view->GetHeight()); + key.layers = std::min(key.layers, view->GetNumLayers()); return true; }; for (std::size_t index = 0; index < std::size(color_attachments); ++index) { - if (MarkAsModifiedAndPush(color_attachments[index])) { + if (try_push(color_attachments[index])) { texture_cache.MarkColorBufferInUse(index); } } - if (MarkAsModifiedAndPush(zeta_attachment)) { + if (try_push(zeta_attachment)) { texture_cache.MarkDepthBufferInUse(); } const auto [fbentry, is_cache_miss] = framebuffer_cache.try_emplace(key); auto& framebuffer = fbentry->second; if (is_cache_miss) { - const vk::FramebufferCreateInfo framebuffer_ci({}, key.renderpass, - static_cast<u32>(key.views.size()), - key.views.data(), key.width, key.height, 1); + const vk::FramebufferCreateInfo framebuffer_ci( + {}, key.renderpass, static_cast<u32>(key.views.size()), key.views.data(), key.width, + key.height, key.layers); const auto dev = device.GetLogical(); const auto& dld = device.GetDispatchLoader(); framebuffer = dev.createFramebufferUnique(framebuffer_ci, nullptr, dld); diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 138903d60..4dc8af6e8 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h @@ -56,6 +56,7 @@ struct FramebufferCacheKey { vk::RenderPass renderpass{}; u32 width = 0; u32 height = 0; + u32 layers = 0; ImageViewsPack views; std::size_t Hash() const noexcept { @@ -66,12 +67,17 @@ struct FramebufferCacheKey { } boost::hash_combine(hash, width); boost::hash_combine(hash, height); + boost::hash_combine(hash, layers); return hash; } bool operator==(const FramebufferCacheKey& rhs) const noexcept { - return std::tie(renderpass, views, width, height) == - std::tie(rhs.renderpass, rhs.views, rhs.width, rhs.height); + return std::tie(renderpass, views, width, height, layers) == + std::tie(rhs.renderpass, rhs.views, rhs.width, rhs.height, rhs.layers); + } + + bool operator!=(const FramebufferCacheKey& rhs) const noexcept { + return !operator==(rhs); } }; diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp index 6d0bf6aa1..2da622d15 100644 --- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp +++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp @@ -86,6 +86,7 @@ struct AttributeType { struct VertexIndices { std::optional<u32> position; + std::optional<u32> layer; std::optional<u32> viewport; std::optional<u32> point_size; std::optional<u32> clip_distances; @@ -284,9 +285,11 @@ public: AddExtension("SPV_KHR_variable_pointers"); AddExtension("SPV_KHR_shader_draw_parameters"); - if (ir.UsesViewportIndex()) { - AddCapability(spv::Capability::MultiViewport); - if (device.IsExtShaderViewportIndexLayerSupported()) { + if (ir.UsesLayer() || ir.UsesViewportIndex()) { + if (ir.UsesViewportIndex()) { + AddCapability(spv::Capability::MultiViewport); + } + if (stage != ShaderType::Geometry && device.IsExtShaderViewportIndexLayerSupported()) { AddExtension("SPV_EXT_shader_viewport_index_layer"); AddCapability(spv::Capability::ShaderViewportIndexLayerEXT); } @@ -928,13 +931,22 @@ private: VertexIndices indices; indices.position = AddBuiltIn(t_float4, spv::BuiltIn::Position, "position"); + if (ir.UsesLayer()) { + if (stage != ShaderType::Vertex || device.IsExtShaderViewportIndexLayerSupported()) { + indices.layer = AddBuiltIn(t_int, spv::BuiltIn::Layer, "layer"); + } else { + LOG_ERROR( + Render_Vulkan, + "Shader requires Layer but it's not supported on this stage with this device."); + } + } + if (ir.UsesViewportIndex()) { if (stage != ShaderType::Vertex || device.IsExtShaderViewportIndexLayerSupported()) { indices.viewport = AddBuiltIn(t_int, spv::BuiltIn::ViewportIndex, "viewport_index"); } else { - LOG_ERROR(Render_Vulkan, - "Shader requires ViewportIndex but it's not supported on this " - "stage with this device."); + LOG_ERROR(Render_Vulkan, "Shader requires ViewportIndex but it's not supported on " + "this stage with this device."); } } @@ -1296,6 +1308,13 @@ private: } case Attribute::Index::LayerViewportPointSize: switch (element) { + case 1: { + if (!out_indices.layer) { + return {}; + } + const u32 index = out_indices.layer.value(); + return {AccessElement(t_out_int, out_vertex, index), Type::Int}; + } case 2: { if (!out_indices.viewport) { return {}; @@ -1366,6 +1385,11 @@ private: UNIMPLEMENTED(); } + if (!target.id) { + // On failure we return a nullptr target.id, skip these stores. + return {}; + } + OpStore(target.id, As(Visit(src), target.type)); return {}; } diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index d3edbe80c..22e3d34de 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.h +++ b/src/video_core/renderer_vulkan/vk_texture_cache.h @@ -151,6 +151,10 @@ public: return params.GetMipHeight(base_level); } + u32 GetNumLayers() const { + return num_layers; + } + bool IsBufferView() const { return buffer_view; } diff --git a/src/video_core/shader/decode/arithmetic.cpp b/src/video_core/shader/decode/arithmetic.cpp index 90240c765..478394682 100644 --- a/src/video_core/shader/decode/arithmetic.cpp +++ b/src/video_core/shader/decode/arithmetic.cpp @@ -53,29 +53,24 @@ u32 ShaderIR::DecodeArithmetic(NodeBlock& bb, u32 pc) { op_b = GetOperandAbsNegFloat(op_b, false, instr.fmul.negate_b); - // TODO(Rodrigo): Should precise be used when there's a postfactor? - Node value = Operation(OperationCode::FMul, PRECISE, op_a, op_b); + static constexpr std::array FmulPostFactor = { + 1.000f, // None + 0.500f, // Divide 2 + 0.250f, // Divide 4 + 0.125f, // Divide 8 + 8.000f, // Mul 8 + 4.000f, // Mul 4 + 2.000f, // Mul 2 + }; if (instr.fmul.postfactor != 0) { - auto postfactor = static_cast<s32>(instr.fmul.postfactor); - - // Postfactor encoded as 3-bit 1's complement in instruction, interpreted with below - // logic. - if (postfactor >= 4) { - postfactor = 7 - postfactor; - } else { - postfactor = 0 - postfactor; - } - - if (postfactor > 0) { - value = Operation(OperationCode::FMul, NO_PRECISE, value, - Immediate(static_cast<f32>(1 << postfactor))); - } else { - value = Operation(OperationCode::FDiv, NO_PRECISE, value, - Immediate(static_cast<f32>(1 << -postfactor))); - } + op_a = Operation(OperationCode::FMul, NO_PRECISE, op_a, + Immediate(FmulPostFactor[instr.fmul.postfactor])); } + // TODO(Rodrigo): Should precise be used when there's a postfactor? + Node value = Operation(OperationCode::FMul, PRECISE, op_a, op_b); + value = GetSaturatedFloat(value, instr.alu.saturate_d); SetInternalFlagsFromFloat(bb, value, instr.generates_cc); diff --git a/src/video_core/shader/decode/arithmetic_integer.cpp b/src/video_core/shader/decode/arithmetic_integer.cpp index 21366869d..2fe787d6f 100644 --- a/src/video_core/shader/decode/arithmetic_integer.cpp +++ b/src/video_core/shader/decode/arithmetic_integer.cpp @@ -293,44 +293,66 @@ u32 ShaderIR::DecodeArithmeticInteger(NodeBlock& bb, u32 pc) { void ShaderIR::WriteLop3Instruction(NodeBlock& bb, Register dest, Node op_a, Node op_b, Node op_c, Node imm_lut, bool sets_cc) { - constexpr u32 lop_iterations = 32; - const Node one = Immediate(1); - const Node two = Immediate(2); - - Node value; - for (u32 i = 0; i < lop_iterations; ++i) { - const Node shift_amount = Immediate(i); - - const Node a = Operation(OperationCode::ILogicalShiftRight, NO_PRECISE, op_c, shift_amount); - const Node pack_0 = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, a, one); - - const Node b = Operation(OperationCode::ILogicalShiftRight, NO_PRECISE, op_b, shift_amount); - const Node c = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, b, one); - const Node pack_1 = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, c, one); - - const Node d = Operation(OperationCode::ILogicalShiftRight, NO_PRECISE, op_a, shift_amount); - const Node e = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, d, one); - const Node pack_2 = Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, e, two); - - const Node pack_01 = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, pack_0, pack_1); - const Node pack_012 = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, pack_01, pack_2); - - const Node shifted_bit = - Operation(OperationCode::ILogicalShiftRight, NO_PRECISE, imm_lut, pack_012); - const Node bit = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, shifted_bit, one); - - const Node right = - Operation(OperationCode::ILogicalShiftLeft, NO_PRECISE, bit, shift_amount); - - if (i > 0) { - value = Operation(OperationCode::IBitwiseOr, NO_PRECISE, value, right); - } else { - value = right; + const Node lop3_fast = [&](const Node na, const Node nb, const Node nc, const Node ttbl) { + Node value = Immediate(0); + const ImmediateNode imm = std::get<ImmediateNode>(*ttbl); + if (imm.GetValue() & 0x01) { + const Node a = Operation(OperationCode::IBitwiseNot, na); + const Node b = Operation(OperationCode::IBitwiseNot, nb); + const Node c = Operation(OperationCode::IBitwiseNot, nc); + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, a, b); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, c); + value = Operation(OperationCode::IBitwiseOr, value, r); } - } + if (imm.GetValue() & 0x02) { + const Node a = Operation(OperationCode::IBitwiseNot, na); + const Node b = Operation(OperationCode::IBitwiseNot, nb); + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, a, b); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, nc); + value = Operation(OperationCode::IBitwiseOr, value, r); + } + if (imm.GetValue() & 0x04) { + const Node a = Operation(OperationCode::IBitwiseNot, na); + const Node c = Operation(OperationCode::IBitwiseNot, nc); + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, a, nb); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, c); + value = Operation(OperationCode::IBitwiseOr, value, r); + } + if (imm.GetValue() & 0x08) { + const Node a = Operation(OperationCode::IBitwiseNot, na); + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, a, nb); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, nc); + value = Operation(OperationCode::IBitwiseOr, value, r); + } + if (imm.GetValue() & 0x10) { + const Node b = Operation(OperationCode::IBitwiseNot, nb); + const Node c = Operation(OperationCode::IBitwiseNot, nc); + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, na, b); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, c); + value = Operation(OperationCode::IBitwiseOr, value, r); + } + if (imm.GetValue() & 0x20) { + const Node b = Operation(OperationCode::IBitwiseNot, nb); + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, na, b); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, nc); + value = Operation(OperationCode::IBitwiseOr, value, r); + } + if (imm.GetValue() & 0x40) { + const Node c = Operation(OperationCode::IBitwiseNot, nc); + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, na, nb); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, c); + value = Operation(OperationCode::IBitwiseOr, value, r); + } + if (imm.GetValue() & 0x80) { + Node r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, na, nb); + r = Operation(OperationCode::IBitwiseAnd, NO_PRECISE, r, nc); + value = Operation(OperationCode::IBitwiseOr, value, r); + } + return value; + }(op_a, op_b, op_c, imm_lut); - SetInternalFlagsFromInteger(bb, value, sets_cc); - SetRegister(bb, dest, value); + SetInternalFlagsFromInteger(bb, lop3_fast, sets_cc); + SetRegister(bb, dest, lop3_fast); } } // namespace VideoCommon::Shader diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp index 1655ccf16..9707c353d 100644 --- a/src/video_core/surface.cpp +++ b/src/video_core/surface.cpp @@ -155,6 +155,8 @@ PixelFormat PixelFormatFromRenderTargetFormat(Tegra::RenderTargetFormat format) return PixelFormat::R16I; case Tegra::RenderTargetFormat::R32_FLOAT: return PixelFormat::R32F; + case Tegra::RenderTargetFormat::R32_SINT: + return PixelFormat::R32I; case Tegra::RenderTargetFormat::R32_UINT: return PixelFormat::R32UI; case Tegra::RenderTargetFormat::RG32_UINT: diff --git a/src/video_core/surface.h b/src/video_core/surface.h index 0d17a93ed..d88109e5a 100644 --- a/src/video_core/surface.h +++ b/src/video_core/surface.h @@ -59,47 +59,48 @@ enum class PixelFormat { RG32UI = 41, RGBX16F = 42, R32UI = 43, - ASTC_2D_8X8 = 44, - ASTC_2D_8X5 = 45, - ASTC_2D_5X4 = 46, - BGRA8_SRGB = 47, - DXT1_SRGB = 48, - DXT23_SRGB = 49, - DXT45_SRGB = 50, - BC7U_SRGB = 51, - R4G4B4A4U = 52, - ASTC_2D_4X4_SRGB = 53, - ASTC_2D_8X8_SRGB = 54, - ASTC_2D_8X5_SRGB = 55, - ASTC_2D_5X4_SRGB = 56, - ASTC_2D_5X5 = 57, - ASTC_2D_5X5_SRGB = 58, - ASTC_2D_10X8 = 59, - ASTC_2D_10X8_SRGB = 60, - ASTC_2D_6X6 = 61, - ASTC_2D_6X6_SRGB = 62, - ASTC_2D_10X10 = 63, - ASTC_2D_10X10_SRGB = 64, - ASTC_2D_12X12 = 65, - ASTC_2D_12X12_SRGB = 66, - ASTC_2D_8X6 = 67, - ASTC_2D_8X6_SRGB = 68, - ASTC_2D_6X5 = 69, - ASTC_2D_6X5_SRGB = 70, - E5B9G9R9F = 71, + R32I = 44, + ASTC_2D_8X8 = 45, + ASTC_2D_8X5 = 46, + ASTC_2D_5X4 = 47, + BGRA8_SRGB = 48, + DXT1_SRGB = 49, + DXT23_SRGB = 50, + DXT45_SRGB = 51, + BC7U_SRGB = 52, + R4G4B4A4U = 53, + ASTC_2D_4X4_SRGB = 54, + ASTC_2D_8X8_SRGB = 55, + ASTC_2D_8X5_SRGB = 56, + ASTC_2D_5X4_SRGB = 57, + ASTC_2D_5X5 = 58, + ASTC_2D_5X5_SRGB = 59, + ASTC_2D_10X8 = 60, + ASTC_2D_10X8_SRGB = 61, + ASTC_2D_6X6 = 62, + ASTC_2D_6X6_SRGB = 63, + ASTC_2D_10X10 = 64, + ASTC_2D_10X10_SRGB = 65, + ASTC_2D_12X12 = 66, + ASTC_2D_12X12_SRGB = 67, + ASTC_2D_8X6 = 68, + ASTC_2D_8X6_SRGB = 69, + ASTC_2D_6X5 = 70, + ASTC_2D_6X5_SRGB = 71, + E5B9G9R9F = 72, MaxColorFormat, // Depth formats - Z32F = 72, - Z16 = 73, + Z32F = 73, + Z16 = 74, MaxDepthFormat, // DepthStencil formats - Z24S8 = 74, - S8Z24 = 75, - Z32FS8 = 76, + Z24S8 = 75, + S8Z24 = 76, + Z32FS8 = 77, MaxDepthStencilFormat, @@ -171,6 +172,7 @@ constexpr std::array<u32, MaxPixelFormat> compression_factor_shift_table = {{ 0, // RG32UI 0, // RGBX16F 0, // R32UI + 0, // R32I 2, // ASTC_2D_8X8 2, // ASTC_2D_8X5 2, // ASTC_2D_5X4 @@ -267,6 +269,7 @@ constexpr std::array<u32, MaxPixelFormat> block_width_table = {{ 1, // RG32UI 1, // RGBX16F 1, // R32UI + 1, // R32I 8, // ASTC_2D_8X8 8, // ASTC_2D_8X5 5, // ASTC_2D_5X4 @@ -355,6 +358,7 @@ constexpr std::array<u32, MaxPixelFormat> block_height_table = {{ 1, // RG32UI 1, // RGBX16F 1, // R32UI + 1, // R32I 8, // ASTC_2D_8X8 5, // ASTC_2D_8X5 4, // ASTC_2D_5X4 @@ -443,6 +447,7 @@ constexpr std::array<u32, MaxPixelFormat> bpp_table = {{ 64, // RG32UI 64, // RGBX16F 32, // R32UI + 32, // R32I 128, // ASTC_2D_8X8 128, // ASTC_2D_8X5 128, // ASTC_2D_5X4 @@ -546,6 +551,7 @@ constexpr std::array<SurfaceCompression, MaxPixelFormat> compression_type_table SurfaceCompression::None, // RG32UI SurfaceCompression::None, // RGBX16F SurfaceCompression::None, // R32UI + SurfaceCompression::None, // R32I SurfaceCompression::Converted, // ASTC_2D_8X8 SurfaceCompression::Converted, // ASTC_2D_8X5 SurfaceCompression::Converted, // ASTC_2D_5X4 diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp index 81fb9f633..cc3ad8417 100644 --- a/src/video_core/texture_cache/format_lookup_table.cpp +++ b/src/video_core/texture_cache/format_lookup_table.cpp @@ -41,7 +41,7 @@ struct Table { ComponentType alpha_component; bool is_srgb; }; -constexpr std::array<Table, 74> DefinitionTable = {{ +constexpr std::array<Table, 75> DefinitionTable = {{ {TextureFormat::A8R8G8B8, C, UNORM, UNORM, UNORM, UNORM, PixelFormat::ABGR8U}, {TextureFormat::A8R8G8B8, C, SNORM, SNORM, SNORM, SNORM, PixelFormat::ABGR8S}, {TextureFormat::A8R8G8B8, C, UINT, UINT, UINT, UINT, PixelFormat::ABGR8UI}, @@ -89,6 +89,7 @@ constexpr std::array<Table, 74> DefinitionTable = {{ {TextureFormat::R32, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::R32F}, {TextureFormat::R32, C, UINT, UINT, UINT, UINT, PixelFormat::R32UI}, + {TextureFormat::R32, C, SINT, SINT, SINT, SINT, PixelFormat::R32I}, {TextureFormat::E5B9G9R9_SHAREDEXP, C, FLOAT, FLOAT, FLOAT, FLOAT, PixelFormat::E5B9G9R9F}, diff --git a/src/video_core/texture_cache/surface_base.cpp b/src/video_core/texture_cache/surface_base.cpp index 84469b7ba..002df414f 100644 --- a/src/video_core/texture_cache/surface_base.cpp +++ b/src/video_core/texture_cache/surface_base.cpp @@ -277,6 +277,10 @@ void SurfaceBaseImpl::FlushBuffer(Tegra::MemoryManager& memory_manager, SwizzleFunc(MortonSwizzleMode::LinearToMorton, host_ptr, params, staging_buffer.data() + host_offset, level); } + } else if (params.IsBuffer()) { + // Buffers don't have pitch or any fancy layout property. We can just memcpy them to guest + // memory. + std::memcpy(host_ptr, staging_buffer.data(), guest_memory_size); } else { ASSERT(params.target == SurfaceTarget::Texture2D); ASSERT(params.num_levels == 1); diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp index 38b3a4ba8..f00839313 100644 --- a/src/video_core/texture_cache/surface_params.cpp +++ b/src/video_core/texture_cache/surface_params.cpp @@ -84,19 +84,16 @@ SurfaceParams SurfaceParams::CreateForTexture(const FormatLookupTable& lookup_ta if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { switch (params.pixel_format) { case PixelFormat::R16U: - case PixelFormat::R16F: { + case PixelFormat::R16F: params.pixel_format = PixelFormat::Z16; break; - } - case PixelFormat::R32F: { + case PixelFormat::R32F: params.pixel_format = PixelFormat::Z32F; break; - } - default: { + default: UNIMPLEMENTED_MSG("Unimplemented shadow convert format: {}", static_cast<u32>(params.pixel_format)); } - } params.type = GetFormatType(params.pixel_format); } params.type = GetFormatType(params.pixel_format); @@ -168,27 +165,29 @@ SurfaceParams SurfaceParams::CreateForImage(const FormatLookupTable& lookup_tabl return params; } -SurfaceParams SurfaceParams::CreateForDepthBuffer( - Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) { +SurfaceParams SurfaceParams::CreateForDepthBuffer(Core::System& system) { + const auto& regs = system.GPU().Maxwell3D().regs; + regs.zeta_width, regs.zeta_height, regs.zeta.format, regs.zeta.memory_layout.type; SurfaceParams params; - params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; + params.is_tiled = regs.zeta.memory_layout.type == + Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear; params.srgb_conversion = false; - params.block_width = std::min(block_width, 5U); - params.block_height = std::min(block_height, 5U); - params.block_depth = std::min(block_depth, 5U); + params.block_width = std::min(regs.zeta.memory_layout.block_width.Value(), 5U); + params.block_height = std::min(regs.zeta.memory_layout.block_height.Value(), 5U); + params.block_depth = std::min(regs.zeta.memory_layout.block_depth.Value(), 5U); params.tile_width_spacing = 1; - params.pixel_format = PixelFormatFromDepthFormat(format); + params.pixel_format = PixelFormatFromDepthFormat(regs.zeta.format); params.type = GetFormatType(params.pixel_format); - params.width = zeta_width; - params.height = zeta_height; - params.target = SurfaceTarget::Texture2D; - params.depth = 1; + params.width = regs.zeta_width; + params.height = regs.zeta_height; params.pitch = 0; params.num_levels = 1; params.emulated_levels = 1; - params.is_layered = false; + + const bool is_layered = regs.zeta_layers > 1 && params.block_depth == 0; + params.is_layered = is_layered; + params.target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; + params.depth = is_layered ? regs.zeta_layers.Value() : 1U; return params; } @@ -214,11 +213,13 @@ SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::siz params.width = params.pitch / bpp; } params.height = config.height; - params.depth = 1; - params.target = SurfaceTarget::Texture2D; params.num_levels = 1; params.emulated_levels = 1; - params.is_layered = false; + + const bool is_layered = config.layers > 1 && params.block_depth == 0; + params.is_layered = is_layered; + params.depth = is_layered ? config.layers.Value() : 1; + params.target = is_layered ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D; return params; } diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h index 9256fd6d9..995cc3818 100644 --- a/src/video_core/texture_cache/surface_params.h +++ b/src/video_core/texture_cache/surface_params.h @@ -35,10 +35,7 @@ public: const VideoCommon::Shader::Image& entry); /// Creates SurfaceCachedParams for a depth buffer configuration. - static SurfaceParams CreateForDepthBuffer( - Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, - u32 block_width, u32 block_height, u32 block_depth, - Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type); + static SurfaceParams CreateForDepthBuffer(Core::System& system); /// Creates SurfaceCachedParams from a framebuffer configuration. static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index); diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h index 0d105d386..c70e4aec2 100644 --- a/src/video_core/texture_cache/texture_cache.h +++ b/src/video_core/texture_cache/texture_cache.h @@ -160,10 +160,7 @@ public: SetEmptyDepthBuffer(); return {}; } - const auto depth_params{SurfaceParams::CreateForDepthBuffer( - system, regs.zeta_width, regs.zeta_height, regs.zeta.format, - regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height, - regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)}; + const auto depth_params{SurfaceParams::CreateForDepthBuffer(system)}; auto surface_view = GetSurface(gpu_addr, cache_addr, depth_params, preserve_contents, true); if (depth_buffer.target) depth_buffer.target->MarkAsRenderTarget(false, NO_RT); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 55a37fffa..c3dbb1a88 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -9,6 +9,9 @@ #include <QKeyEvent> #include <QMessageBox> #include <QOffscreenSurface> +#include <QOpenGLContext> +#include <QOpenGLFunctions> +#include <QOpenGLFunctions_4_3_Core> #include <QOpenGLWindow> #include <QPainter> #include <QScreen> @@ -23,9 +26,10 @@ #include "common/assert.h" #include "common/microprofile.h" #include "common/scm_rev.h" +#include "common/scope_exit.h" #include "core/core.h" #include "core/frontend/framebuffer_layout.h" -#include "core/frontend/scope_acquire_window_context.h" +#include "core/frontend/scope_acquire_context.h" #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" @@ -35,15 +39,27 @@ #include "yuzu/bootmanager.h" #include "yuzu/main.h" -EmuThread::EmuThread(GRenderWindow* render_window) : render_window(render_window) {} +EmuThread::EmuThread(GRenderWindow& window) + : shared_context{window.CreateSharedContext()}, + context{(Settings::values.use_asynchronous_gpu_emulation && shared_context) ? *shared_context + : window} {} EmuThread::~EmuThread() = default; -void EmuThread::run() { - render_window->MakeCurrent(); +static GMainWindow* GetMainWindow() { + for (QWidget* w : qApp->topLevelWidgets()) { + if (GMainWindow* main = qobject_cast<GMainWindow*>(w)) { + return main; + } + } + return nullptr; +} +void EmuThread::run() { MicroProfileOnThreadCreate("EmuThread"); + Core::Frontend::ScopeAcquireContext acquire_context{context}; + emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0); Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources( @@ -53,11 +69,6 @@ void EmuThread::run() { emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0); - if (Settings::values.use_asynchronous_gpu_emulation) { - // Release OpenGL context for the GPU thread - render_window->DoneCurrent(); - } - // Holds whether the cpu was running during the last iteration, // so that the DebugModeLeft signal can be emitted before the // next execution step @@ -98,190 +109,202 @@ void EmuThread::run() { #if MICROPROFILE_ENABLED MicroProfileOnThreadExit(); #endif - - render_window->moveContext(); } class GGLContext : public Core::Frontend::GraphicsContext { public: - explicit GGLContext(QOpenGLContext* shared_context) : shared_context{shared_context} { - context.setFormat(shared_context->format()); - context.setShareContext(shared_context); - context.create(); + explicit GGLContext(QOpenGLContext* shared_context) + : context(new QOpenGLContext(shared_context->parent())), + surface(new QOffscreenSurface(nullptr)) { + + // disable vsync for any shared contexts + auto format = shared_context->format(); + format.setSwapInterval(0); + + context->setShareContext(shared_context); + context->setFormat(format); + context->create(); + surface->setParent(shared_context->parent()); + surface->setFormat(format); + surface->create(); } void MakeCurrent() override { - context.makeCurrent(shared_context->surface()); + context->makeCurrent(surface); } void DoneCurrent() override { - context.doneCurrent(); + context->doneCurrent(); } - void SwapBuffers() override {} - private: - QOpenGLContext* shared_context; - QOpenGLContext context; + QOpenGLContext* context; + QOffscreenSurface* surface; }; -class GWidgetInternal : public QWindow { +class ChildRenderWindow : public QWindow { public: - GWidgetInternal(GRenderWindow* parent) : parent(parent) {} - virtual ~GWidgetInternal() = default; + ChildRenderWindow(QWindow* parent, QWidget* event_handler) + : QWindow{parent}, event_handler{event_handler} {} - void resizeEvent(QResizeEvent* ev) override { - parent->OnClientAreaResized(ev->size().width(), ev->size().height()); - parent->OnFramebufferSizeChanged(); - } + virtual ~ChildRenderWindow() = default; - void keyPressEvent(QKeyEvent* event) override { - InputCommon::GetKeyboard()->PressKey(event->key()); - } + virtual void Present() = 0; - void keyReleaseEvent(QKeyEvent* event) override { - InputCommon::GetKeyboard()->ReleaseKey(event->key()); +protected: + bool event(QEvent* event) override { + switch (event->type()) { + case QEvent::UpdateRequest: + Present(); + return true; + case QEvent::MouseButtonPress: + case QEvent::MouseButtonRelease: + case QEvent::MouseButtonDblClick: + case QEvent::MouseMove: + case QEvent::KeyPress: + case QEvent::KeyRelease: + case QEvent::FocusIn: + case QEvent::FocusOut: + case QEvent::FocusAboutToChange: + case QEvent::Enter: + case QEvent::Leave: + case QEvent::Wheel: + case QEvent::TabletMove: + case QEvent::TabletPress: + case QEvent::TabletRelease: + case QEvent::TabletEnterProximity: + case QEvent::TabletLeaveProximity: + case QEvent::TouchBegin: + case QEvent::TouchUpdate: + case QEvent::TouchEnd: + case QEvent::InputMethodQuery: + case QEvent::TouchCancel: + return QCoreApplication::sendEvent(event_handler, event); + case QEvent::Drop: + GetMainWindow()->DropAction(static_cast<QDropEvent*>(event)); + return true; + case QEvent::DragResponse: + case QEvent::DragEnter: + case QEvent::DragLeave: + case QEvent::DragMove: + GetMainWindow()->AcceptDropEvent(static_cast<QDropEvent*>(event)); + return true; + default: + return QWindow::event(event); + } } - void mousePressEvent(QMouseEvent* event) override { - if (event->source() == Qt::MouseEventSynthesizedBySystem) - return; // touch input is handled in TouchBeginEvent - - const auto pos{event->pos()}; - if (event->button() == Qt::LeftButton) { - const auto [x, y] = parent->ScaleTouch(pos); - parent->TouchPressed(x, y); - } else if (event->button() == Qt::RightButton) { - InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); - } + void exposeEvent(QExposeEvent* event) override { + QWindow::requestUpdate(); + QWindow::exposeEvent(event); } - void mouseMoveEvent(QMouseEvent* event) override { - if (event->source() == Qt::MouseEventSynthesizedBySystem) - return; // touch input is handled in TouchUpdateEvent +private: + QWidget* event_handler{}; +}; - const auto pos{event->pos()}; - const auto [x, y] = parent->ScaleTouch(pos); - parent->TouchMoved(x, y); - InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); - } +class OpenGLWindow final : public ChildRenderWindow { +public: + OpenGLWindow(QWindow* parent, QWidget* event_handler, QOpenGLContext* shared_context) + : ChildRenderWindow{parent, event_handler}, + context(new QOpenGLContext(shared_context->parent())) { - void mouseReleaseEvent(QMouseEvent* event) override { - if (event->source() == Qt::MouseEventSynthesizedBySystem) - return; // touch input is handled in TouchEndEvent + // disable vsync for any shared contexts + auto format = shared_context->format(); + format.setSwapInterval(Settings::values.use_vsync ? 1 : 0); + this->setFormat(format); - if (event->button() == Qt::LeftButton) - parent->TouchReleased(); - else if (event->button() == Qt::RightButton) - InputCommon::GetMotionEmu()->EndTilt(); - } + context->setShareContext(shared_context); + context->setScreen(this->screen()); + context->setFormat(format); + context->create(); - void DisablePainting() { - do_painting = false; - } + setSurfaceType(QWindow::OpenGLSurface); - void EnablePainting() { - do_painting = true; + // TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground, + // WA_DontShowOnScreen, WA_DeleteOnClose } - std::pair<unsigned, unsigned> GetSize() const { - return std::make_pair(width(), height()); + ~OpenGLWindow() override { + context->doneCurrent(); } -protected: - bool IsPaintingEnabled() const { - return do_painting; + void Present() override { + if (!isExposed()) { + return; + } + + context->makeCurrent(this); + Core::System::GetInstance().Renderer().TryPresent(100); + context->swapBuffers(this); + auto f = context->versionFunctions<QOpenGLFunctions_4_3_Core>(); + f->glFinish(); + QWindow::requestUpdate(); } private: - GRenderWindow* parent; - bool do_painting = false; -}; - -// This class overrides paintEvent and resizeEvent to prevent the GUI thread from stealing GL -// context. -// The corresponding functionality is handled in EmuThread instead -class GGLWidgetInternal final : public GWidgetInternal, public QOpenGLWindow { -public: - GGLWidgetInternal(GRenderWindow* parent, QOpenGLContext* shared_context) - : GWidgetInternal(parent), QOpenGLWindow(shared_context) {} - ~GGLWidgetInternal() override = default; - - void paintEvent(QPaintEvent* ev) override { - if (IsPaintingEnabled()) { - QPainter painter(this); - } - } + QOpenGLContext* context{}; }; #ifdef HAS_VULKAN -class GVKWidgetInternal final : public GWidgetInternal { +class VulkanWindow final : public ChildRenderWindow { public: - GVKWidgetInternal(GRenderWindow* parent, QVulkanInstance* instance) : GWidgetInternal(parent) { + VulkanWindow(QWindow* parent, QWidget* event_handler, QVulkanInstance* instance) + : ChildRenderWindow{parent, event_handler} { setSurfaceType(QSurface::SurfaceType::VulkanSurface); setVulkanInstance(instance); } - ~GVKWidgetInternal() override = default; + + ~VulkanWindow() override = default; + + void Present() override { + // TODO(bunnei): ImplementMe + } + +private: + QWidget* event_handler{}; }; #endif -GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread) - : QWidget(parent), emu_thread(emu_thread) { +GRenderWindow::GRenderWindow(QWidget* parent_, EmuThread* emu_thread) + : QWidget(parent_), emu_thread(emu_thread) { setWindowTitle(QStringLiteral("yuzu %1 | %2-%3") .arg(QString::fromUtf8(Common::g_build_name), QString::fromUtf8(Common::g_scm_branch), QString::fromUtf8(Common::g_scm_desc))); setAttribute(Qt::WA_AcceptTouchEvents); - + auto layout = new QHBoxLayout(this); + layout->setMargin(0); + setLayout(layout); InputCommon::Init(); + + GMainWindow* parent = GetMainWindow(); connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete); } GRenderWindow::~GRenderWindow() { InputCommon::Shutdown(); - - // Avoid an unordered destruction that generates a segfault - delete child; } -void GRenderWindow::moveContext() { - if (!context) { - return; +void GRenderWindow::MakeCurrent() { + if (core_context) { + core_context->MakeCurrent(); } - DoneCurrent(); - - // If the thread started running, move the GL Context to the new thread. Otherwise, move it - // back. - auto thread = (QThread::currentThread() == qApp->thread() && emu_thread != nullptr) - ? emu_thread - : qApp->thread(); - context->moveToThread(thread); } -void GRenderWindow::SwapBuffers() { - if (context) { - context->swapBuffers(child); +void GRenderWindow::DoneCurrent() { + if (core_context) { + core_context->DoneCurrent(); } +} + +void GRenderWindow::PollEvents() { if (!first_frame) { first_frame = true; emit FirstFrameDisplayed(); } } -void GRenderWindow::MakeCurrent() { - if (context) { - context->makeCurrent(child); - } -} - -void GRenderWindow::DoneCurrent() { - if (context) { - context->doneCurrent(); - } -} - -void GRenderWindow::PollEvents() {} - bool GRenderWindow::IsShown() const { return !isMinimized(); } @@ -291,7 +314,7 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i #ifdef HAS_VULKAN const auto instance_proc_addr = vk_instance->getInstanceProcAddr("vkGetInstanceProcAddr"); const VkInstance instance_copy = vk_instance->vkInstance(); - const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child); + const VkSurfaceKHR surface_copy = vk_instance->surfaceForWindow(child_window); std::memcpy(get_instance_proc_addr, &instance_proc_addr, sizeof(instance_proc_addr)); std::memcpy(instance, &instance_copy, sizeof(instance_copy)); @@ -309,21 +332,10 @@ void GRenderWindow::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* i void GRenderWindow::OnFramebufferSizeChanged() { // Screen changes potentially incur a change in screen DPI, hence we should update the // framebuffer size - const qreal pixelRatio{GetWindowPixelRatio()}; - const auto size{child->GetSize()}; - UpdateCurrentFramebufferLayout(size.first * pixelRatio, size.second * pixelRatio); -} - -void GRenderWindow::ForwardKeyPressEvent(QKeyEvent* event) { - if (child) { - child->keyPressEvent(event); - } -} - -void GRenderWindow::ForwardKeyReleaseEvent(QKeyEvent* event) { - if (child) { - child->keyReleaseEvent(event); - } + const qreal pixel_ratio = windowPixelRatio(); + const u32 width = this->width() * pixel_ratio; + const u32 height = this->height() * pixel_ratio; + UpdateCurrentFramebufferLayout(width, height); } void GRenderWindow::BackupGeometry() { @@ -351,13 +363,12 @@ QByteArray GRenderWindow::saveGeometry() { return geometry; } -qreal GRenderWindow::GetWindowPixelRatio() const { - // windowHandle() might not be accessible until the window is displayed to screen. - return windowHandle() ? windowHandle()->screen()->devicePixelRatio() : 1.0f; +qreal GRenderWindow::windowPixelRatio() const { + return devicePixelRatio(); } std::pair<u32, u32> GRenderWindow::ScaleTouch(const QPointF pos) const { - const qreal pixel_ratio{GetWindowPixelRatio()}; + const qreal pixel_ratio = windowPixelRatio(); return {static_cast<u32>(std::max(std::round(pos.x() * pixel_ratio), qreal{0.0})), static_cast<u32>(std::max(std::round(pos.y() * pixel_ratio), qreal{0.0}))}; } @@ -367,6 +378,47 @@ void GRenderWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } +void GRenderWindow::keyPressEvent(QKeyEvent* event) { + InputCommon::GetKeyboard()->PressKey(event->key()); +} + +void GRenderWindow::keyReleaseEvent(QKeyEvent* event) { + InputCommon::GetKeyboard()->ReleaseKey(event->key()); +} + +void GRenderWindow::mousePressEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchBeginEvent + + auto pos = event->pos(); + if (event->button() == Qt::LeftButton) { + const auto [x, y] = ScaleTouch(pos); + this->TouchPressed(x, y); + } else if (event->button() == Qt::RightButton) { + InputCommon::GetMotionEmu()->BeginTilt(pos.x(), pos.y()); + } +} + +void GRenderWindow::mouseMoveEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchUpdateEvent + + auto pos = event->pos(); + const auto [x, y] = ScaleTouch(pos); + this->TouchMoved(x, y); + InputCommon::GetMotionEmu()->Tilt(pos.x(), pos.y()); +} + +void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) { + if (event->source() == Qt::MouseEventSynthesizedBySystem) + return; // touch input is handled in TouchEndEvent + + if (event->button() == Qt::LeftButton) + this->TouchReleased(); + else if (event->button() == Qt::RightButton) + InputCommon::GetMotionEmu()->EndTilt(); +} + void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) { // TouchBegin always has exactly one touch point, so take the .first() const auto [x, y] = ScaleTouch(event->touchPoints().first().pos()); @@ -415,26 +467,20 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) { InputCommon::GetKeyboard()->ReleaseAllKeys(); } -void GRenderWindow::OnClientAreaResized(u32 width, u32 height) { - NotifyClientAreaSizeChanged(std::make_pair(width, height)); +void GRenderWindow::resizeEvent(QResizeEvent* event) { + QWidget::resizeEvent(event); + OnFramebufferSizeChanged(); } std::unique_ptr<Core::Frontend::GraphicsContext> GRenderWindow::CreateSharedContext() const { - return std::make_unique<GGLContext>(context.get()); + if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { + return std::make_unique<GGLContext>(QOpenGLContext::globalShareContext()); + } + return {}; } bool GRenderWindow::InitRenderTarget() { - shared_context.reset(); - context.reset(); - if (child) { - delete child; - } - if (container) { - delete container; - } - if (layout()) { - delete layout(); - } + ReleaseRenderTarget(); first_frame = false; @@ -451,13 +497,6 @@ bool GRenderWindow::InitRenderTarget() { break; } - container = QWidget::createWindowContainer(child, this); - QBoxLayout* layout = new QHBoxLayout(this); - - layout->addWidget(container); - layout->setMargin(0); - setLayout(layout); - // Reset minimum required size to avoid resizing issues on the main window after restarting. setMinimumSize(1, 1); @@ -467,14 +506,9 @@ bool GRenderWindow::InitRenderTarget() { hide(); resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); - child->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); - container->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); - OnFramebufferSizeChanged(); - NotifyClientAreaSizeChanged(child->GetSize()); - BackupGeometry(); if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { @@ -486,6 +520,14 @@ bool GRenderWindow::InitRenderTarget() { return true; } +void GRenderWindow::ReleaseRenderTarget() { + if (child_widget) { + layout()->removeWidget(child_widget); + delete child_widget; + child_widget = nullptr; + } +} + void GRenderWindow::CaptureScreenshot(u32 res_scale, const QString& screenshot_path) { auto& renderer = Core::System::GetInstance().Renderer(); @@ -521,16 +563,19 @@ bool GRenderWindow::InitializeOpenGL() { fmt.setOption(QSurfaceFormat::FormatOption::DeprecatedFunctions); // TODO: expose a setting for buffer value (ie default/single/double/triple) fmt.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior); - shared_context = std::make_unique<QOpenGLContext>(); - shared_context->setFormat(fmt); - shared_context->create(); - context = std::make_unique<QOpenGLContext>(); - context->setShareContext(shared_context.get()); - context->setFormat(fmt); - context->create(); - fmt.setSwapInterval(false); - - child = new GGLWidgetInternal(this, shared_context.get()); + fmt.setSwapInterval(0); + QSurfaceFormat::setDefaultFormat(fmt); + + GMainWindow* parent = GetMainWindow(); + QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; + child_window = new OpenGLWindow(parent_win_handle, this, QOpenGLContext::globalShareContext()); + child_window->create(); + child_widget = createWindowContainer(child_window, this); + child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); + layout()->addWidget(child_widget); + + core_context = CreateSharedContext(); + return true; } @@ -559,7 +604,14 @@ bool GRenderWindow::InitializeVulkan() { return false; } - child = new GVKWidgetInternal(this, vk_instance.get()); + GMainWindow* parent = GetMainWindow(); + QWindow* parent_win_handle = parent ? parent->windowHandle() : nullptr; + child_window = new VulkanWindow(parent_win_handle, this, vk_instance.get()); + child_window->create(); + child_widget = createWindowContainer(child_window, this); + child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height); + layout()->addWidget(child_widget); + return true; #else QMessageBox::critical(this, tr("Vulkan not available!"), @@ -569,7 +621,7 @@ bool GRenderWindow::InitializeVulkan() { } bool GRenderWindow::LoadOpenGL() { - Core::Frontend::ScopeAcquireWindowContext acquire_context{*this}; + Core::Frontend::ScopeAcquireContext acquire_context{*this}; if (!gladLoadGL()) { QMessageBox::critical(this, tr("Error while initializing OpenGL 4.3!"), tr("Your GPU may not support OpenGL 4.3, or you do not have the " @@ -621,12 +673,10 @@ QStringList GRenderWindow::GetUnsupportedGLExtensions() const { void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) { this->emu_thread = emu_thread; - child->DisablePainting(); } void GRenderWindow::OnEmulationStopping() { emu_thread = nullptr; - child->EnablePainting(); } void GRenderWindow::showEvent(QShowEvent* event) { diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h index 71a2fa321..79b030304 100644 --- a/src/yuzu/bootmanager.h +++ b/src/yuzu/bootmanager.h @@ -11,11 +11,13 @@ #include <QImage> #include <QThread> #include <QWidget> +#include <QWindow> #include "common/thread.h" #include "core/core.h" #include "core/frontend/emu_window.h" +class GRenderWindow; class QKeyEvent; class QScreen; class QTouchEvent; @@ -26,14 +28,6 @@ class QOpenGLContext; class QVulkanInstance; #endif -class GWidgetInternal; -class GGLWidgetInternal; -class GVKWidgetInternal; -class GMainWindow; -class GRenderWindow; -class QSurface; -class QOpenGLContext; - namespace VideoCore { enum class LoadCallbackStage; } @@ -42,7 +36,7 @@ class EmuThread final : public QThread { Q_OBJECT public: - explicit EmuThread(GRenderWindow* render_window); + explicit EmuThread(GRenderWindow& window); ~EmuThread() override; /** @@ -96,7 +90,11 @@ private: std::mutex running_mutex; std::condition_variable running_cv; - GRenderWindow* render_window; + /// Only used in asynchronous GPU mode + std::unique_ptr<Core::Frontend::GraphicsContext> shared_context; + + /// This is shared_context in asynchronous GPU mode, core_context in synchronous GPU mode + Core::Frontend::GraphicsContext& context; signals: /** @@ -126,11 +124,10 @@ class GRenderWindow : public QWidget, public Core::Frontend::EmuWindow { Q_OBJECT public: - GRenderWindow(GMainWindow* parent, EmuThread* emu_thread); + GRenderWindow(QWidget* parent, EmuThread* emu_thread); ~GRenderWindow() override; - // EmuWindow implementation - void SwapBuffers() override; + // EmuWindow implementation. void MakeCurrent() override; void DoneCurrent() override; void PollEvents() override; @@ -139,30 +136,36 @@ public: void* surface) const override; std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; - void ForwardKeyPressEvent(QKeyEvent* event); - void ForwardKeyReleaseEvent(QKeyEvent* event); - void BackupGeometry(); void RestoreGeometry(); void restoreGeometry(const QByteArray& geometry); // overridden QByteArray saveGeometry(); // overridden - qreal GetWindowPixelRatio() const; - std::pair<u32, u32> ScaleTouch(QPointF pos) const; + qreal windowPixelRatio() const; void closeEvent(QCloseEvent* event) override; + + void resizeEvent(QResizeEvent* event) override; + + void keyPressEvent(QKeyEvent* event) override; + void keyReleaseEvent(QKeyEvent* event) override; + + void mousePressEvent(QMouseEvent* event) override; + void mouseMoveEvent(QMouseEvent* event) override; + void mouseReleaseEvent(QMouseEvent* event) override; + bool event(QEvent* event) override; - void focusOutEvent(QFocusEvent* event) override; - void OnClientAreaResized(u32 width, u32 height); + void focusOutEvent(QFocusEvent* event) override; bool InitRenderTarget(); + /// Destroy the previous run's child_widget which should also destroy the child_window + void ReleaseRenderTarget(); + void CaptureScreenshot(u32 res_scale, const QString& screenshot_path); public slots: - void moveContext(); // overridden - void OnEmulationStarting(EmuThread* emu_thread); void OnEmulationStopping(); void OnFramebufferSizeChanged(); @@ -173,6 +176,7 @@ signals: void FirstFrameDisplayed(); private: + std::pair<u32, u32> ScaleTouch(QPointF pos) const; void TouchBeginEvent(const QTouchEvent* event); void TouchUpdateEvent(const QTouchEvent* event); void TouchEndEvent(); @@ -184,15 +188,9 @@ private: bool LoadOpenGL(); QStringList GetUnsupportedGLExtensions() const; - QWidget* container = nullptr; - GWidgetInternal* child = nullptr; - EmuThread* emu_thread; - // Context that backs the GGLWidgetInternal (and will be used by core to render) - std::unique_ptr<QOpenGLContext> context; - // Context that will be shared between all newly created contexts. This should never be made - // current - std::unique_ptr<QOpenGLContext> shared_context; + + std::unique_ptr<GraphicsContext> core_context; #ifdef HAS_VULKAN std::unique_ptr<QVulkanInstance> vk_instance; @@ -202,6 +200,15 @@ private: QImage screenshot_image; QByteArray geometry; + + /// Native window handle that backs this presentation widget + QWindow* child_window = nullptr; + + /// In order to embed the window into GRenderWindow, you need to use createWindowContainer to + /// put the child_window into a widget then add it to the layout. This child_widget can be + /// parented to GRenderWindow and use Qt's lifetime system + QWidget* child_widget = nullptr; + bool first_frame = false; protected: diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 6209fff75..d0f574147 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -640,6 +640,7 @@ void Config::ReadRendererValues() { ReadSetting(QStringLiteral("use_accurate_gpu_emulation"), false).toBool(); Settings::values.use_asynchronous_gpu_emulation = ReadSetting(QStringLiteral("use_asynchronous_gpu_emulation"), false).toBool(); + Settings::values.use_vsync = ReadSetting(QStringLiteral("use_vsync"), true).toBool(); Settings::values.force_30fps_mode = ReadSetting(QStringLiteral("force_30fps_mode"), false).toBool(); @@ -1074,6 +1075,7 @@ void Config::SaveRendererValues() { Settings::values.use_accurate_gpu_emulation, false); WriteSetting(QStringLiteral("use_asynchronous_gpu_emulation"), Settings::values.use_asynchronous_gpu_emulation, false); + WriteSetting(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); WriteSetting(QStringLiteral("force_30fps_mode"), Settings::values.force_30fps_mode, false); // Cast to double because Qt's written float values are not human-readable diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index ea899c080..fe64c7d81 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -103,6 +103,8 @@ void ConfigureGraphics::SetConfiguration() { ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); ui->use_asynchronous_gpu_emulation->setChecked(Settings::values.use_asynchronous_gpu_emulation); + ui->use_vsync->setEnabled(runtime_lock); + ui->use_vsync->setChecked(Settings::values.use_vsync); ui->force_30fps_mode->setEnabled(runtime_lock); ui->force_30fps_mode->setChecked(Settings::values.force_30fps_mode); UpdateBackgroundColorButton(QColor::fromRgbF(Settings::values.bg_red, Settings::values.bg_green, @@ -120,6 +122,7 @@ void ConfigureGraphics::ApplyConfiguration() { Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); Settings::values.use_asynchronous_gpu_emulation = ui->use_asynchronous_gpu_emulation->isChecked(); + Settings::values.use_vsync = ui->use_vsync->isChecked(); Settings::values.force_30fps_mode = ui->force_30fps_mode->isChecked(); Settings::values.bg_red = static_cast<float>(bg_color.redF()); Settings::values.bg_green = static_cast<float>(bg_color.greenF()); diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index db60426ab..9acc7dd93 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui @@ -85,6 +85,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="use_vsync"> + <property name="toolTip"> + <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string> + </property> + <property name="text"> + <string>Use VSync (OpenGL only)</string> + </property> + </widget> + </item> + <item> <widget class="QCheckBox" name="use_accurate_gpu_emulation"> <property name="text"> <string>Use accurate GPU emulation (slow)</string> diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 54ca2dc1d..47615adfe 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -20,7 +20,6 @@ #include "core/file_sys/vfs.h" #include "core/file_sys/vfs_real.h" #include "core/frontend/applets/general_frontend.h" -#include "core/frontend/scope_acquire_window_context.h" #include "core/hle/service/acc/profile_manager.h" #include "core/hle/service/am/applet_ae.h" #include "core/hle/service/am/applet_oe.h" @@ -985,11 +984,8 @@ void GMainWindow::BootGame(const QString& filename) { return; // Create and start the emulation thread - emu_thread = std::make_unique<EmuThread>(render_window); + emu_thread = std::make_unique<EmuThread>(*render_window); emit EmulationStarting(emu_thread.get()); - if (Settings::values.renderer_backend == Settings::RendererBackend::OpenGL) { - render_window->moveContext(); - } emu_thread->start(); connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame); @@ -1087,6 +1083,9 @@ void GMainWindow::ShutdownGame() { emulation_running = false; game_path.clear(); + + // When closing the game, destroy the GLWindow to clear the context after the game is closed + render_window->ReleaseRenderTarget(); } void GMainWindow::StoreRecentFile(const QString& filename) { @@ -2215,48 +2214,47 @@ void GMainWindow::closeEvent(QCloseEvent* event) { QWidget::closeEvent(event); } -void GMainWindow::keyPressEvent(QKeyEvent* event) { - if (render_window) { - render_window->ForwardKeyPressEvent(event); - } +static bool IsSingleFileDropEvent(const QMimeData* mime) { + return mime->hasUrls() && mime->urls().length() == 1; } -void GMainWindow::keyReleaseEvent(QKeyEvent* event) { - if (render_window) { - render_window->ForwardKeyReleaseEvent(event); +void GMainWindow::AcceptDropEvent(QDropEvent* event) { + if (IsSingleFileDropEvent(event->mimeData())) { + event->setDropAction(Qt::DropAction::LinkAction); + event->accept(); } } -static bool IsSingleFileDropEvent(QDropEvent* event) { - const QMimeData* mimeData = event->mimeData(); - return mimeData->hasUrls() && mimeData->urls().length() == 1; -} - -void GMainWindow::dropEvent(QDropEvent* event) { - if (!IsSingleFileDropEvent(event)) { - return; +bool GMainWindow::DropAction(QDropEvent* event) { + if (!IsSingleFileDropEvent(event->mimeData())) { + return false; } const QMimeData* mime_data = event->mimeData(); - const QString filename = mime_data->urls().at(0).toLocalFile(); + const QString& filename = mime_data->urls().at(0).toLocalFile(); if (emulation_running && QFileInfo(filename).suffix() == QStringLiteral("bin")) { + // Amiibo LoadAmiibo(filename); } else { + // Game if (ConfirmChangeGame()) { BootGame(filename); } } + return true; +} + +void GMainWindow::dropEvent(QDropEvent* event) { + DropAction(event); } void GMainWindow::dragEnterEvent(QDragEnterEvent* event) { - if (IsSingleFileDropEvent(event)) { - event->acceptProposedAction(); - } + AcceptDropEvent(event); } void GMainWindow::dragMoveEvent(QDragMoveEvent* event) { - event->acceptProposedAction(); + AcceptDropEvent(event); } bool GMainWindow::ConfirmChangeGame() { @@ -2377,6 +2375,7 @@ int main(int argc, char* argv[]) { // Enables the core to make the qt created contexts current on std::threads QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); + QCoreApplication::setAttribute(Qt::AA_ShareOpenGLContexts); QApplication app(argc, argv); // Qt changes the locale and causes issues in float conversion using std::to_string() when diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 8eba2172c..a67125567 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h @@ -78,6 +78,9 @@ public: std::unique_ptr<DiscordRPC::DiscordInterface> discord_rpc; + bool DropAction(QDropEvent* event); + void AcceptDropEvent(QDropEvent* event); + signals: /** @@ -264,8 +267,4 @@ protected: void dropEvent(QDropEvent* event) override; void dragEnterEvent(QDragEnterEvent* event) override; void dragMoveEvent(QDragMoveEvent* event) override; - - // Overrides used to forward signals to the render window when the focus moves out. - void keyPressEvent(QKeyEvent* event) override; - void keyReleaseEvent(QKeyEvent* event) override; }; diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 96f1ce3af..b77c12baf 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -390,6 +390,8 @@ void Config::ReadValues() { sdl2_config->GetBoolean("Renderer", "use_accurate_gpu_emulation", false); Settings::values.use_asynchronous_gpu_emulation = sdl2_config->GetBoolean("Renderer", "use_asynchronous_gpu_emulation", false); + Settings::values.use_vsync = + static_cast<u16>(sdl2_config->GetInteger("Renderer", "use_vsync", 1)); Settings::values.bg_red = static_cast<float>(sdl2_config->GetReal("Renderer", "bg_red", 0.0)); Settings::values.bg_green = diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 8a2b658cd..df7473858 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -150,6 +150,11 @@ use_accurate_gpu_emulation = # 0 : Off (slow), 1 (default): On (fast) use_asynchronous_gpu_emulation = +# Forces VSync on the display thread. Usually doesn't impact performance, but on some drivers it can +# so only turn this off if you notice a speed difference. +# 0: Off, 1 (default): On +use_vsync = + # The clear color for the renderer. What shows up on the sides of the bottom screen. # Must be in range of 0.0-1.0. Defaults to 1.0 for all. bg_red = diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp index e96139885..19584360c 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp @@ -13,7 +13,7 @@ #include "input_common/sdl/sdl.h" #include "yuzu_cmd/emu_window/emu_window_sdl2.h" -EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) { +EmuWindow_SDL2::EmuWindow_SDL2(Core::System& system, bool fullscreen) : system{system} { if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting..."); exit(1); diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.h b/src/yuzu_cmd/emu_window/emu_window_sdl2.h index b38f56661..fffac4252 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.h @@ -10,9 +10,13 @@ struct SDL_Window; +namespace Core { +class System; +} + class EmuWindow_SDL2 : public Core::Frontend::EmuWindow { public: - explicit EmuWindow_SDL2(bool fullscreen); + explicit EmuWindow_SDL2(Core::System& system, bool fullscreen); ~EmuWindow_SDL2(); /// Polls window events @@ -24,6 +28,9 @@ public: /// Returns if window is shown (not minimized) bool IsShown() const override; + /// Presents the next frame + virtual void Present() = 0; + protected: /// Called by PollEvents when a key is pressed or released. void OnKeyEvent(int key, u8 state); @@ -55,6 +62,9 @@ protected: /// Called when a configuration change affects the minimal size of the window void OnMinimalClientAreaChangeRequest(std::pair<unsigned, unsigned> minimal_size) override; + /// Instance of the system, used to access renderer for the presentation thread + Core::System& system; + /// Is the window still open? bool is_open = true; @@ -62,7 +72,7 @@ protected: bool is_shown = true; /// Internal SDL2 render window - SDL_Window* render_window; + SDL_Window* render_window{}; /// Keeps track of how often to update the title bar during gameplay u32 last_time = 0; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index 7ffa0ac09..c0d373477 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp @@ -13,24 +13,25 @@ #include "common/logging/log.h" #include "common/scm_rev.h" #include "common/string_util.h" +#include "core/core.h" #include "core/settings.h" #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" +#include "video_core/renderer_base.h" #include "yuzu_cmd/emu_window/emu_window_sdl2_gl.h" class SDLGLContext : public Core::Frontend::GraphicsContext { public: explicit SDLGLContext() { // create a hidden window to make the shared context against - window = SDL_CreateWindow("", SDL_WINDOWPOS_UNDEFINED, // x position - SDL_WINDOWPOS_UNDEFINED, // y position - Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height, - SDL_WINDOW_OPENGL | SDL_WINDOW_HIDDEN); + window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, + SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); context = SDL_GL_CreateContext(window); } ~SDLGLContext() { + DoneCurrent(); SDL_GL_DeleteContext(context); SDL_DestroyWindow(window); } @@ -43,8 +44,6 @@ public: SDL_GL_MakeCurrent(window, nullptr); } - void SwapBuffers() override {} - private: SDL_Window* window; SDL_GLContext context; @@ -80,7 +79,8 @@ bool EmuWindow_SDL2_GL::SupportsRequiredGLExtensions() { return unsupported_ext.empty(); } -EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscreen) { +EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system, bool fullscreen) + : EmuWindow_SDL2{system, fullscreen} { SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY); @@ -90,6 +90,7 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 0); SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1); + SDL_GL_SetSwapInterval(0); std::string window_title = fmt::format("yuzu {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); @@ -105,13 +106,22 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree exit(1); } + dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, + SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); + if (fullscreen) { Fullscreen(); } - gl_context = SDL_GL_CreateContext(render_window); - if (gl_context == nullptr) { - LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context! {}", SDL_GetError()); + window_context = SDL_GL_CreateContext(render_window); + core_context = CreateSharedContext(); + + if (window_context == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create SDL2 GL context: {}", SDL_GetError()); + exit(1); + } + if (core_context == nullptr) { + LOG_CRITICAL(Frontend, "Failed to create shared SDL2 GL context: {}", SDL_GetError()); exit(1); } @@ -128,28 +138,22 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(bool fullscreen) : EmuWindow_SDL2(fullscree OnResize(); OnMinimalClientAreaChangeRequest(GetActiveConfig().min_client_area_size); SDL_PumpEvents(); - SDL_GL_SetSwapInterval(false); LOG_INFO(Frontend, "yuzu Version: {} | {}-{}", Common::g_build_fullname, Common::g_scm_branch, Common::g_scm_desc); Settings::LogSettings(); - - DoneCurrent(); } EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { - SDL_GL_DeleteContext(gl_context); -} - -void EmuWindow_SDL2_GL::SwapBuffers() { - SDL_GL_SwapWindow(render_window); + core_context.reset(); + SDL_GL_DeleteContext(window_context); } void EmuWindow_SDL2_GL::MakeCurrent() { - SDL_GL_MakeCurrent(render_window, gl_context); + core_context->MakeCurrent(); } void EmuWindow_SDL2_GL::DoneCurrent() { - SDL_GL_MakeCurrent(render_window, nullptr); + core_context->DoneCurrent(); } void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance, @@ -161,3 +165,13 @@ void EmuWindow_SDL2_GL::RetrieveVulkanHandlers(void* get_instance_proc_addr, voi std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { return std::make_unique<SDLGLContext>(); } + +void EmuWindow_SDL2_GL::Present() { + SDL_GL_MakeCurrent(render_window, window_context); + SDL_GL_SetSwapInterval(Settings::values.use_vsync ? 1 : 0); + while (IsOpen()) { + system.Renderer().TryPresent(100); + SDL_GL_SwapWindow(render_window); + } + SDL_GL_MakeCurrent(render_window, nullptr); +} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h index c753085a8..b80669ff0 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h @@ -10,17 +10,12 @@ class EmuWindow_SDL2_GL final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_GL(bool fullscreen); + explicit EmuWindow_SDL2_GL(Core::System& system, bool fullscreen); ~EmuWindow_SDL2_GL(); - /// Swap buffers to display the next frame - void SwapBuffers() override; - - /// Makes the graphics context current for the caller thread void MakeCurrent() override; - - /// Releases the GL context from the caller thread void DoneCurrent() override; + void Present() override; /// Ignored in OpenGL void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance, @@ -29,10 +24,17 @@ public: std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; private: + /// Fake hidden window for the core context + SDL_Window* dummy_window{}; + /// Whether the GPU and driver supports the OpenGL extension required bool SupportsRequiredGLExtensions(); using SDL_GLContext = void*; + /// The OpenGL context associated with the window - SDL_GLContext gl_context; + SDL_GLContext window_context; + + /// The OpenGL context associated with the core + std::unique_ptr<Core::Frontend::GraphicsContext> core_context; }; diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp index a203f0da9..abcc58165 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.cpp @@ -15,7 +15,8 @@ #include "core/settings.h" #include "yuzu_cmd/emu_window/emu_window_sdl2_vk.h" -EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(bool fullscreen) : EmuWindow_SDL2(fullscreen) { +EmuWindow_SDL2_VK::EmuWindow_SDL2_VK(Core::System& system, bool fullscreen) + : EmuWindow_SDL2{system, fullscreen} { if (SDL_Vulkan_LoadLibrary(nullptr) != 0) { LOG_CRITICAL(Frontend, "SDL failed to load the Vulkan library: {}", SDL_GetError()); exit(EXIT_FAILURE); @@ -110,8 +111,6 @@ EmuWindow_SDL2_VK::~EmuWindow_SDL2_VK() { vkDestroyInstance(vk_instance, nullptr); } -void EmuWindow_SDL2_VK::SwapBuffers() {} - void EmuWindow_SDL2_VK::MakeCurrent() { // Unused on Vulkan } @@ -160,3 +159,7 @@ bool EmuWindow_SDL2_VK::UseStandardLayers(PFN_vkGetInstanceProcAddr vkGetInstanc return layer.layerName == std::string("VK_LAYER_LUNARG_standard_validation"); }) != layers.end(); } + +void EmuWindow_SDL2_VK::Present() { + // TODO (bunnei): ImplementMe +} diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h index 2a7c06a24..1eb8c0868 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_vk.h @@ -10,19 +10,12 @@ class EmuWindow_SDL2_VK final : public EmuWindow_SDL2 { public: - explicit EmuWindow_SDL2_VK(bool fullscreen); + explicit EmuWindow_SDL2_VK(Core::System& system, bool fullscreen); ~EmuWindow_SDL2_VK(); - /// Swap buffers to display the next frame - void SwapBuffers() override; - - /// Makes the graphics context current for the caller thread void MakeCurrent() override; - - /// Releases the GL context from the caller thread void DoneCurrent() override; - - /// Retrieves Vulkan specific handlers from the window + void Present() override; void RetrieveVulkanHandlers(void* get_instance_proc_addr, void* instance, void* surface) const override; diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp index 325795321..babf4c3a4 100644 --- a/src/yuzu_cmd/yuzu.cpp +++ b/src/yuzu_cmd/yuzu.cpp @@ -177,14 +177,16 @@ int main(int argc, char** argv) { Settings::values.use_gdbstub = use_gdbstub; Settings::Apply(); + Core::System& system{Core::System::GetInstance()}; + std::unique_ptr<EmuWindow_SDL2> emu_window; switch (Settings::values.renderer_backend) { case Settings::RendererBackend::OpenGL: - emu_window = std::make_unique<EmuWindow_SDL2_GL>(fullscreen); + emu_window = std::make_unique<EmuWindow_SDL2_GL>(system, fullscreen); break; case Settings::RendererBackend::Vulkan: #ifdef HAS_VULKAN - emu_window = std::make_unique<EmuWindow_SDL2_VK>(fullscreen); + emu_window = std::make_unique<EmuWindow_SDL2_VK>(system, fullscreen); break; #else LOG_CRITICAL(Frontend, "Vulkan backend has not been compiled!"); @@ -192,12 +194,6 @@ int main(int argc, char** argv) { #endif } - if (!Settings::values.use_multi_core) { - // Single core mode must acquire OpenGL context for entire emulation session - emu_window->MakeCurrent(); - } - - Core::System& system{Core::System::GetInstance()}; system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); @@ -234,12 +230,23 @@ int main(int argc, char** argv) { system.TelemetrySession().AddField(Telemetry::FieldType::App, "Frontend", "SDL"); - emu_window->MakeCurrent(); system.Renderer().Rasterizer().LoadDiskResources(); + // Acquire render context for duration of the thread if this is the rendering thread + if (!Settings::values.use_asynchronous_gpu_emulation) { + emu_window->MakeCurrent(); + } + SCOPE_EXIT({ + if (!Settings::values.use_asynchronous_gpu_emulation) { + emu_window->DoneCurrent(); + } + }); + + std::thread render_thread([&emu_window] { emu_window->Present(); }); while (emu_window->IsOpen()) { system.RunLoop(); } + render_thread.join(); system.Shutdown(); diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp index f2cc4a797..a1bdb1a12 100644 --- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.cpp @@ -112,10 +112,6 @@ EmuWindow_SDL2_Hide::~EmuWindow_SDL2_Hide() { SDL_Quit(); } -void EmuWindow_SDL2_Hide::SwapBuffers() { - SDL_GL_SwapWindow(render_window); -} - void EmuWindow_SDL2_Hide::PollEvents() {} void EmuWindow_SDL2_Hide::MakeCurrent() { diff --git a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h index c7fccc002..b13e15309 100644 --- a/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h +++ b/src/yuzu_tester/emu_window/emu_window_sdl2_hide.h @@ -13,9 +13,6 @@ public: explicit EmuWindow_SDL2_Hide(); ~EmuWindow_SDL2_Hide(); - /// Swap buffers to display the next frame - void SwapBuffers() override; - /// Polls window events void PollEvents() override; |