New upstream version 0-1284+ds

This commit is contained in:
Andrea Pappacoda 2022-12-27 01:25:32 +01:00
parent 2d51d0b97d
commit d3b74ab2d5
54 changed files with 1305 additions and 781 deletions

View file

@ -208,7 +208,7 @@ find_package(libusb 1.0.24)
find_package(lz4 REQUIRED)
find_package(nlohmann_json 3.8 REQUIRED)
find_package(Opus 1.3)
find_package(Vulkan 1.3.213)
find_package(Vulkan 1.3.238)
find_package(ZLIB 1.2 REQUIRED)
find_package(zstd 1.5 REQUIRED)

View file

@ -78,6 +78,7 @@ add_library(common STATIC
logging/types.h
lz4_compression.cpp
lz4_compression.h
make_unique_for_overwrite.h
math_util.h
memory_detect.cpp
memory_detect.h
@ -101,6 +102,7 @@ add_library(common STATIC
${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
scm_rev.h
scope_exit.h
scratch_buffer.h
settings.cpp
settings.h
settings_input.cpp

View file

@ -0,0 +1,25 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <type_traits>
namespace Common {
template <class T>
requires(!std::is_array_v<T>) std::unique_ptr<T> make_unique_for_overwrite() {
return std::unique_ptr<T>(new T);
}
template <class T>
requires std::is_unbounded_array_v<T> std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) {
return std::unique_ptr<T>(new std::remove_extent_t<T>[n]);
}
template <class T, class... Args>
requires std::is_bounded_array_v<T>
void make_unique_for_overwrite(Args&&...) = delete;
} // namespace Common

View file

@ -0,0 +1,95 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/make_unique_for_overwrite.h"
namespace Common {
/**
* ScratchBuffer class
* This class creates a default initialized heap allocated buffer for cases such as intermediate
* buffers being copied into entirely, where value initializing members during allocation or resize
* is redundant.
*/
template <typename T>
class ScratchBuffer {
public:
ScratchBuffer() = default;
explicit ScratchBuffer(size_t initial_capacity)
: last_requested_size{initial_capacity}, buffer_capacity{initial_capacity},
buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {}
~ScratchBuffer() = default;
/// This will only grow the buffer's capacity if size is greater than the current capacity.
/// The previously held data will remain intact.
void resize(size_t size) {
if (size > buffer_capacity) {
auto new_buffer = Common::make_unique_for_overwrite<T[]>(size);
std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get());
buffer = std::move(new_buffer);
buffer_capacity = size;
}
last_requested_size = size;
}
/// This will only grow the buffer's capacity if size is greater than the current capacity.
/// The previously held data will be destroyed if a reallocation occurs.
void resize_destructive(size_t size) {
if (size > buffer_capacity) {
buffer_capacity = size;
buffer = Common::make_unique_for_overwrite<T[]>(buffer_capacity);
}
last_requested_size = size;
}
[[nodiscard]] T* data() noexcept {
return buffer.get();
}
[[nodiscard]] const T* data() const noexcept {
return buffer.get();
}
[[nodiscard]] T* begin() noexcept {
return data();
}
[[nodiscard]] const T* begin() const noexcept {
return data();
}
[[nodiscard]] T* end() noexcept {
return data() + last_requested_size;
}
[[nodiscard]] const T* end() const noexcept {
return data() + last_requested_size;
}
[[nodiscard]] T& operator[](size_t i) {
return buffer[i];
}
[[nodiscard]] const T& operator[](size_t i) const {
return buffer[i];
}
[[nodiscard]] size_t size() const noexcept {
return last_requested_size;
}
[[nodiscard]] size_t capacity() const noexcept {
return buffer_capacity;
}
private:
size_t last_requested_size{};
size_t buffer_capacity{};
std::unique_ptr<T[]> buffer{};
};
} // namespace Common

View file

@ -201,6 +201,9 @@ add_library(core STATIC
hle/kernel/k_event_info.h
hle/kernel/k_handle_table.cpp
hle/kernel/k_handle_table.h
hle/kernel/k_hardware_timer_base.h
hle/kernel/k_hardware_timer.cpp
hle/kernel/k_hardware_timer.h
hle/kernel/k_interrupt_manager.cpp
hle/kernel/k_interrupt_manager.h
hle/kernel/k_light_condition_variable.cpp
@ -268,6 +271,7 @@ add_library(core STATIC
hle/kernel/k_thread_local_page.h
hle/kernel/k_thread_queue.cpp
hle/kernel/k_thread_queue.h
hle/kernel/k_timer_task.h
hle/kernel/k_trace.h
hle/kernel/k_transfer_memory.cpp
hle/kernel/k_transfer_memory.h
@ -290,8 +294,6 @@ add_library(core STATIC
hle/kernel/svc_common.h
hle/kernel/svc_types.h
hle/kernel/svc_wrap.h
hle/kernel/time_manager.cpp
hle/kernel/time_manager.h
hle/result.h
hle/service/acc/acc.cpp
hle/service/acc/acc.h

View file

@ -183,26 +183,20 @@ struct System::Impl {
Initialize(system);
}
SystemResultStatus Run() {
void Run() {
std::unique_lock<std::mutex> lk(suspend_guard);
status = SystemResultStatus::Success;
kernel.Suspend(false);
core_timing.SyncPause(false);
is_paused.store(false, std::memory_order_relaxed);
return status;
}
SystemResultStatus Pause() {
void Pause() {
std::unique_lock<std::mutex> lk(suspend_guard);
status = SystemResultStatus::Success;
core_timing.SyncPause(true);
kernel.Suspend(true);
is_paused.store(true, std::memory_order_relaxed);
return status;
}
bool IsPaused() const {
@ -553,12 +547,12 @@ void System::Initialize() {
impl->Initialize(*this);
}
SystemResultStatus System::Run() {
return impl->Run();
void System::Run() {
impl->Run();
}
SystemResultStatus System::Pause() {
return impl->Pause();
void System::Pause() {
impl->Pause();
}
bool System::IsPaused() const {

View file

@ -152,13 +152,13 @@ public:
* Run the OS and Application
* This function will start emulation and run the relevant devices
*/
[[nodiscard]] SystemResultStatus Run();
void Run();
/**
* Pause the OS and Application
* This function will pause emulation and stop the relevant devices
*/
[[nodiscard]] SystemResultStatus Pause();
void Pause();
/// Check if the core is currently paused.
[[nodiscard]] bool IsPaused() const;

View file

@ -210,6 +210,13 @@ void EmulatedController::LoadTASParams() {
tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
// set to optimal stick to avoid sanitizing the stick and tweaking the coordinates
// making sure they play back in the game as originally written down in the script file
tas_stick_params[Settings::NativeAnalog::LStick].Set("deadzone", 0.0f);
tas_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f);
tas_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f);
tas_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f);
}
void EmulatedController::LoadVirtualGamepadParams() {

View file

@ -10,7 +10,6 @@
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
#include "core/hle/kernel/time_manager.h"
#include "core/memory.h"
namespace Kernel {

View file

@ -0,0 +1,74 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_scheduler.h"
namespace Kernel {
void KHardwareTimer::Initialize() {
// Create the timing callback to register with CoreTiming.
m_event_type = Core::Timing::CreateEvent(
"KHardwareTimer::Callback", [](std::uintptr_t timer_handle, s64, std::chrono::nanoseconds) {
reinterpret_cast<KHardwareTimer*>(timer_handle)->DoTask();
return std::nullopt;
});
}
void KHardwareTimer::Finalize() {
this->DisableInterrupt();
m_event_type.reset();
}
void KHardwareTimer::DoTask() {
// Handle the interrupt.
{
KScopedSchedulerLock slk{m_kernel};
KScopedSpinLock lk(this->GetLock());
//! Ignore this event if needed.
if (!this->GetInterruptEnabled()) {
return;
}
// Disable the timer interrupt while we handle this.
this->DisableInterrupt();
if (const s64 next_time = this->DoInterruptTaskImpl(GetTick());
0 < next_time && next_time <= m_wakeup_time) {
// We have a next time, so we should set the time to interrupt and turn the interrupt
// on.
this->EnableInterrupt(next_time);
}
}
// Clear the timer interrupt.
// Kernel::GetInterruptManager().ClearInterrupt(KInterruptName_NonSecurePhysicalTimer,
// GetCurrentCoreId());
}
void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
this->DisableInterrupt();
m_wakeup_time = wakeup_time;
m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
m_event_type, reinterpret_cast<uintptr_t>(this),
true);
}
void KHardwareTimer::DisableInterrupt() {
m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this));
m_wakeup_time = std::numeric_limits<s64>::max();
}
s64 KHardwareTimer::GetTick() const {
return m_kernel.System().CoreTiming().GetGlobalTimeNs().count();
}
bool KHardwareTimer::GetInterruptEnabled() {
return m_wakeup_time != std::numeric_limits<s64>::max();
}
} // namespace Kernel

View file

@ -0,0 +1,54 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/kernel/k_hardware_timer_base.h"
namespace Core::Timing {
struct EventType;
} // namespace Core::Timing
namespace Kernel {
class KHardwareTimer : /* public KInterruptTask, */ public KHardwareTimerBase {
public:
explicit KHardwareTimer(KernelCore& kernel) : KHardwareTimerBase{kernel} {}
// Public API.
void Initialize();
void Finalize();
s64 GetCount() const {
return GetTick();
}
void RegisterTask(KTimerTask* task, s64 time_from_now) {
this->RegisterAbsoluteTask(task, GetTick() + time_from_now);
}
void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk{this->GetLock()};
if (this->RegisterAbsoluteTaskImpl(task, task_time)) {
if (task_time <= m_wakeup_time) {
this->EnableInterrupt(task_time);
}
}
}
private:
void EnableInterrupt(s64 wakeup_time);
void DisableInterrupt();
bool GetInterruptEnabled();
s64 GetTick() const;
void DoTask();
private:
// Absolute time in nanoseconds
s64 m_wakeup_time{std::numeric_limits<s64>::max()};
std::shared_ptr<Core::Timing::EventType> m_event_type{};
};
} // namespace Kernel

View file

@ -0,0 +1,92 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "core/hle/kernel/k_spin_lock.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/k_timer_task.h"
namespace Kernel {
class KHardwareTimerBase {
public:
explicit KHardwareTimerBase(KernelCore& kernel) : m_kernel{kernel} {}
void CancelTask(KTimerTask* task) {
KScopedDisableDispatch dd{m_kernel};
KScopedSpinLock lk{m_lock};
if (const s64 task_time = task->GetTime(); task_time > 0) {
this->RemoveTaskFromTree(task);
}
}
protected:
KSpinLock& GetLock() {
return m_lock;
}
s64 DoInterruptTaskImpl(s64 cur_time) {
// We want to handle all tasks, returning the next time that a task is scheduled.
while (true) {
// Get the next task. If there isn't one, return 0.
KTimerTask* task = m_next_task;
if (task == nullptr) {
return 0;
}
// If the task needs to be done in the future, do it in the future and not now.
if (const s64 task_time = task->GetTime(); task_time > cur_time) {
return task_time;
}
// Remove the task from the tree of tasks, and update our next task.
this->RemoveTaskFromTree(task);
// Handle the task.
task->OnTimer();
}
}
bool RegisterAbsoluteTaskImpl(KTimerTask* task, s64 task_time) {
ASSERT(task_time > 0);
// Set the task's time, and insert it into our tree.
task->SetTime(task_time);
m_task_tree.insert(*task);
// Update our next task if relevant.
if (m_next_task != nullptr && m_next_task->GetTime() <= task_time) {
return false;
}
m_next_task = task;
return true;
}
private:
void RemoveTaskFromTree(KTimerTask* task) {
// Erase from the tree.
auto it = m_task_tree.erase(m_task_tree.iterator_to(*task));
// Clear the task's scheduled time.
task->SetTime(0);
// Update our next task if relevant.
if (m_next_task == task) {
m_next_task = (it != m_task_tree.end()) ? std::addressof(*it) : nullptr;
}
}
protected:
KernelCore& m_kernel;
private:
using TimerTaskTree = Common::IntrusiveRedBlackTreeBaseTraits<KTimerTask>::TreeType<KTimerTask>;
KSpinLock m_lock{};
TimerTaskTree m_task_tree{};
KTimerTask* m_next_task{};
};
} // namespace Kernel

View file

@ -5,9 +5,9 @@
#include "common/common_types.h"
#include "core/hle/kernel/global_scheduler_context.h"
#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel {
@ -22,7 +22,7 @@ public:
~KScopedSchedulerLockAndSleep() {
// Register the sleep.
if (timeout_tick > 0) {
kernel.TimeManager().ScheduleTimeEvent(thread, timeout_tick);
kernel.HardwareTimer().RegisterTask(thread, timeout_tick);
}
// Unlock the scheduler.

View file

@ -6,6 +6,7 @@
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
@ -18,19 +19,19 @@ KSharedMemory::~KSharedMemory() {
}
Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_,
Svc::MemoryPermission user_permission_, PAddr physical_address_,
std::size_t size_, std::string name_) {
Svc::MemoryPermission owner_permission_,
Svc::MemoryPermission user_permission_, std::size_t size_,
std::string name_) {
// Set members.
owner_process = owner_process_;
device_memory = &device_memory_;
page_list = std::move(page_list_);
owner_permission = owner_permission_;
user_permission = user_permission_;
physical_address = physical_address_;
size = size_;
size = Common::AlignUp(size_, PageSize);
name = std::move(name_);
const size_t num_pages = Common::DivideUp(size, PageSize);
// Get the resource limit.
KResourceLimit* reslimit = kernel.GetSystemResourceLimit();
@ -39,6 +40,17 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
size_);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate the memory.
//! HACK: Open continuous mapping from sysmodule pool.
auto option = KMemoryManager::EncodeOption(KMemoryManager::Pool::Secure,
KMemoryManager::Direction::FromBack);
physical_address = kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, option);
R_UNLESS(physical_address != 0, ResultOutOfMemory);
//! Insert the result into our page group.
page_group.emplace(physical_address, num_pages);
// Commit our reservation.
memory_reservation.Commit();
@ -50,12 +62,23 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
is_initialized = true;
// Clear all pages in the memory.
std::memset(device_memory_.GetPointer<void>(physical_address_), 0, size_);
for (const auto& block : page_group->Nodes()) {
std::memset(device_memory_.GetPointer<void>(block.GetAddress()), 0, block.GetSize());
}
return ResultSuccess;
}
void KSharedMemory::Finalize() {
// Close and finalize the page group.
// page_group->Close();
// page_group->Finalize();
//! HACK: Manually close.
for (const auto& block : page_group->Nodes()) {
kernel.MemoryManager().Close(block.GetAddress(), block.GetNumPages());
}
// Release the memory reservation.
resource_limit->Release(LimitableResource::PhysicalMemoryMax, size);
resource_limit->Close();
@ -65,32 +88,28 @@ void KSharedMemory::Finalize() {
}
Result KSharedMemory::Map(KProcess& target_process, VAddr address, std::size_t map_size,
Svc::MemoryPermission permissions) {
const u64 page_count{(map_size + PageSize - 1) / PageSize};
Svc::MemoryPermission map_perm) {
// Validate the size.
R_UNLESS(size == map_size, ResultInvalidSize);
if (page_list.GetNumPages() != page_count) {
UNIMPLEMENTED_MSG("Page count does not match");
}
const Svc::MemoryPermission expected =
// Validate the permission.
const Svc::MemoryPermission test_perm =
&target_process == owner_process ? owner_permission : user_permission;
if (permissions != expected) {
UNIMPLEMENTED_MSG("Permission does not match");
if (test_perm == Svc::MemoryPermission::DontCare) {
ASSERT(map_perm == Svc::MemoryPermission::Read || map_perm == Svc::MemoryPermission::Write);
} else {
R_UNLESS(map_perm == test_perm, ResultInvalidNewMemoryPermission);
}
return target_process.PageTable().MapPages(address, page_list, KMemoryState::Shared,
ConvertToKMemoryPermission(permissions));
return target_process.PageTable().MapPages(address, *page_group, KMemoryState::Shared,
ConvertToKMemoryPermission(map_perm));
}
Result KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) {
const u64 page_count{(unmap_size + PageSize - 1) / PageSize};
// Validate the size.
R_UNLESS(size == unmap_size, ResultInvalidSize);
if (page_list.GetNumPages() != page_count) {
UNIMPLEMENTED_MSG("Page count does not match");
}
return target_process.PageTable().UnmapPages(address, page_list, KMemoryState::Shared);
return target_process.PageTable().UnmapPages(address, *page_group, KMemoryState::Shared);
}
} // namespace Kernel

View file

@ -3,6 +3,7 @@
#pragma once
#include <optional>
#include <string>
#include "common/common_types.h"
@ -26,9 +27,8 @@ public:
~KSharedMemory() override;
Result Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_,
Svc::MemoryPermission user_permission_, PAddr physical_address_,
std::size_t size_, std::string name_);
Svc::MemoryPermission owner_permission_,
Svc::MemoryPermission user_permission_, std::size_t size_, std::string name_);
/**
* Maps a shared memory block to an address in the target process' address space
@ -76,7 +76,7 @@ public:
private:
Core::DeviceMemory* device_memory{};
KProcess* owner_process{};
KPageGroup page_list;
std::optional<KPageGroup> page_group{};
Svc::MemoryPermission owner_permission{};
Svc::MemoryPermission user_permission{};
PAddr physical_address{};

View file

@ -22,6 +22,7 @@
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_spin_lock.h"
#include "core/hle/kernel/k_synchronization_object.h"
#include "core/hle/kernel/k_timer_task.h"
#include "core/hle/kernel/k_worker_task.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_common.h"
@ -112,7 +113,8 @@ void SetCurrentThread(KernelCore& kernel, KThread* thread);
[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
class KThread final : public KAutoObjectWithSlabHeapAndContainer<KThread, KWorkerTask>,
public boost::intrusive::list_base_hook<> {
public boost::intrusive::list_base_hook<>,
public KTimerTask {
KERNEL_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject);
private:
@ -840,4 +842,8 @@ private:
KernelCore& kernel;
};
inline void KTimerTask::OnTimer() {
static_cast<KThread*>(this)->OnTimer();
}
} // namespace Kernel

View file

@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/time_manager.h"
namespace Kernel {
@ -22,7 +22,7 @@ void KThreadQueue::EndWait(KThread* waiting_thread, Result wait_result) {
waiting_thread->ClearWaitQueue();
// Cancel the thread task.
kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
kernel.HardwareTimer().CancelTask(waiting_thread);
}
void KThreadQueue::CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) {
@ -37,7 +37,7 @@ void KThreadQueue::CancelWait(KThread* waiting_thread, Result wait_result, bool
// Cancel the thread task.
if (cancel_timer_task) {
kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
kernel.HardwareTimer().CancelTask(waiting_thread);
}
}

View file

@ -0,0 +1,40 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include "common/intrusive_red_black_tree.h"
namespace Kernel {
class KTimerTask : public Common::IntrusiveRedBlackTreeBaseNode<KTimerTask> {
public:
static constexpr int Compare(const KTimerTask& lhs, const KTimerTask& rhs) {
if (lhs.GetTime() < rhs.GetTime()) {
return -1;
} else {
return 1;
}
}
constexpr explicit KTimerTask() = default;
constexpr void SetTime(s64 t) {
m_time = t;
}
constexpr s64 GetTime() const {
return m_time;
}
// NOTE: This is virtual in Nintendo's kernel. Prior to 13.0.0, KWaitObject was also a
// TimerTask; this is no longer the case. Since this is now KThread exclusive, we have
// devirtualized (see inline declaration for this inside k_thread.h).
void OnTimer();
private:
// Absolute time in nanoseconds
s64 m_time{};
};
} // namespace Kernel

View file

@ -26,6 +26,7 @@
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_dynamic_resource_manager.h"
#include "core/hle/kernel/k_handle_table.h"
#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_page_buffer.h"
@ -39,7 +40,6 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/service_thread.h"
#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h"
#include "core/hle/service/sm/sm.h"
#include "core/memory.h"
@ -55,7 +55,7 @@ struct KernelCore::Impl {
static constexpr size_t ReservedDynamicPageCount = 64;
explicit Impl(Core::System& system_, KernelCore& kernel_)
: time_manager{system_}, service_threads_manager{1, "ServiceThreadsManager"},
: service_threads_manager{1, "ServiceThreadsManager"},
service_thread_barrier{2}, system{system_} {}
void SetMulticore(bool is_multi) {
@ -63,6 +63,9 @@ struct KernelCore::Impl {
}
void Initialize(KernelCore& kernel) {
hardware_timer = std::make_unique<Kernel::KHardwareTimer>(kernel);
hardware_timer->Initialize();
global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel);
@ -91,6 +94,7 @@ struct KernelCore::Impl {
pt_heap_region.GetSize());
}
InitializeHackSharedMemory();
RegisterHostThread(nullptr);
default_service_thread = &CreateServiceThread(kernel, "DefaultServiceThread");
@ -193,6 +197,9 @@ struct KernelCore::Impl {
// Ensure that the object list container is finalized and properly shutdown.
global_object_list_container->Finalize();
global_object_list_container.reset();
hardware_timer->Finalize();
hardware_timer.reset();
}
void CloseServices() {
@ -720,14 +727,14 @@ struct KernelCore::Impl {
}
void InitializeMemoryLayout() {
const auto system_pool = memory_layout->GetKernelSystemPoolRegionPhysicalExtents();
// Initialize the memory manager.
memory_manager = std::make_unique<KMemoryManager>(system);
const auto& management_region = memory_layout->GetPoolManagementRegion();
ASSERT(management_region.GetEndAddress() != 0);
memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize());
}
void InitializeHackSharedMemory() {
// Setup memory regions for emulated processes
// TODO(bunnei): These should not be hardcoded regions initialized within the kernel
constexpr std::size_t hid_size{0x40000};
@ -736,39 +743,23 @@ struct KernelCore::Impl {
constexpr std::size_t time_size{0x1000};
constexpr std::size_t hidbus_size{0x1000};
const PAddr hid_phys_addr{system_pool.GetAddress()};
const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size};
const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size};
const PAddr hidbus_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size +
time_size};
hid_shared_mem = KSharedMemory::Create(system.Kernel());
font_shared_mem = KSharedMemory::Create(system.Kernel());
irs_shared_mem = KSharedMemory::Create(system.Kernel());
time_shared_mem = KSharedMemory::Create(system.Kernel());
hidbus_shared_mem = KSharedMemory::Create(system.Kernel());
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr,
{hid_phys_addr, hid_size / PageSize},
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
hid_phys_addr, hid_size, "HID:SharedMemory");
font_shared_mem->Initialize(system.DeviceMemory(), nullptr,
{font_phys_addr, font_size / PageSize},
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
font_phys_addr, font_size, "Font:SharedMemory");
irs_shared_mem->Initialize(system.DeviceMemory(), nullptr,
{irs_phys_addr, irs_size / PageSize},
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
irs_phys_addr, irs_size, "IRS:SharedMemory");
time_shared_mem->Initialize(system.DeviceMemory(), nullptr,
{time_phys_addr, time_size / PageSize},
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
time_phys_addr, time_size, "Time:SharedMemory");
hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr,
{hidbus_phys_addr, hidbus_size / PageSize},
Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
hidbus_phys_addr, hidbus_size, "HidBus:SharedMemory");
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, hid_size, "HID:SharedMemory");
font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, font_size, "Font:SharedMemory");
irs_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, irs_size, "IRS:SharedMemory");
time_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, time_size, "Time:SharedMemory");
hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, hidbus_size,
"HidBus:SharedMemory");
}
KClientPort* CreateNamedServicePort(std::string name) {
@ -832,7 +823,7 @@ struct KernelCore::Impl {
std::vector<KProcess*> process_list;
std::atomic<KProcess*> current_process{};
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
Kernel::TimeManager time_manager;
std::unique_ptr<Kernel::KHardwareTimer> hardware_timer;
Init::KSlabResourceCounts slab_resource_counts{};
KResourceLimit* system_resource_limit{};
@ -1019,12 +1010,8 @@ Kernel::KScheduler* KernelCore::CurrentScheduler() {
return impl->schedulers[core_id].get();
}
Kernel::TimeManager& KernelCore::TimeManager() {
return impl->time_manager;
}
const Kernel::TimeManager& KernelCore::TimeManager() const {
return impl->time_manager;
Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
return *impl->hardware_timer;
}
Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() {

View file

@ -39,6 +39,7 @@ class KDynamicPageManager;
class KEvent;
class KEventInfo;
class KHandleTable;
class KHardwareTimer;
class KLinkedListNode;
class KMemoryLayout;
class KMemoryManager;
@ -63,7 +64,6 @@ class KCodeMemory;
class PhysicalCore;
class ServiceThread;
class Synchronization;
class TimeManager;
using ServiceInterfaceFactory =
std::function<KClientPort&(Service::SM::ServiceManager&, Core::System&)>;
@ -175,11 +175,8 @@ public:
/// Gets the an instance of the current physical CPU core.
const Kernel::PhysicalCore& CurrentPhysicalCore() 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;
/// Gets the an instance of the hardware timer.
Kernel::KHardwareTimer& HardwareTimer();
/// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id);

View file

@ -1,44 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/assert.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/kernel/k_scheduler.h"
#include "core/hle/kernel/k_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](std::uintptr_t thread_handle, s64 time,
std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
KThread* thread = reinterpret_cast<KThread*>(thread_handle);
{
KScopedSchedulerLock sl(system.Kernel());
thread->OnTimer();
}
return std::nullopt;
});
}
void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) {
std::scoped_lock lock{mutex};
if (nanoseconds > 0) {
ASSERT(thread);
ASSERT(thread->GetState() != ThreadState::Runnable);
system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds},
time_manager_event_type,
reinterpret_cast<uintptr_t>(thread));
}
}
void TimeManager::UnscheduleTimeEvent(KThread* thread) {
std::scoped_lock lock{mutex};
system.CoreTiming().UnscheduleEvent(time_manager_event_type,
reinterpret_cast<uintptr_t>(thread));
}
} // namespace Kernel

View file

@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <memory>
#include <mutex>
namespace Core {
class System;
} // namespace Core
namespace Core::Timing {
struct EventType;
} // namespace Core::Timing
namespace Kernel {
class KThread;
/**
* 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'
void ScheduleTimeEvent(KThread* time_task, s64 nanoseconds);
/// Unschedule an existing time event
void UnscheduleTimeEvent(KThread* thread);
private:
Core::System& system;
std::shared_ptr<Core::Timing::EventType> time_manager_event_type;
std::mutex mutex;
};
} // namespace Kernel

View file

@ -49,6 +49,7 @@ struct SteadyClockContext {
static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size");
static_assert(std::is_trivially_copyable_v<SteadyClockContext>,
"SteadyClockContext must be trivially copyable");
using StandardSteadyClockTimePointType = SteadyClockContext;
struct SystemClockContext {
s64 offset;

View file

@ -26,23 +26,24 @@ void SharedMemory::SetupStandardSteadyClock(const Common::UUID& clock_source_id,
const Clock::SteadyClockContext context{
static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds),
clock_source_id};
shared_memory_format.standard_steady_clock_timepoint.StoreData(
system.Kernel().GetTimeSharedMem().GetPointer(), context);
StoreToLockFreeAtomicType(&GetFormat()->standard_steady_clock_timepoint, context);
}
void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) {
shared_memory_format.standard_local_system_clock_context.StoreData(
system.Kernel().GetTimeSharedMem().GetPointer(), context);
StoreToLockFreeAtomicType(&GetFormat()->standard_local_system_clock_context, context);
}
void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) {
shared_memory_format.standard_network_system_clock_context.StoreData(
system.Kernel().GetTimeSharedMem().GetPointer(), context);
StoreToLockFreeAtomicType(&GetFormat()->standard_network_system_clock_context, context);
}
void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) {
shared_memory_format.standard_user_system_clock_automatic_correction.StoreData(
system.Kernel().GetTimeSharedMem().GetPointer(), is_enabled);
StoreToLockFreeAtomicType(
&GetFormat()->is_standard_user_system_clock_automatic_correction_enabled, is_enabled);
}
SharedMemory::Format* SharedMemory::GetFormat() {
return reinterpret_cast<SharedMemory::Format*>(system.Kernel().GetTimeSharedMem().GetPointer());
}
} // namespace Service::Time

View file

@ -10,45 +10,68 @@
namespace Service::Time {
// Note: this type is not safe for concurrent writes.
template <typename T>
struct LockFreeAtomicType {
u32 counter_;
std::array<T, 2> value_;
};
template <typename T>
static inline void StoreToLockFreeAtomicType(LockFreeAtomicType<T>* p, const T& value) {
// Get the current counter.
auto counter = p->counter_;
// Increment the counter.
++counter;
// Store the updated value.
p->value_[counter % 2] = value;
// Fence memory.
std::atomic_thread_fence(std::memory_order_release);
// Set the updated counter.
p->counter_ = counter;
}
template <typename T>
static inline T LoadFromLockFreeAtomicType(const LockFreeAtomicType<T>* p) {
while (true) {
// Get the counter.
auto counter = p->counter_;
// Get the value.
auto value = p->value_[counter % 2];
// Fence memory.
std::atomic_thread_fence(std::memory_order_acquire);
// Check that the counter matches.
if (counter == p->counter_) {
return value;
}
}
}
class SharedMemory final {
public:
explicit SharedMemory(Core::System& system_);
~SharedMemory();
// TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this?
template <typename T, std::size_t Offset>
struct MemoryBarrier {
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
u32_le read_attempt{};
std::array<T, 2> data{};
// These are not actually memory barriers at the moment as we don't have multicore and all
// HLE is mutexed. This will need to properly be implemented when we start updating the time
// points on threads. As of right now, we'll be updated both values synchronously and just
// incrementing the read_attempt to indicate that we waited.
void StoreData(u8* shared_memory, T data_to_store) {
std::memcpy(this, shared_memory + Offset, sizeof(*this));
read_attempt++;
data[read_attempt & 1] = data_to_store;
std::memcpy(shared_memory + Offset, this, sizeof(*this));
}
// For reading we're just going to read the last stored value. If there was no value stored
// it will just end up reading an empty value as intended.
T ReadData(u8* shared_memory) {
std::memcpy(this, shared_memory + Offset, sizeof(*this));
return data[(read_attempt - 1) & 1];
}
};
// Shared memory format
struct Format {
MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint;
MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context;
MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context;
MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction;
u32_le format_version;
LockFreeAtomicType<Clock::StandardSteadyClockTimePointType> standard_steady_clock_timepoint;
LockFreeAtomicType<Clock::SystemClockContext> standard_local_system_clock_context;
LockFreeAtomicType<Clock::SystemClockContext> standard_network_system_clock_context;
LockFreeAtomicType<bool> is_standard_user_system_clock_automatic_correction_enabled;
u32 format_version;
};
static_assert(offsetof(Format, standard_steady_clock_timepoint) == 0x0);
static_assert(offsetof(Format, standard_local_system_clock_context) == 0x38);
static_assert(offsetof(Format, standard_network_system_clock_context) == 0x80);
static_assert(offsetof(Format, is_standard_user_system_clock_automatic_correction_enabled) ==
0xc8);
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");
void SetupStandardSteadyClock(const Common::UUID& clock_source_id,
@ -56,10 +79,10 @@ public:
void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context);
void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context);
void SetAutomaticCorrectionEnabled(bool is_enabled);
Format* GetFormat();
private:
Core::System& system;
Format shared_memory_format{};
};
} // namespace Service::Time

View file

@ -200,12 +200,6 @@ bool MappingFactory::IsDriverValid(const MappingData& data) const {
return false;
}
// The following drivers don't need to be mapped
if (data.engine == "tas") {
return false;
}
if (data.engine == "touch") {
return false;
}
if (data.engine == "touch_from_button") {
return false;
}

View file

@ -26,79 +26,33 @@
namespace InputCommon {
struct InputSubsystem::Impl {
void Initialize() {
mapping_factory = std::make_shared<MappingFactory>();
template <typename Engine>
void RegisterEngine(std::string name, std::shared_ptr<Engine>& engine) {
MappingCallback mapping_callback{[this](const MappingData& data) { RegisterInput(data); }};
keyboard = std::make_shared<Keyboard>("keyboard");
keyboard->SetMappingCallback(mapping_callback);
keyboard_factory = std::make_shared<InputFactory>(keyboard);
keyboard_output_factory = std::make_shared<OutputFactory>(keyboard);
Common::Input::RegisterInputFactory(keyboard->GetEngineName(), keyboard_factory);
Common::Input::RegisterOutputFactory(keyboard->GetEngineName(), keyboard_output_factory);
engine = std::make_shared<Engine>(name);
engine->SetMappingCallback(mapping_callback);
mouse = std::make_shared<Mouse>("mouse");
mouse->SetMappingCallback(mapping_callback);
mouse_factory = std::make_shared<InputFactory>(mouse);
mouse_output_factory = std::make_shared<OutputFactory>(mouse);
Common::Input::RegisterInputFactory(mouse->GetEngineName(), mouse_factory);
Common::Input::RegisterOutputFactory(mouse->GetEngineName(), mouse_output_factory);
std::shared_ptr<InputFactory> input_factory = std::make_shared<InputFactory>(engine);
std::shared_ptr<OutputFactory> output_factory = std::make_shared<OutputFactory>(engine);
Common::Input::RegisterInputFactory(engine->GetEngineName(), std::move(input_factory));
Common::Input::RegisterOutputFactory(engine->GetEngineName(), std::move(output_factory));
}
touch_screen = std::make_shared<TouchScreen>("touch");
touch_screen_factory = std::make_shared<InputFactory>(touch_screen);
Common::Input::RegisterInputFactory(touch_screen->GetEngineName(), touch_screen_factory);
gcadapter = std::make_shared<GCAdapter>("gcpad");
gcadapter->SetMappingCallback(mapping_callback);
gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter);
gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter);
Common::Input::RegisterInputFactory(gcadapter->GetEngineName(), gcadapter_input_factory);
Common::Input::RegisterOutputFactory(gcadapter->GetEngineName(), gcadapter_output_factory);
udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp");
udp_client->SetMappingCallback(mapping_callback);
udp_client_input_factory = std::make_shared<InputFactory>(udp_client);
udp_client_output_factory = std::make_shared<OutputFactory>(udp_client);
Common::Input::RegisterInputFactory(udp_client->GetEngineName(), udp_client_input_factory);
Common::Input::RegisterOutputFactory(udp_client->GetEngineName(),
udp_client_output_factory);
tas_input = std::make_shared<TasInput::Tas>("tas");
tas_input->SetMappingCallback(mapping_callback);
tas_input_factory = std::make_shared<InputFactory>(tas_input);
tas_output_factory = std::make_shared<OutputFactory>(tas_input);
Common::Input::RegisterInputFactory(tas_input->GetEngineName(), tas_input_factory);
Common::Input::RegisterOutputFactory(tas_input->GetEngineName(), tas_output_factory);
camera = std::make_shared<Camera>("camera");
camera->SetMappingCallback(mapping_callback);
camera_input_factory = std::make_shared<InputFactory>(camera);
camera_output_factory = std::make_shared<OutputFactory>(camera);
Common::Input::RegisterInputFactory(camera->GetEngineName(), camera_input_factory);
Common::Input::RegisterOutputFactory(camera->GetEngineName(), camera_output_factory);
virtual_amiibo = std::make_shared<VirtualAmiibo>("virtual_amiibo");
virtual_amiibo->SetMappingCallback(mapping_callback);
virtual_amiibo_input_factory = std::make_shared<InputFactory>(virtual_amiibo);
virtual_amiibo_output_factory = std::make_shared<OutputFactory>(virtual_amiibo);
Common::Input::RegisterInputFactory(virtual_amiibo->GetEngineName(),
virtual_amiibo_input_factory);
Common::Input::RegisterOutputFactory(virtual_amiibo->GetEngineName(),
virtual_amiibo_output_factory);
virtual_gamepad = std::make_shared<VirtualGamepad>("virtual_gamepad");
virtual_gamepad->SetMappingCallback(mapping_callback);
virtual_gamepad_input_factory = std::make_shared<InputFactory>(virtual_gamepad);
Common::Input::RegisterInputFactory(virtual_gamepad->GetEngineName(),
virtual_gamepad_input_factory);
void Initialize() {
mapping_factory = std::make_shared<MappingFactory>();
RegisterEngine("keyboard", keyboard);
RegisterEngine("mouse", mouse);
RegisterEngine("touch", touch_screen);
RegisterEngine("gcpad", gcadapter);
RegisterEngine("cemuhookudp", udp_client);
RegisterEngine("tas", tas_input);
RegisterEngine("camera", camera);
RegisterEngine("virtual_amiibo", virtual_amiibo);
RegisterEngine("virtual_gamepad", virtual_gamepad);
#ifdef HAVE_SDL2
sdl = std::make_shared<SDLDriver>("sdl");
sdl->SetMappingCallback(mapping_callback);
sdl_input_factory = std::make_shared<InputFactory>(sdl);
sdl_output_factory = std::make_shared<OutputFactory>(sdl);
Common::Input::RegisterInputFactory(sdl->GetEngineName(), sdl_input_factory);
Common::Input::RegisterOutputFactory(sdl->GetEngineName(), sdl_output_factory);
RegisterEngine("sdl", sdl);
#endif
Common::Input::RegisterInputFactory("touch_from_button",
@ -107,45 +61,25 @@ struct InputSubsystem::Impl {
std::make_shared<StickFromButton>());
}
template <typename Engine>
void UnregisterEngine(std::shared_ptr<Engine>& engine) {
Common::Input::UnregisterInputFactory(engine->GetEngineName());
Common::Input::UnregisterOutputFactory(engine->GetEngineName());
engine.reset();
}
void Shutdown() {
Common::Input::UnregisterInputFactory(keyboard->GetEngineName());
Common::Input::UnregisterOutputFactory(keyboard->GetEngineName());
keyboard.reset();
Common::Input::UnregisterInputFactory(mouse->GetEngineName());
Common::Input::UnregisterOutputFactory(mouse->GetEngineName());
mouse.reset();
Common::Input::UnregisterInputFactory(touch_screen->GetEngineName());
touch_screen.reset();
Common::Input::UnregisterInputFactory(gcadapter->GetEngineName());
Common::Input::UnregisterOutputFactory(gcadapter->GetEngineName());
gcadapter.reset();
Common::Input::UnregisterInputFactory(udp_client->GetEngineName());
Common::Input::UnregisterOutputFactory(udp_client->GetEngineName());
udp_client.reset();
Common::Input::UnregisterInputFactory(tas_input->GetEngineName());
Common::Input::UnregisterOutputFactory(tas_input->GetEngineName());
tas_input.reset();
Common::Input::UnregisterInputFactory(camera->GetEngineName());
Common::Input::UnregisterOutputFactory(camera->GetEngineName());
camera.reset();
Common::Input::UnregisterInputFactory(virtual_amiibo->GetEngineName());
Common::Input::UnregisterOutputFactory(virtual_amiibo->GetEngineName());
virtual_amiibo.reset();
Common::Input::UnregisterInputFactory(virtual_gamepad->GetEngineName());
virtual_gamepad.reset();
UnregisterEngine(keyboard);
UnregisterEngine(mouse);
UnregisterEngine(touch_screen);
UnregisterEngine(gcadapter);
UnregisterEngine(udp_client);
UnregisterEngine(tas_input);
UnregisterEngine(camera);
UnregisterEngine(virtual_amiibo);
UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2
Common::Input::UnregisterInputFactory(sdl->GetEngineName());
Common::Input::UnregisterOutputFactory(sdl->GetEngineName());
sdl.reset();
UnregisterEngine(sdl);
#endif
Common::Input::UnregisterInputFactory("touch_from_button");
@ -173,117 +107,86 @@ struct InputSubsystem::Impl {
return devices;
}
[[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
[[nodiscard]] std::shared_ptr<InputEngine> GetInputEngine(
const Common::ParamPackage& params) const {
if (!params.Has("engine") || params.Get("engine", "") == "any") {
return {};
return nullptr;
}
const std::string engine = params.Get("engine", "");
if (engine == keyboard->GetEngineName()) {
return keyboard;
}
if (engine == mouse->GetEngineName()) {
return mouse->GetAnalogMappingForDevice(params);
return mouse;
}
if (engine == gcadapter->GetEngineName()) {
return gcadapter->GetAnalogMappingForDevice(params);
return gcadapter;
}
if (engine == udp_client->GetEngineName()) {
return udp_client->GetAnalogMappingForDevice(params);
}
if (engine == tas_input->GetEngineName()) {
return tas_input->GetAnalogMappingForDevice(params);
return udp_client;
}
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
return sdl->GetAnalogMappingForDevice(params);
return sdl;
}
#endif
return {};
return nullptr;
}
[[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
const Common::ParamPackage& params) const {
const auto input_engine = GetInputEngine(params);
if (input_engine == nullptr) {
return {};
}
return input_engine->GetAnalogMappingForDevice(params);
}
[[nodiscard]] ButtonMapping GetButtonMappingForDevice(
const Common::ParamPackage& params) const {
if (!params.Has("engine") || params.Get("engine", "") == "any") {
const auto input_engine = GetInputEngine(params);
if (input_engine == nullptr) {
return {};
}
const std::string engine = params.Get("engine", "");
if (engine == gcadapter->GetEngineName()) {
return gcadapter->GetButtonMappingForDevice(params);
}
if (engine == udp_client->GetEngineName()) {
return udp_client->GetButtonMappingForDevice(params);
}
if (engine == tas_input->GetEngineName()) {
return tas_input->GetButtonMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
return sdl->GetButtonMappingForDevice(params);
}
#endif
return {};
return input_engine->GetButtonMappingForDevice(params);
}
[[nodiscard]] MotionMapping GetMotionMappingForDevice(
const Common::ParamPackage& params) const {
if (!params.Has("engine") || params.Get("engine", "") == "any") {
const auto input_engine = GetInputEngine(params);
if (input_engine == nullptr) {
return {};
}
const std::string engine = params.Get("engine", "");
if (engine == udp_client->GetEngineName()) {
return udp_client->GetMotionMappingForDevice(params);
}
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
return sdl->GetMotionMappingForDevice(params);
}
#endif
return {};
return input_engine->GetMotionMappingForDevice(params);
}
Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const {
if (!params.Has("engine") || params.Get("engine", "") == "any") {
return Common::Input::ButtonNames::Undefined;
}
const std::string engine = params.Get("engine", "");
if (engine == mouse->GetEngineName()) {
return mouse->GetUIName(params);
const auto input_engine = GetInputEngine(params);
if (input_engine == nullptr) {
return Common::Input::ButtonNames::Invalid;
}
if (engine == gcadapter->GetEngineName()) {
return gcadapter->GetUIName(params);
}
if (engine == udp_client->GetEngineName()) {
return udp_client->GetUIName(params);
}
if (engine == tas_input->GetEngineName()) {
return tas_input->GetUIName(params);
}
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
return sdl->GetUIName(params);
}
#endif
return Common::Input::ButtonNames::Invalid;
return input_engine->GetUIName(params);
}
bool IsStickInverted(const Common::ParamPackage& params) {
const std::string engine = params.Get("engine", "");
if (engine == mouse->GetEngineName()) {
return mouse->IsStickInverted(params);
const auto input_engine = GetInputEngine(params);
if (input_engine == nullptr) {
return false;
}
if (engine == gcadapter->GetEngineName()) {
return gcadapter->IsStickInverted(params);
}
if (engine == udp_client->GetEngineName()) {
return udp_client->IsStickInverted(params);
}
if (engine == tas_input->GetEngineName()) {
return tas_input->IsStickInverted(params);
}
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
return sdl->IsStickInverted(params);
}
#endif
return false;
return input_engine->IsStickInverted(params);
}
bool IsController(const Common::ParamPackage& params) {
@ -353,28 +256,8 @@ struct InputSubsystem::Impl {
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
std::shared_ptr<VirtualGamepad> virtual_gamepad;
std::shared_ptr<InputFactory> keyboard_factory;
std::shared_ptr<InputFactory> mouse_factory;
std::shared_ptr<InputFactory> gcadapter_input_factory;
std::shared_ptr<InputFactory> touch_screen_factory;
std::shared_ptr<InputFactory> udp_client_input_factory;
std::shared_ptr<InputFactory> tas_input_factory;
std::shared_ptr<InputFactory> camera_input_factory;
std::shared_ptr<InputFactory> virtual_amiibo_input_factory;
std::shared_ptr<InputFactory> virtual_gamepad_input_factory;
std::shared_ptr<OutputFactory> keyboard_output_factory;
std::shared_ptr<OutputFactory> mouse_output_factory;
std::shared_ptr<OutputFactory> gcadapter_output_factory;
std::shared_ptr<OutputFactory> udp_client_output_factory;
std::shared_ptr<OutputFactory> tas_output_factory;
std::shared_ptr<OutputFactory> camera_output_factory;
std::shared_ptr<OutputFactory> virtual_amiibo_output_factory;
#ifdef HAVE_SDL2
std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<InputFactory> sdl_input_factory;
std::shared_ptr<OutputFactory> sdl_output_factory;
#endif
};

View file

@ -8,6 +8,7 @@ add_executable(tests
common/host_memory.cpp
common/param_package.cpp
common/ring_buffer.cpp
common/scratch_buffer.cpp
common/unique_function.cpp
core/core_timing.cpp
core/internal_network/network.cpp

View file

@ -0,0 +1,200 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <cstring>
#include <span>
#include <catch2/catch.hpp>
#include "common/common_types.h"
#include "common/scratch_buffer.h"
namespace Common {
TEST_CASE("ScratchBuffer: Basic Test", "[common]") {
ScratchBuffer<u8> buf;
REQUIRE(buf.size() == 0U);
REQUIRE(buf.capacity() == 0U);
std::array<u8, 10> payload;
payload.fill(66);
buf.resize(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
std::memcpy(buf.data(), payload.data(), payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
}
TEST_CASE("ScratchBuffer: resize_destructive Grow", "[common]") {
std::array<u8, 10> payload;
payload.fill(66);
ScratchBuffer<u8> buf(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
// Increasing the size should reallocate the buffer
buf.resize_destructive(payload.size() * 2);
REQUIRE(buf.size() == payload.size() * 2);
REQUIRE(buf.capacity() == payload.size() * 2);
// Since the buffer is not value initialized, reading its data will be garbage
}
TEST_CASE("ScratchBuffer: resize_destructive Shrink", "[common]") {
std::array<u8, 10> payload;
payload.fill(66);
ScratchBuffer<u8> buf(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
std::memcpy(buf.data(), payload.data(), payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
// Decreasing the size should not cause a buffer reallocation
// This can be tested by ensuring the buffer capacity and data has not changed,
buf.resize_destructive(1U);
REQUIRE(buf.size() == 1U);
REQUIRE(buf.capacity() == payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
}
TEST_CASE("ScratchBuffer: resize Grow u8", "[common]") {
std::array<u8, 10> payload;
payload.fill(66);
ScratchBuffer<u8> buf(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
std::memcpy(buf.data(), payload.data(), payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
// Increasing the size should reallocate the buffer
buf.resize(payload.size() * 2);
REQUIRE(buf.size() == payload.size() * 2);
REQUIRE(buf.capacity() == payload.size() * 2);
// resize() keeps the previous data intact
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
}
TEST_CASE("ScratchBuffer: resize Grow u64", "[common]") {
std::array<u64, 10> payload;
payload.fill(6666);
ScratchBuffer<u64> buf(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
std::memcpy(buf.data(), payload.data(), payload.size() * sizeof(u64));
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
// Increasing the size should reallocate the buffer
buf.resize(payload.size() * 2);
REQUIRE(buf.size() == payload.size() * 2);
REQUIRE(buf.capacity() == payload.size() * 2);
// resize() keeps the previous data intact
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
}
TEST_CASE("ScratchBuffer: resize Shrink", "[common]") {
std::array<u8, 10> payload;
payload.fill(66);
ScratchBuffer<u8> buf(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
std::memcpy(buf.data(), payload.data(), payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
// Decreasing the size should not cause a buffer reallocation
// This can be tested by ensuring the buffer capacity and data has not changed,
buf.resize(1U);
REQUIRE(buf.size() == 1U);
REQUIRE(buf.capacity() == payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
}
TEST_CASE("ScratchBuffer: Span Size", "[common]") {
std::array<u8, 10> payload;
payload.fill(66);
ScratchBuffer<u8> buf(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
std::memcpy(buf.data(), payload.data(), payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
buf.resize(3U);
REQUIRE(buf.size() == 3U);
REQUIRE(buf.capacity() == payload.size());
const auto buf_span = std::span<u8>(buf);
// The span size is the last requested size of the buffer, not its capacity
REQUIRE(buf_span.size() == buf.size());
for (size_t i = 0; i < buf_span.size(); ++i) {
REQUIRE(buf_span[i] == buf[i]);
REQUIRE(buf_span[i] == payload[i]);
}
}
TEST_CASE("ScratchBuffer: Span Writes", "[common]") {
std::array<u8, 10> payload;
payload.fill(66);
ScratchBuffer<u8> buf(payload.size());
REQUIRE(buf.size() == payload.size());
REQUIRE(buf.capacity() == payload.size());
std::memcpy(buf.data(), payload.data(), payload.size());
for (size_t i = 0; i < payload.size(); ++i) {
REQUIRE(buf[i] == payload[i]);
}
buf.resize(3U);
REQUIRE(buf.size() == 3U);
REQUIRE(buf.capacity() == payload.size());
const auto buf_span = std::span<u8>(buf);
REQUIRE(buf_span.size() == buf.size());
for (size_t i = 0; i < buf_span.size(); ++i) {
const auto new_value = static_cast<u8>(i + 1U);
// Writes to a span of the scratch buffer will propogate to the buffer itself
buf_span[i] = new_value;
REQUIRE(buf[i] == new_value);
}
}
} // namespace Common

View file

@ -20,6 +20,7 @@
#include "common/lru_cache.h"
#include "common/microprofile.h"
#include "common/polyfill_ranges.h"
#include "common/scratch_buffer.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/buffer_cache/buffer_base.h"
@ -422,8 +423,7 @@ private:
IntervalSet common_ranges;
std::deque<IntervalSet> committed_ranges;
size_t immediate_buffer_capacity = 0;
std::unique_ptr<u8[]> immediate_buffer_alloc;
Common::ScratchBuffer<u8> immediate_buffer_alloc;
struct LRUItemParams {
using ObjectType = BufferId;
@ -666,9 +666,10 @@ void BufferCache<P>::BindHostGeometryBuffers(bool is_indexed) {
BindHostIndexBuffer();
} else if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT) {
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads) {
runtime.BindQuadArrayIndexBuffer(draw_state.vertex_buffer.first,
draw_state.vertex_buffer.count);
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads ||
draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
runtime.BindQuadIndexBuffer(draw_state.topology, draw_state.vertex_buffer.first,
draw_state.vertex_buffer.count);
}
}
BindHostVertexBuffers();
@ -1926,11 +1927,8 @@ std::span<const u8> BufferCache<P>::ImmediateBufferWithData(VAddr cpu_addr, size
template <class P>
std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) {
if (wanted_capacity > immediate_buffer_capacity) {
immediate_buffer_capacity = wanted_capacity;
immediate_buffer_alloc = std::make_unique<u8[]>(wanted_capacity);
}
return std::span<u8>(immediate_buffer_alloc.get(), wanted_capacity);
immediate_buffer_alloc.resize_destructive(wanted_capacity);
return std::span<u8>(immediate_buffer_alloc.data(), wanted_capacity);
}
template <class P>

View file

@ -56,7 +56,7 @@ bool DmaPusher::Step() {
if (command_list.prefetch_command_list.size()) {
// Prefetched command list from nvdrv, used for things like synchronization
command_headers = std::move(command_list.prefetch_command_list);
ProcessCommands(command_list.prefetch_command_list);
dma_pushbuffer.pop();
} else {
const CommandListHeader command_list_header{
@ -74,7 +74,7 @@ bool DmaPusher::Step() {
}
// Push buffer non-empty, read a word
command_headers.resize(command_list_header.size);
command_headers.resize_destructive(command_list_header.size);
if (Settings::IsGPULevelHigh()) {
memory_manager.ReadBlock(dma_get, command_headers.data(),
command_list_header.size * sizeof(u32));
@ -82,16 +82,21 @@ bool DmaPusher::Step() {
memory_manager.ReadBlockUnsafe(dma_get, command_headers.data(),
command_list_header.size * sizeof(u32));
}
ProcessCommands(command_headers);
}
for (std::size_t index = 0; index < command_headers.size();) {
const CommandHeader& command_header = command_headers[index];
return true;
}
void DmaPusher::ProcessCommands(std::span<const CommandHeader> commands) {
for (std::size_t index = 0; index < commands.size();) {
const CommandHeader& command_header = commands[index];
if (dma_state.method_count) {
// Data word of methods command
if (dma_state.non_incrementing) {
const u32 max_write = static_cast<u32>(
std::min<std::size_t>(index + dma_state.method_count, command_headers.size()) -
index);
std::min<std::size_t>(index + dma_state.method_count, commands.size()) - index);
CallMultiMethod(&command_header.argument, max_write);
dma_state.method_count -= max_write;
dma_state.is_last_call = true;
@ -142,8 +147,6 @@ bool DmaPusher::Step() {
}
index++;
}
return true;
}
void DmaPusher::SetState(const CommandHeader& command_header) {

View file

@ -4,11 +4,13 @@
#pragma once
#include <array>
#include <span>
#include <vector>
#include <queue>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/scratch_buffer.h"
#include "video_core/engines/engine_interface.h"
#include "video_core/engines/puller.h"
@ -136,13 +138,15 @@ private:
static constexpr u32 non_puller_methods = 0x40;
static constexpr u32 max_subchannels = 8;
bool Step();
void ProcessCommands(std::span<const CommandHeader> commands);
void SetState(const CommandHeader& command_header);
void CallMethod(u32 argument) const;
void CallMultiMethod(const u32* base_start, u32 num_methods) const;
std::vector<CommandHeader> command_headers; ///< Buffer for list of commands fetched at once
Common::ScratchBuffer<CommandHeader>
command_headers; ///< Buffer for list of commands fetched at once
std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed
std::size_t dma_pushbuffer_subindex{}; ///< Index within a command list within the pushbuffer
@ -159,7 +163,7 @@ private:
DmaState dma_state{};
bool dma_increment_once{};
bool ib_enable{true}; ///< IB mode enabled
const bool ib_enable{true}; ///< IB mode enabled
std::array<Engines::EngineInterface*, max_subchannels> subchannels{};

View file

@ -24,7 +24,7 @@ void State::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
void State::ProcessExec(const bool is_linear_) {
write_offset = 0;
copy_size = regs.line_length_in * regs.line_count;
inner_buffer.resize(copy_size);
inner_buffer.resize_destructive(copy_size);
is_linear = is_linear_;
}
@ -70,7 +70,7 @@ void State::ProcessData(std::span<const u8> read_buffer) {
const std::size_t dst_size = Tegra::Texture::CalculateSize(
true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth,
regs.dest.BlockHeight(), regs.dest.BlockDepth());
tmp_buffer.resize(dst_size);
tmp_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width,
regs.dest.height, regs.dest.depth, x_offset, regs.dest.y,

View file

@ -4,9 +4,10 @@
#pragma once
#include <span>
#include <vector>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/scratch_buffer.h"
namespace Tegra {
class MemoryManager;
@ -73,8 +74,8 @@ private:
u32 write_offset = 0;
u32 copy_size = 0;
std::vector<u8> inner_buffer;
std::vector<u8> tmp_buffer;
Common::ScratchBuffer<u8> inner_buffer;
Common::ScratchBuffer<u8> tmp_buffer;
bool is_linear = false;
Registers& regs;
MemoryManager& memory_manager;

View file

@ -184,12 +184,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
const size_t src_size =
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
if (read_buffer.size() < src_size) {
read_buffer.resize(src_size);
}
if (write_buffer.size() < dst_size) {
write_buffer.resize(dst_size);
}
read_buffer.resize_destructive(src_size);
write_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
@ -235,12 +231,8 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
if (read_buffer.size() < src_size) {
read_buffer.resize(src_size);
}
if (write_buffer.size() < dst_size) {
write_buffer.resize(dst_size);
}
read_buffer.resize_destructive(src_size);
write_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
if (Settings::IsGPULevelExtreme()) {
@ -269,12 +261,8 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
pos_x = pos_x % x_in_gob;
pos_y = pos_y % 8;
if (read_buffer.size() < src_size) {
read_buffer.resize(src_size);
}
if (write_buffer.size() < dst_size) {
write_buffer.resize(dst_size);
}
read_buffer.resize_destructive(src_size);
write_buffer.resize_destructive(dst_size);
if (Settings::IsGPULevelExtreme()) {
memory_manager.ReadBlock(regs.offset_in + offset, read_buffer.data(), src_size);
@ -333,14 +321,10 @@ void MaxwellDMA::CopyBlockLinearToBlockLinear() {
const u32 pitch = x_elements * bytes_per_pixel;
const size_t mid_buffer_size = pitch * regs.line_count;
if (read_buffer.size() < src_size) {
read_buffer.resize(src_size);
}
if (write_buffer.size() < dst_size) {
write_buffer.resize(dst_size);
}
read_buffer.resize_destructive(src_size);
write_buffer.resize_destructive(dst_size);
intermediate_buffer.resize(mid_buffer_size);
intermediate_buffer.resize_destructive(mid_buffer_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);

View file

@ -6,8 +6,10 @@
#include <array>
#include <cstddef>
#include <vector>
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/scratch_buffer.h"
#include "video_core/engines/engine_interface.h"
namespace Core {
@ -234,9 +236,9 @@ private:
MemoryManager& memory_manager;
VideoCore::RasterizerInterface* rasterizer = nullptr;
std::vector<u8> read_buffer;
std::vector<u8> write_buffer;
std::vector<u8> intermediate_buffer;
Common::ScratchBuffer<u8> read_buffer;
Common::ScratchBuffer<u8> write_buffer;
Common::ScratchBuffer<u8> intermediate_buffer;
static constexpr std::size_t NUM_REGS = 0x800;
struct Regs {

View file

@ -155,7 +155,7 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
// swizzle pitch linear to block linear
const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
const auto size = Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
luma_buffer.resize(size);
luma_buffer.resize_destructive(size);
std::span<const u8> frame_buff(converted_frame_buf_addr, 4 * width * height);
Texture::SwizzleSubrect(luma_buffer, frame_buff, 4, width, height, 1, 0, 0, width, height,
block_height, 0, width * 4);
@ -181,8 +181,8 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
const auto stride = static_cast<size_t>(frame->linesize[0]);
luma_buffer.resize(aligned_width * surface_height);
chroma_buffer.resize(aligned_width * surface_height / 2);
luma_buffer.resize_destructive(aligned_width * surface_height);
chroma_buffer.resize_destructive(aligned_width * surface_height / 2);
// Populate luma buffer
const u8* luma_src = frame->data[0];

View file

@ -4,8 +4,9 @@
#pragma once
#include <memory>
#include <vector>
#include "common/common_types.h"
#include "common/scratch_buffer.h"
struct SwsContext;
@ -49,8 +50,8 @@ private:
/// size does not change during a stream
using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;
AVMallocPtr converted_frame_buffer;
std::vector<u8> luma_buffer;
std::vector<u8> chroma_buffer;
Common::ScratchBuffer<u8> luma_buffer;
Common::ScratchBuffer<u8> chroma_buffer;
GPUVAddr config_struct_address{};
GPUVAddr output_surface_luma_address{};

View file

@ -16,6 +16,7 @@ layout (std430, set = 0, binding = 1) writeonly buffer OutputBuffer {
layout (push_constant) uniform PushConstants {
uint base_vertex;
int index_shift; // 0: uint8, 1: uint16, 2: uint32
int is_strip; // 0: quads 1: quadstrip
};
void main() {
@ -28,9 +29,10 @@ void main() {
int flipped_shift = 2 - index_shift;
int mask = (1 << flipped_shift) - 1;
const int quad_swizzle[6] = int[](0, 1, 2, 0, 2, 3);
const int quads_swizzle[6] = int[](0, 1, 2, 0, 2, 3);
const int quad_strip_swizzle[6] = int[](0, 3, 1, 0, 2, 3);
for (uint vertex = 0; vertex < 6; ++vertex) {
int offset = primitive * 4 + quad_swizzle[vertex];
int offset = (is_strip == 0 ? primitive * 4 + quads_swizzle[vertex] : primitive * 2 + quad_strip_swizzle[vertex]);
int int_offset = offset >> flipped_shift;
int bit_offset = (offset & mask) * index_size;
uint packed_input = input_indexes[int_offset];

View file

@ -301,6 +301,8 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
return VK_PRIMITIVE_TOPOLOGY_POINT_LIST;
case Maxwell::PrimitiveTopology::Lines:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST;
case Maxwell::PrimitiveTopology::LineLoop:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
case Maxwell::PrimitiveTopology::LineStrip:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP;
case Maxwell::PrimitiveTopology::Triangles:
@ -309,15 +311,28 @@ VkPrimitiveTopology PrimitiveTopology([[maybe_unused]] const Device& device,
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP;
case Maxwell::PrimitiveTopology::TriangleFan:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
case Maxwell::PrimitiveTopology::LinesAdjacency:
return VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY;
case Maxwell::PrimitiveTopology::LineStripAdjacency:
return VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY;
case Maxwell::PrimitiveTopology::TrianglesAdjacency:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY;
case Maxwell::PrimitiveTopology::TriangleStripAdjacency:
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY;
case Maxwell::PrimitiveTopology::Quads:
// TODO(Rodrigo): Use VK_PRIMITIVE_TOPOLOGY_QUAD_LIST_EXT whenever it releases
case Maxwell::PrimitiveTopology::QuadStrip:
// TODO: Use VK_PRIMITIVE_TOPOLOGY_QUAD_LIST_EXT/VK_PRIMITIVE_TOPOLOGY_QUAD_STRIP_EXT
// whenever it releases
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
case Maxwell::PrimitiveTopology::Patches:
return VK_PRIMITIVE_TOPOLOGY_PATCH_LIST;
default:
UNIMPLEMENTED_MSG("Unimplemented topology={}", topology);
return {};
case Maxwell::PrimitiveTopology::Polygon:
LOG_WARNING(Render_Vulkan, "Draw mode is Polygon with a polygon mode of lines should be a "
"single body and not a bunch of triangles.");
return VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN;
}
UNIMPLEMENTED_MSG("Unimplemented topology={}", topology);
return {};
}
VkFormat VertexFormat(const Device& device, Maxwell::VertexAttribute::Type type,

View file

@ -51,15 +51,6 @@ size_t BytesPerIndex(VkIndexType index_type) {
}
}
template <typename T>
std::array<T, 6> MakeQuadIndices(u32 quad, u32 first) {
std::array<T, 6> indices{0, 1, 2, 0, 2, 3};
for (T& index : indices) {
index = static_cast<T>(first + index + quad * 4);
}
return indices;
}
vk::Buffer CreateBuffer(const Device& device, u64 size) {
VkBufferUsageFlags flags =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
@ -123,6 +114,187 @@ VkBufferView Buffer::View(u32 offset, u32 size, VideoCore::Surface::PixelFormat
return *views.back().handle;
}
class QuadIndexBuffer {
public:
QuadIndexBuffer(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_)
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
staging_pool{staging_pool_} {}
virtual ~QuadIndexBuffer() = default;
void UpdateBuffer(u32 num_indices_) {
if (num_indices_ <= num_indices) {
return;
}
scheduler.Finish();
num_indices = num_indices_;
index_type = IndexTypeFromNumElements(device, num_indices);
const u32 num_quads = GetQuadsNum(num_indices);
const u32 num_triangle_indices = num_quads * 6;
const u32 num_first_offset_copies = 4;
const size_t bytes_per_index = BytesPerIndex(index_type);
const size_t size_bytes = num_triangle_indices * bytes_per_index * num_first_offset_copies;
buffer = device.GetLogical().CreateBuffer(VkBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = size_bytes,
.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
});
if (device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT("Quad LUT");
}
memory_commit = memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
const StagingBufferRef staging = staging_pool.Request(size_bytes, MemoryUsage::Upload);
u8* staging_data = staging.mapped_span.data();
const size_t quad_size = bytes_per_index * 6;
for (u32 first = 0; first < num_first_offset_copies; ++first) {
for (u32 quad = 0; quad < num_quads; ++quad) {
MakeAndUpdateIndices(staging_data, quad_size, quad, first);
staging_data += quad_size;
}
}
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([src_buffer = staging.buffer, src_offset = staging.offset,
dst_buffer = *buffer, size_bytes](vk::CommandBuffer cmdbuf) {
const VkBufferCopy copy{
.srcOffset = src_offset,
.dstOffset = 0,
.size = size_bytes,
};
const VkBufferMemoryBarrier write_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_INDEX_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = dst_buffer,
.offset = 0,
.size = size_bytes,
};
cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_VERTEX_INPUT_BIT, 0, write_barrier);
});
}
void BindBuffer(u32 first) {
const VkIndexType index_type_ = index_type;
const size_t sub_first_offset = static_cast<size_t>(first % 4) * GetQuadsNum(num_indices);
const size_t offset =
(sub_first_offset + GetQuadsNum(first)) * 6ULL * BytesPerIndex(index_type);
scheduler.Record([buffer = *buffer, index_type_, offset](vk::CommandBuffer cmdbuf) {
cmdbuf.BindIndexBuffer(buffer, offset, index_type_);
});
}
protected:
virtual u32 GetQuadsNum(u32 num_indices) const = 0;
virtual void MakeAndUpdateIndices(u8* staging_data, size_t quad_size, u32 quad, u32 first) = 0;
const Device& device;
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
StagingBufferPool& staging_pool;
vk::Buffer buffer{};
MemoryCommit memory_commit{};
VkIndexType index_type{};
u32 num_indices = 0;
};
class QuadArrayIndexBuffer : public QuadIndexBuffer {
public:
QuadArrayIndexBuffer(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_)
: QuadIndexBuffer(device_, memory_allocator_, scheduler_, staging_pool_) {}
~QuadArrayIndexBuffer() = default;
private:
u32 GetQuadsNum(u32 num_indices_) const override {
return num_indices_ / 4;
}
template <typename T>
static std::array<T, 6> MakeIndices(u32 quad, u32 first) {
std::array<T, 6> indices{0, 1, 2, 0, 2, 3};
for (T& index : indices) {
index = static_cast<T>(first + index + quad * 4);
}
return indices;
}
void MakeAndUpdateIndices(u8* staging_data, size_t quad_size, u32 quad, u32 first) {
switch (index_type) {
case VK_INDEX_TYPE_UINT8_EXT:
std::memcpy(staging_data, MakeIndices<u8>(quad, first).data(), quad_size);
break;
case VK_INDEX_TYPE_UINT16:
std::memcpy(staging_data, MakeIndices<u16>(quad, first).data(), quad_size);
break;
case VK_INDEX_TYPE_UINT32:
std::memcpy(staging_data, MakeIndices<u32>(quad, first).data(), quad_size);
break;
default:
ASSERT(false);
break;
}
}
};
class QuadStripIndexBuffer : public QuadIndexBuffer {
public:
QuadStripIndexBuffer(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_)
: QuadIndexBuffer(device_, memory_allocator_, scheduler_, staging_pool_) {}
~QuadStripIndexBuffer() = default;
private:
u32 GetQuadsNum(u32 num_indices_) const override {
return num_indices_ >= 4 ? (num_indices_ - 2) / 2 : 0;
}
template <typename T>
static std::array<T, 6> MakeIndices(u32 quad, u32 first) {
std::array<T, 6> indices{0, 3, 1, 0, 2, 3};
for (T& index : indices) {
index = static_cast<T>(first + index + quad * 2);
}
return indices;
}
void MakeAndUpdateIndices(u8* staging_data, size_t quad_size, u32 quad, u32 first) {
switch (index_type) {
case VK_INDEX_TYPE_UINT8_EXT:
std::memcpy(staging_data, MakeIndices<u8>(quad, first).data(), quad_size);
break;
case VK_INDEX_TYPE_UINT16:
std::memcpy(staging_data, MakeIndices<u16>(quad, first).data(), quad_size);
break;
case VK_INDEX_TYPE_UINT32:
std::memcpy(staging_data, MakeIndices<u32>(quad, first).data(), quad_size);
break;
default:
ASSERT(false);
break;
}
}
};
BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_,
UpdateDescriptorQueue& update_descriptor_queue_,
@ -130,7 +302,12 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
staging_pool{staging_pool_}, update_descriptor_queue{update_descriptor_queue_},
uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
quad_index_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue) {}
quad_index_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue) {
quad_array_index_buffer = std::make_shared<QuadArrayIndexBuffer>(device_, memory_allocator_,
scheduler_, staging_pool_);
quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(device_, memory_allocator_,
scheduler_, staging_pool_);
}
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Upload);
@ -245,10 +422,11 @@ void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat
VkIndexType vk_index_type = MaxwellToVK::IndexFormat(index_format);
VkDeviceSize vk_offset = offset;
VkBuffer vk_buffer = buffer;
if (topology == PrimitiveTopology::Quads) {
if (topology == PrimitiveTopology::Quads || topology == PrimitiveTopology::QuadStrip) {
vk_index_type = VK_INDEX_TYPE_UINT32;
std::tie(vk_buffer, vk_offset) =
quad_index_pass.Assemble(index_format, num_indices, base_vertex, buffer, offset);
quad_index_pass.Assemble(index_format, num_indices, base_vertex, buffer, offset,
topology == PrimitiveTopology::QuadStrip);
} else if (vk_index_type == VK_INDEX_TYPE_UINT8_EXT && !device.IsExtIndexTypeUint8Supported()) {
vk_index_type = VK_INDEX_TYPE_UINT16;
std::tie(vk_buffer, vk_offset) = uint8_pass.Assemble(num_indices, buffer, offset);
@ -263,7 +441,7 @@ void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat
});
}
void BufferCacheRuntime::BindQuadArrayIndexBuffer(u32 first, u32 count) {
void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count) {
if (count == 0) {
ReserveNullBuffer();
scheduler.Record([this](vk::CommandBuffer cmdbuf) {
@ -271,16 +449,14 @@ void BufferCacheRuntime::BindQuadArrayIndexBuffer(u32 first, u32 count) {
});
return;
}
ReserveQuadArrayLUT(first + count, true);
// The LUT has the indices 0, 1, 2, and 3 copied as an array
// To apply these 'first' offsets we can apply an offset based on the modulus.
const VkIndexType index_type = quad_array_lut_index_type;
const size_t sub_first_offset = static_cast<size_t>(first % 4) * (current_num_indices / 4);
const size_t offset = (sub_first_offset + first / 4) * 6ULL * BytesPerIndex(index_type);
scheduler.Record([buffer = *quad_array_lut, index_type, offset](vk::CommandBuffer cmdbuf) {
cmdbuf.BindIndexBuffer(buffer, offset, index_type);
});
if (topology == PrimitiveTopology::Quads) {
quad_array_index_buffer->UpdateBuffer(first + count);
quad_array_index_buffer->BindBuffer(first);
} else if (topology == PrimitiveTopology::QuadStrip) {
quad_strip_index_buffer->UpdateBuffer(first + count);
quad_strip_index_buffer->BindBuffer(first);
}
}
void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size,
@ -323,83 +499,6 @@ void BufferCacheRuntime::BindTransformFeedbackBuffer(u32 index, VkBuffer buffer,
});
}
void BufferCacheRuntime::ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle) {
if (num_indices <= current_num_indices) {
return;
}
if (wait_for_idle) {
scheduler.Finish();
}
current_num_indices = num_indices;
quad_array_lut_index_type = IndexTypeFromNumElements(device, num_indices);
const u32 num_quads = num_indices / 4;
const u32 num_triangle_indices = num_quads * 6;
const u32 num_first_offset_copies = 4;
const size_t bytes_per_index = BytesPerIndex(quad_array_lut_index_type);
const size_t size_bytes = num_triangle_indices * bytes_per_index * num_first_offset_copies;
quad_array_lut = device.GetLogical().CreateBuffer(VkBufferCreateInfo{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
.size = size_bytes,
.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
});
if (device.HasDebuggingToolAttached()) {
quad_array_lut.SetObjectNameEXT("Quad LUT");
}
quad_array_lut_commit = memory_allocator.Commit(quad_array_lut, MemoryUsage::DeviceLocal);
const StagingBufferRef staging = staging_pool.Request(size_bytes, MemoryUsage::Upload);
u8* staging_data = staging.mapped_span.data();
const size_t quad_size = bytes_per_index * 6;
for (u32 first = 0; first < num_first_offset_copies; ++first) {
for (u32 quad = 0; quad < num_quads; ++quad) {
switch (quad_array_lut_index_type) {
case VK_INDEX_TYPE_UINT8_EXT:
std::memcpy(staging_data, MakeQuadIndices<u8>(quad, first).data(), quad_size);
break;
case VK_INDEX_TYPE_UINT16:
std::memcpy(staging_data, MakeQuadIndices<u16>(quad, first).data(), quad_size);
break;
case VK_INDEX_TYPE_UINT32:
std::memcpy(staging_data, MakeQuadIndices<u32>(quad, first).data(), quad_size);
break;
default:
ASSERT(false);
break;
}
staging_data += quad_size;
}
}
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([src_buffer = staging.buffer, src_offset = staging.offset,
dst_buffer = *quad_array_lut, size_bytes](vk::CommandBuffer cmdbuf) {
const VkBufferCopy copy{
.srcOffset = src_offset,
.dstOffset = 0,
.size = size_bytes,
};
const VkBufferMemoryBarrier write_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_INDEX_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = dst_buffer,
.offset = 0,
.size = size_bytes,
};
cmdbuf.CopyBuffer(src_buffer, dst_buffer, copy);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_VERTEX_INPUT_BIT,
0, write_barrier);
});
}
void BufferCacheRuntime::ReserveNullBuffer() {
if (null_buffer) {
return;

View file

@ -50,6 +50,9 @@ private:
std::vector<BufferView> views;
};
class QuadArrayIndexBuffer;
class QuadStripIndexBuffer;
class BufferCacheRuntime {
friend Buffer;
@ -86,7 +89,7 @@ public:
void BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format, u32 num_indices,
u32 base_vertex, VkBuffer buffer, u32 offset, u32 size);
void BindQuadArrayIndexBuffer(u32 first, u32 count);
void BindQuadIndexBuffer(PrimitiveTopology topology, u32 first, u32 count);
void BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size, u32 stride);
@ -118,8 +121,6 @@ private:
update_descriptor_queue.AddBuffer(buffer, offset, size);
}
void ReserveQuadArrayLUT(u32 num_indices, bool wait_for_idle);
void ReserveNullBuffer();
const Device& device;
@ -128,10 +129,8 @@ private:
StagingBufferPool& staging_pool;
UpdateDescriptorQueue& update_descriptor_queue;
vk::Buffer quad_array_lut;
MemoryCommit quad_array_lut_commit;
VkIndexType quad_array_lut_index_type{};
u32 current_num_indices = 0;
std::shared_ptr<QuadArrayIndexBuffer> quad_array_index_buffer;
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
vk::Buffer null_buffer;
MemoryCommit null_buffer_commit;

View file

@ -245,7 +245,7 @@ QuadIndexedPass::QuadIndexedPass(const Device& device_, Scheduler& scheduler_,
UpdateDescriptorQueue& update_descriptor_queue_)
: ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(u32) * 2>, VULKAN_QUAD_INDEXED_COMP_SPV),
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(u32) * 3>, VULKAN_QUAD_INDEXED_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
update_descriptor_queue{update_descriptor_queue_} {}
@ -253,7 +253,7 @@ QuadIndexedPass::~QuadIndexedPass() = default;
std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, u32 num_vertices, u32 base_vertex,
VkBuffer src_buffer, u32 src_offset) {
VkBuffer src_buffer, u32 src_offset, bool is_strip) {
const u32 index_shift = [index_format] {
switch (index_format) {
case Tegra::Engines::Maxwell3D::Regs::IndexFormat::UnsignedByte:
@ -267,7 +267,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
return 2;
}();
const u32 input_size = num_vertices << index_shift;
const u32 num_tri_vertices = (num_vertices / 4) * 6;
const u32 num_tri_vertices = (is_strip ? (num_vertices - 2) / 2 : num_vertices / 4) * 6;
const std::size_t staging_size = num_tri_vertices * sizeof(u32);
const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
@ -278,8 +278,8 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
const void* const descriptor_data{update_descriptor_queue.UpdateData()};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, descriptor_data, num_tri_vertices, base_vertex,
index_shift](vk::CommandBuffer cmdbuf) {
scheduler.Record([this, descriptor_data, num_tri_vertices, base_vertex, index_shift,
is_strip](vk::CommandBuffer cmdbuf) {
static constexpr u32 DISPATCH_SIZE = 1024;
static constexpr VkMemoryBarrier WRITE_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
@ -287,7 +287,7 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_INDEX_READ_BIT,
};
const std::array<u32, 2> push_constants{base_vertex, index_shift};
const std::array<u32, 3> push_constants{base_vertex, index_shift, is_strip ? 1u : 0u};
const VkDescriptorSet set = descriptor_allocator.Commit();
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);

View file

@ -74,7 +74,7 @@ public:
std::pair<VkBuffer, VkDeviceSize> Assemble(
Tegra::Engines::Maxwell3D::Regs::IndexFormat index_format, u32 num_vertices,
u32 base_vertex, VkBuffer src_buffer, u32 src_offset);
u32 base_vertex, VkBuffer src_buffer, u32 src_offset, bool is_strip);
private:
Scheduler& scheduler;

View file

@ -138,12 +138,16 @@ DrawParams MakeDrawParams(const MaxwellDrawState& draw_state, u32 num_instances,
.first_index = is_indexed ? draw_state.index_buffer.first : 0,
.is_indexed = is_indexed,
};
// 6 triangle vertices per quad, base vertex is part of the index
// See BindQuadIndexBuffer for more details
if (draw_state.topology == Maxwell::PrimitiveTopology::Quads) {
// 6 triangle vertices per quad, base vertex is part of the index
// See BindQuadArrayIndexBuffer for more details
params.num_vertices = (params.num_vertices / 4) * 6;
params.base_vertex = 0;
params.is_indexed = true;
} else if (draw_state.topology == Maxwell::PrimitiveTopology::QuadStrip) {
params.num_vertices = (params.num_vertices - 2) / 2 * 6;
params.base_vertex = 0;
params.is_indexed = true;
}
return params;
}

View file

@ -314,6 +314,18 @@ const char* ToString(VkResult result) noexcept {
return "VK_ERROR_VALIDATION_FAILED_EXT";
case VkResult::VK_ERROR_INVALID_SHADER_NV:
return "VK_ERROR_INVALID_SHADER_NV";
case VkResult::VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR:
return "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR";
case VkResult::VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR:
return "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR";
case VkResult::VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR:
return "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR";
case VkResult::VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR:
return "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR";
case VkResult::VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR:
return "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR";
case VkResult::VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR:
return "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR";
case VkResult::VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
case VkResult::VK_ERROR_FRAGMENTATION_EXT:

View file

@ -46,30 +46,28 @@
static Core::Frontend::WindowSystemType GetWindowSystemType();
EmuThread::EmuThread(Core::System& system_) : system{system_} {}
EmuThread::EmuThread(Core::System& system) : m_system{system} {}
EmuThread::~EmuThread() = default;
void EmuThread::run() {
std::string name = "EmuControlThread";
MicroProfileOnThreadCreate(name.c_str());
Common::SetCurrentThreadName(name.c_str());
const char* name = "EmuControlThread";
MicroProfileOnThreadCreate(name);
Common::SetCurrentThreadName(name);
auto& gpu = system.GPU();
auto stop_token = stop_source.get_token();
bool debugger_should_start = system.DebuggerEnabled();
auto& gpu = m_system.GPU();
auto stop_token = m_stop_source.get_token();
system.RegisterHostThread();
m_system.RegisterHostThread();
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
// execution.
gpu.ObtainContext();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
if (Settings::values.use_disk_shader_cache.GetValue()) {
system.Renderer().ReadRasterizer()->LoadDiskResources(
system.GetCurrentProcessProgramID(), stop_token,
m_system.Renderer().ReadRasterizer()->LoadDiskResources(
m_system.GetCurrentProcessProgramID(), stop_token,
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
@ -79,57 +77,34 @@ void EmuThread::run() {
gpu.ReleaseContext();
gpu.Start();
system.GetCpuManager().OnGpuReady();
m_system.GetCpuManager().OnGpuReady();
system.RegisterExitCallback([this]() {
stop_source.request_stop();
SetRunning(false);
});
if (m_system.DebuggerEnabled()) {
m_system.InitializeDebugger();
}
// Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step
bool was_active = false;
while (!stop_token.stop_requested()) {
if (running) {
if (was_active) {
emit DebugModeLeft();
}
std::unique_lock lk{m_should_run_mutex};
if (m_should_run) {
m_system.Run();
m_is_running.store(true);
m_is_running.notify_all();
running_guard = true;
Core::SystemResultStatus result = system.Run();
if (result != Core::SystemResultStatus::Success) {
running_guard = false;
this->SetRunning(false);
emit ErrorThrown(result, system.GetStatusDetails());
}
if (debugger_should_start) {
system.InitializeDebugger();
debugger_should_start = false;
}
running_wait.Wait();
result = system.Pause();
if (result != Core::SystemResultStatus::Success) {
running_guard = false;
this->SetRunning(false);
emit ErrorThrown(result, system.GetStatusDetails());
}
running_guard = false;
if (!stop_token.stop_requested()) {
was_active = true;
emit DebugModeEntered();
}
Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return !m_should_run; });
} else {
std::unique_lock lock{running_mutex};
Common::CondvarWait(running_cv, lock, stop_token, [&] { return IsRunning(); });
m_system.Pause();
m_is_running.store(false);
m_is_running.notify_all();
emit DebugModeEntered();
Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return m_should_run; });
emit DebugModeLeft();
}
}
// Shutdown the main emulated process
system.ShutdownMainProcess();
m_system.DetachDebugger();
m_system.ShutdownMainProcess();
#if MICROPROFILE_ENABLED
MicroProfileOnThreadExit();

View file

@ -47,7 +47,7 @@ class EmuThread final : public QThread {
Q_OBJECT
public:
explicit EmuThread(Core::System& system_);
explicit EmuThread(Core::System& system);
~EmuThread() override;
/**
@ -57,30 +57,30 @@ public:
void run() override;
/**
* Sets whether the emulation thread is running or not
* @param running_ Boolean value, set the emulation thread to running if true
* @note This function is thread-safe
* Sets whether the emulation thread should run or not
* @param should_run Boolean value, set the emulation thread to running if true
*/
void SetRunning(bool running_) {
std::unique_lock lock{running_mutex};
running = running_;
lock.unlock();
running_cv.notify_all();
if (!running) {
running_wait.Set();
/// Wait until effectively paused
while (running_guard)
;
void SetRunning(bool should_run) {
// TODO: Prevent other threads from modifying the state until we finish.
{
// Notify the running thread to change state.
std::unique_lock run_lk{m_should_run_mutex};
m_should_run = should_run;
m_should_run_cv.notify_one();
}
// Wait until paused, if pausing.
if (!should_run) {
m_is_running.wait(true);
}
}
/**
* Check if the emulation thread is running or not
* @return True if the emulation thread is running, otherwise false
* @note This function is thread-safe
*/
bool IsRunning() const {
return running;
return m_is_running.load() || m_should_run;
}
/**
@ -88,18 +88,17 @@ public:
*/
void ForceStop() {
LOG_WARNING(Frontend, "Force stopping EmuThread");
stop_source.request_stop();
SetRunning(false);
m_stop_source.request_stop();
}
private:
bool running = false;
std::stop_source stop_source;
std::mutex running_mutex;
std::condition_variable_any running_cv;
Common::Event running_wait{};
std::atomic_bool running_guard{false};
Core::System& system;
Core::System& m_system;
std::stop_source m_stop_source;
std::mutex m_should_run_mutex;
std::condition_variable_any m_should_run_cv;
std::atomic<bool> m_is_running{false};
bool m_should_run{true};
signals:
/**
@ -120,8 +119,6 @@ signals:
*/
void DebugModeLeft();
void ErrorThrown(Core::SystemResultStatus, std::string);
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
};

View file

@ -1497,7 +1497,7 @@ void GMainWindow::SetupSigInterrupts() {
void GMainWindow::HandleSigInterrupt(int sig) {
if (sig == SIGINT) {
exit(1);
_exit(1);
}
// Calling into Qt directly from a signal handler is not safe,
@ -1549,8 +1549,9 @@ void GMainWindow::AllowOSSleep() {
bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) {
// Shutdown previous session if the emu thread is still active...
if (emu_thread != nullptr)
if (emu_thread != nullptr) {
ShutdownGame();
}
if (!render_window->InitRenderTarget()) {
return false;
@ -1709,6 +1710,11 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
system->RegisterExecuteProgramCallback(
[this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); });
system->RegisterExitCallback([this] {
emu_thread->ForceStop();
render_window->Exit();
});
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
// BlockingQueuedConnection is important here, it makes sure we've finished refreshing our views
@ -1778,9 +1784,9 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
OnStartGame();
}
void GMainWindow::ShutdownGame() {
bool GMainWindow::OnShutdownBegin() {
if (!emulation_running) {
return;
return false;
}
if (ui->action_Fullscreen->isChecked()) {
@ -1792,21 +1798,55 @@ void GMainWindow::ShutdownGame() {
// Disable unlimited frame rate
Settings::values.use_speed_limit.SetValue(true);
if (system->IsShuttingDown()) {
return false;
}
system->SetShuttingDown(true);
system->DetachDebugger();
discord_rpc->Pause();
RequestGameExit();
emu_thread->disconnect();
emu_thread->SetRunning(true);
emit EmulationStopping();
// Wait for emulation thread to complete and delete it
if (!emu_thread->wait(5000)) {
shutdown_timer.setSingleShot(true);
shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000);
connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
// Disable everything to prevent anything from being triggered here
ui->action_Pause->setEnabled(false);
ui->action_Restart->setEnabled(false);
ui->action_Stop->setEnabled(false);
return true;
}
void GMainWindow::OnShutdownBeginDialog() {
shutdown_dialog = new OverlayDialog(this, *system, QString{}, tr("Closing software..."),
QString{}, QString{}, Qt::AlignHCenter | Qt::AlignVCenter);
shutdown_dialog->open();
}
void GMainWindow::OnEmulationStopTimeExpired() {
if (emu_thread) {
emu_thread->ForceStop();
emu_thread->wait();
}
}
void GMainWindow::OnEmulationStopped() {
shutdown_timer.stop();
emu_thread->disconnect();
emu_thread->wait();
emu_thread = nullptr;
if (shutdown_dialog) {
shutdown_dialog->deleteLater();
shutdown_dialog = nullptr;
}
emulation_running = false;
discord_rpc->Update();
@ -1852,6 +1892,20 @@ void GMainWindow::ShutdownGame() {
// When closing the game, destroy the GLWindow to clear the context after the game is closed
render_window->ReleaseRenderTarget();
Settings::RestoreGlobalState(system->IsPoweredOn());
system->HIDCore().ReloadInputDevices();
UpdateStatusButtons();
}
void GMainWindow::ShutdownGame() {
if (!emulation_running) {
return;
}
OnShutdownBegin();
OnEmulationStopTimeExpired();
OnEmulationStopped();
}
void GMainWindow::StoreRecentFile(const QString& filename) {
@ -2918,8 +2972,6 @@ void GMainWindow::OnStartGame() {
emu_thread->SetRunning(true);
connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
UpdateMenuState();
OnTasStateChanged();
@ -2956,11 +3008,9 @@ void GMainWindow::OnStopGame() {
return;
}
ShutdownGame();
Settings::RestoreGlobalState(system->IsPoweredOn());
system->HIDCore().ReloadInputDevices();
UpdateStatusButtons();
if (OnShutdownBegin()) {
OnShutdownBeginDialog();
}
}
void GMainWindow::OnLoadComplete() {
@ -3903,79 +3953,6 @@ void GMainWindow::OnMouseActivity() {
mouse_center_timer.stop();
}
void GMainWindow::OnCoreError(Core::SystemResultStatus result, std::string details) {
QMessageBox::StandardButton answer;
QString status_message;
const QString common_message =
tr("The game you are trying to load requires additional files from your Switch to be "
"dumped "
"before playing.<br/><br/>For more information on dumping these files, please see the "
"following wiki page: <a "
"href='https://yuzu-emu.org/wiki/"
"dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
"Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to "
"quit "
"back to the game list? Continuing emulation may result in crashes, corrupted save "
"data, or other bugs.");
switch (result) {
case Core::SystemResultStatus::ErrorSystemFiles: {
QString message;
if (details.empty()) {
message =
tr("yuzu was unable to locate a Switch system archive. %1").arg(common_message);
} else {
message = tr("yuzu was unable to locate a Switch system archive: %1. %2")
.arg(QString::fromStdString(details), common_message);
}
answer = QMessageBox::question(this, tr("System Archive Not Found"), message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
status_message = tr("System Archive Missing");
break;
}
case Core::SystemResultStatus::ErrorSharedFont: {
const QString message =
tr("yuzu was unable to locate the Switch shared fonts. %1").arg(common_message);
answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
status_message = tr("Shared Font Missing");
break;
}
default:
answer = QMessageBox::question(
this, tr("Fatal Error"),
tr("yuzu has encountered a fatal error, please see the log for more details. "
"For more information on accessing the log, please see the following page: "
"<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
"to "
"Upload the Log File</a>.<br/><br/>Would you like to quit back to the game "
"list? "
"Continuing emulation may result in crashes, corrupted save data, or other "
"bugs."),
QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
status_message = tr("Fatal Error encountered");
break;
}
if (answer == QMessageBox::Yes) {
if (emu_thread) {
ShutdownGame();
Settings::RestoreGlobalState(system->IsPoweredOn());
system->HIDCore().ReloadInputDevices();
UpdateStatusButtons();
}
} else {
// Only show the message if the game is still running.
if (emu_thread) {
emu_thread->SetRunning(true);
message_label->setText(status_message);
}
}
}
void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
if (behavior == ReinitializeKeyBehavior::Warning) {
const auto res = QMessageBox::information(
@ -4120,10 +4097,6 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
// Shutdown session if the emu thread is active...
if (emu_thread != nullptr) {
ShutdownGame();
Settings::RestoreGlobalState(system->IsPoweredOn());
system->HIDCore().ReloadInputDevices();
UpdateStatusButtons();
}
render_window->close();
@ -4216,6 +4189,10 @@ bool GMainWindow::ConfirmForceLockedExit() {
}
void GMainWindow::RequestGameExit() {
if (!system->IsPoweredOn()) {
return;
}
auto& sm{system->ServiceManager()};
auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");

View file

@ -29,6 +29,7 @@ class GImageInfo;
class GRenderWindow;
class LoadingScreen;
class MicroProfileDialog;
class OverlayDialog;
class ProfilerWidget;
class ControllerDialog;
class QLabel;
@ -332,10 +333,13 @@ private slots:
void ResetWindowSize900();
void ResetWindowSize1080();
void OnCaptureScreenshot();
void OnCoreError(Core::SystemResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
void OnLanguageChanged(const QString& locale);
void OnMouseActivity();
bool OnShutdownBegin();
void OnShutdownBeginDialog();
void OnEmulationStopped();
void OnEmulationStopTimeExpired();
private:
QString GetGameListErrorRemoving(InstalledEntryType type) const;
@ -385,6 +389,8 @@ private:
GRenderWindow* render_window;
GameList* game_list;
LoadingScreen* loading_screen;
QTimer shutdown_timer;
OverlayDialog* shutdown_dialog{};
GameListPlaceholder* game_list_placeholder;

View file

@ -3,6 +3,7 @@
#include <QKeyEvent>
#include <QScreen>
#include <QWindow>
#include "core/core.h"
#include "core/hid/hid_types.h"
@ -42,7 +43,7 @@ OverlayDialog::OverlayDialog(QWidget* parent, Core::System& system, const QStrin
MoveAndResizeWindow();
// TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend
if (system.IsPoweredOn()) {
if (system.IsPoweredOn() && !ui->buttonsDialog->isHidden()) {
input_interpreter = std::make_unique<InputInterpreter>(system);
StartInputThread();
@ -83,6 +84,11 @@ void OverlayDialog::InitializeRegularTextDialog(const QString& title_text, const
ui->button_ok_label->setEnabled(false);
}
if (ui->button_cancel->isHidden() && ui->button_ok_label->isHidden()) {
ui->buttonsDialog->hide();
return;
}
connect(
ui->button_cancel, &QPushButton::clicked, this,
[this](bool) {
@ -130,6 +136,11 @@ void OverlayDialog::InitializeRichTextDialog(const QString& title_text, const QS
ui->button_ok_rich->setEnabled(false);
}
if (ui->button_cancel_rich->isHidden() && ui->button_ok_rich->isHidden()) {
ui->buttonsRichDialog->hide();
return;
}
connect(
ui->button_cancel_rich, &QPushButton::clicked, this,
[this](bool) {
@ -152,7 +163,7 @@ void OverlayDialog::MoveAndResizeWindow() {
const auto height = static_cast<float>(parentWidget()->height());
// High DPI
const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f;
const float dpi_scale = parentWidget()->windowHandle()->screen()->logicalDotsPerInch() / 96.0f;
const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
const auto body_text_font_size =
@ -249,3 +260,9 @@ void OverlayDialog::InputThread() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
void OverlayDialog::keyPressEvent(QKeyEvent* e) {
if (!ui->buttonsDialog->isHidden() || e->key() != Qt::Key_Escape) {
QDialog::keyPressEvent(e);
}
}

View file

@ -94,6 +94,7 @@ private:
/// The thread where input is being polled and processed.
void InputThread();
void keyPressEvent(QKeyEvent* e) override;
std::unique_ptr<Ui::OverlayDialog> ui;

View file

@ -49,6 +49,15 @@ if(UNIX AND NOT APPLE)
install(TARGETS yuzu-cmd)
endif()
if(WIN32)
# compile as a win32 gui application instead of a console application
if(MSVC)
set_target_properties(yuzu-cmd PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
elseif(MINGW)
set_target_properties(yuzu-cmd PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows")
endif()
endif()
if (MSVC)
include(CopyYuzuSDLDeps)
copy_yuzu_SDL_deps(yuzu-cmd)

View file

@ -174,6 +174,13 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
/// Application entry point
int main(int argc, char** argv) {
#ifdef _WIN32
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "wb", stdout);
freopen("CONOUT$", "wb", stderr);
}
#endif
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();