Compare commits

...

32 Commits

Author SHA1 Message Date
Fernando Sahmkow
2d1984c20c System: Address Feedback 2020-01-27 09:54:11 -04:00
Fernando Sahmkow
de4b01f75d System: Correct PrepareReschedule. 2020-01-26 14:32:50 -04:00
Fernando Sahmkow
a1630ab53e Kernel: Remove a few global instances from the kernel. 2020-01-26 14:23:46 -04:00
Fernando Sahmkow
e4a1ead897 Core: Refactor CpuCoreManager to CpuManager and Cpu to Core Manager.
This commit instends on better naming the new purpose of this classes.
2020-01-26 14:07:22 -04:00
Fernando Sahmkow
450341b397 ArmInterface: Delegate Exclusive monitor factory to exclusive monitor interfasce. 2020-01-26 10:28:23 -04:00
Fernando Sahmkow
4d6a86b03f Core: Refactor CPU Management.
This commit moves ARM Interface and Scheduler handling into the kernel.
2020-01-25 18:55:32 -04:00
Fernando Sahmkow
ab89ced244 Kernel: Implement Physical Core. 2020-01-24 15:38:20 -04:00
bunnei
deb97f6a8e Merge pull request #2800 from FearlessTobi/port-4049
Port citra-emu/citra#4049: "Input: UDP Client to provide motion and touch controls"
2020-01-23 20:18:47 -05:00
FearlessTobi
d01eb12f36 Replace GetString with Get function
This should hopefully fix compilation errors.
2020-01-23 20:55:26 +01:00
FearlessTobi
bbd85a495a Address second part of review comments 2020-01-23 20:55:26 +01:00
FearlessTobi
0fe11746fc Address review comments 2020-01-23 20:55:26 +01:00
fearlessTobi
ac3690f205 Input: UDP Client to provide motion and touch controls
An implementation of the cemuhook motion/touch protocol, this adds the
ability for users to connect several different devices to citra to send
direct motion and touch data to citra.

Co-Authored-By: jroweboy <jroweboy@gmail.com>
2020-01-23 20:55:26 +01:00
bunnei
a167da4278 Merge pull request #3341 from bunnei/time-posix-myrule
service: time: Implement ToPosixTimeWithMyRule.
2020-01-23 12:04:01 -05:00
Fernando Sahmkow
9c6b5cae68 Merge pull request #3338 from ReinUsesLisp/no-fastmath
gl_shader_cache: Disable fastmath on Nvidia
2020-01-23 10:08:45 -04:00
bunnei
ed76c71319 service: time: Implement ToPosixTimeWithMyRule.
- Used by Pokemon Mystery Dungeon.
2020-01-22 23:20:19 -05:00
bunnei
89b326e396 Merge pull request #3324 from FearlessTobi/port-5037
Port citra-emu/citra#5037: "CMake: Create thin archives on Linux"
2020-01-22 19:48:15 -05:00
Zach Hilman
d8e0d839bd Merge pull request #3339 from Simek/dark-theme-update
GUI: fix minor issues with dark themes + rename and reorder themes
2020-01-22 18:39:21 -05:00
Bartosz Kaszubowski
c7055f3670 fix qss stylesheet whitespaces 2020-01-22 22:36:42 +01:00
Bartosz Kaszubowski
9a22b6dced GUI: fix minor issues with dark themes
GUI: rename and reorder themes
2020-01-22 21:12:45 +01:00
ReinUsesLisp
3ce28342a2 gl_shader_cache: Disable fastmath on Nvidia 2020-01-21 19:08:08 -03:00
Fernando Sahmkow
79e0991d9b Merge pull request #3330 from ReinUsesLisp/vk-blit-screen
vk_blit_screen: Initial implementation
2020-01-20 22:32:16 -04:00
ReinUsesLisp
a665581684 vk_blit_screen: Address feedback 2020-01-20 18:43:11 -03:00
bunnei
bc55c05947 Merge pull request #3334 from bunnei/time-fix
time: Fix month off-by-one error.
2020-01-20 15:36:30 -05:00
bunnei
7113236b30 time: Fix month off-by-one error.
- Fixes timestamp in ZLA and Astral Chain saves.
2020-01-20 14:20:32 -05:00
bunnei
4ea073c286 Merge pull request #3332 from bunnei/config-audio-tab
yuzu_qt: config: Move audio to its own tab.
2020-01-20 04:24:51 -05:00
Bartosz Kaszubowski
4043ba5222 GUI/gamelist: add "None" as an option for second row and remove dynamically duplicate row options (#3309)
* GUI/gamelist: add "None" as an option for second row and remove duplicated row options

* fix clang-format warnings
2020-01-20 00:44:03 -05:00
bunnei
69b44392a7 Merge pull request #3328 from ReinUsesLisp/vulkan-atoms
vk_shader_decompiler: Implement UAtomicAdd (ATOMS) on SPIR-V
2020-01-20 00:01:52 -05:00
bunnei
5a077c95ce Merge pull request #3322 from ReinUsesLisp/vk-front-face
vk_graphics_pipeline: Set front facing properly
2020-01-19 23:22:34 -05:00
ReinUsesLisp
f5dfe68a94 vk_blit_screen: Initial implementation
This abstraction takes care of presenting accelerated and
non-accelerated or "framebuffer" images to the Vulkan swapchain.
2020-01-19 21:12:43 -03:00
ReinUsesLisp
b2c976ad0e vk_shader_decompiler: Implement UAtomicAdd (ATOMS) on SPIR-V
Also updates sirit to include atomic instructions.
2020-01-19 16:40:31 -03:00
Léo Lam
f98cd210ab CMake: Create thin archives on Linux
This significantly reduces unnecessary disk writes and space usage
when building Citra.

libcore.a is now only ~1MB rather than several hundred megabytes.
2020-01-19 12:52:09 +01:00
ReinUsesLisp
94915d4ea1 vk_graphics_pipeline: Set front facing properly
Front face was being forced to a certain value when cull face is
disabled. Set a default value on initialization and drop the forcefully
set front facing value with culling disabled.
2020-01-18 18:50:47 -03:00
55 changed files with 2293 additions and 553 deletions

View File

@@ -350,6 +350,13 @@ function(create_target_directory_groups target_name)
endforeach()
endfunction()
# Prevent boost from linking against libs when building
add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY
-DBOOST_SYSTEM_NO_LIB
-DBOOST_DATE_TIME_NO_LIB
-DBOOST_REGEX_NO_LIB
)
enable_testing()
add_subdirectory(externals)
add_subdirectory(src)

View File

@@ -181,7 +181,7 @@ QMenu::icon {
}
QMenu::item {
padding: 5px 30px 5px 30px;
padding: 5px 16px 5px 40px;
border: 1px solid transparent;
/* reserve space for selection border */
}
@@ -192,12 +192,13 @@ QMenu::item:selected {
QMenu::separator {
height: 2px;
background: lightblue;
background: #76797C;
margin-left: 10px;
margin-right: 5px;
}
QMenu::indicator {
margin: 0 -26px 0 8px;
width: 18px;
height: 18px;
}
@@ -252,7 +253,7 @@ QWidget:disabled {
}
QAbstractItemView {
alternate-background-color: #31363b;
alternate-background-color: #2c2f32;
color: #eff0f1;
border: 1px solid #3A3939;
border-radius: 2px;
@@ -577,8 +578,6 @@ QTreeView:hover {
}
QComboBox:on {
padding-top: 3px;
padding-left: 4px;
selection-background-color: #4a4a4a;
}
@@ -703,10 +702,10 @@ QTabBar::close-button:pressed {
QTabBar::tab:top {
color: #eff0f1;
border: 1px solid #76797C;
border-bottom: 1px transparent black;
border-bottom: 2px transparent;
background-color: #31363b;
padding: 5px;
min-width: 50px;
padding: 4px 16px 2px;
min-width: 38px;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
}
@@ -1078,7 +1077,7 @@ QListView::item:selected:active {
}
QHeaderView {
background-color: #31363b;
background-color: #403F3F;
border: 1px transparent;
border-radius: 0px;
margin: 0px;
@@ -1086,30 +1085,32 @@ QHeaderView {
}
QHeaderView::section {
background-color: #31363b;
background-color: #232629;
color: #eff0f1;
padding: 5px;
border: 1px solid #76797C;
padding: 0 5px;
border: 1px solid #403F3F;
border-bottom: 0;
border-radius: 0px;
text-align: center;
}
QHeaderView::section::vertical::first,
QHeaderView::section::vertical::only-one {
border-top: 1px solid #76797C;
border-top: 1px solid #31363b;
}
QHeaderView::section::vertical {
border-top: transparent;
}
QHeaderView::section::horizontal,
QHeaderView::section::horizontal::first,
QHeaderView::section::horizontal::only-one {
border-left: 1px solid #76797C;
border-left: transparent;
}
QHeaderView::section::horizontal {
border-left: transparent;
QHeaderView::section::horizontal::last {
border-right: transparent;
}
QHeaderView::section:checked {

View File

@@ -77,6 +77,15 @@ else()
add_compile_options("-static")
endif()
endif()
if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
# GNU ar: Create thin archive files.
# Requires binutils-2.19 or later.
set(CMAKE_C_ARCHIVE_CREATE "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_C_ARCHIVE_APPEND "<CMAKE_AR> qTP <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_CXX_ARCHIVE_CREATE "<CMAKE_AR> qcTP <TARGET> <LINK_FLAGS> <OBJECTS>")
set(CMAKE_CXX_ARCHIVE_APPEND "<CMAKE_AR> qTP <TARGET> <LINK_FLAGS> <OBJECTS>")
endif()
endif()
add_subdirectory(common)

View File

@@ -28,6 +28,15 @@ public:
is_set = false;
}
template <class Duration>
bool WaitFor(const std::chrono::duration<Duration>& time) {
std::unique_lock lk{mutex};
if (!condvar.wait_for(lk, time, [this] { return is_set; }))
return false;
is_set = false;
return true;
}
template <class Clock, class Duration>
bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
std::unique_lock lk{mutex};

View File

@@ -15,14 +15,14 @@ add_library(core STATIC
constants.h
core.cpp
core.h
core_cpu.cpp
core_cpu.h
core_manager.cpp
core_manager.h
core_timing.cpp
core_timing.h
core_timing_util.cpp
core_timing_util.h
cpu_core_manager.cpp
cpu_core_manager.h
cpu_manager.cpp
cpu_manager.h
crypto/aes_util.cpp
crypto/aes_util.h
crypto/encryption_layer.cpp
@@ -158,6 +158,8 @@ add_library(core STATIC
hle/kernel/mutex.h
hle/kernel/object.cpp
hle/kernel/object.h
hle/kernel/physical_core.cpp
hle/kernel/physical_core.h
hle/kernel/process.cpp
hle/kernel/process.h
hle/kernel/process_capability.cpp

View File

@@ -10,7 +10,7 @@
#include "common/microprofile.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/gdbstub/gdbstub.h"

View File

@@ -2,10 +2,24 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic.h"
#endif
#include "core/arm/exclusive_monitor.h"
#include "core/memory.h"
namespace Core {
ExclusiveMonitor::~ExclusiveMonitor() = default;
std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
std::size_t num_cores) {
#ifdef ARCHITECTURE_x86_64
return std::make_unique<Core::DynarmicExclusiveMonitor>(memory, num_cores);
#else
// TODO(merry): Passthrough exclusive monitor
return nullptr;
#endif
}
} // namespace Core

View File

@@ -4,8 +4,14 @@
#pragma once
#include <memory>
#include "common/common_types.h"
namespace Memory {
class Memory;
}
namespace Core {
class ExclusiveMonitor {
@@ -22,4 +28,7 @@ public:
virtual bool ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) = 0;
};
std::unique_ptr<Core::ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
std::size_t num_cores);
} // namespace Core

View File

@@ -11,9 +11,9 @@
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/cpu_core_manager.h"
#include "core/cpu_manager.h"
#include "core/file_sys/bis_factory.h"
#include "core/file_sys/card_image.h"
#include "core/file_sys/mode.h"
@@ -28,6 +28,7 @@
#include "core/hardware_interrupt_manager.h"
#include "core/hle/kernel/client_port.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
@@ -113,16 +114,25 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
struct System::Impl {
explicit Impl(System& system)
: kernel{system}, fs_controller{system}, memory{system},
cpu_core_manager{system}, reporter{system}, applet_manager{system} {}
cpu_manager{system}, reporter{system}, applet_manager{system} {}
Cpu& CurrentCpuCore() {
return cpu_core_manager.GetCurrentCore();
CoreManager& CurrentCoreManager() {
return cpu_manager.GetCurrentCoreManager();
}
Kernel::PhysicalCore& CurrentPhysicalCore() {
const auto index = cpu_manager.GetActiveCoreIndex();
return kernel.PhysicalCore(index);
}
Kernel::PhysicalCore& GetPhysicalCore(std::size_t index) {
return kernel.PhysicalCore(index);
}
ResultStatus RunLoop(bool tight_loop) {
status = ResultStatus::Success;
cpu_core_manager.RunLoop(tight_loop);
cpu_manager.RunLoop(tight_loop);
return status;
}
@@ -131,8 +141,8 @@ struct System::Impl {
LOG_DEBUG(HW_Memory, "initialized OK");
core_timing.Initialize();
cpu_core_manager.Initialize();
kernel.Initialize();
cpu_manager.Initialize();
const auto current_time = std::chrono::duration_cast<std::chrono::seconds>(
std::chrono::system_clock::now().time_since_epoch());
@@ -205,7 +215,6 @@ struct System::Impl {
// Main process has been loaded and been made current.
// Begin GPU and CPU execution.
gpu_core->Start();
cpu_core_manager.StartThreads();
// Initialize cheat engine
if (cheat_engine) {
@@ -272,7 +281,7 @@ struct System::Impl {
gpu_core.reset();
// Close all CPU/threading state
cpu_core_manager.Shutdown();
cpu_manager.Shutdown();
// Shutdown kernel and core timing
kernel.Shutdown();
@@ -342,7 +351,7 @@ struct System::Impl {
std::unique_ptr<Tegra::GPU> gpu_core;
std::unique_ptr<Hardware::InterruptManager> interrupt_manager;
Memory::Memory memory;
CpuCoreManager cpu_core_manager;
CpuManager cpu_manager;
bool is_powered_on = false;
bool exit_lock = false;
@@ -377,12 +386,12 @@ struct System::Impl {
System::System() : impl{std::make_unique<Impl>(*this)} {}
System::~System() = default;
Cpu& System::CurrentCpuCore() {
return impl->CurrentCpuCore();
CoreManager& System::CurrentCoreManager() {
return impl->CurrentCoreManager();
}
const Cpu& System::CurrentCpuCore() const {
return impl->CurrentCpuCore();
const CoreManager& System::CurrentCoreManager() const {
return impl->CurrentCoreManager();
}
System::ResultStatus System::RunLoop(bool tight_loop) {
@@ -394,7 +403,7 @@ System::ResultStatus System::SingleStep() {
}
void System::InvalidateCpuInstructionCaches() {
impl->cpu_core_manager.InvalidateAllInstructionCaches();
impl->kernel.InvalidateAllInstructionCaches();
}
System::ResultStatus System::Load(Frontend::EmuWindow& emu_window, const std::string& filepath) {
@@ -406,13 +415,11 @@ bool System::IsPoweredOn() const {
}
void System::PrepareReschedule() {
CurrentCpuCore().PrepareReschedule();
impl->CurrentPhysicalCore().Stop();
}
void System::PrepareReschedule(const u32 core_index) {
if (core_index < GlobalScheduler().CpuCoresCount()) {
CpuCore(core_index).PrepareReschedule();
}
impl->kernel.PrepareReschedule(core_index);
}
PerfStatsResults System::GetAndResetPerfStats() {
@@ -428,31 +435,31 @@ const TelemetrySession& System::TelemetrySession() const {
}
ARM_Interface& System::CurrentArmInterface() {
return CurrentCpuCore().ArmInterface();
return impl->CurrentPhysicalCore().ArmInterface();
}
const ARM_Interface& System::CurrentArmInterface() const {
return CurrentCpuCore().ArmInterface();
return impl->CurrentPhysicalCore().ArmInterface();
}
std::size_t System::CurrentCoreIndex() const {
return CurrentCpuCore().CoreIndex();
return impl->cpu_manager.GetActiveCoreIndex();
}
Kernel::Scheduler& System::CurrentScheduler() {
return CurrentCpuCore().Scheduler();
return impl->CurrentPhysicalCore().Scheduler();
}
const Kernel::Scheduler& System::CurrentScheduler() const {
return CurrentCpuCore().Scheduler();
return impl->CurrentPhysicalCore().Scheduler();
}
Kernel::Scheduler& System::Scheduler(std::size_t core_index) {
return CpuCore(core_index).Scheduler();
return impl->GetPhysicalCore(core_index).Scheduler();
}
const Kernel::Scheduler& System::Scheduler(std::size_t core_index) const {
return CpuCore(core_index).Scheduler();
return impl->GetPhysicalCore(core_index).Scheduler();
}
/// Gets the global scheduler
@@ -474,28 +481,28 @@ const Kernel::Process* System::CurrentProcess() const {
}
ARM_Interface& System::ArmInterface(std::size_t core_index) {
return CpuCore(core_index).ArmInterface();
return impl->GetPhysicalCore(core_index).ArmInterface();
}
const ARM_Interface& System::ArmInterface(std::size_t core_index) const {
return CpuCore(core_index).ArmInterface();
return impl->GetPhysicalCore(core_index).ArmInterface();
}
Cpu& System::CpuCore(std::size_t core_index) {
return impl->cpu_core_manager.GetCore(core_index);
CoreManager& System::GetCoreManager(std::size_t core_index) {
return impl->cpu_manager.GetCoreManager(core_index);
}
const Cpu& System::CpuCore(std::size_t core_index) const {
const CoreManager& System::GetCoreManager(std::size_t core_index) const {
ASSERT(core_index < NUM_CPU_CORES);
return impl->cpu_core_manager.GetCore(core_index);
return impl->cpu_manager.GetCoreManager(core_index);
}
ExclusiveMonitor& System::Monitor() {
return impl->cpu_core_manager.GetExclusiveMonitor();
return impl->kernel.GetExclusiveMonitor();
}
const ExclusiveMonitor& System::Monitor() const {
return impl->cpu_core_manager.GetExclusiveMonitor();
return impl->kernel.GetExclusiveMonitor();
}
Memory::Memory& System::Memory() {

View File

@@ -93,7 +93,7 @@ class Memory;
namespace Core {
class ARM_Interface;
class Cpu;
class CoreManager;
class ExclusiveMonitor;
class FrameLimiter;
class PerfStats;
@@ -218,10 +218,10 @@ public:
const ARM_Interface& ArmInterface(std::size_t core_index) const;
/// Gets a CPU interface to the CPU core with the specified index
Cpu& CpuCore(std::size_t core_index);
CoreManager& GetCoreManager(std::size_t core_index);
/// Gets a CPU interface to the CPU core with the specified index
const Cpu& CpuCore(std::size_t core_index) const;
const CoreManager& GetCoreManager(std::size_t core_index) const;
/// Gets a reference to the exclusive monitor
ExclusiveMonitor& Monitor();
@@ -364,10 +364,10 @@ private:
System();
/// Returns the currently running CPU core
Cpu& CurrentCpuCore();
CoreManager& CurrentCoreManager();
/// Returns the currently running CPU core
const Cpu& CurrentCpuCore() const;
const CoreManager& CurrentCoreManager() const;
/**
* Initialize the emulated system.

View File

@@ -1,127 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <condition_variable>
#include <mutex>
#include "common/logging/log.h"
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic.h"
#endif
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/settings.h"
namespace Core {
void CpuBarrier::NotifyEnd() {
std::unique_lock lock{mutex};
end = true;
condition.notify_all();
}
bool CpuBarrier::Rendezvous() {
if (!Settings::values.use_multi_core) {
// Meaningless when running in single-core mode
return true;
}
if (!end) {
std::unique_lock lock{mutex};
--cores_waiting;
if (!cores_waiting) {
cores_waiting = NUM_CPU_CORES;
condition.notify_all();
return true;
}
condition.wait(lock);
return true;
}
return false;
}
Cpu::Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
std::size_t core_index)
: cpu_barrier{cpu_barrier}, global_scheduler{system.GlobalScheduler()},
core_timing{system.CoreTiming()}, core_index{core_index} {
#ifdef ARCHITECTURE_x86_64
arm_interface = std::make_unique<ARM_Dynarmic>(system, exclusive_monitor, core_index);
#else
arm_interface = std::make_unique<ARM_Unicorn>(system);
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
scheduler = std::make_unique<Kernel::Scheduler>(system, *arm_interface, core_index);
}
Cpu::~Cpu() = default;
std::unique_ptr<ExclusiveMonitor> Cpu::MakeExclusiveMonitor(
[[maybe_unused]] Memory::Memory& memory, [[maybe_unused]] std::size_t num_cores) {
#ifdef ARCHITECTURE_x86_64
return std::make_unique<DynarmicExclusiveMonitor>(memory, num_cores);
#else
// TODO(merry): Passthrough exclusive monitor
return nullptr;
#endif
}
void Cpu::RunLoop(bool tight_loop) {
// Wait for all other CPU cores to complete the previous slice, such that they run in lock-step
if (!cpu_barrier.Rendezvous()) {
// If rendezvous failed, session has been killed
return;
}
Reschedule();
// If we don't have a currently active thread then don't execute instructions,
// instead advance to the next event and try to yield to the next thread
if (Kernel::GetCurrentThread() == nullptr) {
LOG_TRACE(Core, "Core-{} idling", core_index);
core_timing.Idle();
} else {
if (tight_loop) {
arm_interface->Run();
} else {
arm_interface->Step();
}
// We are stopping a run, exclusive state must be cleared
arm_interface->ClearExclusiveState();
}
core_timing.Advance();
Reschedule();
}
void Cpu::SingleStep() {
return RunLoop(false);
}
void Cpu::PrepareReschedule() {
arm_interface->PrepareReschedule();
}
void Cpu::Reschedule() {
// Lock the global kernel mutex when we manipulate the HLE state
std::lock_guard lock(HLE::g_hle_lock);
global_scheduler.SelectThread(core_index);
scheduler->TryDoContextSwitch();
}
void Cpu::Shutdown() {
scheduler->Shutdown();
}
} // namespace Core

View File

@@ -1,120 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <memory>
#include <mutex>
#include "common/common_types.h"
namespace Kernel {
class GlobalScheduler;
class Scheduler;
} // namespace Kernel
namespace Core {
class System;
}
namespace Core::Timing {
class CoreTiming;
}
namespace Memory {
class Memory;
}
namespace Core {
class ARM_Interface;
class ExclusiveMonitor;
constexpr unsigned NUM_CPU_CORES{4};
class CpuBarrier {
public:
bool IsAlive() const {
return !end;
}
void NotifyEnd();
bool Rendezvous();
private:
unsigned cores_waiting{NUM_CPU_CORES};
std::mutex mutex;
std::condition_variable condition;
std::atomic<bool> end{};
};
class Cpu {
public:
Cpu(System& system, ExclusiveMonitor& exclusive_monitor, CpuBarrier& cpu_barrier,
std::size_t core_index);
~Cpu();
void RunLoop(bool tight_loop = true);
void SingleStep();
void PrepareReschedule();
ARM_Interface& ArmInterface() {
return *arm_interface;
}
const ARM_Interface& ArmInterface() const {
return *arm_interface;
}
Kernel::Scheduler& Scheduler() {
return *scheduler;
}
const Kernel::Scheduler& Scheduler() const {
return *scheduler;
}
bool IsMainCore() const {
return core_index == 0;
}
std::size_t CoreIndex() const {
return core_index;
}
void Shutdown();
/**
* Creates an exclusive monitor to handle exclusive reads/writes.
*
* @param memory The current memory subsystem that the monitor may wish
* to keep track of.
*
* @param num_cores The number of cores to assume about the CPU.
*
* @returns The constructed exclusive monitor instance, or nullptr if the current
* CPU backend is unable to use an exclusive monitor.
*/
static std::unique_ptr<ExclusiveMonitor> MakeExclusiveMonitor(Memory::Memory& memory,
std::size_t num_cores);
private:
void Reschedule();
std::unique_ptr<ARM_Interface> arm_interface;
CpuBarrier& cpu_barrier;
Kernel::GlobalScheduler& global_scheduler;
std::unique_ptr<Kernel::Scheduler> scheduler;
Timing::CoreTiming& core_timing;
std::atomic<bool> reschedule_pending = false;
std::size_t core_index;
};
} // namespace Core

70
src/core/core_manager.cpp Normal file
View File

@@ -0,0 +1,70 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <condition_variable>
#include <mutex>
#include "common/logging/log.h"
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic.h"
#endif
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/lock.h"
#include "core/settings.h"
namespace Core {
CoreManager::CoreManager(System& system, std::size_t core_index)
: global_scheduler{system.GlobalScheduler()}, physical_core{system.Kernel().PhysicalCore(
core_index)},
core_timing{system.CoreTiming()}, core_index{core_index} {}
CoreManager::~CoreManager() = default;
void CoreManager::RunLoop(bool tight_loop) {
Reschedule();
// If we don't have a currently active thread then don't execute instructions,
// instead advance to the next event and try to yield to the next thread
if (Kernel::GetCurrentThread() == nullptr) {
LOG_TRACE(Core, "Core-{} idling", core_index);
core_timing.Idle();
} else {
if (tight_loop) {
physical_core.Run();
} else {
physical_core.Step();
}
}
core_timing.Advance();
Reschedule();
}
void CoreManager::SingleStep() {
return RunLoop(false);
}
void CoreManager::PrepareReschedule() {
physical_core.Stop();
}
void CoreManager::Reschedule() {
// Lock the global kernel mutex when we manipulate the HLE state
std::lock_guard lock(HLE::g_hle_lock);
global_scheduler.SelectThread(core_index);
physical_core.Scheduler().TryDoContextSwitch();
}
} // namespace Core

63
src/core/core_manager.h Normal file
View File

@@ -0,0 +1,63 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <atomic>
#include <cstddef>
#include <memory>
#include "common/common_types.h"
namespace Kernel {
class GlobalScheduler;
class PhysicalCore;
} // namespace Kernel
namespace Core {
class System;
}
namespace Core::Timing {
class CoreTiming;
}
namespace Memory {
class Memory;
}
namespace Core {
constexpr unsigned NUM_CPU_CORES{4};
class CoreManager {
public:
CoreManager(System& system, std::size_t core_index);
~CoreManager();
void RunLoop(bool tight_loop = true);
void SingleStep();
void PrepareReschedule();
bool IsMainCore() const {
return core_index == 0;
}
std::size_t CoreIndex() const {
return core_index;
}
private:
void Reschedule();
Kernel::GlobalScheduler& global_scheduler;
Kernel::PhysicalCore& physical_core;
Timing::CoreTiming& core_timing;
std::atomic<bool> reschedule_pending = false;
std::size_t core_index;
};
} // namespace Core

View File

@@ -1,152 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/cpu_core_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/settings.h"
namespace Core {
namespace {
void RunCpuCore(const System& system, Cpu& cpu_state) {
while (system.IsPoweredOn()) {
cpu_state.RunLoop(true);
}
}
} // Anonymous namespace
CpuCoreManager::CpuCoreManager(System& system) : system{system} {}
CpuCoreManager::~CpuCoreManager() = default;
void CpuCoreManager::Initialize() {
barrier = std::make_unique<CpuBarrier>();
exclusive_monitor = Cpu::MakeExclusiveMonitor(system.Memory(), cores.size());
for (std::size_t index = 0; index < cores.size(); ++index) {
cores[index] = std::make_unique<Cpu>(system, *exclusive_monitor, *barrier, index);
}
}
void CpuCoreManager::StartThreads() {
// Create threads for CPU cores 1-3, and build thread_to_cpu map
// CPU core 0 is run on the main thread
thread_to_cpu[std::this_thread::get_id()] = cores[0].get();
if (!Settings::values.use_multi_core) {
return;
}
for (std::size_t index = 0; index < core_threads.size(); ++index) {
core_threads[index] = std::make_unique<std::thread>(RunCpuCore, std::cref(system),
std::ref(*cores[index + 1]));
thread_to_cpu[core_threads[index]->get_id()] = cores[index + 1].get();
}
}
void CpuCoreManager::Shutdown() {
barrier->NotifyEnd();
if (Settings::values.use_multi_core) {
for (auto& thread : core_threads) {
thread->join();
thread.reset();
}
}
thread_to_cpu.clear();
for (auto& cpu_core : cores) {
cpu_core->Shutdown();
cpu_core.reset();
}
exclusive_monitor.reset();
barrier.reset();
}
Cpu& CpuCoreManager::GetCore(std::size_t index) {
return *cores.at(index);
}
const Cpu& CpuCoreManager::GetCore(std::size_t index) const {
return *cores.at(index);
}
ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() {
return *exclusive_monitor;
}
const ExclusiveMonitor& CpuCoreManager::GetExclusiveMonitor() const {
return *exclusive_monitor;
}
Cpu& CpuCoreManager::GetCurrentCore() {
if (Settings::values.use_multi_core) {
const auto& search = thread_to_cpu.find(std::this_thread::get_id());
ASSERT(search != thread_to_cpu.end());
ASSERT(search->second);
return *search->second;
}
// Otherwise, use single-threaded mode active_core variable
return *cores[active_core];
}
const Cpu& CpuCoreManager::GetCurrentCore() const {
if (Settings::values.use_multi_core) {
const auto& search = thread_to_cpu.find(std::this_thread::get_id());
ASSERT(search != thread_to_cpu.end());
ASSERT(search->second);
return *search->second;
}
// Otherwise, use single-threaded mode active_core variable
return *cores[active_core];
}
void CpuCoreManager::RunLoop(bool tight_loop) {
// Update thread_to_cpu in case Core 0 is run from a different host thread
thread_to_cpu[std::this_thread::get_id()] = cores[0].get();
if (GDBStub::IsServerEnabled()) {
GDBStub::HandlePacket();
// If the loop is halted and we want to step, use a tiny (1) number of instructions to
// execute. Otherwise, get out of the loop function.
if (GDBStub::GetCpuHaltFlag()) {
if (GDBStub::GetCpuStepFlag()) {
tight_loop = false;
} else {
return;
}
}
}
auto& core_timing = system.CoreTiming();
core_timing.ResetRun();
bool keep_running{};
do {
keep_running = false;
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
core_timing.SwitchContext(active_core);
if (core_timing.CanCurrentContextRun()) {
cores[active_core]->RunLoop(tight_loop);
}
keep_running |= core_timing.CanCurrentContextRun();
}
} while (keep_running);
if (GDBStub::IsServerEnabled()) {
GDBStub::SetCpuStepFlag(false);
}
}
void CpuCoreManager::InvalidateAllInstructionCaches() {
for (auto& cpu : cores) {
cpu->ArmInterface().ClearInstructionCache();
}
}
} // namespace Core

View File

@@ -1,62 +0,0 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <map>
#include <memory>
#include <thread>
namespace Core {
class Cpu;
class CpuBarrier;
class ExclusiveMonitor;
class System;
class CpuCoreManager {
public:
explicit CpuCoreManager(System& system);
CpuCoreManager(const CpuCoreManager&) = delete;
CpuCoreManager(CpuCoreManager&&) = delete;
~CpuCoreManager();
CpuCoreManager& operator=(const CpuCoreManager&) = delete;
CpuCoreManager& operator=(CpuCoreManager&&) = delete;
void Initialize();
void StartThreads();
void Shutdown();
Cpu& GetCore(std::size_t index);
const Cpu& GetCore(std::size_t index) const;
Cpu& GetCurrentCore();
const Cpu& GetCurrentCore() const;
ExclusiveMonitor& GetExclusiveMonitor();
const ExclusiveMonitor& GetExclusiveMonitor() const;
void RunLoop(bool tight_loop);
void InvalidateAllInstructionCaches();
private:
static constexpr std::size_t NUM_CPU_CORES = 4;
std::unique_ptr<ExclusiveMonitor> exclusive_monitor;
std::unique_ptr<CpuBarrier> barrier;
std::array<std::unique_ptr<Cpu>, NUM_CPU_CORES> cores;
std::array<std::unique_ptr<std::thread>, NUM_CPU_CORES - 1> core_threads;
std::size_t active_core{}; ///< Active core, only used in single thread mode
/// Map of guest threads to CPU cores
std::map<std::thread::id, Cpu*> thread_to_cpu;
System& system;
};
} // namespace Core

83
src/core/cpu_manager.cpp Normal file
View File

@@ -0,0 +1,83 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/assert.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/cpu_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/settings.h"
namespace Core {
CpuManager::CpuManager(System& system) : system{system} {}
CpuManager::~CpuManager() = default;
void CpuManager::Initialize() {
for (std::size_t index = 0; index < core_managers.size(); ++index) {
core_managers[index] = std::make_unique<CoreManager>(system, index);
}
}
void CpuManager::Shutdown() {
for (auto& cpu_core : core_managers) {
cpu_core.reset();
}
}
CoreManager& CpuManager::GetCoreManager(std::size_t index) {
return *core_managers.at(index);
}
const CoreManager& CpuManager::GetCoreManager(std::size_t index) const {
return *core_managers.at(index);
}
CoreManager& CpuManager::GetCurrentCoreManager() {
// Otherwise, use single-threaded mode active_core variable
return *core_managers[active_core];
}
const CoreManager& CpuManager::GetCurrentCoreManager() const {
// Otherwise, use single-threaded mode active_core variable
return *core_managers[active_core];
}
void CpuManager::RunLoop(bool tight_loop) {
if (GDBStub::IsServerEnabled()) {
GDBStub::HandlePacket();
// If the loop is halted and we want to step, use a tiny (1) number of instructions to
// execute. Otherwise, get out of the loop function.
if (GDBStub::GetCpuHaltFlag()) {
if (GDBStub::GetCpuStepFlag()) {
tight_loop = false;
} else {
return;
}
}
}
auto& core_timing = system.CoreTiming();
core_timing.ResetRun();
bool keep_running{};
do {
keep_running = false;
for (active_core = 0; active_core < NUM_CPU_CORES; ++active_core) {
core_timing.SwitchContext(active_core);
if (core_timing.CanCurrentContextRun()) {
core_managers[active_core]->RunLoop(tight_loop);
}
keep_running |= core_timing.CanCurrentContextRun();
}
} while (keep_running);
if (GDBStub::IsServerEnabled()) {
GDBStub::SetCpuStepFlag(false);
}
}
} // namespace Core

50
src/core/cpu_manager.h Normal file
View File

@@ -0,0 +1,50 @@
// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
namespace Core {
class CoreManager;
class System;
class CpuManager {
public:
explicit CpuManager(System& system);
CpuManager(const CpuManager&) = delete;
CpuManager(CpuManager&&) = delete;
~CpuManager();
CpuManager& operator=(const CpuManager&) = delete;
CpuManager& operator=(CpuManager&&) = delete;
void Initialize();
void Shutdown();
CoreManager& GetCoreManager(std::size_t index);
const CoreManager& GetCoreManager(std::size_t index) const;
CoreManager& GetCurrentCoreManager();
const CoreManager& GetCurrentCoreManager() const;
std::size_t GetActiveCoreIndex() const {
return active_core;
}
void RunLoop(bool tight_loop);
private:
static constexpr std::size_t NUM_CPU_CORES = 4;
std::array<std::unique_ptr<CoreManager>, NUM_CPU_CORES> core_managers;
std::size_t active_core{}; ///< Active core, only used in single thread mode
System& system;
};
} // namespace Core

View File

@@ -35,7 +35,7 @@
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_manager.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/scheduler.h"

View File

@@ -8,7 +8,6 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/scheduler.h"

View File

@@ -3,13 +3,15 @@
// Refer to the license.txt file included.
#include <atomic>
#include <functional>
#include <memory>
#include <mutex>
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
@@ -17,6 +19,7 @@
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
#include "core/hle/kernel/scheduler.h"
@@ -98,6 +101,7 @@ struct KernelCore::Impl {
void Initialize(KernelCore& kernel) {
Shutdown();
InitializePhysicalCores(kernel);
InitializeSystemResourceLimit(kernel);
InitializeThreads();
InitializePreemption();
@@ -121,6 +125,21 @@ struct KernelCore::Impl {
global_scheduler.Shutdown();
named_ports.clear();
for (auto& core : cores) {
core.Shutdown();
}
cores.clear();
exclusive_monitor.reset(nullptr);
}
void InitializePhysicalCores(KernelCore& kernel) {
exclusive_monitor =
Core::MakeExclusiveMonitor(system.Memory(), global_scheduler.CpuCoresCount());
for (std::size_t i = 0; i < global_scheduler.CpuCoresCount(); i++) {
cores.emplace_back(system, kernel, i, *exclusive_monitor);
}
}
// Creates the default system resource limit
@@ -186,6 +205,9 @@ struct KernelCore::Impl {
/// the ConnectToPort SVC.
NamedPortTable named_ports;
std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
std::vector<Kernel::PhysicalCore> cores;
// System context
Core::System& system;
};
@@ -240,6 +262,34 @@ const Kernel::GlobalScheduler& KernelCore::GlobalScheduler() const {
return impl->global_scheduler;
}
Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) {
return impl->cores[id];
}
const Kernel::PhysicalCore& KernelCore::PhysicalCore(std::size_t id) const {
return impl->cores[id];
}
Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() {
return *impl->exclusive_monitor;
}
const Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() const {
return *impl->exclusive_monitor;
}
void KernelCore::InvalidateAllInstructionCaches() {
for (std::size_t i = 0; i < impl->global_scheduler.CpuCoresCount(); i++) {
PhysicalCore(i).ArmInterface().ClearInstructionCache();
}
}
void KernelCore::PrepareReschedule(std::size_t id) {
if (id < impl->global_scheduler.CpuCoresCount()) {
impl->cores[id].Stop();
}
}
void KernelCore::AddNamedPort(std::string name, std::shared_ptr<ClientPort> port) {
impl->named_ports.emplace(std::move(name), std::move(port));
}

View File

@@ -11,8 +11,9 @@
#include "core/hle/kernel/object.h"
namespace Core {
class ExclusiveMonitor;
class System;
}
} // namespace Core
namespace Core::Timing {
class CoreTiming;
@@ -25,6 +26,7 @@ class AddressArbiter;
class ClientPort;
class GlobalScheduler;
class HandleTable;
class PhysicalCore;
class Process;
class ResourceLimit;
class Thread;
@@ -84,6 +86,21 @@ public:
/// Gets the sole instance of the global scheduler
const Kernel::GlobalScheduler& GlobalScheduler() const;
/// Gets the an instance of the respective physical CPU core.
Kernel::PhysicalCore& PhysicalCore(std::size_t id);
/// Gets the an instance of the respective physical CPU core.
const Kernel::PhysicalCore& PhysicalCore(std::size_t id) const;
/// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id);
Core::ExclusiveMonitor& GetExclusiveMonitor();
const Core::ExclusiveMonitor& GetExclusiveMonitor() const;
void InvalidateAllInstructionCaches();
/// Adds a port to the named port table
void AddNamedPort(std::string name, std::shared_ptr<ClientPort> port);

View File

@@ -0,0 +1,52 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#ifdef ARCHITECTURE_x86_64
#include "core/arm/dynarmic/arm_dynarmic.h"
#endif
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
#include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
PhysicalCore::PhysicalCore(Core::System& system, KernelCore& kernel, std::size_t id,
Core::ExclusiveMonitor& exclusive_monitor)
: core_index{id}, kernel{kernel} {
#ifdef ARCHITECTURE_x86_64
arm_interface = std::make_shared<Core::ARM_Dynarmic>(system, exclusive_monitor, core_index);
#else
arm_interface = std::make_shared<Core::ARM_Unicorn>(system);
LOG_WARNING(Core, "CPU JIT requested, but Dynarmic not available");
#endif
scheduler = std::make_shared<Kernel::Scheduler>(system, *arm_interface, core_index);
}
PhysicalCore::~PhysicalCore() = default;
void PhysicalCore::Run() {
arm_interface->Run();
arm_interface->ClearExclusiveState();
}
void PhysicalCore::Step() {
arm_interface->Step();
}
void PhysicalCore::Stop() {
arm_interface->PrepareReschedule();
}
void PhysicalCore::Shutdown() {
scheduler->Shutdown();
}
} // namespace Kernel

View File

@@ -0,0 +1,74 @@
// Copyright 2020 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <cstddef>
#include <memory>
namespace Kernel {
class Scheduler;
} // namespace Kernel
namespace Core {
class ARM_Interface;
class ExclusiveMonitor;
class System;
} // namespace Core
namespace Kernel {
class PhysicalCore {
public:
PhysicalCore(Core::System& system, KernelCore& kernel, std::size_t id,
Core::ExclusiveMonitor& exclusive_monitor);
~PhysicalCore();
/// Execute current jit state
void Run();
/// Execute a single instruction in current jit.
void Step();
/// Stop JIT execution/exit
void Stop();
// Shutdown this physical core.
void Shutdown();
Core::ARM_Interface& ArmInterface() {
return *arm_interface;
}
const Core::ARM_Interface& ArmInterface() const {
return *arm_interface;
}
bool IsMainCore() const {
return core_index == 0;
}
bool IsSystemCore() const {
return core_index == 3;
}
std::size_t CoreIndex() const {
return core_index;
}
Kernel::Scheduler& Scheduler() {
return *scheduler;
}
const Kernel::Scheduler& Scheduler() const {
return *scheduler;
}
private:
std::size_t core_index;
KernelCore& kernel;
std::shared_ptr<Core::ARM_Interface> arm_interface;
std::shared_ptr<Kernel::Scheduler> scheduler;
};
} // namespace Kernel

View File

@@ -14,7 +14,6 @@
#include "common/logging/log.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"

View File

@@ -15,7 +15,7 @@
#include "common/string_util.h"
#include "core/arm/exclusive_monitor.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_manager.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hle/kernel/address_arbiter.h"

View File

@@ -13,7 +13,6 @@
#include "common/thread_queue_list.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
#include "core/core_timing_util.h"
#include "core/hle/kernel/errors.h"
@@ -356,7 +355,7 @@ void Thread::SetActivity(ThreadActivity value) {
// Set status if not waiting
if (status == ThreadStatus::Ready || status == ThreadStatus::Running) {
SetStatus(ThreadStatus::Paused);
Core::System::GetInstance().CpuCore(processor_id).PrepareReschedule();
kernel.PrepareReschedule(processor_id);
}
} else if (status == ThreadStatus::Paused) {
// Ready to reschedule

View File

@@ -7,7 +7,6 @@
#include "common/common_types.h"
#include "common/logging/log.h"
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/process.h"
@@ -96,7 +95,7 @@ void WaitObject::WakeupWaitingThread(std::shared_ptr<Thread> thread) {
}
if (resume) {
thread->ResumeFromWait();
Core::System::GetInstance().PrepareReschedule(thread->GetProcessorID());
kernel.PrepareReschedule(thread->GetProcessorID());
}
}

View File

@@ -820,7 +820,7 @@ static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, Calend
const ResultCode result{
ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)};
calendar.time.year = static_cast<s16>(calendar_time.year);
calendar.time.month = calendar_time.month;
calendar.time.month = calendar_time.month + 1; // Internal impl. uses 0-indexed month
calendar.time.day = calendar_time.day;
calendar.time.hour = calendar_time.hour;
calendar.time.minute = calendar_time.minute;
@@ -874,7 +874,7 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
CalendarTimeInternal internal_time{};
internal_time.year = calendar_time.year;
internal_time.month = calendar_time.month;
internal_time.month = calendar_time.month - 1; // Internal impl. uses 0-indexed month
internal_time.day = calendar_time.day;
internal_time.hour = calendar_time.hour;
internal_time.minute = calendar_time.minute;
@@ -1019,6 +1019,15 @@ ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules,
return RESULT_SUCCESS;
}
ResultCode TimeZoneManager::ToPosixTimeWithMyRule(const CalendarTime& calendar_time,
s64& posix_time) const {
if (is_initialized) {
return ToPosixTime(time_zone_rule, calendar_time, posix_time);
}
posix_time = 0;
return ERROR_UNINITIALIZED_CLOCK;
}
ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const {
if (!is_initialized) {
return ERROR_UNINITIALIZED_CLOCK;

View File

@@ -39,6 +39,7 @@ public:
ResultCode ParseTimeZoneRuleBinary(TimeZoneRule& rules, FileSys::VirtualFile& vfs_file) const;
ResultCode ToPosixTime(const TimeZoneRule& rules, const CalendarTime& calendar_time,
s64& posix_time) const;
ResultCode ToPosixTimeWithMyRule(const CalendarTime& calendar_time, s64& posix_time) const;
private:
bool is_initialized{};

View File

@@ -22,7 +22,7 @@ ITimeZoneService ::ITimeZoneService(TimeZone::TimeZoneContentManager& time_zone_
{100, &ITimeZoneService::ToCalendarTime, "ToCalendarTime"},
{101, &ITimeZoneService::ToCalendarTimeWithMyRule, "ToCalendarTimeWithMyRule"},
{201, &ITimeZoneService::ToPosixTime, "ToPosixTime"},
{202, nullptr, "ToPosixTimeWithMyRule"},
{202, &ITimeZoneService::ToPosixTimeWithMyRule, "ToPosixTimeWithMyRule"},
};
RegisterHandlers(functions);
}
@@ -145,4 +145,26 @@ void ITimeZoneService::ToPosixTime(Kernel::HLERequestContext& ctx) {
ctx.WriteBuffer(&posix_time, sizeof(s64));
}
void ITimeZoneService::ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_Time, "called");
IPC::RequestParser rp{ctx};
const auto calendar_time{rp.PopRaw<TimeZone::CalendarTime>()};
s64 posix_time{};
if (const ResultCode result{
time_zone_content_manager.GetTimeZoneManager().ToPosixTimeWithMyRule(calendar_time,
posix_time)};
result != RESULT_SUCCESS) {
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
return;
}
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
rb.PushRaw<u32>(1); // Number of times we're returning
ctx.WriteBuffer(&posix_time, sizeof(s64));
}
} // namespace Service::Time

View File

@@ -22,6 +22,7 @@ private:
void ToCalendarTime(Kernel::HLERequestContext& ctx);
void ToCalendarTimeWithMyRule(Kernel::HLERequestContext& ctx);
void ToPosixTime(Kernel::HLERequestContext& ctx);
void ToPosixTimeWithMyRule(Kernel::HLERequestContext& ctx);
private:
TimeZone::TimeZoneContentManager& time_zone_content_manager;

View File

@@ -401,6 +401,9 @@ struct Values {
std::string motion_device;
TouchscreenInput touchscreen;
std::atomic_bool is_device_reload_pending{true};
std::string udp_input_address;
u16 udp_input_port;
u8 udp_pad_index;
// Core
bool use_multi_core;

View File

@@ -9,6 +9,12 @@ add_library(input_common STATIC
motion_emu.h
sdl/sdl.cpp
sdl/sdl.h
udp/client.cpp
udp/client.h
udp/protocol.cpp
udp/protocol.h
udp/udp.cpp
udp/udp.h
)
if(SDL2_FOUND)
@@ -21,4 +27,4 @@ if(SDL2_FOUND)
endif()
create_target_directory_groups(input_common)
target_link_libraries(input_common PUBLIC core PRIVATE common)
target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES})

View File

@@ -9,6 +9,7 @@
#include "input_common/keyboard.h"
#include "input_common/main.h"
#include "input_common/motion_emu.h"
#include "input_common/udp/udp.h"
#ifdef HAVE_SDL2
#include "input_common/sdl/sdl.h"
#endif
@@ -18,6 +19,7 @@ namespace InputCommon {
static std::shared_ptr<Keyboard> keyboard;
static std::shared_ptr<MotionEmu> motion_emu;
static std::unique_ptr<SDL::State> sdl;
static std::unique_ptr<CemuhookUDP::State> udp;
void Init() {
keyboard = std::make_shared<Keyboard>();
@@ -28,6 +30,8 @@ void Init() {
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
sdl = SDL::Init();
udp = CemuhookUDP::Init();
}
void Shutdown() {
@@ -72,11 +76,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left,
namespace Polling {
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
std::vector<std::unique_ptr<DevicePoller>> pollers;
#ifdef HAVE_SDL2
return sdl->GetPollers(type);
#else
return {};
pollers = sdl->GetPollers(type);
#endif
return pollers;
}
} // namespace Polling

View File

@@ -0,0 +1,287 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <chrono>
#include <cstring>
#include <functional>
#include <thread>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include "common/logging/log.h"
#include "input_common/udp/client.h"
#include "input_common/udp/protocol.h"
using boost::asio::ip::address_v4;
using boost::asio::ip::udp;
namespace InputCommon::CemuhookUDP {
struct SocketCallback {
std::function<void(Response::Version)> version;
std::function<void(Response::PortInfo)> port_info;
std::function<void(Response::PadData)> pad_data;
};
class Socket {
public:
using clock = std::chrono::system_clock;
explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id,
SocketCallback callback)
: client_id(client_id), timer(io_service),
send_endpoint(udp::endpoint(address_v4::from_string(host), port)),
socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index),
callback(std::move(callback)) {}
void Stop() {
io_service.stop();
}
void Loop() {
io_service.run();
}
void StartSend(const clock::time_point& from) {
timer.expires_at(from + std::chrono::seconds(3));
timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
}
void StartReceive() {
socket.async_receive_from(
boost::asio::buffer(receive_buffer), receive_endpoint,
[this](const boost::system::error_code& error, std::size_t bytes_transferred) {
HandleReceive(error, bytes_transferred);
});
}
private:
void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) {
if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
switch (*type) {
case Type::Version: {
Response::Version version;
std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
callback.version(std::move(version));
break;
}
case Type::PortInfo: {
Response::PortInfo port_info;
std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
sizeof(Response::PortInfo));
callback.port_info(std::move(port_info));
break;
}
case Type::PadData: {
Response::PadData pad_data;
std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
callback.pad_data(std::move(pad_data));
break;
}
}
}
StartReceive();
}
void HandleSend(const boost::system::error_code& error) {
// Send a request for getting port info for the pad
Request::PortInfo port_info{1, {pad_index, 0, 0, 0}};
const auto port_message = Request::Create(port_info, client_id);
std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint);
// Send a request for getting pad data for the pad
Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS};
const auto pad_message = Request::Create(pad_data, client_id);
std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint);
StartSend(timer.expiry());
}
SocketCallback callback;
boost::asio::io_service io_service;
boost::asio::basic_waitable_timer<clock> timer;
udp::socket socket;
u32 client_id{};
u8 pad_index{};
static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
std::array<u8, PORT_INFO_SIZE> send_buffer1;
std::array<u8, PAD_DATA_SIZE> send_buffer2;
udp::endpoint send_endpoint;
std::array<u8, MAX_PACKET_SIZE> receive_buffer;
udp::endpoint receive_endpoint;
};
static void SocketLoop(Socket* socket) {
socket->StartReceive();
socket->StartSend(Socket::clock::now());
socket->Loop();
}
Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port,
u8 pad_index, u32 client_id)
: status(status) {
StartCommunication(host, port, pad_index, client_id);
}
Client::~Client() {
socket->Stop();
thread.join();
}
void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
socket->Stop();
thread.join();
StartCommunication(host, port, pad_index, client_id);
}
void Client::OnVersion(Response::Version data) {
LOG_TRACE(Input, "Version packet received: {}", data.version);
}
void Client::OnPortInfo(Response::PortInfo data) {
LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
}
void Client::OnPadData(Response::PadData data) {
LOG_TRACE(Input, "PadData packet received");
if (data.packet_counter <= packet_sequence) {
LOG_WARNING(
Input,
"PadData packet dropped because its stale info. Current count: {} Packet count: {}",
packet_sequence, data.packet_counter);
return;
}
packet_sequence = data.packet_counter;
// TODO: Check how the Switch handles motions and how the CemuhookUDP motion
// directions correspond to the ones of the Switch
Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z);
Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll);
{
std::lock_guard guard(status->update_mutex);
status->motion_status = {accel, gyro};
// TODO: add a setting for "click" touch. Click touch refers to a device that differentiates
// between a simple "tap" and a hard press that causes the touch screen to click.
const bool is_active = data.touch_1.is_active != 0;
float x = 0;
float y = 0;
if (is_active && status->touch_calibration) {
const u16 min_x = status->touch_calibration->min_x;
const u16 max_x = status->touch_calibration->max_x;
const u16 min_y = status->touch_calibration->min_y;
const u16 max_y = status->touch_calibration->max_y;
x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) /
static_cast<float>(max_x - min_x);
y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) /
static_cast<float>(max_y - min_y);
}
status->touch_status = {x, y, is_active};
}
}
void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) {
SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
[this](Response::PortInfo info) { OnPortInfo(info); },
[this](Response::PadData data) { OnPadData(data); }};
LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback);
thread = std::thread{SocketLoop, this->socket.get()};
}
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
std::function<void()> success_callback,
std::function<void()> failure_callback) {
std::thread([=] {
Common::Event success_event;
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
[&](Response::PadData data) { success_event.Set(); }};
Socket socket{host, port, pad_index, client_id, callback};
std::thread worker_thread{SocketLoop, &socket};
bool result = success_event.WaitFor(std::chrono::seconds(8));
socket.Stop();
worker_thread.join();
if (result) {
success_callback();
} else {
failure_callback();
}
})
.detach();
}
CalibrationConfigurationJob::CalibrationConfigurationJob(
const std::string& host, u16 port, u8 pad_index, u32 client_id,
std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback) {
std::thread([=] {
constexpr u16 CALIBRATION_THRESHOLD = 100;
u16 min_x{UINT16_MAX};
u16 min_y{UINT16_MAX};
u16 max_x{};
u16 max_y{};
Status current_status{Status::Initialized};
SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {},
[&](Response::PadData data) {
if (current_status == Status::Initialized) {
// Receiving data means the communication is ready now
current_status = Status::Ready;
status_callback(current_status);
}
if (!data.touch_1.is_active) {
return;
}
LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x,
data.touch_1.y);
min_x = std::min(min_x, static_cast<u16>(data.touch_1.x));
min_y = std::min(min_y, static_cast<u16>(data.touch_1.y));
if (current_status == Status::Ready) {
// First touch - min data (min_x/min_y)
current_status = Status::Stage1Completed;
status_callback(current_status);
}
if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD &&
data.touch_1.y - min_y > CALIBRATION_THRESHOLD) {
// Set the current position as max value and finishes
// configuration
max_x = data.touch_1.x;
max_y = data.touch_1.y;
current_status = Status::Completed;
data_callback(min_x, min_y, max_x, max_y);
status_callback(current_status);
complete_event.Set();
}
}};
Socket socket{host, port, pad_index, client_id, callback};
std::thread worker_thread{SocketLoop, &socket};
complete_event.Wait();
socket.Stop();
worker_thread.join();
})
.detach();
}
CalibrationConfigurationJob::~CalibrationConfigurationJob() {
Stop();
}
void CalibrationConfigurationJob::Stop() {
complete_event.Set();
}
} // namespace InputCommon::CemuhookUDP

View File

@@ -0,0 +1,96 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <thread>
#include <tuple>
#include <vector>
#include "common/common_types.h"
#include "common/thread.h"
#include "common/vector_math.h"
namespace InputCommon::CemuhookUDP {
constexpr u16 DEFAULT_PORT = 26760;
constexpr char DEFAULT_ADDR[] = "127.0.0.1";
class Socket;
namespace Response {
struct PadData;
struct PortInfo;
struct Version;
} // namespace Response
struct DeviceStatus {
std::mutex update_mutex;
std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status;
std::tuple<float, float, bool> touch_status;
// calibration data for scaling the device's touch area to 3ds
struct CalibrationData {
u16 min_x{};
u16 min_y{};
u16 max_x{};
u16 max_y{};
};
std::optional<CalibrationData> touch_calibration;
};
class Client {
public:
explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR,
u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872);
~Client();
void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0,
u32 client_id = 24872);
private:
void OnVersion(Response::Version);
void OnPortInfo(Response::PortInfo);
void OnPadData(Response::PadData);
void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id);
std::unique_ptr<Socket> socket;
std::shared_ptr<DeviceStatus> status;
std::thread thread;
u64 packet_sequence = 0;
};
/// An async job allowing configuration of the touchpad calibration.
class CalibrationConfigurationJob {
public:
enum class Status {
Initialized,
Ready,
Stage1Completed,
Completed,
};
/**
* Constructs and starts the job with the specified parameter.
*
* @param status_callback Callback for job status updates
* @param data_callback Called when calibration data is ready
*/
explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index,
u32 client_id, std::function<void(Status)> status_callback,
std::function<void(u16, u16, u16, u16)> data_callback);
~CalibrationConfigurationJob();
void Stop();
private:
Common::Event complete_event;
};
void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id,
std::function<void()> success_callback,
std::function<void()> failure_callback);
} // namespace InputCommon::CemuhookUDP

View File

@@ -0,0 +1,79 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <cstddef>
#include <cstring>
#include "common/logging/log.h"
#include "input_common/udp/protocol.h"
namespace InputCommon::CemuhookUDP {
static constexpr std::size_t GetSizeOfResponseType(Type t) {
switch (t) {
case Type::Version:
return sizeof(Response::Version);
case Type::PortInfo:
return sizeof(Response::PortInfo);
case Type::PadData:
return sizeof(Response::PadData);
}
return 0;
}
namespace Response {
/**
* Returns Type if the packet is valid, else none
*
* Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without
* copying the buffer)
*/
std::optional<Type> Validate(u8* data, std::size_t size) {
if (size < sizeof(Header)) {
LOG_DEBUG(Input, "Invalid UDP packet received");
return std::nullopt;
}
Header header{};
std::memcpy(&header, data, sizeof(Header));
if (header.magic != SERVER_MAGIC) {
LOG_ERROR(Input, "UDP Packet has an unexpected magic value");
return std::nullopt;
}
if (header.protocol_version != PROTOCOL_VERSION) {
LOG_ERROR(Input, "UDP Packet protocol mismatch");
return std::nullopt;
}
if (header.type < Type::Version || header.type > Type::PadData) {
LOG_ERROR(Input, "UDP Packet is an unknown type");
return std::nullopt;
}
// Packet size must equal sizeof(Header) + sizeof(Data)
// and also verify that the packet info mentions the correct size. Since the spec includes the
// type of the packet as part of the data, we need to include it in size calculations here
// ie: payload_length == sizeof(T) + sizeof(Type)
const std::size_t data_len = GetSizeOfResponseType(header.type);
if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) {
LOG_ERROR(
Input,
"UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}",
size, header.payload_length, data_len + sizeof(Type));
return std::nullopt;
}
const u32 crc32 = header.crc;
boost::crc_32_type result;
// zero out the crc in the buffer and then run the crc against it
std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le));
result.process_bytes(data, data_len + sizeof(Header));
if (crc32 != result.checksum()) {
LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc));
return std::nullopt;
}
return header.type;
}
} // namespace Response
} // namespace InputCommon::CemuhookUDP

View File

@@ -0,0 +1,256 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <optional>
#include <type_traits>
#include <vector>
#include <boost/crc.hpp>
#include "common/bit_field.h"
#include "common/swap.h"
namespace InputCommon::CemuhookUDP {
constexpr std::size_t MAX_PACKET_SIZE = 100;
constexpr u16 PROTOCOL_VERSION = 1001;
constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE)
constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE)
enum class Type : u32 {
Version = 0x00100000,
PortInfo = 0x00100001,
PadData = 0x00100002,
};
struct Header {
u32_le magic{};
u16_le protocol_version{};
u16_le payload_length{};
u32_le crc{};
u32_le id{};
///> In the protocol, the type of the packet is not part of the header, but its convenient to
///> include in the header so the callee doesn't have to duplicate the type twice when building
///> the data
Type type{};
};
static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size");
static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable");
using MacAddress = std::array<u8, 6>;
constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0};
#pragma pack(push, 1)
template <typename T>
struct Message {
Header header{};
T data;
};
#pragma pack(pop)
template <typename T>
constexpr Type GetMessageType();
namespace Request {
struct Version {};
/**
* Requests the server to send information about what controllers are plugged into the ports
* In citra's case, we only have one controller, so for simplicity's sake, we can just send a
* request explicitly for the first controller port and leave it at that. In the future it would be
* nice to make this configurable
*/
constexpr u32 MAX_PORTS = 4;
struct PortInfo {
u32_le pad_count{}; ///> Number of ports to request data for
std::array<u8, MAX_PORTS> port;
};
static_assert(std::is_trivially_copyable_v<PortInfo>,
"UDP Request PortInfo is not trivially copyable");
/**
* Request the latest pad information from the server. If the server hasn't received this message
* from the client in a reasonable time frame, the server will stop sending updates. The default
* timeout seems to be 5 seconds.
*/
struct PadData {
enum class Flags : u8 {
AllPorts,
Id,
Mac,
};
/// Determines which method will be used as a look up for the controller
Flags flags{};
/// Index of the port of the controller to retrieve data about
u8 port_id{};
/// Mac address of the controller to retrieve data about
MacAddress mac;
};
static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size");
static_assert(std::is_trivially_copyable_v<PadData>,
"UDP Request PadData is not trivially copyable");
/**
* Creates a message with the proper header data that can be sent to the server.
* @param T data Request body to send
* @param client_id ID of the udp client (usually not checked on the server)
*/
template <typename T>
Message<T> Create(const T data, const u32 client_id = 0) {
boost::crc_32_type crc;
Header header{
CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(),
};
Message<T> message{header, data};
crc.process_bytes(&message, sizeof(Message<T>));
message.header.crc = crc.checksum();
return message;
}
} // namespace Request
namespace Response {
struct Version {
u16_le version{};
};
static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size");
static_assert(std::is_trivially_copyable_v<Version>,
"UDP Response Version is not trivially copyable");
struct PortInfo {
u8 id{};
u8 state{};
u8 model{};
u8 connection_type{};
MacAddress mac;
u8 battery{};
u8 is_pad_active{};
};
static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size");
static_assert(std::is_trivially_copyable_v<PortInfo>,
"UDP Response PortInfo is not trivially copyable");
#pragma pack(push, 1)
struct PadData {
PortInfo info{};
u32_le packet_counter{};
u16_le digital_button{};
// The following union isn't trivially copyable but we don't use this input anyway.
// union DigitalButton {
// u16_le button;
// BitField<0, 1, u16> button_1; // Share
// BitField<1, 1, u16> button_2; // L3
// BitField<2, 1, u16> button_3; // R3
// BitField<3, 1, u16> button_4; // Options
// BitField<4, 1, u16> button_5; // Up
// BitField<5, 1, u16> button_6; // Right
// BitField<6, 1, u16> button_7; // Down
// BitField<7, 1, u16> button_8; // Left
// BitField<8, 1, u16> button_9; // L2
// BitField<9, 1, u16> button_10; // R2
// BitField<10, 1, u16> button_11; // L1
// BitField<11, 1, u16> button_12; // R1
// BitField<12, 1, u16> button_13; // Triangle
// BitField<13, 1, u16> button_14; // Circle
// BitField<14, 1, u16> button_15; // Cross
// BitField<15, 1, u16> button_16; // Square
// } digital_button;
u8 home;
/// If the device supports a "click" on the touchpad, this will change to 1 when a click happens
u8 touch_hard_press{};
u8 left_stick_x{};
u8 left_stick_y{};
u8 right_stick_x{};
u8 right_stick_y{};
struct AnalogButton {
u8 button_8{};
u8 button_7{};
u8 button_6{};
u8 button_5{};
u8 button_12{};
u8 button_11{};
u8 button_10{};
u8 button_9{};
u8 button_16{};
u8 button_15{};
u8 button_14{};
u8 button_13{};
} analog_button;
struct TouchPad {
u8 is_active{};
u8 id{};
u16_le x{};
u16_le y{};
} touch_1, touch_2;
u64_le motion_timestamp;
struct Accelerometer {
float x{};
float y{};
float z{};
} accel;
struct Gyroscope {
float pitch{};
float yaw{};
float roll{};
} gyro;
};
#pragma pack(pop)
static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size ");
static_assert(std::is_trivially_copyable_v<PadData>,
"UDP Response PadData is not trivially copyable");
static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE,
"UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>");
static_assert(sizeof(PadData::AnalogButton) == 12,
"UDP Response AnalogButton struct has wrong size ");
static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size ");
static_assert(sizeof(PadData::Accelerometer) == 12,
"UDP Response Accelerometer struct has wrong size ");
static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size ");
/**
* Create a Response Message from the data
* @param data array of bytes sent from the server
* @return boost::none if it failed to parse or Type if it succeeded. The client can then safely
* copy the data into the appropriate struct for that Type
*/
std::optional<Type> Validate(u8* data, std::size_t size);
} // namespace Response
template <>
constexpr Type GetMessageType<Request::Version>() {
return Type::Version;
}
template <>
constexpr Type GetMessageType<Request::PortInfo>() {
return Type::PortInfo;
}
template <>
constexpr Type GetMessageType<Request::PadData>() {
return Type::PadData;
}
template <>
constexpr Type GetMessageType<Response::Version>() {
return Type::Version;
}
template <>
constexpr Type GetMessageType<Response::PortInfo>() {
return Type::PortInfo;
}
template <>
constexpr Type GetMessageType<Response::PadData>() {
return Type::PadData;
}
} // namespace InputCommon::CemuhookUDP

View File

@@ -0,0 +1,96 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/logging/log.h"
#include "common/param_package.h"
#include "core/frontend/input.h"
#include "core/settings.h"
#include "input_common/udp/client.h"
#include "input_common/udp/udp.h"
namespace InputCommon::CemuhookUDP {
class UDPTouchDevice final : public Input::TouchDevice {
public:
explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
std::tuple<float, float, bool> GetStatus() const {
std::lock_guard guard(status->update_mutex);
return status->touch_status;
}
private:
std::shared_ptr<DeviceStatus> status;
};
class UDPMotionDevice final : public Input::MotionDevice {
public:
explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const {
std::lock_guard guard(status->update_mutex);
return status->motion_status;
}
private:
std::shared_ptr<DeviceStatus> status;
};
class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
public:
explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override {
{
std::lock_guard guard(status->update_mutex);
status->touch_calibration.emplace();
// These default values work well for DS4 but probably not other touch inputs
status->touch_calibration->min_x = params.Get("min_x", 100);
status->touch_calibration->min_y = params.Get("min_y", 50);
status->touch_calibration->max_x = params.Get("max_x", 1800);
status->touch_calibration->max_y = params.Get("max_y", 850);
}
return std::make_unique<UDPTouchDevice>(status);
}
private:
std::shared_ptr<DeviceStatus> status;
};
class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
public:
explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {}
std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override {
return std::make_unique<UDPMotionDevice>(status);
}
private:
std::shared_ptr<DeviceStatus> status;
};
State::State() {
auto status = std::make_shared<DeviceStatus>();
client =
std::make_unique<Client>(status, Settings::values.udp_input_address,
Settings::values.udp_input_port, Settings::values.udp_pad_index);
Input::RegisterFactory<Input::TouchDevice>("cemuhookudp",
std::make_shared<UDPTouchFactory>(status));
Input::RegisterFactory<Input::MotionDevice>("cemuhookudp",
std::make_shared<UDPMotionFactory>(status));
}
State::~State() {
Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp");
Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp");
}
void State::ReloadUDPClient() {
client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port,
Settings::values.udp_pad_index);
}
std::unique_ptr<State> Init() {
return std::make_unique<State>();
}
} // namespace InputCommon::CemuhookUDP

View File

@@ -0,0 +1,27 @@
// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <memory>
#include <unordered_map>
#include "input_common/main.h"
#include "input_common/udp/client.h"
namespace InputCommon::CemuhookUDP {
class UDPTouchDevice;
class UDPMotionDevice;
class State {
public:
State();
~State();
void ReloadUDPClient();
private:
std::unique_ptr<Client> client;
};
std::unique_ptr<State> Init();
} // namespace InputCommon::CemuhookUDP

View File

@@ -154,6 +154,8 @@ if (ENABLE_VULKAN)
renderer_vulkan/maxwell_to_vk.cpp
renderer_vulkan/maxwell_to_vk.h
renderer_vulkan/renderer_vulkan.h
renderer_vulkan/vk_blit_screen.cpp
renderer_vulkan/vk_blit_screen.h
renderer_vulkan/vk_buffer_cache.cpp
renderer_vulkan/vk_buffer_cache.h
renderer_vulkan/vk_compute_pass.cpp

View File

@@ -91,6 +91,7 @@ void Maxwell3D::InitializeRegisterDefaults() {
regs.rasterize_enable = 1;
regs.rt_separate_frag_data = 1;
regs.framebuffer_srgb = 1;
regs.cull.front_face = Maxwell3D::Regs::Cull::FrontFace::ClockWise;
mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_end_gl)] = true;
mme_inline[MAXWELL3D_REG_INDEX(draw.vertex_begin_gl)] = true;

View File

@@ -260,6 +260,10 @@ CachedProgram BuildShader(const Device& device, u64 unique_identifier, ShaderTyp
"#extension GL_NV_shader_thread_group : require\n"
"#extension GL_NV_shader_thread_shuffle : require\n";
}
// This pragma stops Nvidia's driver from over optimizing math (probably using fp16 operations)
// on places where we don't want to.
// Thanks to Ryujinx for finding this workaround.
source += "#pragma optionNV(fastmath off)\n";
if (shader_type == ShaderType::Geometry) {
const auto [glsl_topology, max_vertices] = GetPrimitiveDescription(variant.primitive_mode);

View File

@@ -0,0 +1,627 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include <algorithm>
#include <array>
#include <cstring>
#include <memory>
#include <tuple>
#include <vector>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/math_util.h"
#include "core/core.h"
#include "core/frontend/emu_window.h"
#include "core/memory.h"
#include "video_core/gpu.h"
#include "video_core/morton.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_vulkan/declarations.h"
#include "video_core/renderer_vulkan/renderer_vulkan.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
#include "video_core/renderer_vulkan/vk_device.h"
#include "video_core/renderer_vulkan/vk_image.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
#include "video_core/renderer_vulkan/vk_resource_manager.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/surface.h"
namespace Vulkan {
namespace {
// Generated from the "shaders/" directory, read the instructions there.
constexpr u8 blit_vertex_code[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x27, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00,
0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
0x00, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
0x25, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
0x48, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x05, 0x00, 0x00, 0x00,
0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x48, 0x00, 0x05, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x47, 0x00, 0x03, 0x00, 0x11, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x13, 0x00, 0x00, 0x00, 0x21, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x19, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x24, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x25, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00, 0x21, 0x00, 0x03, 0x00,
0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00, 0x06, 0x00, 0x00, 0x00,
0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x04, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x06, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00,
0x0c, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x15, 0x00, 0x04, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x00, 0x04, 0x00,
0x10, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x03, 0x00,
0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x12, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00,
0x19, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x1b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2b, 0x00, 0x04, 0x00, 0x06, 0x00, 0x00, 0x00,
0x1c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x3f, 0x20, 0x00, 0x04, 0x00, 0x21, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x23, 0x00, 0x00, 0x00,
0x24, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x18, 0x00, 0x00, 0x00,
0x25, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x05, 0x00, 0x00, 0x00, 0x41, 0x00, 0x05, 0x00, 0x14, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
0x16, 0x00, 0x00, 0x00, 0x15, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00,
0x1a, 0x00, 0x00, 0x00, 0x19, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00, 0x06, 0x00, 0x00, 0x00,
0x1d, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x51, 0x00, 0x05, 0x00,
0x06, 0x00, 0x00, 0x00, 0x1e, 0x00, 0x00, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x50, 0x00, 0x07, 0x00, 0x07, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00, 0x1d, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x91, 0x00, 0x05, 0x00,
0x07, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x16, 0x00, 0x00, 0x00, 0x1f, 0x00, 0x00, 0x00,
0x41, 0x00, 0x05, 0x00, 0x21, 0x00, 0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x0d, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x22, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00,
0x3d, 0x00, 0x04, 0x00, 0x17, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0x25, 0x00, 0x00, 0x00,
0x3e, 0x00, 0x03, 0x00, 0x24, 0x00, 0x00, 0x00, 0x26, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00,
0x38, 0x00, 0x01, 0x00};
constexpr u8 blit_fragment_code[] = {
0x03, 0x02, 0x23, 0x07, 0x00, 0x00, 0x01, 0x00, 0x07, 0x00, 0x08, 0x00, 0x14, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x11, 0x00, 0x02, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x06, 0x00,
0x01, 0x00, 0x00, 0x00, 0x47, 0x4c, 0x53, 0x4c, 0x2e, 0x73, 0x74, 0x64, 0x2e, 0x34, 0x35, 0x30,
0x00, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x03, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
0x0f, 0x00, 0x07, 0x00, 0x04, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x6d, 0x61, 0x69, 0x6e,
0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x10, 0x00, 0x03, 0x00,
0x04, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x09, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00,
0x22, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x0d, 0x00, 0x00, 0x00,
0x21, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x47, 0x00, 0x04, 0x00, 0x11, 0x00, 0x00, 0x00,
0x1e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x13, 0x00, 0x02, 0x00, 0x02, 0x00, 0x00, 0x00,
0x21, 0x00, 0x03, 0x00, 0x03, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x16, 0x00, 0x03, 0x00,
0x06, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x07, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00, 0x00, 0x07, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x08, 0x00, 0x00, 0x00,
0x09, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x19, 0x00, 0x09, 0x00, 0x0a, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1b, 0x00, 0x03, 0x00,
0x0b, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x0c, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00,
0x06, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x20, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x3b, 0x00, 0x04, 0x00, 0x10, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x36, 0x00, 0x05, 0x00, 0x02, 0x00, 0x00, 0x00,
0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x02, 0x00,
0x05, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0b, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
0x0d, 0x00, 0x00, 0x00, 0x3d, 0x00, 0x04, 0x00, 0x0f, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00,
0x11, 0x00, 0x00, 0x00, 0x57, 0x00, 0x05, 0x00, 0x07, 0x00, 0x00, 0x00, 0x13, 0x00, 0x00, 0x00,
0x0e, 0x00, 0x00, 0x00, 0x12, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x03, 0x00, 0x09, 0x00, 0x00, 0x00,
0x13, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x01, 0x00, 0x38, 0x00, 0x01, 0x00};
struct ScreenRectVertex {
ScreenRectVertex() = default;
explicit ScreenRectVertex(f32 x, f32 y, f32 u, f32 v) : position{{x, y}}, tex_coord{{u, v}} {}
std::array<f32, 2> position;
std::array<f32, 2> tex_coord;
static vk::VertexInputBindingDescription GetDescription() {
return vk::VertexInputBindingDescription(0, sizeof(ScreenRectVertex),
vk::VertexInputRate::eVertex);
}
static std::array<vk::VertexInputAttributeDescription, 2> GetAttributes() {
return {vk::VertexInputAttributeDescription(0, 0, vk::Format::eR32G32Sfloat,
offsetof(ScreenRectVertex, position)),
vk::VertexInputAttributeDescription(1, 0, vk::Format::eR32G32Sfloat,
offsetof(ScreenRectVertex, tex_coord))};
}
};
constexpr std::array<f32, 4 * 4> MakeOrthographicMatrix(f32 width, f32 height) {
// clang-format off
return { 2.f / width, 0.f, 0.f, 0.f,
0.f, 2.f / height, 0.f, 0.f,
0.f, 0.f, 1.f, 0.f,
-1.f, -1.f, 0.f, 1.f};
// clang-format on
}
std::size_t GetBytesPerPixel(const Tegra::FramebufferConfig& framebuffer) {
using namespace VideoCore::Surface;
return GetBytesPerPixel(PixelFormatFromGPUPixelFormat(framebuffer.pixel_format));
}
std::size_t GetSizeInBytes(const Tegra::FramebufferConfig& framebuffer) {
return static_cast<std::size_t>(framebuffer.stride) *
static_cast<std::size_t>(framebuffer.height) * GetBytesPerPixel(framebuffer);
}
vk::Format GetFormat(const Tegra::FramebufferConfig& framebuffer) {
switch (framebuffer.pixel_format) {
case Tegra::FramebufferConfig::PixelFormat::ABGR8:
return vk::Format::eA8B8G8R8UnormPack32;
case Tegra::FramebufferConfig::PixelFormat::RGB565:
return vk::Format::eR5G6B5UnormPack16;
default:
UNIMPLEMENTED_MSG("Unknown framebuffer pixel format: {}",
static_cast<u32>(framebuffer.pixel_format));
return vk::Format::eA8B8G8R8UnormPack32;
}
}
} // Anonymous namespace
struct VKBlitScreen::BufferData {
struct {
std::array<f32, 4 * 4> modelview_matrix;
} uniform;
std::array<ScreenRectVertex, 4> vertices;
// Unaligned image data goes here
};
VKBlitScreen::VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
VKSwapchain& swapchain, VKScheduler& scheduler,
const VKScreenInfo& screen_info)
: system{system}, render_window{render_window}, rasterizer{rasterizer}, device{device},
resource_manager{resource_manager}, memory_manager{memory_manager}, swapchain{swapchain},
scheduler{scheduler}, image_count{swapchain.GetImageCount()}, screen_info{screen_info} {
watches.resize(image_count);
std::generate(watches.begin(), watches.end(),
[]() { return std::make_unique<VKFenceWatch>(); });
CreateStaticResources();
CreateDynamicResources();
}
VKBlitScreen::~VKBlitScreen() = default;
void VKBlitScreen::Recreate() {
CreateDynamicResources();
}
std::tuple<VKFence&, vk::Semaphore> VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
bool use_accelerated) {
RefreshResources(framebuffer);
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
const std::size_t image_index = swapchain.GetImageIndex();
watches[image_index]->Watch(scheduler.GetFence());
VKImage* blit_image = use_accelerated ? screen_info.image : raw_images[image_index].get();
UpdateDescriptorSet(image_index, blit_image->GetPresentView());
BufferData data;
SetUniformData(data, framebuffer);
SetVertexData(data, framebuffer);
auto map = buffer_commit->Map();
std::memcpy(map.GetAddress(), &data, sizeof(data));
if (!use_accelerated) {
const u64 image_offset = GetRawImageOffset(framebuffer, image_index);
const auto pixel_format =
VideoCore::Surface::PixelFormatFromGPUPixelFormat(framebuffer.pixel_format);
const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
const auto host_ptr = system.Memory().GetPointer(framebuffer_addr);
rasterizer.FlushRegion(ToCacheAddr(host_ptr), GetSizeInBytes(framebuffer));
// TODO(Rodrigo): Read this from HLE
constexpr u32 block_height_log2 = 4;
VideoCore::MortonSwizzle(VideoCore::MortonSwizzleMode::MortonToLinear, pixel_format,
framebuffer.stride, block_height_log2, framebuffer.height, 0, 1, 1,
map.GetAddress() + image_offset, host_ptr);
blit_image->Transition(0, 1, 0, 1, vk::PipelineStageFlagBits::eTransfer,
vk::AccessFlagBits::eTransferWrite,
vk::ImageLayout::eTransferDstOptimal);
const vk::BufferImageCopy copy(image_offset, 0, 0,
{vk::ImageAspectFlagBits::eColor, 0, 0, 1}, {0, 0, 0},
{framebuffer.width, framebuffer.height, 1});
scheduler.Record([buffer_handle = *buffer, image = blit_image->GetHandle(),
copy](auto cmdbuf, auto& dld) {
cmdbuf.copyBufferToImage(buffer_handle, image, vk::ImageLayout::eTransferDstOptimal,
{copy}, dld);
});
}
map.Release();
blit_image->Transition(0, 1, 0, 1, vk::PipelineStageFlagBits::eFragmentShader,
vk::AccessFlagBits::eShaderRead,
vk::ImageLayout::eShaderReadOnlyOptimal);
scheduler.Record([renderpass = *renderpass, framebuffer = *framebuffers[image_index],
descriptor_set = descriptor_sets[image_index], buffer = *buffer,
size = swapchain.GetSize(), pipeline = *pipeline,
layout = *pipeline_layout](auto cmdbuf, auto& dld) {
const vk::ClearValue clear_color{std::array{0.0f, 0.0f, 0.0f, 1.0f}};
const vk::RenderPassBeginInfo renderpass_bi(renderpass, framebuffer, {{0, 0}, size}, 1,
&clear_color);
cmdbuf.beginRenderPass(renderpass_bi, vk::SubpassContents::eInline, dld);
cmdbuf.bindPipeline(vk::PipelineBindPoint::eGraphics, pipeline, dld);
cmdbuf.setViewport(
0,
{{0.0f, 0.0f, static_cast<f32>(size.width), static_cast<f32>(size.height), 0.0f, 1.0f}},
dld);
cmdbuf.setScissor(0, {{{0, 0}, size}}, dld);
cmdbuf.bindVertexBuffers(0, {buffer}, {offsetof(BufferData, vertices)}, dld);
cmdbuf.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, layout, 0, {descriptor_set}, {},
dld);
cmdbuf.draw(4, 1, 0, 0, dld);
cmdbuf.endRenderPass(dld);
});
return {scheduler.GetFence(), *semaphores[image_index]};
}
void VKBlitScreen::CreateStaticResources() {
CreateShaders();
CreateSemaphores();
CreateDescriptorPool();
CreateDescriptorSetLayout();
CreateDescriptorSets();
CreatePipelineLayout();
CreateSampler();
}
void VKBlitScreen::CreateDynamicResources() {
CreateRenderPass();
CreateFramebuffers();
CreateGraphicsPipeline();
}
void VKBlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer) {
if (framebuffer.width == raw_width && framebuffer.height == raw_height && !raw_images.empty()) {
return;
}
raw_width = framebuffer.width;
raw_height = framebuffer.height;
ReleaseRawImages();
CreateStagingBuffer(framebuffer);
CreateRawImages(framebuffer);
}
void VKBlitScreen::CreateShaders() {
vertex_shader = BuildShader(device, sizeof(blit_vertex_code), blit_vertex_code);
fragment_shader = BuildShader(device, sizeof(blit_fragment_code), blit_fragment_code);
}
void VKBlitScreen::CreateSemaphores() {
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
semaphores.resize(image_count);
for (std::size_t i = 0; i < image_count; ++i) {
semaphores[i] = dev.createSemaphoreUnique({}, nullptr, dld);
}
}
void VKBlitScreen::CreateDescriptorPool() {
const std::array<vk::DescriptorPoolSize, 2> pool_sizes{
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, static_cast<u32>(image_count)},
vk::DescriptorPoolSize{vk::DescriptorType::eCombinedImageSampler,
static_cast<u32>(image_count)}};
const vk::DescriptorPoolCreateInfo pool_ci(
{}, static_cast<u32>(image_count), static_cast<u32>(pool_sizes.size()), pool_sizes.data());
const auto dev = device.GetLogical();
descriptor_pool = dev.createDescriptorPoolUnique(pool_ci, nullptr, device.GetDispatchLoader());
}
void VKBlitScreen::CreateRenderPass() {
const vk::AttachmentDescription color_attachment(
{}, swapchain.GetImageFormat(), vk::SampleCountFlagBits::e1, vk::AttachmentLoadOp::eClear,
vk::AttachmentStoreOp::eStore, vk::AttachmentLoadOp::eDontCare,
vk::AttachmentStoreOp::eDontCare, vk::ImageLayout::eUndefined,
vk::ImageLayout::ePresentSrcKHR);
const vk::AttachmentReference color_attachment_ref(0, vk::ImageLayout::eColorAttachmentOptimal);
const vk::SubpassDescription subpass_description({}, vk::PipelineBindPoint::eGraphics, 0,
nullptr, 1, &color_attachment_ref, nullptr,
nullptr, 0, nullptr);
const vk::SubpassDependency dependency(
VK_SUBPASS_EXTERNAL, 0, vk::PipelineStageFlagBits::eColorAttachmentOutput,
vk::PipelineStageFlagBits::eColorAttachmentOutput, {},
vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite, {});
const vk::RenderPassCreateInfo renderpass_ci({}, 1, &color_attachment, 1, &subpass_description,
1, &dependency);
const auto dev = device.GetLogical();
renderpass = dev.createRenderPassUnique(renderpass_ci, nullptr, device.GetDispatchLoader());
}
void VKBlitScreen::CreateDescriptorSetLayout() {
const std::array<vk::DescriptorSetLayoutBinding, 2> layout_bindings{
vk::DescriptorSetLayoutBinding(0, vk::DescriptorType::eUniformBuffer, 1,
vk::ShaderStageFlagBits::eVertex, nullptr),
vk::DescriptorSetLayoutBinding(1, vk::DescriptorType::eCombinedImageSampler, 1,
vk::ShaderStageFlagBits::eFragment, nullptr)};
const vk::DescriptorSetLayoutCreateInfo descriptor_layout_ci(
{}, static_cast<u32>(layout_bindings.size()), layout_bindings.data());
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
descriptor_set_layout = dev.createDescriptorSetLayoutUnique(descriptor_layout_ci, nullptr, dld);
}
void VKBlitScreen::CreateDescriptorSets() {
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
descriptor_sets.resize(image_count);
for (std::size_t i = 0; i < image_count; ++i) {
const vk::DescriptorSetLayout layout = *descriptor_set_layout;
const vk::DescriptorSetAllocateInfo descriptor_set_ai(*descriptor_pool, 1, &layout);
const vk::Result result =
dev.allocateDescriptorSets(&descriptor_set_ai, &descriptor_sets[i], dld);
ASSERT(result == vk::Result::eSuccess);
}
}
void VKBlitScreen::CreatePipelineLayout() {
const vk::PipelineLayoutCreateInfo pipeline_layout_ci({}, 1, &descriptor_set_layout.get(), 0,
nullptr);
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
pipeline_layout = dev.createPipelineLayoutUnique(pipeline_layout_ci, nullptr, dld);
}
void VKBlitScreen::CreateGraphicsPipeline() {
const std::array shader_stages = {
vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eVertex, *vertex_shader,
"main", nullptr),
vk::PipelineShaderStageCreateInfo({}, vk::ShaderStageFlagBits::eFragment, *fragment_shader,
"main", nullptr)};
const auto vertex_binding_description = ScreenRectVertex::GetDescription();
const auto vertex_attrs_description = ScreenRectVertex::GetAttributes();
const vk::PipelineVertexInputStateCreateInfo vertex_input(
{}, 1, &vertex_binding_description, static_cast<u32>(vertex_attrs_description.size()),
vertex_attrs_description.data());
const vk::PipelineInputAssemblyStateCreateInfo input_assembly(
{}, vk::PrimitiveTopology::eTriangleStrip, false);
// Set a dummy viewport, it's going to be replaced by dynamic states.
const vk::Viewport viewport(0.0f, 0.0f, 1.0f, 1.0f, 0.0f, 1.0f);
const vk::Rect2D scissor({0, 0}, {1, 1});
const vk::PipelineViewportStateCreateInfo viewport_state({}, 1, &viewport, 1, &scissor);
const vk::PipelineRasterizationStateCreateInfo rasterizer(
{}, false, false, vk::PolygonMode::eFill, vk::CullModeFlagBits::eNone,
vk::FrontFace::eClockwise, false, 0.0f, 0.0f, 0.0f, 1.0f);
const vk::PipelineMultisampleStateCreateInfo multisampling({}, vk::SampleCountFlagBits::e1,
false, 0.0f, nullptr, false, false);
const vk::PipelineColorBlendAttachmentState color_blend_attachment(
false, vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
vk::BlendFactor::eZero, vk::BlendFactor::eZero, vk::BlendOp::eAdd,
vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG |
vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA);
const vk::PipelineColorBlendStateCreateInfo color_blending(
{}, false, vk::LogicOp::eCopy, 1, &color_blend_attachment, {0.0f, 0.0f, 0.0f, 0.0f});
const std::array<vk::DynamicState, 2> dynamic_states = {vk::DynamicState::eViewport,
vk::DynamicState::eScissor};
const vk::PipelineDynamicStateCreateInfo dynamic_state(
{}, static_cast<u32>(dynamic_states.size()), dynamic_states.data());
const vk::GraphicsPipelineCreateInfo pipeline_ci(
{}, static_cast<u32>(shader_stages.size()), shader_stages.data(), &vertex_input,
&input_assembly, nullptr, &viewport_state, &rasterizer, &multisampling, nullptr,
&color_blending, &dynamic_state, *pipeline_layout, *renderpass, 0, nullptr, 0);
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
pipeline = dev.createGraphicsPipelineUnique({}, pipeline_ci, nullptr, dld);
}
void VKBlitScreen::CreateSampler() {
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const vk::SamplerCreateInfo sampler_ci(
{}, vk::Filter::eLinear, vk::Filter::eLinear, vk::SamplerMipmapMode::eLinear,
vk::SamplerAddressMode::eClampToBorder, vk::SamplerAddressMode::eClampToBorder,
vk::SamplerAddressMode::eClampToBorder, 0.0f, false, 0.0f, false, vk::CompareOp::eNever,
0.0f, 0.0f, vk::BorderColor::eFloatOpaqueBlack, false);
sampler = dev.createSamplerUnique(sampler_ci, nullptr, dld);
}
void VKBlitScreen::CreateFramebuffers() {
const vk::Extent2D size{swapchain.GetSize()};
framebuffers.clear();
framebuffers.resize(image_count);
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
for (std::size_t i = 0; i < image_count; ++i) {
const vk::ImageView image_view{swapchain.GetImageViewIndex(i)};
const vk::FramebufferCreateInfo framebuffer_ci({}, *renderpass, 1, &image_view, size.width,
size.height, 1);
framebuffers[i] = dev.createFramebufferUnique(framebuffer_ci, nullptr, dld);
}
}
void VKBlitScreen::ReleaseRawImages() {
for (std::size_t i = 0; i < raw_images.size(); ++i) {
watches[i]->Wait();
}
raw_images.clear();
raw_buffer_commits.clear();
buffer.reset();
buffer_commit.reset();
}
void VKBlitScreen::CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer) {
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const vk::BufferCreateInfo buffer_ci({}, CalculateBufferSize(framebuffer),
vk::BufferUsageFlagBits::eTransferSrc |
vk::BufferUsageFlagBits::eVertexBuffer |
vk::BufferUsageFlagBits::eUniformBuffer,
vk::SharingMode::eExclusive, 0, nullptr);
buffer = dev.createBufferUnique(buffer_ci, nullptr, dld);
buffer_commit = memory_manager.Commit(*buffer, true);
}
void VKBlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
raw_images.resize(image_count);
raw_buffer_commits.resize(image_count);
const auto format = GetFormat(framebuffer);
for (std::size_t i = 0; i < image_count; ++i) {
const vk::ImageCreateInfo image_ci(
{}, vk::ImageType::e2D, format, {framebuffer.width, framebuffer.height, 1}, 1, 1,
vk::SampleCountFlagBits::e1, vk::ImageTiling::eOptimal,
vk::ImageUsageFlagBits::eTransferDst | vk::ImageUsageFlagBits::eSampled,
vk::SharingMode::eExclusive, 0, nullptr, vk::ImageLayout::eUndefined);
raw_images[i] =
std::make_unique<VKImage>(device, scheduler, image_ci, vk::ImageAspectFlagBits::eColor);
raw_buffer_commits[i] = memory_manager.Commit(raw_images[i]->GetHandle(), false);
}
}
void VKBlitScreen::UpdateDescriptorSet(std::size_t image_index, vk::ImageView image_view) const {
const vk::DescriptorSet descriptor_set = descriptor_sets[image_index];
const vk::DescriptorBufferInfo buffer_info(*buffer, offsetof(BufferData, uniform),
sizeof(BufferData::uniform));
const vk::WriteDescriptorSet ubo_write(descriptor_set, 0, 0, 1,
vk::DescriptorType::eUniformBuffer, nullptr,
&buffer_info, nullptr);
const vk::DescriptorImageInfo image_info(*sampler, image_view,
vk::ImageLayout::eShaderReadOnlyOptimal);
const vk::WriteDescriptorSet sampler_write(descriptor_set, 1, 0, 1,
vk::DescriptorType::eCombinedImageSampler,
&image_info, nullptr, nullptr);
const auto dev = device.GetLogical();
const auto& dld = device.GetDispatchLoader();
dev.updateDescriptorSets({ubo_write, sampler_write}, {}, dld);
}
void VKBlitScreen::SetUniformData(BufferData& data,
const Tegra::FramebufferConfig& framebuffer) const {
const auto& layout = render_window.GetFramebufferLayout();
data.uniform.modelview_matrix =
MakeOrthographicMatrix(static_cast<f32>(layout.width), static_cast<f32>(layout.height));
}
void VKBlitScreen::SetVertexData(BufferData& data,
const Tegra::FramebufferConfig& framebuffer) const {
const auto& framebuffer_transform_flags = framebuffer.transform_flags;
const auto& framebuffer_crop_rect = framebuffer.crop_rect;
static constexpr Common::Rectangle<f32> texcoords{0.f, 0.f, 1.f, 1.f};
auto left = texcoords.left;
auto right = texcoords.right;
switch (framebuffer_transform_flags) {
case Tegra::FramebufferConfig::TransformFlags::Unset:
break;
case Tegra::FramebufferConfig::TransformFlags::FlipV:
// Flip the framebuffer vertically
left = texcoords.right;
right = texcoords.left;
break;
default:
UNIMPLEMENTED_MSG("Unsupported framebuffer_transform_flags={}",
static_cast<u32>(framebuffer_transform_flags));
break;
}
UNIMPLEMENTED_IF(framebuffer_crop_rect.top != 0);
UNIMPLEMENTED_IF(framebuffer_crop_rect.left != 0);
// Scale the output by the crop width/height. This is commonly used with 1280x720 rendering
// (e.g. handheld mode) on a 1920x1080 framebuffer.
f32 scale_u = 1.0f;
f32 scale_v = 1.0f;
if (framebuffer_crop_rect.GetWidth() > 0) {
scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) /
static_cast<f32>(screen_info.width);
}
if (framebuffer_crop_rect.GetHeight() > 0) {
scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) /
static_cast<f32>(screen_info.height);
}
const auto& screen = render_window.GetFramebufferLayout().screen;
const auto x = static_cast<f32>(screen.left);
const auto y = static_cast<f32>(screen.top);
const auto w = static_cast<f32>(screen.GetWidth());
const auto h = static_cast<f32>(screen.GetHeight());
data.vertices[0] = ScreenRectVertex(x, y, texcoords.top * scale_u, left * scale_v);
data.vertices[1] = ScreenRectVertex(x + w, y, texcoords.bottom * scale_u, left * scale_v);
data.vertices[2] = ScreenRectVertex(x, y + h, texcoords.top * scale_u, right * scale_v);
data.vertices[3] = ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v);
}
u64 VKBlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const {
return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count;
}
u64 VKBlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
std::size_t image_index) const {
constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData));
return first_image_offset + GetSizeInBytes(framebuffer) * image_index;
}
} // namespace Vulkan

View File

@@ -0,0 +1,119 @@
// Copyright 2018 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include <array>
#include <memory>
#include <tuple>
#include "video_core/renderer_vulkan/declarations.h"
#include "video_core/renderer_vulkan/vk_memory_manager.h"
#include "video_core/renderer_vulkan/vk_resource_manager.h"
namespace Core {
class System;
}
namespace Core::Frontend {
class EmuWindow;
}
namespace Tegra {
struct FramebufferConfig;
}
namespace VideoCore {
class RasterizerInterface;
}
namespace Vulkan {
struct ScreenInfo;
class RasterizerVulkan;
class VKDevice;
class VKFence;
class VKImage;
class VKScheduler;
class VKSwapchain;
class VKBlitScreen final {
public:
explicit VKBlitScreen(Core::System& system, Core::Frontend::EmuWindow& render_window,
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
VKResourceManager& resource_manager, VKMemoryManager& memory_manager,
VKSwapchain& swapchain, VKScheduler& scheduler,
const VKScreenInfo& screen_info);
~VKBlitScreen();
void Recreate();
std::tuple<VKFence&, vk::Semaphore> Draw(const Tegra::FramebufferConfig& framebuffer,
bool use_accelerated);
private:
struct BufferData;
void CreateStaticResources();
void CreateShaders();
void CreateSemaphores();
void CreateDescriptorPool();
void CreateRenderPass();
void CreateDescriptorSetLayout();
void CreateDescriptorSets();
void CreatePipelineLayout();
void CreateGraphicsPipeline();
void CreateSampler();
void CreateDynamicResources();
void CreateFramebuffers();
void RefreshResources(const Tegra::FramebufferConfig& framebuffer);
void ReleaseRawImages();
void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer);
void CreateRawImages(const Tegra::FramebufferConfig& framebuffer);
void UpdateDescriptorSet(std::size_t image_index, vk::ImageView image_view) const;
void SetUniformData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const;
void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer) const;
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
std::size_t image_index) const;
Core::System& system;
Core::Frontend::EmuWindow& render_window;
VideoCore::RasterizerInterface& rasterizer;
const VKDevice& device;
VKResourceManager& resource_manager;
VKMemoryManager& memory_manager;
VKSwapchain& swapchain;
VKScheduler& scheduler;
const std::size_t image_count;
const VKScreenInfo& screen_info;
UniqueShaderModule vertex_shader;
UniqueShaderModule fragment_shader;
UniqueDescriptorPool descriptor_pool;
UniqueDescriptorSetLayout descriptor_set_layout;
UniquePipelineLayout pipeline_layout;
UniquePipeline pipeline;
UniqueRenderPass renderpass;
std::vector<UniqueFramebuffer> framebuffers;
std::vector<vk::DescriptorSet> descriptor_sets;
UniqueSampler sampler;
UniqueBuffer buffer;
VKMemoryCommit buffer_commit;
std::vector<std::unique_ptr<VKFenceWatch>> watches;
std::vector<UniqueSemaphore> semaphores;
std::vector<std::unique_ptr<VKImage>> raw_images;
std::vector<VKMemoryCommit> raw_buffer_commits;
u32 raw_width = 0;
u32 raw_height = 0;
};
} // namespace Vulkan

View File

@@ -191,8 +191,7 @@ UniquePipeline VKGraphicsPipeline::CreatePipeline(const RenderPassParams& render
const vk::PipelineRasterizationStateCreateInfo rasterizer_ci(
{}, rs.depth_clamp_enable, false, vk::PolygonMode::eFill,
rs.cull_enable ? MaxwellToVK::CullFace(rs.cull_face) : vk::CullModeFlagBits::eNone,
rs.cull_enable ? MaxwellToVK::FrontFace(rs.front_face) : vk::FrontFace::eCounterClockwise,
rs.depth_bias_enable, 0.0f, 0.0f, 0.0f, 1.0f);
MaxwellToVK::FrontFace(rs.front_face), rs.depth_bias_enable, 0.0f, 0.0f, 0.0f, 1.0f);
const vk::PipelineMultisampleStateCreateInfo multisampling_ci(
{}, vk::SampleCountFlagBits::e1, false, 0.0f, nullptr, false, false);

View File

@@ -1796,9 +1796,17 @@ private:
return {};
}
Expression UAtomicAdd(Operation) {
UNIMPLEMENTED();
return {};
Expression UAtomicAdd(Operation operation) {
const auto& smem = std::get<SmemNode>(*operation[0]);
Id address = AsUint(Visit(smem.GetAddress()));
address = OpShiftRightLogical(t_uint, address, Constant(t_uint, 2U));
const Id pointer = OpAccessChain(t_smem_uint, shared_memory, address);
const Id scope = Constant(t_uint, static_cast<u32>(spv::Scope::Device));
const Id semantics = Constant(t_uint, 0U);
const Id value = AsUint(Visit(operation[1]));
return {OpAtomicIAdd(t_uint, pointer, scope, semantics, value), Type::Uint};
}
Expression Branch(Operation operation) {

View File

@@ -123,7 +123,7 @@ bool VKSwapchain::Present(vk::Semaphore render_semaphore, VKFence& fence) {
ASSERT(fences[image_index] == nullptr);
fences[image_index] = &fence;
frame_index = (frame_index + 1) % image_count;
frame_index = (frame_index + 1) % static_cast<u32>(image_count);
return recreated;
}

View File

@@ -40,19 +40,19 @@ public:
return extent;
}
u32 GetImageCount() const {
std::size_t GetImageCount() const {
return image_count;
}
u32 GetImageIndex() const {
std::size_t GetImageIndex() const {
return image_index;
}
vk::Image GetImageIndex(u32 index) const {
vk::Image GetImageIndex(std::size_t index) const {
return images[index];
}
vk::ImageView GetImageViewIndex(u32 index) const {
vk::ImageView GetImageViewIndex(std::size_t index) const {
return *image_views[index];
}
@@ -77,7 +77,7 @@ private:
UniqueSwapchainKHR swapchain;
u32 image_count{};
std::size_t image_count{};
std::vector<vk::Image> images;
std::vector<UniqueImageView> image_views;
std::vector<UniqueFramebuffer> framebuffers;

View File

@@ -10,6 +10,7 @@
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "yuzu/configuration/config.h"
#include "yuzu/uisettings.h"
@@ -429,6 +430,16 @@ void Config::ReadControlValues() {
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"))
.toString()
.toStdString();
Settings::values.udp_input_address =
ReadSetting(QStringLiteral("udp_input_address"),
QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR))
.toString()
.toStdString();
Settings::values.udp_input_port = static_cast<u16>(
ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT)
.toInt());
Settings::values.udp_pad_index =
static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt());
qt_config->endGroup();
}
@@ -911,6 +922,12 @@ void Config::SaveControlValues() {
QString::fromStdString(Settings::values.motion_device),
QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01"));
WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false);
WriteSetting(QStringLiteral("udp_input_address"),
QString::fromStdString(Settings::values.udp_input_address),
QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR));
WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port,
InputCommon::CemuhookUDP::DEFAULT_PORT);
WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0);
qt_config->endGroup();
}

View File

@@ -7,10 +7,10 @@
namespace UISettings {
const Themes themes{{
{"Default", "default"},
{"Light", "default"},
{"Light Colorful", "colorful"},
{"Dark", "qdarkstyle"},
{"Colorful", "colorful"},
{"Colorful Dark", "colorful_dark"},
{"Dark Colorful", "colorful_dark"},
}};
Values values = {};

View File

@@ -12,6 +12,7 @@
#include "core/hle/service/acc/profile_manager.h"
#include "core/settings.h"
#include "input_common/main.h"
#include "input_common/udp/client.h"
#include "yuzu_cmd/config.h"
#include "yuzu_cmd/default_ini.h"
@@ -297,6 +298,10 @@ void Config::ReadValues() {
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15);
Settings::values.touchscreen.diameter_y =
sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15);
Settings::values.udp_input_address =
sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR);
Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger(
"Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT));
std::transform(keyboard_keys.begin(), keyboard_keys.end(),
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);

View File

@@ -69,12 +69,29 @@ rstick=
# - "motion_emu" (default) for emulating motion input from mouse input. Required parameters:
# - "update_period": update period in milliseconds (default to 100)
# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
# - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol
motion_device=
# for touch input, the following devices are available:
# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
# - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol
# - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system
touch_device=
# Most desktop operating systems do not expose a way to poll the motion state of the controllers
# so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly
# from a controller device to the client program. Citra has a client that can connect and read
# from any cemuhook compatible motion program.
# IPv4 address of the udp input server (Default "127.0.0.1")
udp_input_address=
# Port of the udp input server. (Default 26760)
udp_input_port=
# The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0)
udp_pad_index=
[Core]
# Whether to use multi-core for CPU emulation
# 0 (default): Disabled, 1: Enabled