summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp20
-rw-r--r--src/core/frontend/framebuffer_layout.cpp7
-rw-r--r--src/core/frontend/framebuffer_layout.h11
-rw-r--r--src/core/hle/kernel/kernel.cpp22
-rw-r--r--src/core/hle/service/pm/pm.cpp47
-rw-r--r--src/video_core/renderer_vulkan/blit_image.cpp38
-rw-r--r--src/video_core/renderer_vulkan/blit_image.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp6
-rw-r--r--src/video_core/video_core.cpp6
-rw-r--r--src/video_core/video_core.h2
-rw-r--r--src/yuzu/bootmanager.cpp11
-rw-r--r--src/yuzu/bootmanager.h6
-rw-r--r--src/yuzu/main.cpp99
-rw-r--r--src/yuzu/main.h5
-rw-r--r--src/yuzu/main.ui90
15 files changed, 248 insertions, 125 deletions
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
index 4e73cc03a..56836bd05 100644
--- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp
@@ -86,6 +86,26 @@ public:
86 num_instructions, MemoryReadCode(pc)); 86 num_instructions, MemoryReadCode(pc));
87 } 87 }
88 88
89 void InstructionCacheOperationRaised(Dynarmic::A64::InstructionCacheOperation op,
90 VAddr value) override {
91 switch (op) {
92 case Dynarmic::A64::InstructionCacheOperation::InvalidateByVAToPoU: {
93 static constexpr u64 ICACHE_LINE_SIZE = 64;
94
95 const u64 cache_line_start = value & ~(ICACHE_LINE_SIZE - 1);
96 parent.InvalidateCacheRange(cache_line_start, ICACHE_LINE_SIZE);
97 break;
98 }
99 case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoU:
100 parent.ClearInstructionCache();
101 break;
102 case Dynarmic::A64::InstructionCacheOperation::InvalidateAllToPoUInnerSharable:
103 default:
104 LOG_DEBUG(Core_ARM, "Unprocesseed instruction cache operation: {}", op);
105 break;
106 }
107 }
108
89 void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override { 109 void ExceptionRaised(u64 pc, Dynarmic::A64::Exception exception) override {
90 switch (exception) { 110 switch (exception) {
91 case Dynarmic::A64::Exception::WaitForInterrupt: 111 case Dynarmic::A64::Exception::WaitForInterrupt:
diff --git a/src/core/frontend/framebuffer_layout.cpp b/src/core/frontend/framebuffer_layout.cpp
index 4b58b672a..26a5b12aa 100644
--- a/src/core/frontend/framebuffer_layout.cpp
+++ b/src/core/frontend/framebuffer_layout.cpp
@@ -25,7 +25,12 @@ FramebufferLayout DefaultFrameLayout(u32 width, u32 height) {
25 ASSERT(height > 0); 25 ASSERT(height > 0);
26 // The drawing code needs at least somewhat valid values for both screens 26 // The drawing code needs at least somewhat valid values for both screens
27 // so just calculate them both even if the other isn't showing. 27 // so just calculate them both even if the other isn't showing.
28 FramebufferLayout res{width, height, false, {}}; 28 FramebufferLayout res{
29 .width = width,
30 .height = height,
31 .screen = {},
32 .is_srgb = false,
33 };
29 34
30 const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width); 35 const float window_aspect_ratio = static_cast<float>(height) / static_cast<float>(width);
31 const float emulation_aspect_ratio = EmulationAspectRatio( 36 const float emulation_aspect_ratio = EmulationAspectRatio(
diff --git a/src/core/frontend/framebuffer_layout.h b/src/core/frontend/framebuffer_layout.h
index 2e36c0163..8e341e4e2 100644
--- a/src/core/frontend/framebuffer_layout.h
+++ b/src/core/frontend/framebuffer_layout.h
@@ -35,17 +35,8 @@ enum class AspectRatio {
35struct FramebufferLayout { 35struct FramebufferLayout {
36 u32 width{ScreenUndocked::Width}; 36 u32 width{ScreenUndocked::Width};
37 u32 height{ScreenUndocked::Height}; 37 u32 height{ScreenUndocked::Height};
38 bool is_srgb{};
39
40 Common::Rectangle<u32> screen; 38 Common::Rectangle<u32> screen;
41 39 bool is_srgb{};
42 /**
43 * Returns the ration of pixel size of the screen, compared to the native size of the undocked
44 * Switch screen.
45 */
46 float GetScalingRatio() const {
47 return static_cast<float>(screen.GetWidth()) / ScreenUndocked::Width;
48 }
49}; 40};
50 41
51/** 42/**
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index e42a6d36f..45e86a677 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -300,15 +300,16 @@ struct KernelCore::Impl {
300 // Gets the dummy KThread for the caller, allocating a new one if this is the first time 300 // Gets the dummy KThread for the caller, allocating a new one if this is the first time
301 KThread* GetHostDummyThread() { 301 KThread* GetHostDummyThread() {
302 auto make_thread = [this]() { 302 auto make_thread = [this]() {
303 std::unique_ptr<KThread> thread = std::make_unique<KThread>(system.Kernel()); 303 std::lock_guard lk(dummy_thread_lock);
304 auto& thread = dummy_threads.emplace_back(std::make_unique<KThread>(system.Kernel()));
304 KAutoObject::Create(thread.get()); 305 KAutoObject::Create(thread.get());
305 ASSERT(KThread::InitializeDummyThread(thread.get()).IsSuccess()); 306 ASSERT(KThread::InitializeDummyThread(thread.get()).IsSuccess());
306 thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId())); 307 thread->SetName(fmt::format("DummyThread:{}", GetHostThreadId()));
307 return thread; 308 return thread.get();
308 }; 309 };
309 310
310 thread_local auto thread = make_thread(); 311 thread_local KThread* saved_thread = make_thread();
311 return thread.get(); 312 return saved_thread;
312 } 313 }
313 314
314 /// Registers a CPU core thread by allocating a host thread ID for it 315 /// Registers a CPU core thread by allocating a host thread ID for it
@@ -695,6 +696,12 @@ struct KernelCore::Impl {
695 return port; 696 return port;
696 } 697 }
697 698
699 std::mutex server_ports_lock;
700 std::mutex server_sessions_lock;
701 std::mutex registered_objects_lock;
702 std::mutex registered_in_use_objects_lock;
703 std::mutex dummy_thread_lock;
704
698 std::atomic<u32> next_object_id{0}; 705 std::atomic<u32> next_object_id{0};
699 std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin}; 706 std::atomic<u64> next_kernel_process_id{KProcess::InitialKIPIDMin};
700 std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin}; 707 std::atomic<u64> next_user_process_id{KProcess::ProcessIDMin};
@@ -725,10 +732,6 @@ struct KernelCore::Impl {
725 std::unordered_set<KServerSession*> server_sessions; 732 std::unordered_set<KServerSession*> server_sessions;
726 std::unordered_set<KAutoObject*> registered_objects; 733 std::unordered_set<KAutoObject*> registered_objects;
727 std::unordered_set<KAutoObject*> registered_in_use_objects; 734 std::unordered_set<KAutoObject*> registered_in_use_objects;
728 std::mutex server_ports_lock;
729 std::mutex server_sessions_lock;
730 std::mutex registered_objects_lock;
731 std::mutex registered_in_use_objects_lock;
732 735
733 std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor; 736 std::unique_ptr<Core::ExclusiveMonitor> exclusive_monitor;
734 std::vector<Kernel::PhysicalCore> cores; 737 std::vector<Kernel::PhysicalCore> cores;
@@ -753,6 +756,9 @@ struct KernelCore::Impl {
753 std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{}; 756 std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
754 std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{}; 757 std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
755 758
759 // Specifically tracked to be automatically destroyed with kernel
760 std::vector<std::unique_ptr<KThread>> dummy_threads;
761
756 bool is_multicore{}; 762 bool is_multicore{};
757 bool is_phantom_mode_for_singlecore{}; 763 bool is_phantom_mode_for_singlecore{};
758 u32 single_core_thread_id{}; 764 u32 single_core_thread_id{};
diff --git a/src/core/hle/service/pm/pm.cpp b/src/core/hle/service/pm/pm.cpp
index 88fc5b5cc..277abc17a 100644
--- a/src/core/hle/service/pm/pm.cpp
+++ b/src/core/hle/service/pm/pm.cpp
@@ -13,7 +13,12 @@ namespace Service::PM {
13 13
14namespace { 14namespace {
15 15
16constexpr ResultCode ERROR_PROCESS_NOT_FOUND{ErrorModule::PM, 1}; 16constexpr ResultCode ResultProcessNotFound{ErrorModule::PM, 1};
17[[maybe_unused]] constexpr ResultCode ResultAlreadyStarted{ErrorModule::PM, 2};
18[[maybe_unused]] constexpr ResultCode ResultNotTerminated{ErrorModule::PM, 3};
19[[maybe_unused]] constexpr ResultCode ResultDebugHookInUse{ErrorModule::PM, 4};
20[[maybe_unused]] constexpr ResultCode ResultApplicationRunning{ErrorModule::PM, 5};
21[[maybe_unused]] constexpr ResultCode ResultInvalidSize{ErrorModule::PM, 6};
17 22
18constexpr u64 NO_PROCESS_FOUND_PID{0}; 23constexpr u64 NO_PROCESS_FOUND_PID{0};
19 24
@@ -95,18 +100,18 @@ public:
95private: 100private:
96 void GetProcessId(Kernel::HLERequestContext& ctx) { 101 void GetProcessId(Kernel::HLERequestContext& ctx) {
97 IPC::RequestParser rp{ctx}; 102 IPC::RequestParser rp{ctx};
98 const auto title_id = rp.PopRaw<u64>(); 103 const auto program_id = rp.PopRaw<u64>();
99 104
100 LOG_DEBUG(Service_PM, "called, title_id={:016X}", title_id); 105 LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
101 106
102 const auto process = 107 const auto process =
103 SearchProcessList(kernel.GetProcessList(), [title_id](const auto& proc) { 108 SearchProcessList(kernel.GetProcessList(), [program_id](const auto& proc) {
104 return proc->GetProgramID() == title_id; 109 return proc->GetProgramID() == program_id;
105 }); 110 });
106 111
107 if (!process.has_value()) { 112 if (!process.has_value()) {
108 IPC::ResponseBuilder rb{ctx, 2}; 113 IPC::ResponseBuilder rb{ctx, 2};
109 rb.Push(ERROR_PROCESS_NOT_FOUND); 114 rb.Push(ResultProcessNotFound);
110 return; 115 return;
111 } 116 }
112 117
@@ -128,13 +133,16 @@ public:
128 explicit Info(Core::System& system_, const std::vector<Kernel::KProcess*>& process_list_) 133 explicit Info(Core::System& system_, const std::vector<Kernel::KProcess*>& process_list_)
129 : ServiceFramework{system_, "pm:info"}, process_list{process_list_} { 134 : ServiceFramework{system_, "pm:info"}, process_list{process_list_} {
130 static const FunctionInfo functions[] = { 135 static const FunctionInfo functions[] = {
131 {0, &Info::GetTitleId, "GetTitleId"}, 136 {0, &Info::GetProgramId, "GetProgramId"},
137 {65000, &Info::AtmosphereGetProcessId, "AtmosphereGetProcessId"},
138 {65001, nullptr, "AtmosphereHasLaunchedProgram"},
139 {65002, nullptr, "AtmosphereGetProcessInfo"},
132 }; 140 };
133 RegisterHandlers(functions); 141 RegisterHandlers(functions);
134 } 142 }
135 143
136private: 144private:
137 void GetTitleId(Kernel::HLERequestContext& ctx) { 145 void GetProgramId(Kernel::HLERequestContext& ctx) {
138 IPC::RequestParser rp{ctx}; 146 IPC::RequestParser rp{ctx};
139 const auto process_id = rp.PopRaw<u64>(); 147 const auto process_id = rp.PopRaw<u64>();
140 148
@@ -146,7 +154,7 @@ private:
146 154
147 if (!process.has_value()) { 155 if (!process.has_value()) {
148 IPC::ResponseBuilder rb{ctx, 2}; 156 IPC::ResponseBuilder rb{ctx, 2};
149 rb.Push(ERROR_PROCESS_NOT_FOUND); 157 rb.Push(ResultProcessNotFound);
150 return; 158 return;
151 } 159 }
152 160
@@ -155,6 +163,27 @@ private:
155 rb.Push((*process)->GetProgramID()); 163 rb.Push((*process)->GetProgramID());
156 } 164 }
157 165
166 void AtmosphereGetProcessId(Kernel::HLERequestContext& ctx) {
167 IPC::RequestParser rp{ctx};
168 const auto program_id = rp.PopRaw<u64>();
169
170 LOG_DEBUG(Service_PM, "called, program_id={:016X}", program_id);
171
172 const auto process = SearchProcessList(process_list, [program_id](const auto& proc) {
173 return proc->GetProgramID() == program_id;
174 });
175
176 if (!process.has_value()) {
177 IPC::ResponseBuilder rb{ctx, 2};
178 rb.Push(ResultProcessNotFound);
179 return;
180 }
181
182 IPC::ResponseBuilder rb{ctx, 4};
183 rb.Push(ResultSuccess);
184 rb.Push((*process)->GetProcessID());
185 }
186
158 const std::vector<Kernel::KProcess*>& process_list; 187 const std::vector<Kernel::KProcess*>& process_list;
159}; 188};
160 189
diff --git a/src/video_core/renderer_vulkan/blit_image.cpp b/src/video_core/renderer_vulkan/blit_image.cpp
index 28b631f73..a63d4d222 100644
--- a/src/video_core/renderer_vulkan/blit_image.cpp
+++ b/src/video_core/renderer_vulkan/blit_image.cpp
@@ -749,8 +749,9 @@ void BlitImageHelper::ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRend
749 }); 749 });
750} 750}
751 751
752void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 752void BlitImageHelper::ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
753 vk::ShaderModule& module, bool single_texture) { 753 vk::ShaderModule& module, bool is_target_depth,
754 bool single_texture) {
754 if (pipeline) { 755 if (pipeline) {
755 return; 756 return;
756 } 757 }
@@ -767,7 +768,7 @@ void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRen
767 .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO, 768 .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
768 .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO, 769 .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
769 .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, 770 .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
770 .pDepthStencilState = nullptr, 771 .pDepthStencilState = is_target_depth ? &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO : nullptr,
771 .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO, 772 .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_GENERIC_CREATE_INFO,
772 .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO, 773 .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
773 .layout = single_texture ? *one_texture_pipeline_layout : *two_textures_pipeline_layout, 774 .layout = single_texture ? *one_texture_pipeline_layout : *two_textures_pipeline_layout,
@@ -778,33 +779,14 @@ void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRen
778 }); 779 });
779} 780}
780 781
782void BlitImageHelper::ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
783 vk::ShaderModule& module, bool single_texture) {
784 ConvertPipelineEx(pipeline, renderpass, module, false, single_texture);
785}
786
781void BlitImageHelper::ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 787void BlitImageHelper::ConvertPipelineDepthTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
782 vk::ShaderModule& module, bool single_texture) { 788 vk::ShaderModule& module, bool single_texture) {
783 if (pipeline) { 789 ConvertPipelineEx(pipeline, renderpass, module, true, single_texture);
784 return;
785 }
786 const std::array stages = MakeStages(*full_screen_vert, *module);
787 pipeline = device.GetLogical().CreateGraphicsPipeline({
788 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
789 .pNext = nullptr,
790 .flags = 0,
791 .stageCount = static_cast<u32>(stages.size()),
792 .pStages = stages.data(),
793 .pVertexInputState = &PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
794 .pInputAssemblyState = &PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
795 .pTessellationState = nullptr,
796 .pViewportState = &PIPELINE_VIEWPORT_STATE_CREATE_INFO,
797 .pRasterizationState = &PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
798 .pMultisampleState = &PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
799 .pDepthStencilState = &PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
800 .pColorBlendState = &PIPELINE_COLOR_BLEND_STATE_EMPTY_CREATE_INFO,
801 .pDynamicState = &PIPELINE_DYNAMIC_STATE_CREATE_INFO,
802 .layout = single_texture ? *one_texture_pipeline_layout : *two_textures_pipeline_layout,
803 .renderPass = renderpass,
804 .subpass = 0,
805 .basePipelineHandle = VK_NULL_HANDLE,
806 .basePipelineIndex = 0,
807 });
808} 790}
809 791
810} // namespace Vulkan 792} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/blit_image.h b/src/video_core/renderer_vulkan/blit_image.h
index cec095341..3455c75f4 100644
--- a/src/video_core/renderer_vulkan/blit_image.h
+++ b/src/video_core/renderer_vulkan/blit_image.h
@@ -89,6 +89,9 @@ private:
89 89
90 void ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass); 90 void ConvertColorToDepthPipeline(vk::Pipeline& pipeline, VkRenderPass renderpass);
91 91
92 void ConvertPipelineEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
93 vk::ShaderModule& module, bool is_target_depth, bool single_texture);
94
92 void ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass, 95 void ConvertPipelineColorTargetEx(vk::Pipeline& pipeline, VkRenderPass renderpass,
93 vk::ShaderModule& module, bool single_texture); 96 vk::ShaderModule& module, bool single_texture);
94 97
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 3964424af..c72f0c897 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -787,9 +787,9 @@ VkBuffer TextureCacheRuntime::GetTemporaryBuffer(size_t needed_size) {
787 return *buffers[level]; 787 return *buffers[level];
788 } 788 }
789 const auto new_size = Common::NextPow2(needed_size); 789 const auto new_size = Common::NextPow2(needed_size);
790 VkBufferUsageFlags flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT | 790 static constexpr VkBufferUsageFlags flags =
791 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | 791 VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
792 VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT; 792 VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT;
793 buffers[level] = device.GetLogical().CreateBuffer({ 793 buffers[level] = device.GetLogical().CreateBuffer({
794 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, 794 .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
795 .pNext = nullptr, 795 .pNext = nullptr,
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index e852c817e..329bf4def 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -55,10 +55,4 @@ std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Cor
55 } 55 }
56} 56}
57 57
58float GetResolutionScaleFactor(const RendererBase& renderer) {
59 return Settings::values.resolution_info.active
60 ? Settings::values.resolution_info.up_factor
61 : renderer.GetRenderWindow().GetFramebufferLayout().GetScalingRatio();
62}
63
64} // namespace VideoCore 58} // namespace VideoCore
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index f86877e86..084df641f 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -25,6 +25,4 @@ class RendererBase;
25/// Creates an emulated GPU instance using the given system context. 25/// Creates an emulated GPU instance using the given system context.
26std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system); 26std::unique_ptr<Tegra::GPU> CreateGPU(Core::Frontend::EmuWindow& emu_window, Core::System& system);
27 27
28float GetResolutionScaleFactor(const RendererBase& renderer);
29
30} // namespace VideoCore 28} // namespace VideoCore
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 976acd176..fd0a130a3 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -303,6 +303,7 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
303 connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram, 303 connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram,
304 Qt::QueuedConnection); 304 Qt::QueuedConnection);
305 connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection); 305 connect(this, &GRenderWindow::ExitSignal, parent, &GMainWindow::OnExit, Qt::QueuedConnection);
306 connect(this, &GRenderWindow::TasPlaybackStateChanged, parent, &GMainWindow::OnTasStateChanged);
306} 307}
307 308
308void GRenderWindow::ExecuteProgram(std::size_t program_index) { 309void GRenderWindow::ExecuteProgram(std::size_t program_index) {
@@ -319,10 +320,18 @@ GRenderWindow::~GRenderWindow() {
319 320
320void GRenderWindow::OnFrameDisplayed() { 321void GRenderWindow::OnFrameDisplayed() {
321 input_subsystem->GetTas()->UpdateThread(); 322 input_subsystem->GetTas()->UpdateThread();
323 const TasInput::TasState new_tas_state = std::get<0>(input_subsystem->GetTas()->GetStatus());
324
322 if (!first_frame) { 325 if (!first_frame) {
326 last_tas_state = new_tas_state;
323 first_frame = true; 327 first_frame = true;
324 emit FirstFrameDisplayed(); 328 emit FirstFrameDisplayed();
325 } 329 }
330
331 if (new_tas_state != last_tas_state) {
332 last_tas_state = new_tas_state;
333 emit TasPlaybackStateChanged();
334 }
326} 335}
327 336
328bool GRenderWindow::IsShown() const { 337bool GRenderWindow::IsShown() const {
@@ -630,7 +639,7 @@ void GRenderWindow::ReleaseRenderTarget() {
630 639
631void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) { 640void GRenderWindow::CaptureScreenshot(const QString& screenshot_path) {
632 auto& renderer = system.Renderer(); 641 auto& renderer = system.Renderer();
633 const f32 res_scale = VideoCore::GetResolutionScaleFactor(renderer); 642 const f32 res_scale = Settings::values.resolution_info.up_factor;
634 643
635 const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)}; 644 const Layout::FramebufferLayout layout{Layout::FrameLayoutFromResolutionScale(res_scale)};
636 screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32); 645 screenshot_image = QImage(QSize(layout.width, layout.height), QImage::Format_RGB32);
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index 40fd4a9d6..061e3605f 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -41,6 +41,10 @@ enum class LoadCallbackStage;
41class RendererBase; 41class RendererBase;
42} // namespace VideoCore 42} // namespace VideoCore
43 43
44namespace TasInput {
45enum class TasState;
46}
47
44class EmuThread final : public QThread { 48class EmuThread final : public QThread {
45 Q_OBJECT 49 Q_OBJECT
46 50
@@ -203,6 +207,7 @@ signals:
203 void ExecuteProgramSignal(std::size_t program_index); 207 void ExecuteProgramSignal(std::size_t program_index);
204 void ExitSignal(); 208 void ExitSignal();
205 void MouseActivity(); 209 void MouseActivity();
210 void TasPlaybackStateChanged();
206 211
207private: 212private:
208 void TouchBeginEvent(const QTouchEvent* event); 213 void TouchBeginEvent(const QTouchEvent* event);
@@ -236,6 +241,7 @@ private:
236 QWidget* child_widget = nullptr; 241 QWidget* child_widget = nullptr;
237 242
238 bool first_frame = false; 243 bool first_frame = false;
244 TasInput::TasState last_tas_state;
239 245
240 std::array<std::size_t, 16> touch_ids{}; 246 std::array<std::size_t, 16> touch_ids{};
241 247
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index c4c76b094..5058c3e4e 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -965,6 +965,9 @@ void GMainWindow::InitializeHotkeys() {
965 const QString toggle_status_bar = QStringLiteral("Toggle Status Bar"); 965 const QString toggle_status_bar = QStringLiteral("Toggle Status Bar");
966 const QString fullscreen = QStringLiteral("Fullscreen"); 966 const QString fullscreen = QStringLiteral("Fullscreen");
967 const QString capture_screenshot = QStringLiteral("Capture Screenshot"); 967 const QString capture_screenshot = QStringLiteral("Capture Screenshot");
968 const QString tas_start_stop = QStringLiteral("TAS Start/Stop");
969 const QString tas_record = QStringLiteral("TAS Record");
970 const QString tas_reset = QStringLiteral("TAS Reset");
968 971
969 ui->action_Load_File->setShortcut(hotkey_registry.GetKeySequence(main_window, load_file)); 972 ui->action_Load_File->setShortcut(hotkey_registry.GetKeySequence(main_window, load_file));
970 ui->action_Load_File->setShortcutContext( 973 ui->action_Load_File->setShortcutContext(
@@ -1005,6 +1008,18 @@ void GMainWindow::InitializeHotkeys() {
1005 ui->action_Fullscreen->setShortcutContext( 1008 ui->action_Fullscreen->setShortcutContext(
1006 hotkey_registry.GetShortcutContext(main_window, fullscreen)); 1009 hotkey_registry.GetShortcutContext(main_window, fullscreen));
1007 1010
1011 ui->action_TAS_Start->setShortcut(hotkey_registry.GetKeySequence(main_window, tas_start_stop));
1012 ui->action_TAS_Start->setShortcutContext(
1013 hotkey_registry.GetShortcutContext(main_window, tas_start_stop));
1014
1015 ui->action_TAS_Record->setShortcut(hotkey_registry.GetKeySequence(main_window, tas_record));
1016 ui->action_TAS_Record->setShortcutContext(
1017 hotkey_registry.GetShortcutContext(main_window, tas_record));
1018
1019 ui->action_TAS_Reset->setShortcut(hotkey_registry.GetKeySequence(main_window, tas_reset));
1020 ui->action_TAS_Reset->setShortcutContext(
1021 hotkey_registry.GetShortcutContext(main_window, tas_reset));
1022
1008 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this), 1023 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("Load File"), this),
1009 &QShortcut::activated, this, &GMainWindow::OnMenuLoadFile); 1024 &QShortcut::activated, this, &GMainWindow::OnMenuLoadFile);
1010 connect( 1025 connect(
@@ -1095,28 +1110,6 @@ void GMainWindow::InitializeHotkeys() {
1095 render_window->setAttribute(Qt::WA_Hover, true); 1110 render_window->setAttribute(Qt::WA_Hover, true);
1096 } 1111 }
1097 }); 1112 });
1098 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Start/Stop"), this),
1099 &QShortcut::activated, this, [&] {
1100 if (!emulation_running) {
1101 return;
1102 }
1103 input_subsystem->GetTas()->StartStop();
1104 });
1105 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Reset"), this),
1106 &QShortcut::activated, this, [&] { input_subsystem->GetTas()->Reset(); });
1107 connect(hotkey_registry.GetHotkey(main_window, QStringLiteral("TAS Record"), this),
1108 &QShortcut::activated, this, [&] {
1109 if (!emulation_running) {
1110 return;
1111 }
1112 bool is_recording = input_subsystem->GetTas()->Record();
1113 if (!is_recording) {
1114 const auto res = QMessageBox::question(this, tr("TAS Recording"),
1115 tr("Overwrite file of player 1?"),
1116 QMessageBox::Yes | QMessageBox::No);
1117 input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
1118 }
1119 });
1120} 1113}
1121 1114
1122void GMainWindow::SetDefaultUIGeometry() { 1115void GMainWindow::SetDefaultUIGeometry() {
@@ -1236,11 +1229,11 @@ void GMainWindow::ConnectMenuEvents() {
1236 connect(ui->action_Restart, &QAction::triggered, this, 1229 connect(ui->action_Restart, &QAction::triggered, this,
1237 [this] { BootGame(QString(game_path)); }); 1230 [this] { BootGame(QString(game_path)); });
1238 connect(ui->action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure); 1231 connect(ui->action_Configure, &QAction::triggered, this, &GMainWindow::OnConfigure);
1239 connect(ui->action_Configure_Tas, &QAction::triggered, this, &GMainWindow::OnConfigureTas);
1240 connect(ui->action_Configure_Current_Game, &QAction::triggered, this, 1232 connect(ui->action_Configure_Current_Game, &QAction::triggered, this,
1241 &GMainWindow::OnConfigurePerGame); 1233 &GMainWindow::OnConfigurePerGame);
1242 1234
1243 // View 1235 // View
1236 connect(ui->action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen);
1244 connect(ui->action_Single_Window_Mode, &QAction::triggered, this, 1237 connect(ui->action_Single_Window_Mode, &QAction::triggered, this,
1245 &GMainWindow::ToggleWindowMode); 1238 &GMainWindow::ToggleWindowMode);
1246 connect(ui->action_Display_Dock_Widget_Headers, &QAction::triggered, this, 1239 connect(ui->action_Display_Dock_Widget_Headers, &QAction::triggered, this,
@@ -1258,17 +1251,20 @@ void GMainWindow::ConnectMenuEvents() {
1258 ui->menu_Reset_Window_Size->addAction(ui->action_Reset_Window_Size_900); 1251 ui->menu_Reset_Window_Size->addAction(ui->action_Reset_Window_Size_900);
1259 ui->menu_Reset_Window_Size->addAction(ui->action_Reset_Window_Size_1080); 1252 ui->menu_Reset_Window_Size->addAction(ui->action_Reset_Window_Size_1080);
1260 1253
1261 // Fullscreen 1254 // Tools
1262 connect(ui->action_Fullscreen, &QAction::triggered, this, &GMainWindow::ToggleFullscreen); 1255 connect(ui->action_Rederive, &QAction::triggered, this,
1263 1256 std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));
1264 // Movie
1265 connect(ui->action_Capture_Screenshot, &QAction::triggered, this, 1257 connect(ui->action_Capture_Screenshot, &QAction::triggered, this,
1266 &GMainWindow::OnCaptureScreenshot); 1258 &GMainWindow::OnCaptureScreenshot);
1267 1259
1260 // TAS
1261 connect(ui->action_TAS_Start, &QAction::triggered, this, &GMainWindow::OnTasStartStop);
1262 connect(ui->action_TAS_Record, &QAction::triggered, this, &GMainWindow::OnTasRecord);
1263 connect(ui->action_TAS_Reset, &QAction::triggered, this, &GMainWindow::OnTasReset);
1264 connect(ui->action_Configure_Tas, &QAction::triggered, this, &GMainWindow::OnConfigureTas);
1265
1268 // Help 1266 // Help
1269 connect(ui->action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder); 1267 connect(ui->action_Open_yuzu_Folder, &QAction::triggered, this, &GMainWindow::OnOpenYuzuFolder);
1270 connect(ui->action_Rederive, &QAction::triggered, this,
1271 std::bind(&GMainWindow::OnReinitializeKeys, this, ReinitializeKeyBehavior::Warning));
1272 connect(ui->action_About, &QAction::triggered, this, &GMainWindow::OnAbout); 1268 connect(ui->action_About, &QAction::triggered, this, &GMainWindow::OnAbout);
1273} 1269}
1274 1270
@@ -1582,6 +1578,7 @@ void GMainWindow::ShutdownGame() {
1582 game_list->SetFilterFocus(); 1578 game_list->SetFilterFocus();
1583 tas_label->clear(); 1579 tas_label->clear();
1584 input_subsystem->GetTas()->Stop(); 1580 input_subsystem->GetTas()->Stop();
1581 OnTasStateChanged();
1585 1582
1586 render_window->removeEventFilter(render_window); 1583 render_window->removeEventFilter(render_window);
1587 render_window->setAttribute(Qt::WA_Hover, false); 1584 render_window->setAttribute(Qt::WA_Hover, false);
@@ -2509,6 +2506,7 @@ void GMainWindow::OnStartGame() {
2509 ui->action_Restart->setEnabled(true); 2506 ui->action_Restart->setEnabled(true);
2510 ui->action_Configure_Current_Game->setEnabled(true); 2507 ui->action_Configure_Current_Game->setEnabled(true);
2511 ui->action_Report_Compatibility->setEnabled(true); 2508 ui->action_Report_Compatibility->setEnabled(true);
2509 OnTasStateChanged();
2512 2510
2513 discord_rpc->Update(); 2511 discord_rpc->Update();
2514 ui->action_Load_Amiibo->setEnabled(true); 2512 ui->action_Load_Amiibo->setEnabled(true);
@@ -2821,6 +2819,32 @@ void GMainWindow::OnConfigureTas() {
2821 } 2819 }
2822} 2820}
2823 2821
2822void GMainWindow::OnTasStartStop() {
2823 if (!emulation_running) {
2824 return;
2825 }
2826 input_subsystem->GetTas()->StartStop();
2827 OnTasStateChanged();
2828}
2829
2830void GMainWindow::OnTasRecord() {
2831 if (!emulation_running) {
2832 return;
2833 }
2834 const bool is_recording = input_subsystem->GetTas()->Record();
2835 if (!is_recording) {
2836 const auto res =
2837 QMessageBox::question(this, tr("TAS Recording"), tr("Overwrite file of player 1?"),
2838 QMessageBox::Yes | QMessageBox::No);
2839 input_subsystem->GetTas()->SaveRecording(res == QMessageBox::Yes);
2840 }
2841 OnTasStateChanged();
2842}
2843
2844void GMainWindow::OnTasReset() {
2845 input_subsystem->GetTas()->Reset();
2846}
2847
2824void GMainWindow::OnConfigurePerGame() { 2848void GMainWindow::OnConfigurePerGame() {
2825 const u64 title_id = system->GetCurrentProcessProgramID(); 2849 const u64 title_id = system->GetCurrentProcessProgramID();
2826 OpenPerGameConfiguration(title_id, game_path.toStdString()); 2850 OpenPerGameConfiguration(title_id, game_path.toStdString());
@@ -3014,6 +3038,23 @@ QString GMainWindow::GetTasStateDescription() const {
3014 } 3038 }
3015} 3039}
3016 3040
3041void GMainWindow::OnTasStateChanged() {
3042 bool is_running = false;
3043 bool is_recording = false;
3044 if (emulation_running) {
3045 const TasInput::TasState tas_status = std::get<0>(input_subsystem->GetTas()->GetStatus());
3046 is_running = tas_status == TasInput::TasState::Running;
3047 is_recording = tas_status == TasInput::TasState::Recording;
3048 }
3049
3050 ui->action_TAS_Start->setText(is_running ? tr("&Stop Running") : tr("&Start"));
3051 ui->action_TAS_Record->setText(is_recording ? tr("Stop R&ecording") : tr("R&ecord"));
3052
3053 ui->action_TAS_Start->setEnabled(emulation_running);
3054 ui->action_TAS_Record->setEnabled(emulation_running);
3055 ui->action_TAS_Reset->setEnabled(emulation_running);
3056}
3057
3017void GMainWindow::UpdateStatusBar() { 3058void GMainWindow::UpdateStatusBar() {
3018 if (emu_thread == nullptr) { 3059 if (emu_thread == nullptr) {
3019 status_bar_update_timer.stop(); 3060 status_bar_update_timer.stop();
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 24633ff2d..556cbbaf7 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -177,6 +177,7 @@ public slots:
177 void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args, 177 void WebBrowserOpenWebPage(const std::string& main_url, const std::string& additional_args,
178 bool is_local); 178 bool is_local);
179 void OnAppFocusStateChanged(Qt::ApplicationState state); 179 void OnAppFocusStateChanged(Qt::ApplicationState state);
180 void OnTasStateChanged();
180 181
181private: 182private:
182 void RegisterMetaTypes(); 183 void RegisterMetaTypes();
@@ -268,6 +269,9 @@ private slots:
268 void OnMenuRecentFile(); 269 void OnMenuRecentFile();
269 void OnConfigure(); 270 void OnConfigure();
270 void OnConfigureTas(); 271 void OnConfigureTas();
272 void OnTasStartStop();
273 void OnTasRecord();
274 void OnTasReset();
271 void OnConfigurePerGame(); 275 void OnConfigurePerGame();
272 void OnLoadAmiibo(); 276 void OnLoadAmiibo();
273 void OnOpenYuzuFolder(); 277 void OnOpenYuzuFolder();
@@ -313,6 +317,7 @@ private:
313 void OpenURL(const QUrl& url); 317 void OpenURL(const QUrl& url);
314 void LoadTranslation(); 318 void LoadTranslation();
315 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name); 319 void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
320
316 QString GetTasStateDescription() const; 321 QString GetTasStateDescription() const;
317 322
318 std::unique_ptr<Ui::MainWindow> ui; 323 std::unique_ptr<Ui::MainWindow> ui;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index a62e39a06..c58aa2866 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -79,39 +79,39 @@
79 <string>&amp;View</string> 79 <string>&amp;View</string>
80 </property> 80 </property>
81 <widget class="QMenu" name="menu_Reset_Window_Size"> 81 <widget class="QMenu" name="menu_Reset_Window_Size">
82 <property name="title"> 82 <property name="title">
83 <string>&amp;Reset Window Size</string> 83 <string>&amp;Reset Window Size</string>
84 </property> 84 </property>
85 </widget>
86 <widget class="QMenu" name="menu_View_Debugging">
87 <property name="title">
88 <string>&amp;Debugging</string>
89 </property>
85 </widget> 90 </widget>
86 <action name="action_Reset_Window_Size_720"> 91 <action name="action_Reset_Window_Size_720">
87 <property name="text"> 92 <property name="text">
88 <string>Reset Window Size to &amp;720p</string> 93 <string>Reset Window Size to &amp;720p</string>
89 </property> 94 </property>
90 <property name="iconText"> 95 <property name="iconText">
91 <string>Reset Window Size to 720p</string> 96 <string>Reset Window Size to 720p</string>
92 </property> 97 </property>
93 </action> 98 </action>
94 <action name="action_Reset_Window_Size_900"> 99 <action name="action_Reset_Window_Size_900">
95 <property name="text"> 100 <property name="text">
96 <string>Reset Window Size to &amp;900p</string> 101 <string>Reset Window Size to &amp;900p</string>
97 </property> 102 </property>
98 <property name="iconText"> 103 <property name="iconText">
99 <string>Reset Window Size to 900p</string> 104 <string>Reset Window Size to 900p</string>
100 </property> 105 </property>
101 </action> 106 </action>
102 <action name="action_Reset_Window_Size_1080"> 107 <action name="action_Reset_Window_Size_1080">
103 <property name="text"> 108 <property name="text">
104 <string>Reset Window Size to &amp;1080p</string> 109 <string>Reset Window Size to &amp;1080p</string>
105 </property>
106 <property name="iconText">
107 <string>Reset Window Size to 1080p</string>
108 </property>
109 </action>
110 <widget class="QMenu" name="menu_View_Debugging">
111 <property name="title">
112 <string>&amp;Debugging</string>
113 </property> 110 </property>
114 </widget> 111 <property name="iconText">
112 <string>Reset Window Size to 1080p</string>
113 </property>
114 </action>
115 <addaction name="action_Fullscreen"/> 115 <addaction name="action_Fullscreen"/>
116 <addaction name="action_Single_Window_Mode"/> 116 <addaction name="action_Single_Window_Mode"/>
117 <addaction name="action_Display_Dock_Widget_Headers"/> 117 <addaction name="action_Display_Dock_Widget_Headers"/>
@@ -125,10 +125,20 @@
125 <property name="title"> 125 <property name="title">
126 <string>&amp;Tools</string> 126 <string>&amp;Tools</string>
127 </property> 127 </property>
128 <widget class="QMenu" name="menuTAS">
129 <property name="title">
130 <string>&amp;TAS</string>
131 </property>
132 <addaction name="action_TAS_Start"/>
133 <addaction name="action_TAS_Record"/>
134 <addaction name="action_TAS_Reset"/>
135 <addaction name="separator"/>
136 <addaction name="action_Configure_Tas"/>
137 </widget>
128 <addaction name="action_Rederive"/> 138 <addaction name="action_Rederive"/>
129 <addaction name="separator"/> 139 <addaction name="separator"/>
130 <addaction name="action_Capture_Screenshot"/> 140 <addaction name="action_Capture_Screenshot"/>
131 <addaction name="action_Configure_Tas"/> 141 <addaction name="menuTAS"/>
132 </widget> 142 </widget>
133 <widget class="QMenu" name="menu_Help"> 143 <widget class="QMenu" name="menu_Help">
134 <property name="title"> 144 <property name="title">
@@ -309,7 +319,7 @@
309 </action> 319 </action>
310 <action name="action_Configure_Tas"> 320 <action name="action_Configure_Tas">
311 <property name="text"> 321 <property name="text">
312 <string>Configure &amp;TAS...</string> 322 <string>&amp;Configure TAS...</string>
313 </property> 323 </property>
314 </action> 324 </action>
315 <action name="action_Configure_Current_Game"> 325 <action name="action_Configure_Current_Game">
@@ -320,6 +330,30 @@
320 <string>Configure C&amp;urrent Game...</string> 330 <string>Configure C&amp;urrent Game...</string>
321 </property> 331 </property>
322 </action> 332 </action>
333 <action name="action_TAS_Start">
334 <property name="enabled">
335 <bool>false</bool>
336 </property>
337 <property name="text">
338 <string>&amp;Start</string>
339 </property>
340 </action>
341 <action name="action_TAS_Reset">
342 <property name="enabled">
343 <bool>false</bool>
344 </property>
345 <property name="text">
346 <string>&amp;Reset</string>
347 </property>
348 </action>
349 <action name="action_TAS_Record">
350 <property name="enabled">
351 <bool>false</bool>
352 </property>
353 <property name="text">
354 <string>R&amp;ecord</string>
355 </property>
356 </action>
323 </widget> 357 </widget>
324 <resources> 358 <resources>
325 <include location="yuzu.qrc"/> 359 <include location="yuzu.qrc"/>