summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio_core/device/audio_buffers.h8
-rw-r--r--src/audio_core/device/device_session.cpp6
-rw-r--r--src/audio_core/device/device_session.h5
-rw-r--r--src/audio_core/in/audio_in_system.cpp7
-rw-r--r--src/audio_core/out/audio_out_system.cpp7
-rw-r--r--src/common/CMakeLists.txt2
-rw-r--r--src/common/assert.h4
-rw-r--r--src/common/make_unique_for_overwrite.h25
-rw-r--r--src/common/scratch_buffer.h95
-rw-r--r--src/common/settings.cpp1
-rw-r--r--src/common/settings.h1
-rw-r--r--src/common/thread.h11
-rw-r--r--src/core/CMakeLists.txt7
-rw-r--r--src/core/core.cpp22
-rw-r--r--src/core/core.h4
-rw-r--r--src/core/cpu_manager.cpp15
-rw-r--r--src/core/cpu_manager.h4
-rw-r--r--src/core/frontend/emu_window.h6
-rw-r--r--src/core/hid/emulated_controller.cpp89
-rw-r--r--src/core/hid/emulated_controller.h9
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp22
-rw-r--r--src/core/hle/kernel/k_address_arbiter.cpp1
-rw-r--r--src/core/hle/kernel/k_code_memory.cpp29
-rw-r--r--src/core/hle/kernel/k_code_memory.h6
-rw-r--r--src/core/hle/kernel/k_hardware_timer.cpp74
-rw-r--r--src/core/hle/kernel/k_hardware_timer.h54
-rw-r--r--src/core/hle/kernel/k_hardware_timer_base.h92
-rw-r--r--src/core/hle/kernel/k_memory_manager.cpp8
-rw-r--r--src/core/hle/kernel/k_page_group.cpp121
-rw-r--r--src/core/hle/kernel/k_page_group.h163
-rw-r--r--src/core/hle/kernel/k_page_table.cpp142
-rw-r--r--src/core/hle/kernel/k_page_table.h9
-rw-r--r--src/core/hle/kernel/k_process.cpp11
-rw-r--r--src/core/hle/kernel/k_process.h3
-rw-r--r--src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h4
-rw-r--r--src/core/hle/kernel/k_shared_memory.cpp70
-rw-r--r--src/core/hle/kernel/k_shared_memory.h8
-rw-r--r--src/core/hle/kernel/k_thread.h8
-rw-r--r--src/core/hle/kernel/k_thread_queue.cpp6
-rw-r--r--src/core/hle/kernel/k_timer_task.h40
-rw-r--r--src/core/hle/kernel/kernel.cpp77
-rw-r--r--src/core/hle/kernel/kernel.h9
-rw-r--r--src/core/hle/kernel/memory_types.h3
-rw-r--r--src/core/hle/kernel/svc.cpp132
-rw-r--r--src/core/hle/kernel/time_manager.cpp44
-rw-r--r--src/core/hle/kernel/time_manager.h41
-rw-r--r--src/core/hle/service/audio/audin_u.cpp5
-rw-r--r--src/core/hle/service/audio/audout_u.cpp10
-rw-r--r--src/core/hle/service/audio/audren_u.cpp15
-rw-r--r--src/core/hle/service/nfc/nfc_user.cpp2
-rw-r--r--src/core/hle/service/nfp/nfp_user.cpp2
-rw-r--r--src/core/hle/service/set/set.cpp9
-rw-r--r--src/core/hle/service/set/set.h1
-rw-r--r--src/core/hle/service/set/set_sys.cpp10
-rw-r--r--src/core/hle/service/set/set_sys.h1
-rw-r--r--src/core/hle/service/time/clock_types.h1
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp17
-rw-r--r--src/core/hle/service/time/time_sharedmemory.h87
-rw-r--r--src/input_common/CMakeLists.txt2
-rw-r--r--src/input_common/drivers/camera.cpp2
-rw-r--r--src/input_common/drivers/camera.h5
-rw-r--r--src/input_common/drivers/sdl_driver.cpp2
-rw-r--r--src/input_common/drivers/virtual_gamepad.cpp78
-rw-r--r--src/input_common/drivers/virtual_gamepad.h73
-rw-r--r--src/input_common/input_mapping.cpp6
-rw-r--r--src/input_common/main.cpp286
-rw-r--r--src/input_common/main.h7
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp2
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_special.cpp5
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp2
-rw-r--r--src/shader_recompiler/profile.h1
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/common/scratch_buffer.cpp200
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h11
-rw-r--r--src/video_core/dma_pusher.cpp19
-rw-r--r--src/video_core/dma_pusher.h8
-rw-r--r--src/video_core/engines/draw_manager.cpp58
-rw-r--r--src/video_core/engines/draw_manager.h4
-rw-r--r--src/video_core/engines/engine_upload.cpp4
-rw-r--r--src/video_core/engines/engine_upload.h7
-rw-r--r--src/video_core/engines/maxwell_dma.cpp34
-rw-r--r--src/video_core/engines/maxwell_dma.h8
-rw-r--r--src/video_core/gpu.cpp5
-rw-r--r--src/video_core/host1x/vic.cpp6
-rw-r--r--src/video_core/host1x/vic.h7
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_device.h8
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp3
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp77
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h1
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp4
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp14
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp3
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp28
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp10
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h14
-rw-r--r--src/video_core/texture_cache/texture_cache.h35
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h6
-rw-r--r--src/video_core/texture_cache/util.cpp20
-rw-r--r--src/video_core/texture_cache/util.h5
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp32
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h16
-rw-r--r--src/video_core/vulkan_common/vulkan_wrapper.cpp12
-rw-r--r--src/yuzu/bootmanager.cpp117
-rw-r--r--src/yuzu/bootmanager.h61
-rw-r--r--src/yuzu/configuration/config.cpp4
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp12
-rw-r--r--src/yuzu/configuration/configure_input_player.h3
-rw-r--r--src/yuzu/configuration/configure_input_player.ui19
-rw-r--r--src/yuzu/configuration/configure_system.cpp4
-rw-r--r--src/yuzu/configuration/configure_system.ui14
-rw-r--r--src/yuzu/game_list.cpp14
-rw-r--r--src/yuzu/game_list.h7
-rw-r--r--src/yuzu/main.cpp365
-rw-r--r--src/yuzu/main.h16
-rw-r--r--src/yuzu/startup_checks.cpp2
-rw-r--r--src/yuzu/uisettings.h1
-rw-r--r--src/yuzu/util/overlay_dialog.cpp21
-rw-r--r--src/yuzu/util/overlay_dialog.h1
-rw-r--r--src/yuzu_cmd/CMakeLists.txt9
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp2
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp2
-rw-r--r--src/yuzu_cmd/yuzu.cpp7
125 files changed, 2391 insertions, 1117 deletions
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h
index 3dae1a3b7..15082f6c6 100644
--- a/src/audio_core/device/audio_buffers.h
+++ b/src/audio_core/device/audio_buffers.h
@@ -91,9 +91,10 @@ public:
* @param core_timing - The CoreTiming instance
* @param session - The device session
*
- * @return Is the buffer was released.
+ * @return If any buffer was released.
*/
- bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) {
+ bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session,
+ bool force) {
std::scoped_lock l{lock};
bool buffer_released{false};
while (registered_count > 0) {
@@ -103,7 +104,8 @@ public:
}
// Check with the backend if this buffer can be released yet.
- if (!session.IsBufferConsumed(buffers[index])) {
+ // If we're shutting down, we don't care if it's been played or not.
+ if (!force && !session.IsBufferConsumed(buffers[index])) {
break;
}
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp
index 995060414..5a327a606 100644
--- a/src/audio_core/device/device_session.cpp
+++ b/src/audio_core/device/device_session.cpp
@@ -73,6 +73,12 @@ void DeviceSession::Stop() {
}
}
+void DeviceSession::ClearBuffers() {
+ if (stream) {
+ stream->ClearQueue();
+ }
+}
+
void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const {
for (const auto& buffer : buffers) {
Sink::SinkBuffer new_buffer{
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h
index 74f4dc085..75f766c68 100644
--- a/src/audio_core/device/device_session.h
+++ b/src/audio_core/device/device_session.h
@@ -91,6 +91,11 @@ public:
void Stop();
/**
+ * Clear out the underlying audio buffers in the backend stream.
+ */
+ void ClearBuffers();
+
+ /**
* Set this device session's volume.
*
* @param volume - New volume for this session.
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp
index 4324cafd8..934ef8c1c 100644
--- a/src/audio_core/in/audio_in_system.cpp
+++ b/src/audio_core/in/audio_in_system.cpp
@@ -23,7 +23,6 @@ System::~System() {
void System::Finalize() {
Stop();
session->Finalize();
- buffer_event->Signal();
}
void System::StartSession() {
@@ -102,6 +101,10 @@ Result System::Stop() {
if (state == State::Started) {
session->Stop();
session->SetVolume(0.0f);
+ session->ClearBuffers();
+ if (buffers.ReleaseBuffers(system.CoreTiming(), *session, true)) {
+ buffer_event->Signal();
+ }
state = State::Stopped;
}
@@ -138,7 +141,7 @@ void System::RegisterBuffers() {
}
void System::ReleaseBuffers() {
- bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
+ bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session, false)};
if (signal) {
// Signal if any buffer was released, or if none are registered, we need more.
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp
index a66208ed9..e096a1dac 100644
--- a/src/audio_core/out/audio_out_system.cpp
+++ b/src/audio_core/out/audio_out_system.cpp
@@ -24,7 +24,6 @@ System::~System() {
void System::Finalize() {
Stop();
session->Finalize();
- buffer_event->Signal();
}
std::string_view System::GetDefaultOutputDeviceName() const {
@@ -102,6 +101,10 @@ Result System::Stop() {
if (state == State::Started) {
session->Stop();
session->SetVolume(0.0f);
+ session->ClearBuffers();
+ if (buffers.ReleaseBuffers(system.CoreTiming(), *session, true)) {
+ buffer_event->Signal();
+ }
state = State::Stopped;
}
@@ -138,7 +141,7 @@ void System::RegisterBuffers() {
}
void System::ReleaseBuffers() {
- bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session)};
+ bool signal{buffers.ReleaseBuffers(system.CoreTiming(), *session, false)};
if (signal) {
// Signal if any buffer was released, or if none are registered, we need more.
buffer_event->Signal();
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 25b22a281..eb05e46a8 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -78,6 +78,7 @@ add_library(common STATIC
logging/types.h
lz4_compression.cpp
lz4_compression.h
+ make_unique_for_overwrite.h
math_util.h
memory_detect.cpp
memory_detect.h
@@ -101,6 +102,7 @@ add_library(common STATIC
${CMAKE_CURRENT_BINARY_DIR}/scm_rev.cpp
scm_rev.h
scope_exit.h
+ scratch_buffer.h
settings.cpp
settings.h
settings_input.cpp
diff --git a/src/common/assert.h b/src/common/assert.h
index 8c927fcc0..67e7e9375 100644
--- a/src/common/assert.h
+++ b/src/common/assert.h
@@ -69,7 +69,7 @@ void assert_fail_impl();
#define ASSERT_OR_EXECUTE(_a_, _b_) \
do { \
ASSERT(_a_); \
- if (!(_a_)) { \
+ if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)
@@ -78,7 +78,7 @@ void assert_fail_impl();
#define ASSERT_OR_EXECUTE_MSG(_a_, _b_, ...) \
do { \
ASSERT_MSG(_a_, __VA_ARGS__); \
- if (!(_a_)) { \
+ if (!(_a_)) [[unlikely]] { \
_b_ \
} \
} while (0)
diff --git a/src/common/make_unique_for_overwrite.h b/src/common/make_unique_for_overwrite.h
new file mode 100644
index 000000000..c7413cf51
--- /dev/null
+++ b/src/common/make_unique_for_overwrite.h
@@ -0,0 +1,25 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <memory>
+#include <type_traits>
+
+namespace Common {
+
+template <class T>
+requires(!std::is_array_v<T>) std::unique_ptr<T> make_unique_for_overwrite() {
+ return std::unique_ptr<T>(new T);
+}
+
+template <class T>
+requires std::is_unbounded_array_v<T> std::unique_ptr<T> make_unique_for_overwrite(std::size_t n) {
+ return std::unique_ptr<T>(new std::remove_extent_t<T>[n]);
+}
+
+template <class T, class... Args>
+requires std::is_bounded_array_v<T>
+void make_unique_for_overwrite(Args&&...) = delete;
+
+} // namespace Common
diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h
new file mode 100644
index 000000000..1245a5086
--- /dev/null
+++ b/src/common/scratch_buffer.h
@@ -0,0 +1,95 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/make_unique_for_overwrite.h"
+
+namespace Common {
+
+/**
+ * ScratchBuffer class
+ * This class creates a default initialized heap allocated buffer for cases such as intermediate
+ * buffers being copied into entirely, where value initializing members during allocation or resize
+ * is redundant.
+ */
+template <typename T>
+class ScratchBuffer {
+public:
+ ScratchBuffer() = default;
+
+ explicit ScratchBuffer(size_t initial_capacity)
+ : last_requested_size{initial_capacity}, buffer_capacity{initial_capacity},
+ buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {}
+
+ ~ScratchBuffer() = default;
+
+ /// This will only grow the buffer's capacity if size is greater than the current capacity.
+ /// The previously held data will remain intact.
+ void resize(size_t size) {
+ if (size > buffer_capacity) {
+ auto new_buffer = Common::make_unique_for_overwrite<T[]>(size);
+ std::move(buffer.get(), buffer.get() + buffer_capacity, new_buffer.get());
+ buffer = std::move(new_buffer);
+ buffer_capacity = size;
+ }
+ last_requested_size = size;
+ }
+
+ /// This will only grow the buffer's capacity if size is greater than the current capacity.
+ /// The previously held data will be destroyed if a reallocation occurs.
+ void resize_destructive(size_t size) {
+ if (size > buffer_capacity) {
+ buffer_capacity = size;
+ buffer = Common::make_unique_for_overwrite<T[]>(buffer_capacity);
+ }
+ last_requested_size = size;
+ }
+
+ [[nodiscard]] T* data() noexcept {
+ return buffer.get();
+ }
+
+ [[nodiscard]] const T* data() const noexcept {
+ return buffer.get();
+ }
+
+ [[nodiscard]] T* begin() noexcept {
+ return data();
+ }
+
+ [[nodiscard]] const T* begin() const noexcept {
+ return data();
+ }
+
+ [[nodiscard]] T* end() noexcept {
+ return data() + last_requested_size;
+ }
+
+ [[nodiscard]] const T* end() const noexcept {
+ return data() + last_requested_size;
+ }
+
+ [[nodiscard]] T& operator[](size_t i) {
+ return buffer[i];
+ }
+
+ [[nodiscard]] const T& operator[](size_t i) const {
+ return buffer[i];
+ }
+
+ [[nodiscard]] size_t size() const noexcept {
+ return last_requested_size;
+ }
+
+ [[nodiscard]] size_t capacity() const noexcept {
+ return buffer_capacity;
+ }
+
+private:
+ size_t last_requested_size{};
+ size_t buffer_capacity{};
+ std::unique_ptr<T[]> buffer{};
+};
+
+} // namespace Common
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index d8ffe34c3..149e621f9 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -40,6 +40,7 @@ void LogSettings() {
LOG_INFO(Config, "yuzu Configuration:");
log_setting("Controls_UseDockedMode", values.use_docked_mode.GetValue());
log_setting("System_RngSeed", values.rng_seed.GetValue().value_or(0));
+ log_setting("System_DeviceName", values.device_name.GetValue());
log_setting("System_CurrentUser", values.current_user.GetValue());
log_setting("System_LanguageIndex", values.language_index.GetValue());
log_setting("System_RegionIndex", values.region_index.GetValue());
diff --git a/src/common/settings.h b/src/common/settings.h
index 7ce9ea23c..6b199af93 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -458,6 +458,7 @@ struct Values {
// System
SwitchableSetting<std::optional<u32>> rng_seed{std::optional<u32>(), "rng_seed"};
+ Setting<std::string> device_name{"Yuzu", "device_name"};
// Measured in seconds since epoch
std::optional<s64> custom_rtc;
// Set on game boot, reset on stop. Seconds difference between current time and `custom_rtc`
diff --git a/src/common/thread.h b/src/common/thread.h
index e17a7850f..8ae169b4e 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -11,6 +11,7 @@
#include <mutex>
#include <thread>
#include "common/common_types.h"
+#include "common/polyfill_thread.h"
namespace Common {
@@ -69,7 +70,7 @@ public:
explicit Barrier(std::size_t count_) : count(count_) {}
/// Blocks until all "count" threads have called Sync()
- void Sync() {
+ bool Sync(std::stop_token token = {}) {
std::unique_lock lk{mutex};
const std::size_t current_generation = generation;
@@ -77,14 +78,16 @@ public:
generation++;
waiting = 0;
condvar.notify_all();
+ return true;
} else {
- condvar.wait(lk,
- [this, current_generation] { return current_generation != generation; });
+ CondvarWait(condvar, lk, token,
+ [this, current_generation] { return current_generation != generation; });
+ return !token.stop_requested();
}
}
private:
- std::condition_variable condvar;
+ std::condition_variable_any condvar;
std::mutex mutex;
std::size_t count;
std::size_t waiting = 0;
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index c6b5ac196..5afdeb5ff 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -201,6 +201,9 @@ add_library(core STATIC
hle/kernel/k_event_info.h
hle/kernel/k_handle_table.cpp
hle/kernel/k_handle_table.h
+ hle/kernel/k_hardware_timer_base.h
+ hle/kernel/k_hardware_timer.cpp
+ hle/kernel/k_hardware_timer.h
hle/kernel/k_interrupt_manager.cpp
hle/kernel/k_interrupt_manager.h
hle/kernel/k_light_condition_variable.cpp
@@ -223,6 +226,7 @@ add_library(core STATIC
hle/kernel/k_page_buffer.h
hle/kernel/k_page_heap.cpp
hle/kernel/k_page_heap.h
+ hle/kernel/k_page_group.cpp
hle/kernel/k_page_group.h
hle/kernel/k_page_table.cpp
hle/kernel/k_page_table.h
@@ -268,6 +272,7 @@ add_library(core STATIC
hle/kernel/k_thread_local_page.h
hle/kernel/k_thread_queue.cpp
hle/kernel/k_thread_queue.h
+ hle/kernel/k_timer_task.h
hle/kernel/k_trace.h
hle/kernel/k_transfer_memory.cpp
hle/kernel/k_transfer_memory.h
@@ -290,8 +295,6 @@ add_library(core STATIC
hle/kernel/svc_common.h
hle/kernel/svc_types.h
hle/kernel/svc_wrap.h
- hle/kernel/time_manager.cpp
- hle/kernel/time_manager.h
hle/result.h
hle/service/acc/acc.cpp
hle/service/acc/acc.h
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 94d4e2212..47292cd78 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -183,26 +183,20 @@ struct System::Impl {
Initialize(system);
}
- SystemResultStatus Run() {
+ void Run() {
std::unique_lock<std::mutex> lk(suspend_guard);
- status = SystemResultStatus::Success;
kernel.Suspend(false);
core_timing.SyncPause(false);
is_paused.store(false, std::memory_order_relaxed);
-
- return status;
}
- SystemResultStatus Pause() {
+ void Pause() {
std::unique_lock<std::mutex> lk(suspend_guard);
- status = SystemResultStatus::Success;
core_timing.SyncPause(true);
kernel.Suspend(true);
is_paused.store(true, std::memory_order_relaxed);
-
- return status;
}
bool IsPaused() const {
@@ -389,7 +383,9 @@ struct System::Impl {
kernel.ShutdownCores();
cpu_manager.Shutdown();
debugger.reset();
- services->KillNVNFlinger();
+ if (services) {
+ services->KillNVNFlinger();
+ }
kernel.CloseServices();
services.reset();
service_manager.reset();
@@ -551,12 +547,12 @@ void System::Initialize() {
impl->Initialize(*this);
}
-SystemResultStatus System::Run() {
- return impl->Run();
+void System::Run() {
+ impl->Run();
}
-SystemResultStatus System::Pause() {
- return impl->Pause();
+void System::Pause() {
+ impl->Pause();
}
bool System::IsPaused() const {
diff --git a/src/core/core.h b/src/core/core.h
index 4ebedffd9..fb5cda2f5 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -152,13 +152,13 @@ public:
* Run the OS and Application
* This function will start emulation and run the relevant devices
*/
- [[nodiscard]] SystemResultStatus Run();
+ void Run();
/**
* Pause the OS and Application
* This function will pause emulation and stop the relevant devices
*/
- [[nodiscard]] SystemResultStatus Pause();
+ void Pause();
/// Check if the core is currently paused.
[[nodiscard]] bool IsPaused() const;
diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp
index 0dd4c2196..04a11f444 100644
--- a/src/core/cpu_manager.cpp
+++ b/src/core/cpu_manager.cpp
@@ -20,23 +20,20 @@ namespace Core {
CpuManager::CpuManager(System& system_) : system{system_} {}
CpuManager::~CpuManager() = default;
-void CpuManager::ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager,
- std::size_t core) {
- cpu_manager.RunThread(core);
-}
-
void CpuManager::Initialize() {
num_cores = is_multicore ? Core::Hardware::NUM_CPU_CORES : 1;
gpu_barrier = std::make_unique<Common::Barrier>(num_cores + 1);
for (std::size_t core = 0; core < num_cores; core++) {
- core_data[core].host_thread = std::jthread(ThreadStart, std::ref(*this), core);
+ core_data[core].host_thread =
+ std::jthread([this, core](std::stop_token token) { RunThread(token, core); });
}
}
void CpuManager::Shutdown() {
for (std::size_t core = 0; core < num_cores; core++) {
if (core_data[core].host_thread.joinable()) {
+ core_data[core].host_thread.request_stop();
core_data[core].host_thread.join();
}
}
@@ -184,7 +181,7 @@ void CpuManager::ShutdownThread() {
UNREACHABLE();
}
-void CpuManager::RunThread(std::size_t core) {
+void CpuManager::RunThread(std::stop_token token, std::size_t core) {
/// Initialization
system.RegisterCoreThread(core);
std::string name;
@@ -206,7 +203,9 @@ void CpuManager::RunThread(std::size_t core) {
});
// Running
- gpu_barrier->Sync();
+ if (!gpu_barrier->Sync(token)) {
+ return;
+ }
if (!is_async_gpu && !is_multicore) {
system.GPU().ObtainContext();
diff --git a/src/core/cpu_manager.h b/src/core/cpu_manager.h
index 374367468..0deea9c58 100644
--- a/src/core/cpu_manager.h
+++ b/src/core/cpu_manager.h
@@ -81,12 +81,10 @@ private:
void SingleCoreRunGuestThread();
void SingleCoreRunIdleThread();
- static void ThreadStart(std::stop_token stop_token, CpuManager& cpu_manager, std::size_t core);
-
void GuestActivate();
void HandleInterrupt();
void ShutdownThread();
- void RunThread(std::size_t core);
+ void RunThread(std::stop_token stop_token, std::size_t core);
struct CoreData {
std::shared_ptr<Common::Fiber> host_context;
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 95363b645..cf85ba29e 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -131,6 +131,10 @@ public:
return active_config;
}
+ bool StrictContextRequired() const {
+ return strict_context_required;
+ }
+
/**
* Requests the internal configuration to be replaced by the specified argument at some point in
* the future.
@@ -207,6 +211,8 @@ protected:
WindowSystemInfo window_info;
+ bool strict_context_required = false;
+
private:
/**
* Handler called when the minimal client area was requested to be changed via SetConfig.
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 67969e938..5587ee097 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -145,6 +145,7 @@ void EmulatedController::LoadDevices() {
output_params[3].Set("output", true);
LoadTASParams();
+ LoadVirtualGamepadParams();
std::ranges::transform(button_params, button_devices.begin(), Common::Input::CreateInputDevice);
std::ranges::transform(stick_params, stick_devices.begin(), Common::Input::CreateInputDevice);
@@ -163,6 +164,12 @@ void EmulatedController::LoadDevices() {
Common::Input::CreateInputDevice);
std::ranges::transform(tas_stick_params, tas_stick_devices.begin(),
Common::Input::CreateInputDevice);
+
+ // Initialize virtual gamepad devices
+ std::ranges::transform(virtual_button_params, virtual_button_devices.begin(),
+ Common::Input::CreateInputDevice);
+ std::ranges::transform(virtual_stick_params, virtual_stick_devices.begin(),
+ Common::Input::CreateInputDevice);
}
void EmulatedController::LoadTASParams() {
@@ -203,6 +210,53 @@ void EmulatedController::LoadTASParams() {
tas_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
tas_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
+
+ // set to optimal stick to avoid sanitizing the stick and tweaking the coordinates
+ // making sure they play back in the game as originally written down in the script file
+ tas_stick_params[Settings::NativeAnalog::LStick].Set("deadzone", 0.0f);
+ tas_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f);
+ tas_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f);
+ tas_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f);
+}
+
+void EmulatedController::LoadVirtualGamepadParams() {
+ const auto player_index = NpadIdTypeToIndex(npad_id_type);
+ Common::ParamPackage common_params{};
+ common_params.Set("engine", "virtual_gamepad");
+ common_params.Set("port", static_cast<int>(player_index));
+ for (auto& param : virtual_button_params) {
+ param = common_params;
+ }
+ for (auto& param : virtual_stick_params) {
+ param = common_params;
+ }
+
+ // TODO(german77): Replace this with an input profile or something better
+ virtual_button_params[Settings::NativeButton::A].Set("button", 0);
+ virtual_button_params[Settings::NativeButton::B].Set("button", 1);
+ virtual_button_params[Settings::NativeButton::X].Set("button", 2);
+ virtual_button_params[Settings::NativeButton::Y].Set("button", 3);
+ virtual_button_params[Settings::NativeButton::LStick].Set("button", 4);
+ virtual_button_params[Settings::NativeButton::RStick].Set("button", 5);
+ virtual_button_params[Settings::NativeButton::L].Set("button", 6);
+ virtual_button_params[Settings::NativeButton::R].Set("button", 7);
+ virtual_button_params[Settings::NativeButton::ZL].Set("button", 8);
+ virtual_button_params[Settings::NativeButton::ZR].Set("button", 9);
+ virtual_button_params[Settings::NativeButton::Plus].Set("button", 10);
+ virtual_button_params[Settings::NativeButton::Minus].Set("button", 11);
+ virtual_button_params[Settings::NativeButton::DLeft].Set("button", 12);
+ virtual_button_params[Settings::NativeButton::DUp].Set("button", 13);
+ virtual_button_params[Settings::NativeButton::DRight].Set("button", 14);
+ virtual_button_params[Settings::NativeButton::DDown].Set("button", 15);
+ virtual_button_params[Settings::NativeButton::SL].Set("button", 16);
+ virtual_button_params[Settings::NativeButton::SR].Set("button", 17);
+ virtual_button_params[Settings::NativeButton::Home].Set("button", 18);
+ virtual_button_params[Settings::NativeButton::Screenshot].Set("button", 19);
+
+ virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_x", 0);
+ virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1);
+ virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2);
+ virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3);
}
void EmulatedController::ReloadInput() {
@@ -322,6 +376,35 @@ void EmulatedController::ReloadInput() {
},
});
}
+
+ // Use a common UUID for Virtual Gamepad
+ static constexpr Common::UUID VIRTUAL_UUID = Common::UUID{
+ {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x7, 0xFF, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}};
+
+ // Register virtual devices. No need to force update
+ for (std::size_t index = 0; index < virtual_button_devices.size(); ++index) {
+ if (!virtual_button_devices[index]) {
+ continue;
+ }
+ virtual_button_devices[index]->SetCallback({
+ .on_change =
+ [this, index](const Common::Input::CallbackStatus& callback) {
+ SetButton(callback, index, VIRTUAL_UUID);
+ },
+ });
+ }
+
+ for (std::size_t index = 0; index < virtual_stick_devices.size(); ++index) {
+ if (!virtual_stick_devices[index]) {
+ continue;
+ }
+ virtual_stick_devices[index]->SetCallback({
+ .on_change =
+ [this, index](const Common::Input::CallbackStatus& callback) {
+ SetStick(callback, index, VIRTUAL_UUID);
+ },
+ });
+ }
}
void EmulatedController::UnloadInput() {
@@ -349,6 +432,12 @@ void EmulatedController::UnloadInput() {
for (auto& stick : tas_stick_devices) {
stick.reset();
}
+ for (auto& button : virtual_button_devices) {
+ button.reset();
+ }
+ for (auto& stick : virtual_stick_devices) {
+ stick.reset();
+ }
camera_devices.reset();
nfc_devices.reset();
}
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index fa7a34278..a398543a6 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -385,6 +385,9 @@ private:
/// Set the params for TAS devices
void LoadTASParams();
+ /// Set the params for virtual pad devices
+ void LoadVirtualGamepadParams();
+
/**
* @param use_temporary_value If true tmp_npad_type will be used
* @return true if the controller style is fullkey
@@ -500,6 +503,12 @@ private:
ButtonDevices tas_button_devices;
StickDevices tas_stick_devices;
+ // Virtual gamepad related variables
+ ButtonParams virtual_button_params;
+ StickParams virtual_stick_params;
+ ButtonDevices virtual_button_devices;
+ StickDevices virtual_stick_devices;
+
mutable std::mutex mutex;
mutable std::mutex callback_mutex;
std::unordered_map<int, ControllerUpdateCallback> callback_list;
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp
index 06010b8d1..738b6d0f1 100644
--- a/src/core/hle/kernel/hle_ipc.cpp
+++ b/src/core/hle/kernel/hle_ipc.cpp
@@ -167,6 +167,9 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
}
if (incoming) {
// Populate the object lists with the data in the IPC request.
+ incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy);
+ incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move);
+
for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
incoming_copy_handles.push_back(rp.Pop<Handle>());
}
@@ -181,6 +184,11 @@ void HLERequestContext::ParseCommandBuffer(const KHandleTable& handle_table, u32
}
}
+ buffer_x_desciptors.reserve(command_header->num_buf_x_descriptors);
+ buffer_a_desciptors.reserve(command_header->num_buf_a_descriptors);
+ buffer_b_desciptors.reserve(command_header->num_buf_b_descriptors);
+ buffer_w_desciptors.reserve(command_header->num_buf_w_descriptors);
+
for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
buffer_x_desciptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>());
}
@@ -318,25 +326,23 @@ Result HLERequestContext::WriteToOutgoingCommandBuffer(KThread& requesting_threa
}
std::vector<u8> HLERequestContext::ReadBuffer(std::size_t buffer_index) const {
- std::vector<u8> buffer{};
const bool is_buffer_a{BufferDescriptorA().size() > buffer_index &&
BufferDescriptorA()[buffer_index].Size()};
-
if (is_buffer_a) {
ASSERT_OR_EXECUTE_MSG(
- BufferDescriptorA().size() > buffer_index, { return buffer; },
+ BufferDescriptorA().size() > buffer_index, { return {}; },
"BufferDescriptorA invalid buffer_index {}", buffer_index);
- buffer.resize(BufferDescriptorA()[buffer_index].Size());
+ std::vector<u8> buffer(BufferDescriptorA()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorA()[buffer_index].Address(), buffer.data(), buffer.size());
+ return buffer;
} else {
ASSERT_OR_EXECUTE_MSG(
- BufferDescriptorX().size() > buffer_index, { return buffer; },
+ BufferDescriptorX().size() > buffer_index, { return {}; },
"BufferDescriptorX invalid buffer_index {}", buffer_index);
- buffer.resize(BufferDescriptorX()[buffer_index].Size());
+ std::vector<u8> buffer(BufferDescriptorX()[buffer_index].Size());
memory.ReadBlock(BufferDescriptorX()[buffer_index].Address(), buffer.data(), buffer.size());
+ return buffer;
}
-
- return buffer;
}
std::size_t HLERequestContext::WriteBuffer(const void* buffer, std::size_t size,
diff --git a/src/core/hle/kernel/k_address_arbiter.cpp b/src/core/hle/kernel/k_address_arbiter.cpp
index f85b11557..a442a3b98 100644
--- a/src/core/hle/kernel/k_address_arbiter.cpp
+++ b/src/core/hle/kernel/k_address_arbiter.cpp
@@ -10,7 +10,6 @@
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
-#include "core/hle/kernel/time_manager.h"
#include "core/memory.h"
namespace Kernel {
diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp
index 4b1c134d4..d9da1e600 100644
--- a/src/core/hle/kernel/k_code_memory.cpp
+++ b/src/core/hle/kernel/k_code_memory.cpp
@@ -27,13 +27,13 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, si
auto& page_table = m_owner->PageTable();
// Construct the page group.
- m_page_group = {};
+ m_page_group.emplace(kernel, page_table.GetBlockInfoManager());
// Lock the memory.
- R_TRY(page_table.LockForCodeMemory(&m_page_group, addr, size))
+ R_TRY(page_table.LockForCodeMemory(std::addressof(*m_page_group), addr, size))
// Clear the memory.
- for (const auto& block : m_page_group.Nodes()) {
+ for (const auto& block : *m_page_group) {
std::memset(device_memory.GetPointer<void>(block.GetAddress()), 0xFF, block.GetSize());
}
@@ -51,12 +51,13 @@ Result KCodeMemory::Initialize(Core::DeviceMemory& device_memory, VAddr addr, si
void KCodeMemory::Finalize() {
// Unlock.
if (!m_is_mapped && !m_is_owner_mapped) {
- const size_t size = m_page_group.GetNumPages() * PageSize;
- m_owner->PageTable().UnlockForCodeMemory(m_address, size, m_page_group);
+ const size_t size = m_page_group->GetNumPages() * PageSize;
+ m_owner->PageTable().UnlockForCodeMemory(m_address, size, *m_page_group);
}
// Close the page group.
- m_page_group = {};
+ m_page_group->Close();
+ m_page_group->Finalize();
// Close our reference to our owner.
m_owner->Close();
@@ -64,7 +65,7 @@ void KCodeMemory::Finalize() {
Result KCodeMemory::Map(VAddr address, size_t size) {
// Validate the size.
- R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
@@ -74,7 +75,7 @@ Result KCodeMemory::Map(VAddr address, size_t size) {
// Map the memory.
R_TRY(kernel.CurrentProcess()->PageTable().MapPages(
- address, m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
+ address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite));
// Mark ourselves as mapped.
m_is_mapped = true;
@@ -84,13 +85,13 @@ Result KCodeMemory::Map(VAddr address, size_t size) {
Result KCodeMemory::Unmap(VAddr address, size_t size) {
// Validate the size.
- R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
// Unmap the memory.
- R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, m_page_group,
+ R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, *m_page_group,
KMemoryState::CodeOut));
// Mark ourselves as unmapped.
@@ -101,7 +102,7 @@ Result KCodeMemory::Unmap(VAddr address, size_t size) {
Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission perm) {
// Validate the size.
- R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
@@ -125,7 +126,7 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission
// Map the memory.
R_TRY(
- m_owner->PageTable().MapPages(address, m_page_group, KMemoryState::GeneratedCode, k_perm));
+ m_owner->PageTable().MapPages(address, *m_page_group, KMemoryState::GeneratedCode, k_perm));
// Mark ourselves as mapped.
m_is_owner_mapped = true;
@@ -135,13 +136,13 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission
Result KCodeMemory::UnmapFromOwner(VAddr address, size_t size) {
// Validate the size.
- R_UNLESS(m_page_group.GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
+ R_UNLESS(m_page_group->GetNumPages() == Common::DivideUp(size, PageSize), ResultInvalidSize);
// Lock ourselves.
KScopedLightLock lk(m_lock);
// Unmap the memory.
- R_TRY(m_owner->PageTable().UnmapPages(address, m_page_group, KMemoryState::GeneratedCode));
+ R_TRY(m_owner->PageTable().UnmapPages(address, *m_page_group, KMemoryState::GeneratedCode));
// Mark ourselves as unmapped.
m_is_owner_mapped = false;
diff --git a/src/core/hle/kernel/k_code_memory.h b/src/core/hle/kernel/k_code_memory.h
index 2e7e1436a..5b260b385 100644
--- a/src/core/hle/kernel/k_code_memory.h
+++ b/src/core/hle/kernel/k_code_memory.h
@@ -3,6 +3,8 @@
#pragma once
+#include <optional>
+
#include "common/common_types.h"
#include "core/device_memory.h"
#include "core/hle/kernel/k_auto_object.h"
@@ -49,11 +51,11 @@ public:
return m_address;
}
size_t GetSize() const {
- return m_is_initialized ? m_page_group.GetNumPages() * PageSize : 0;
+ return m_is_initialized ? m_page_group->GetNumPages() * PageSize : 0;
}
private:
- KPageGroup m_page_group{};
+ std::optional<KPageGroup> m_page_group{};
KProcess* m_owner{};
VAddr m_address{};
KLightLock m_lock;
diff --git a/src/core/hle/kernel/k_hardware_timer.cpp b/src/core/hle/kernel/k_hardware_timer.cpp
new file mode 100644
index 000000000..6bba79ea0
--- /dev/null
+++ b/src/core/hle/kernel/k_hardware_timer.cpp
@@ -0,0 +1,74 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/hle/kernel/k_hardware_timer.h"
+#include "core/hle/kernel/k_scheduler.h"
+
+namespace Kernel {
+
+void KHardwareTimer::Initialize() {
+ // Create the timing callback to register with CoreTiming.
+ m_event_type = Core::Timing::CreateEvent(
+ "KHardwareTimer::Callback", [](std::uintptr_t timer_handle, s64, std::chrono::nanoseconds) {
+ reinterpret_cast<KHardwareTimer*>(timer_handle)->DoTask();
+ return std::nullopt;
+ });
+}
+
+void KHardwareTimer::Finalize() {
+ this->DisableInterrupt();
+ m_event_type.reset();
+}
+
+void KHardwareTimer::DoTask() {
+ // Handle the interrupt.
+ {
+ KScopedSchedulerLock slk{m_kernel};
+ KScopedSpinLock lk(this->GetLock());
+
+ //! Ignore this event if needed.
+ if (!this->GetInterruptEnabled()) {
+ return;
+ }
+
+ // Disable the timer interrupt while we handle this.
+ this->DisableInterrupt();
+
+ if (const s64 next_time = this->DoInterruptTaskImpl(GetTick());
+ 0 < next_time && next_time <= m_wakeup_time) {
+ // We have a next time, so we should set the time to interrupt and turn the interrupt
+ // on.
+ this->EnableInterrupt(next_time);
+ }
+ }
+
+ // Clear the timer interrupt.
+ // Kernel::GetInterruptManager().ClearInterrupt(KInterruptName_NonSecurePhysicalTimer,
+ // GetCurrentCoreId());
+}
+
+void KHardwareTimer::EnableInterrupt(s64 wakeup_time) {
+ this->DisableInterrupt();
+
+ m_wakeup_time = wakeup_time;
+ m_kernel.System().CoreTiming().ScheduleEvent(std::chrono::nanoseconds{m_wakeup_time},
+ m_event_type, reinterpret_cast<uintptr_t>(this),
+ true);
+}
+
+void KHardwareTimer::DisableInterrupt() {
+ m_kernel.System().CoreTiming().UnscheduleEvent(m_event_type, reinterpret_cast<uintptr_t>(this));
+ m_wakeup_time = std::numeric_limits<s64>::max();
+}
+
+s64 KHardwareTimer::GetTick() const {
+ return m_kernel.System().CoreTiming().GetGlobalTimeNs().count();
+}
+
+bool KHardwareTimer::GetInterruptEnabled() {
+ return m_wakeup_time != std::numeric_limits<s64>::max();
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_hardware_timer.h b/src/core/hle/kernel/k_hardware_timer.h
new file mode 100644
index 000000000..00bef6ea1
--- /dev/null
+++ b/src/core/hle/kernel/k_hardware_timer.h
@@ -0,0 +1,54 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/kernel/k_hardware_timer_base.h"
+
+namespace Core::Timing {
+struct EventType;
+} // namespace Core::Timing
+
+namespace Kernel {
+
+class KHardwareTimer : /* public KInterruptTask, */ public KHardwareTimerBase {
+public:
+ explicit KHardwareTimer(KernelCore& kernel) : KHardwareTimerBase{kernel} {}
+
+ // Public API.
+ void Initialize();
+ void Finalize();
+
+ s64 GetCount() const {
+ return GetTick();
+ }
+
+ void RegisterTask(KTimerTask* task, s64 time_from_now) {
+ this->RegisterAbsoluteTask(task, GetTick() + time_from_now);
+ }
+
+ void RegisterAbsoluteTask(KTimerTask* task, s64 task_time) {
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk{this->GetLock()};
+
+ if (this->RegisterAbsoluteTaskImpl(task, task_time)) {
+ if (task_time <= m_wakeup_time) {
+ this->EnableInterrupt(task_time);
+ }
+ }
+ }
+
+private:
+ void EnableInterrupt(s64 wakeup_time);
+ void DisableInterrupt();
+ bool GetInterruptEnabled();
+ s64 GetTick() const;
+ void DoTask();
+
+private:
+ // Absolute time in nanoseconds
+ s64 m_wakeup_time{std::numeric_limits<s64>::max()};
+ std::shared_ptr<Core::Timing::EventType> m_event_type{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_hardware_timer_base.h b/src/core/hle/kernel/k_hardware_timer_base.h
new file mode 100644
index 000000000..6318b35bd
--- /dev/null
+++ b/src/core/hle/kernel/k_hardware_timer_base.h
@@ -0,0 +1,92 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "core/hle/kernel/k_spin_lock.h"
+#include "core/hle/kernel/k_thread.h"
+#include "core/hle/kernel/k_timer_task.h"
+
+namespace Kernel {
+
+class KHardwareTimerBase {
+public:
+ explicit KHardwareTimerBase(KernelCore& kernel) : m_kernel{kernel} {}
+
+ void CancelTask(KTimerTask* task) {
+ KScopedDisableDispatch dd{m_kernel};
+ KScopedSpinLock lk{m_lock};
+
+ if (const s64 task_time = task->GetTime(); task_time > 0) {
+ this->RemoveTaskFromTree(task);
+ }
+ }
+
+protected:
+ KSpinLock& GetLock() {
+ return m_lock;
+ }
+
+ s64 DoInterruptTaskImpl(s64 cur_time) {
+ // We want to handle all tasks, returning the next time that a task is scheduled.
+ while (true) {
+ // Get the next task. If there isn't one, return 0.
+ KTimerTask* task = m_next_task;
+ if (task == nullptr) {
+ return 0;
+ }
+
+ // If the task needs to be done in the future, do it in the future and not now.
+ if (const s64 task_time = task->GetTime(); task_time > cur_time) {
+ return task_time;
+ }
+
+ // Remove the task from the tree of tasks, and update our next task.
+ this->RemoveTaskFromTree(task);
+
+ // Handle the task.
+ task->OnTimer();
+ }
+ }
+
+ bool RegisterAbsoluteTaskImpl(KTimerTask* task, s64 task_time) {
+ ASSERT(task_time > 0);
+
+ // Set the task's time, and insert it into our tree.
+ task->SetTime(task_time);
+ m_task_tree.insert(*task);
+
+ // Update our next task if relevant.
+ if (m_next_task != nullptr && m_next_task->GetTime() <= task_time) {
+ return false;
+ }
+ m_next_task = task;
+ return true;
+ }
+
+private:
+ void RemoveTaskFromTree(KTimerTask* task) {
+ // Erase from the tree.
+ auto it = m_task_tree.erase(m_task_tree.iterator_to(*task));
+
+ // Clear the task's scheduled time.
+ task->SetTime(0);
+
+ // Update our next task if relevant.
+ if (m_next_task == task) {
+ m_next_task = (it != m_task_tree.end()) ? std::addressof(*it) : nullptr;
+ }
+ }
+
+protected:
+ KernelCore& m_kernel;
+
+private:
+ using TimerTaskTree = Common::IntrusiveRedBlackTreeBaseTraits<KTimerTask>::TreeType<KTimerTask>;
+
+ KSpinLock m_lock{};
+ TimerTaskTree m_task_tree{};
+ KTimerTask* m_next_task{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_memory_manager.cpp b/src/core/hle/kernel/k_memory_manager.cpp
index bd33571da..cd6ea388e 100644
--- a/src/core/hle/kernel/k_memory_manager.cpp
+++ b/src/core/hle/kernel/k_memory_manager.cpp
@@ -223,7 +223,7 @@ Result KMemoryManager::AllocatePageGroupImpl(KPageGroup* out, size_t num_pages,
// Ensure that we don't leave anything un-freed.
ON_RESULT_FAILURE {
- for (const auto& it : out->Nodes()) {
+ for (const auto& it : *out) {
auto& manager = this->GetManager(it.GetAddress());
const size_t node_num_pages = std::min<u64>(
it.GetNumPages(), (manager.GetEndAddress() - it.GetAddress()) / PageSize);
@@ -285,7 +285,7 @@ Result KMemoryManager::AllocateAndOpen(KPageGroup* out, size_t num_pages, u32 op
m_has_optimized_process[static_cast<size_t>(pool)], true));
// Open the first reference to the pages.
- for (const auto& block : out->Nodes()) {
+ for (const auto& block : *out) {
PAddr cur_address = block.GetAddress();
size_t remaining_pages = block.GetNumPages();
while (remaining_pages > 0) {
@@ -335,7 +335,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
// Perform optimized memory tracking, if we should.
if (optimized) {
// Iterate over the allocated blocks.
- for (const auto& block : out->Nodes()) {
+ for (const auto& block : *out) {
// Get the block extents.
const PAddr block_address = block.GetAddress();
const size_t block_pages = block.GetNumPages();
@@ -391,7 +391,7 @@ Result KMemoryManager::AllocateForProcess(KPageGroup* out, size_t num_pages, u32
}
} else {
// Set all the allocated memory.
- for (const auto& block : out->Nodes()) {
+ for (const auto& block : *out) {
std::memset(m_system.DeviceMemory().GetPointer<void>(block.GetAddress()), fill_pattern,
block.GetSize());
}
diff --git a/src/core/hle/kernel/k_page_group.cpp b/src/core/hle/kernel/k_page_group.cpp
new file mode 100644
index 000000000..d8c644a33
--- /dev/null
+++ b/src/core/hle/kernel/k_page_group.cpp
@@ -0,0 +1,121 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "core/hle/kernel/k_dynamic_resource_manager.h"
+#include "core/hle/kernel/k_memory_manager.h"
+#include "core/hle/kernel/k_page_group.h"
+#include "core/hle/kernel/kernel.h"
+#include "core/hle/kernel/svc_results.h"
+
+namespace Kernel {
+
+void KPageGroup::Finalize() {
+ KBlockInfo* cur = m_first_block;
+ while (cur != nullptr) {
+ KBlockInfo* next = cur->GetNext();
+ m_manager->Free(cur);
+ cur = next;
+ }
+
+ m_first_block = nullptr;
+ m_last_block = nullptr;
+}
+
+void KPageGroup::CloseAndReset() {
+ auto& mm = m_kernel.MemoryManager();
+
+ KBlockInfo* cur = m_first_block;
+ while (cur != nullptr) {
+ KBlockInfo* next = cur->GetNext();
+ mm.Close(cur->GetAddress(), cur->GetNumPages());
+ m_manager->Free(cur);
+ cur = next;
+ }
+
+ m_first_block = nullptr;
+ m_last_block = nullptr;
+}
+
+size_t KPageGroup::GetNumPages() const {
+ size_t num_pages = 0;
+
+ for (const auto& it : *this) {
+ num_pages += it.GetNumPages();
+ }
+
+ return num_pages;
+}
+
+Result KPageGroup::AddBlock(KPhysicalAddress addr, size_t num_pages) {
+ // Succeed immediately if we're adding no pages.
+ R_SUCCEED_IF(num_pages == 0);
+
+ // Check for overflow.
+ ASSERT(addr < addr + num_pages * PageSize);
+
+ // Try to just append to the last block.
+ if (m_last_block != nullptr) {
+ R_SUCCEED_IF(m_last_block->TryConcatenate(addr, num_pages));
+ }
+
+ // Allocate a new block.
+ KBlockInfo* new_block = m_manager->Allocate();
+ R_UNLESS(new_block != nullptr, ResultOutOfResource);
+
+ // Initialize the block.
+ new_block->Initialize(addr, num_pages);
+
+ // Add the block to our list.
+ if (m_last_block != nullptr) {
+ m_last_block->SetNext(new_block);
+ } else {
+ m_first_block = new_block;
+ }
+ m_last_block = new_block;
+
+ R_SUCCEED();
+}
+
+void KPageGroup::Open() const {
+ auto& mm = m_kernel.MemoryManager();
+
+ for (const auto& it : *this) {
+ mm.Open(it.GetAddress(), it.GetNumPages());
+ }
+}
+
+void KPageGroup::OpenFirst() const {
+ auto& mm = m_kernel.MemoryManager();
+
+ for (const auto& it : *this) {
+ mm.OpenFirst(it.GetAddress(), it.GetNumPages());
+ }
+}
+
+void KPageGroup::Close() const {
+ auto& mm = m_kernel.MemoryManager();
+
+ for (const auto& it : *this) {
+ mm.Close(it.GetAddress(), it.GetNumPages());
+ }
+}
+
+bool KPageGroup::IsEquivalentTo(const KPageGroup& rhs) const {
+ auto lit = this->begin();
+ auto rit = rhs.begin();
+ auto lend = this->end();
+ auto rend = rhs.end();
+
+ while (lit != lend && rit != rend) {
+ if (*lit != *rit) {
+ return false;
+ }
+
+ ++lit;
+ ++rit;
+ }
+
+ return lit == lend && rit == rend;
+}
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_group.h b/src/core/hle/kernel/k_page_group.h
index 316f172f2..c07f17663 100644
--- a/src/core/hle/kernel/k_page_group.h
+++ b/src/core/hle/kernel/k_page_group.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -13,24 +13,23 @@
namespace Kernel {
+class KBlockInfoManager;
+class KernelCore;
class KPageGroup;
class KBlockInfo {
-private:
- friend class KPageGroup;
-
public:
- constexpr KBlockInfo() = default;
+ constexpr explicit KBlockInfo() : m_next(nullptr) {}
- constexpr void Initialize(PAddr addr, size_t np) {
+ constexpr void Initialize(KPhysicalAddress addr, size_t np) {
ASSERT(Common::IsAligned(addr, PageSize));
ASSERT(static_cast<u32>(np) == np);
- m_page_index = static_cast<u32>(addr) / PageSize;
+ m_page_index = static_cast<u32>(addr / PageSize);
m_num_pages = static_cast<u32>(np);
}
- constexpr PAddr GetAddress() const {
+ constexpr KPhysicalAddress GetAddress() const {
return m_page_index * PageSize;
}
constexpr size_t GetNumPages() const {
@@ -39,10 +38,10 @@ public:
constexpr size_t GetSize() const {
return this->GetNumPages() * PageSize;
}
- constexpr PAddr GetEndAddress() const {
+ constexpr KPhysicalAddress GetEndAddress() const {
return (m_page_index + m_num_pages) * PageSize;
}
- constexpr PAddr GetLastAddress() const {
+ constexpr KPhysicalAddress GetLastAddress() const {
return this->GetEndAddress() - 1;
}
@@ -62,8 +61,8 @@ public:
return !(*this == rhs);
}
- constexpr bool IsStrictlyBefore(PAddr addr) const {
- const PAddr end = this->GetEndAddress();
+ constexpr bool IsStrictlyBefore(KPhysicalAddress addr) const {
+ const KPhysicalAddress end = this->GetEndAddress();
if (m_page_index != 0 && end == 0) {
return false;
@@ -72,11 +71,11 @@ public:
return end < addr;
}
- constexpr bool operator<(PAddr addr) const {
+ constexpr bool operator<(KPhysicalAddress addr) const {
return this->IsStrictlyBefore(addr);
}
- constexpr bool TryConcatenate(PAddr addr, size_t np) {
+ constexpr bool TryConcatenate(KPhysicalAddress addr, size_t np) {
if (addr != 0 && addr == this->GetEndAddress()) {
m_num_pages += static_cast<u32>(np);
return true;
@@ -90,96 +89,118 @@ private:
}
private:
+ friend class KPageGroup;
+
KBlockInfo* m_next{};
u32 m_page_index{};
u32 m_num_pages{};
};
static_assert(sizeof(KBlockInfo) <= 0x10);
-class KPageGroup final {
+class KPageGroup {
public:
- class Node final {
+ class Iterator {
public:
- constexpr Node(u64 addr_, std::size_t num_pages_) : addr{addr_}, num_pages{num_pages_} {}
+ using iterator_category = std::forward_iterator_tag;
+ using value_type = const KBlockInfo;
+ using difference_type = std::ptrdiff_t;
+ using pointer = value_type*;
+ using reference = value_type&;
+
+ constexpr explicit Iterator(pointer n) : m_node(n) {}
+
+ constexpr bool operator==(const Iterator& rhs) const {
+ return m_node == rhs.m_node;
+ }
+ constexpr bool operator!=(const Iterator& rhs) const {
+ return !(*this == rhs);
+ }
- constexpr u64 GetAddress() const {
- return addr;
+ constexpr pointer operator->() const {
+ return m_node;
+ }
+ constexpr reference operator*() const {
+ return *m_node;
}
- constexpr std::size_t GetNumPages() const {
- return num_pages;
+ constexpr Iterator& operator++() {
+ m_node = m_node->GetNext();
+ return *this;
}
- constexpr std::size_t GetSize() const {
- return GetNumPages() * PageSize;
+ constexpr Iterator operator++(int) {
+ const Iterator it{*this};
+ ++(*this);
+ return it;
}
private:
- u64 addr{};
- std::size_t num_pages{};
+ pointer m_node{};
};
-public:
- KPageGroup() = default;
- KPageGroup(u64 address, u64 num_pages) {
- ASSERT(AddBlock(address, num_pages).IsSuccess());
+ explicit KPageGroup(KernelCore& kernel, KBlockInfoManager* m)
+ : m_kernel{kernel}, m_manager{m} {}
+ ~KPageGroup() {
+ this->Finalize();
}
- constexpr std::list<Node>& Nodes() {
- return nodes;
- }
+ void CloseAndReset();
+ void Finalize();
- constexpr const std::list<Node>& Nodes() const {
- return nodes;
+ Iterator begin() const {
+ return Iterator{m_first_block};
+ }
+ Iterator end() const {
+ return Iterator{nullptr};
+ }
+ bool empty() const {
+ return m_first_block == nullptr;
}
- std::size_t GetNumPages() const {
- std::size_t num_pages = 0;
- for (const Node& node : nodes) {
- num_pages += node.GetNumPages();
- }
- return num_pages;
- }
-
- bool IsEqual(KPageGroup& other) const {
- auto this_node = nodes.begin();
- auto other_node = other.nodes.begin();
- while (this_node != nodes.end() && other_node != other.nodes.end()) {
- if (this_node->GetAddress() != other_node->GetAddress() ||
- this_node->GetNumPages() != other_node->GetNumPages()) {
- return false;
- }
- this_node = std::next(this_node);
- other_node = std::next(other_node);
- }
+ Result AddBlock(KPhysicalAddress addr, size_t num_pages);
+ void Open() const;
+ void OpenFirst() const;
+ void Close() const;
+
+ size_t GetNumPages() const;
+
+ bool IsEquivalentTo(const KPageGroup& rhs) const;
+
+ bool operator==(const KPageGroup& rhs) const {
+ return this->IsEquivalentTo(rhs);
+ }
- return this_node == nodes.end() && other_node == other.nodes.end();
+ bool operator!=(const KPageGroup& rhs) const {
+ return !(*this == rhs);
}
- Result AddBlock(u64 address, u64 num_pages) {
- if (!num_pages) {
- return ResultSuccess;
+private:
+ KernelCore& m_kernel;
+ KBlockInfo* m_first_block{};
+ KBlockInfo* m_last_block{};
+ KBlockInfoManager* m_manager{};
+};
+
+class KScopedPageGroup {
+public:
+ explicit KScopedPageGroup(const KPageGroup* gp) : m_pg(gp) {
+ if (m_pg) {
+ m_pg->Open();
}
- if (!nodes.empty()) {
- const auto node = nodes.back();
- if (node.GetAddress() + node.GetNumPages() * PageSize == address) {
- address = node.GetAddress();
- num_pages += node.GetNumPages();
- nodes.pop_back();
- }
+ }
+ explicit KScopedPageGroup(const KPageGroup& gp) : KScopedPageGroup(std::addressof(gp)) {}
+ ~KScopedPageGroup() {
+ if (m_pg) {
+ m_pg->Close();
}
- nodes.push_back({address, num_pages});
- return ResultSuccess;
}
- bool Empty() const {
- return nodes.empty();
+ void CancelClose() {
+ m_pg = nullptr;
}
- void Finalize() {}
-
private:
- std::list<Node> nodes;
+ const KPageGroup* m_pg{};
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp
index 612fc76fa..9c7ac22dc 100644
--- a/src/core/hle/kernel/k_page_table.cpp
+++ b/src/core/hle/kernel/k_page_table.cpp
@@ -100,7 +100,7 @@ constexpr size_t GetAddressSpaceWidthFromType(FileSys::ProgramAddressSpaceType a
KPageTable::KPageTable(Core::System& system_)
: m_general_lock{system_.Kernel()},
- m_map_physical_memory_lock{system_.Kernel()}, m_system{system_} {}
+ m_map_physical_memory_lock{system_.Kernel()}, m_system{system_}, m_kernel{system_.Kernel()} {}
KPageTable::~KPageTable() = default;
@@ -373,7 +373,7 @@ Result KPageTable::MapProcessCode(VAddr addr, size_t num_pages, KMemoryState sta
m_memory_block_slab_manager);
// Allocate and open.
- KPageGroup pg;
+ KPageGroup pg{m_kernel, m_block_info_manager};
R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
&pg, num_pages,
KMemoryManager::EncodeOption(KMemoryManager::Pool::Application, m_allocation_option)));
@@ -432,7 +432,7 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si
const size_t num_pages = size / PageSize;
// Create page groups for the memory being mapped.
- KPageGroup pg;
+ KPageGroup pg{m_kernel, m_block_info_manager};
AddRegionToPages(src_address, num_pages, pg);
// Reprotect the source as kernel-read/not mapped.
@@ -593,7 +593,7 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) {
const size_t size = num_pages * PageSize;
// We're making a new group, not adding to an existing one.
- R_UNLESS(pg.Empty(), ResultInvalidCurrentMemory);
+ R_UNLESS(pg.empty(), ResultInvalidCurrentMemory);
// Begin traversal.
Common::PageTable::TraversalContext context;
@@ -640,11 +640,10 @@ Result KPageTable::MakePageGroup(KPageGroup& pg, VAddr addr, size_t num_pages) {
R_SUCCEED();
}
-bool KPageTable::IsValidPageGroup(const KPageGroup& pg_ll, VAddr addr, size_t num_pages) {
+bool KPageTable::IsValidPageGroup(const KPageGroup& pg, VAddr addr, size_t num_pages) {
ASSERT(this->IsLockedByCurrentThread());
const size_t size = num_pages * PageSize;
- const auto& pg = pg_ll.Nodes();
const auto& memory_layout = m_system.Kernel().MemoryLayout();
// Empty groups are necessarily invalid.
@@ -942,9 +941,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
ON_RESULT_FAILURE {
if (cur_mapped_addr != dst_addr) {
- // HACK: Manually close the pages.
- HACK_ClosePages(dst_addr, (cur_mapped_addr - dst_addr) / PageSize);
-
ASSERT(Operate(dst_addr, (cur_mapped_addr - dst_addr) / PageSize,
KMemoryPermission::None, OperationType::Unmap)
.IsSuccess());
@@ -1020,9 +1016,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
// Map the page.
R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, start_partial_page));
- // HACK: Manually open the pages.
- HACK_OpenPages(start_partial_page, 1);
-
// Update tracking extents.
cur_mapped_addr += PageSize;
cur_block_addr += PageSize;
@@ -1051,9 +1044,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
R_TRY(Operate(cur_mapped_addr, cur_block_size / PageSize, test_perm, OperationType::Map,
cur_block_addr));
- // HACK: Manually open the pages.
- HACK_OpenPages(cur_block_addr, cur_block_size / PageSize);
-
// Update tracking extents.
cur_mapped_addr += cur_block_size;
cur_block_addr = next_entry.phys_addr;
@@ -1073,9 +1063,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
R_TRY(Operate(cur_mapped_addr, last_block_size / PageSize, test_perm, OperationType::Map,
cur_block_addr));
- // HACK: Manually open the pages.
- HACK_OpenPages(cur_block_addr, last_block_size / PageSize);
-
// Update tracking extents.
cur_mapped_addr += last_block_size;
cur_block_addr += last_block_size;
@@ -1107,9 +1094,6 @@ Result KPageTable::SetupForIpcServer(VAddr* out_addr, size_t size, VAddr src_add
// Map the page.
R_TRY(Operate(cur_mapped_addr, 1, test_perm, OperationType::Map, end_partial_page));
-
- // HACK: Manually open the pages.
- HACK_OpenPages(end_partial_page, 1);
}
// Update memory blocks to reflect our changes
@@ -1211,9 +1195,6 @@ Result KPageTable::CleanupForIpcServer(VAddr address, size_t size, KMemoryState
const size_t aligned_size = aligned_end - aligned_start;
const size_t aligned_num_pages = aligned_size / PageSize;
- // HACK: Manually close the pages.
- HACK_ClosePages(aligned_start, aligned_num_pages);
-
// Unmap the pages.
R_TRY(Operate(aligned_start, aligned_num_pages, KMemoryPermission::None, OperationType::Unmap));
@@ -1501,17 +1482,6 @@ void KPageTable::CleanupForIpcClientOnServerSetupFailure([[maybe_unused]] PageLi
}
}
-void KPageTable::HACK_OpenPages(PAddr phys_addr, size_t num_pages) {
- m_system.Kernel().MemoryManager().OpenFirst(phys_addr, num_pages);
-}
-
-void KPageTable::HACK_ClosePages(VAddr virt_addr, size_t num_pages) {
- for (size_t index = 0; index < num_pages; ++index) {
- const auto paddr = GetPhysicalAddr(virt_addr + (index * PageSize));
- m_system.Kernel().MemoryManager().Close(paddr, 1);
- }
-}
-
Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Lock the physical memory lock.
KScopedLightLock phys_lk(m_map_physical_memory_lock);
@@ -1572,7 +1542,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the new memory.
- KPageGroup pg;
+ KPageGroup pg{m_kernel, m_block_info_manager};
R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
&pg, (size - mapped_size) / PageSize, m_allocate_option, 0, 0));
@@ -1650,7 +1620,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
KScopedPageTableUpdater updater(this);
// Prepare to iterate over the memory.
- auto pg_it = pg.Nodes().begin();
+ auto pg_it = pg.begin();
PAddr pg_phys_addr = pg_it->GetAddress();
size_t pg_pages = pg_it->GetNumPages();
@@ -1680,9 +1650,6 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
last_unmap_address + 1 - cur_address) /
PageSize;
- // HACK: Manually close the pages.
- HACK_ClosePages(cur_address, cur_pages);
-
// Unmap.
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None,
OperationType::Unmap)
@@ -1703,7 +1670,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Release any remaining unmapped memory.
m_system.Kernel().MemoryManager().OpenFirst(pg_phys_addr, pg_pages);
m_system.Kernel().MemoryManager().Close(pg_phys_addr, pg_pages);
- for (++pg_it; pg_it != pg.Nodes().end(); ++pg_it) {
+ for (++pg_it; pg_it != pg.end(); ++pg_it) {
m_system.Kernel().MemoryManager().OpenFirst(pg_it->GetAddress(),
pg_it->GetNumPages());
m_system.Kernel().MemoryManager().Close(pg_it->GetAddress(),
@@ -1731,7 +1698,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Check if we're at the end of the physical block.
if (pg_pages == 0) {
// Ensure there are more pages to map.
- ASSERT(pg_it != pg.Nodes().end());
+ ASSERT(pg_it != pg.end());
// Advance our physical block.
++pg_it;
@@ -1742,10 +1709,7 @@ Result KPageTable::MapPhysicalMemory(VAddr address, size_t size) {
// Map whatever we can.
const size_t cur_pages = std::min(pg_pages, map_pages);
R_TRY(Operate(cur_address, cur_pages, KMemoryPermission::UserReadWrite,
- OperationType::Map, pg_phys_addr));
-
- // HACK: Manually open the pages.
- HACK_OpenPages(pg_phys_addr, cur_pages);
+ OperationType::MapFirst, pg_phys_addr));
// Advance.
cur_address += cur_pages * PageSize;
@@ -1888,9 +1852,6 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) {
last_address + 1 - cur_address) /
PageSize;
- // HACK: Manually close the pages.
- HACK_ClosePages(cur_address, cur_pages);
-
// Unmap.
ASSERT(Operate(cur_address, cur_pages, KMemoryPermission::None, OperationType::Unmap)
.IsSuccess());
@@ -1955,7 +1916,7 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size)
R_TRY(dst_allocator_result);
// Map the memory.
- KPageGroup page_linked_list;
+ KPageGroup page_linked_list{m_kernel, m_block_info_manager};
const size_t num_pages{size / PageSize};
const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>(
KMemoryPermission::KernelRead | KMemoryPermission::NotMapped);
@@ -2022,14 +1983,14 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size
num_dst_allocator_blocks);
R_TRY(dst_allocator_result);
- KPageGroup src_pages;
- KPageGroup dst_pages;
+ KPageGroup src_pages{m_kernel, m_block_info_manager};
+ KPageGroup dst_pages{m_kernel, m_block_info_manager};
const size_t num_pages{size / PageSize};
AddRegionToPages(src_address, num_pages, src_pages);
AddRegionToPages(dst_address, num_pages, dst_pages);
- R_UNLESS(dst_pages.IsEqual(src_pages), ResultInvalidMemoryRegion);
+ R_UNLESS(dst_pages.IsEquivalentTo(src_pages), ResultInvalidMemoryRegion);
{
auto block_guard = detail::ScopeExit([&] { MapPages(dst_address, dst_pages, dst_perm); });
@@ -2060,7 +2021,7 @@ Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list,
VAddr cur_addr{addr};
- for (const auto& node : page_linked_list.Nodes()) {
+ for (const auto& node : page_linked_list) {
if (const auto result{
Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())};
result.IsError()) {
@@ -2160,7 +2121,7 @@ Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) {
VAddr cur_addr{addr};
- for (const auto& node : page_linked_list.Nodes()) {
+ for (const auto& node : page_linked_list) {
if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None,
OperationType::Unmap)};
result.IsError()) {
@@ -2527,13 +2488,13 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) {
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
// Allocate pages for the heap extension.
- KPageGroup pg;
+ KPageGroup pg{m_kernel, m_block_info_manager};
R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
&pg, allocation_size / PageSize,
KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option)));
// Clear all the newly allocated pages.
- for (const auto& it : pg.Nodes()) {
+ for (const auto& it : pg) {
std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value,
it.GetSize());
}
@@ -2610,11 +2571,23 @@ ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_
if (is_map_only) {
R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr));
} else {
- KPageGroup page_group;
- R_TRY(m_system.Kernel().MemoryManager().AllocateForProcess(
- &page_group, needed_num_pages,
- KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option), 0, 0));
- R_TRY(Operate(addr, needed_num_pages, page_group, OperationType::MapGroup));
+ // Create a page group tohold the pages we allocate.
+ KPageGroup pg{m_kernel, m_block_info_manager};
+
+ R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen(
+ &pg, needed_num_pages,
+ KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option)));
+
+ // Ensure that the page group is closed when we're done working with it.
+ SCOPE_EXIT({ pg.Close(); });
+
+ // Clear all pages.
+ for (const auto& it : pg) {
+ std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()),
+ m_heap_fill_value, it.GetSize());
+ }
+
+ R_TRY(Operate(addr, needed_num_pages, pg, OperationType::MapGroup));
}
// Update the blocks.
@@ -2795,19 +2768,28 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, const KPageGroup& page_
ASSERT(num_pages > 0);
ASSERT(num_pages == page_group.GetNumPages());
- for (const auto& node : page_group.Nodes()) {
- const size_t size{node.GetNumPages() * PageSize};
+ switch (operation) {
+ case OperationType::MapGroup: {
+ // We want to maintain a new reference to every page in the group.
+ KScopedPageGroup spg(page_group);
+
+ for (const auto& node : page_group) {
+ const size_t size{node.GetNumPages() * PageSize};
- switch (operation) {
- case OperationType::MapGroup:
+ // Map the pages.
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, size, node.GetAddress());
- break;
- default:
- ASSERT(false);
- break;
+
+ addr += size;
}
- addr += size;
+ // We succeeded! We want to persist the reference to the pages.
+ spg.CancelClose();
+
+ break;
+ }
+ default:
+ ASSERT(false);
+ break;
}
R_SUCCEED();
@@ -2822,13 +2804,29 @@ Result KPageTable::Operate(VAddr addr, size_t num_pages, KMemoryPermission perm,
ASSERT(ContainsPages(addr, num_pages));
switch (operation) {
- case OperationType::Unmap:
+ case OperationType::Unmap: {
+ // Ensure that any pages we track close on exit.
+ KPageGroup pages_to_close{m_kernel, this->GetBlockInfoManager()};
+ SCOPE_EXIT({ pages_to_close.CloseAndReset(); });
+
+ this->AddRegionToPages(addr, num_pages, pages_to_close);
m_system.Memory().UnmapRegion(*m_page_table_impl, addr, num_pages * PageSize);
break;
+ }
+ case OperationType::MapFirst:
case OperationType::Map: {
ASSERT(map_addr);
ASSERT(Common::IsAligned(map_addr, PageSize));
m_system.Memory().MapMemoryRegion(*m_page_table_impl, addr, num_pages * PageSize, map_addr);
+
+ // Open references to pages, if we should.
+ if (IsHeapPhysicalAddress(m_kernel.MemoryLayout(), map_addr)) {
+ if (operation == OperationType::MapFirst) {
+ m_kernel.MemoryManager().OpenFirst(map_addr, num_pages);
+ } else {
+ m_kernel.MemoryManager().Open(map_addr, num_pages);
+ }
+ }
break;
}
case OperationType::Separate: {
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h
index f1ca785d7..0a454b05b 100644
--- a/src/core/hle/kernel/k_page_table.h
+++ b/src/core/hle/kernel/k_page_table.h
@@ -107,6 +107,10 @@ public:
return *m_page_table_impl;
}
+ KBlockInfoManager* GetBlockInfoManager() {
+ return m_block_info_manager;
+ }
+
bool CanContain(VAddr addr, size_t size, KMemoryState state) const;
protected:
@@ -261,10 +265,6 @@ private:
void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address,
size_t size, KMemoryPermission prot_perm);
- // HACK: These will be removed once we automatically manage page reference counts.
- void HACK_OpenPages(PAddr phys_addr, size_t num_pages);
- void HACK_ClosePages(VAddr virt_addr, size_t num_pages);
-
mutable KLightLock m_general_lock;
mutable KLightLock m_map_physical_memory_lock;
@@ -488,6 +488,7 @@ private:
std::unique_ptr<Common::PageTable> m_page_table_impl;
Core::System& m_system;
+ KernelCore& m_kernel;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp
index d1dc62401..a1abf5d68 100644
--- a/src/core/hle/kernel/k_process.cpp
+++ b/src/core/hle/kernel/k_process.cpp
@@ -285,6 +285,17 @@ void KProcess::UnregisterThread(KThread* thread) {
thread_list.remove(thread);
}
+u64 KProcess::GetFreeThreadCount() const {
+ if (resource_limit != nullptr) {
+ const auto current_value =
+ resource_limit->GetCurrentValue(LimitableResource::ThreadCountMax);
+ const auto limit_value = resource_limit->GetLimitValue(LimitableResource::ThreadCountMax);
+ return limit_value - current_value;
+ } else {
+ return 0;
+ }
+}
+
Result KProcess::Reset() {
// Lock the process and the scheduler.
KScopedLightLock lk(state_lock);
diff --git a/src/core/hle/kernel/k_process.h b/src/core/hle/kernel/k_process.h
index 2e0cc3d0b..09bf2f1d0 100644
--- a/src/core/hle/kernel/k_process.h
+++ b/src/core/hle/kernel/k_process.h
@@ -304,6 +304,9 @@ public:
/// from this process' thread list.
void UnregisterThread(KThread* thread);
+ /// Retrieves the number of available threads for this process.
+ u64 GetFreeThreadCount() const;
+
/// Clears the signaled state of the process if and only if it's signaled.
///
/// @pre The process must not be already terminated. If this is called on a
diff --git a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
index 76c095e69..76db65a4d 100644
--- a/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
+++ b/src/core/hle/kernel/k_scoped_scheduler_lock_and_sleep.h
@@ -5,9 +5,9 @@
#include "common/common_types.h"
#include "core/hle/kernel/global_scheduler_context.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_thread.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/time_manager.h"
namespace Kernel {
@@ -22,7 +22,7 @@ public:
~KScopedSchedulerLockAndSleep() {
// Register the sleep.
if (timeout_tick > 0) {
- kernel.TimeManager().ScheduleTimeEvent(thread, timeout_tick);
+ kernel.HardwareTimer().RegisterTask(thread, timeout_tick);
}
// Unlock the scheduler.
diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp
index 10cd4c43d..3cf2b5d91 100644
--- a/src/core/hle/kernel/k_shared_memory.cpp
+++ b/src/core/hle/kernel/k_shared_memory.cpp
@@ -6,31 +6,29 @@
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_scoped_resource_reservation.h"
#include "core/hle/kernel/k_shared_memory.h"
+#include "core/hle/kernel/k_system_resource.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/svc_results.h"
namespace Kernel {
KSharedMemory::KSharedMemory(KernelCore& kernel_) : KAutoObjectWithSlabHeapAndContainer{kernel_} {}
-
-KSharedMemory::~KSharedMemory() {
- kernel.GetSystemResourceLimit()->Release(LimitableResource::PhysicalMemoryMax, size);
-}
+KSharedMemory::~KSharedMemory() = default;
Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
- KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_,
- Svc::MemoryPermission user_permission_, PAddr physical_address_,
- std::size_t size_, std::string name_) {
+ Svc::MemoryPermission owner_permission_,
+ Svc::MemoryPermission user_permission_, std::size_t size_,
+ std::string name_) {
// Set members.
owner_process = owner_process_;
device_memory = &device_memory_;
- page_list = std::move(page_list_);
owner_permission = owner_permission_;
user_permission = user_permission_;
- physical_address = physical_address_;
- size = size_;
+ size = Common::AlignUp(size_, PageSize);
name = std::move(name_);
+ const size_t num_pages = Common::DivideUp(size, PageSize);
+
// Get the resource limit.
KResourceLimit* reslimit = kernel.GetSystemResourceLimit();
@@ -39,6 +37,18 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
size_);
R_UNLESS(memory_reservation.Succeeded(), ResultLimitReached);
+ // Allocate the memory.
+
+ //! HACK: Open continuous mapping from sysmodule pool.
+ auto option = KMemoryManager::EncodeOption(KMemoryManager::Pool::Secure,
+ KMemoryManager::Direction::FromBack);
+ physical_address = kernel.MemoryManager().AllocateAndOpenContinuous(num_pages, 1, option);
+ R_UNLESS(physical_address != 0, ResultOutOfMemory);
+
+ //! Insert the result into our page group.
+ page_group.emplace(kernel, &kernel.GetSystemSystemResource().GetBlockInfoManager());
+ page_group->AddBlock(physical_address, num_pages);
+
// Commit our reservation.
memory_reservation.Commit();
@@ -50,12 +60,18 @@ Result KSharedMemory::Initialize(Core::DeviceMemory& device_memory_, KProcess* o
is_initialized = true;
// Clear all pages in the memory.
- std::memset(device_memory_.GetPointer<void>(physical_address_), 0, size_);
+ for (const auto& block : *page_group) {
+ std::memset(device_memory_.GetPointer<void>(block.GetAddress()), 0, block.GetSize());
+ }
return ResultSuccess;
}
void KSharedMemory::Finalize() {
+ // Close and finalize the page group.
+ page_group->Close();
+ page_group->Finalize();
+
// Release the memory reservation.
resource_limit->Release(LimitableResource::PhysicalMemoryMax, size);
resource_limit->Close();
@@ -65,32 +81,28 @@ void KSharedMemory::Finalize() {
}
Result KSharedMemory::Map(KProcess& target_process, VAddr address, std::size_t map_size,
- Svc::MemoryPermission permissions) {
- const u64 page_count{(map_size + PageSize - 1) / PageSize};
-
- if (page_list.GetNumPages() != page_count) {
- UNIMPLEMENTED_MSG("Page count does not match");
- }
+ Svc::MemoryPermission map_perm) {
+ // Validate the size.
+ R_UNLESS(size == map_size, ResultInvalidSize);
- const Svc::MemoryPermission expected =
+ // Validate the permission.
+ const Svc::MemoryPermission test_perm =
&target_process == owner_process ? owner_permission : user_permission;
-
- if (permissions != expected) {
- UNIMPLEMENTED_MSG("Permission does not match");
+ if (test_perm == Svc::MemoryPermission::DontCare) {
+ ASSERT(map_perm == Svc::MemoryPermission::Read || map_perm == Svc::MemoryPermission::Write);
+ } else {
+ R_UNLESS(map_perm == test_perm, ResultInvalidNewMemoryPermission);
}
- return target_process.PageTable().MapPages(address, page_list, KMemoryState::Shared,
- ConvertToKMemoryPermission(permissions));
+ return target_process.PageTable().MapPages(address, *page_group, KMemoryState::Shared,
+ ConvertToKMemoryPermission(map_perm));
}
Result KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) {
- const u64 page_count{(unmap_size + PageSize - 1) / PageSize};
-
- if (page_list.GetNumPages() != page_count) {
- UNIMPLEMENTED_MSG("Page count does not match");
- }
+ // Validate the size.
+ R_UNLESS(size == unmap_size, ResultInvalidSize);
- return target_process.PageTable().UnmapPages(address, page_list, KMemoryState::Shared);
+ return target_process.PageTable().UnmapPages(address, *page_group, KMemoryState::Shared);
}
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_shared_memory.h b/src/core/hle/kernel/k_shared_memory.h
index a96c55a3e..8b29f0b4a 100644
--- a/src/core/hle/kernel/k_shared_memory.h
+++ b/src/core/hle/kernel/k_shared_memory.h
@@ -3,6 +3,7 @@
#pragma once
+#include <optional>
#include <string>
#include "common/common_types.h"
@@ -26,9 +27,8 @@ public:
~KSharedMemory() override;
Result Initialize(Core::DeviceMemory& device_memory_, KProcess* owner_process_,
- KPageGroup&& page_list_, Svc::MemoryPermission owner_permission_,
- Svc::MemoryPermission user_permission_, PAddr physical_address_,
- std::size_t size_, std::string name_);
+ Svc::MemoryPermission owner_permission_,
+ Svc::MemoryPermission user_permission_, std::size_t size_, std::string name_);
/**
* Maps a shared memory block to an address in the target process' address space
@@ -76,7 +76,7 @@ public:
private:
Core::DeviceMemory* device_memory{};
KProcess* owner_process{};
- KPageGroup page_list;
+ std::optional<KPageGroup> page_group{};
Svc::MemoryPermission owner_permission{};
Svc::MemoryPermission user_permission{};
PAddr physical_address{};
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index dc52b4ed3..7cd94a340 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -22,6 +22,7 @@
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_spin_lock.h"
#include "core/hle/kernel/k_synchronization_object.h"
+#include "core/hle/kernel/k_timer_task.h"
#include "core/hle/kernel/k_worker_task.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_common.h"
@@ -112,7 +113,8 @@ void SetCurrentThread(KernelCore& kernel, KThread* thread);
[[nodiscard]] s32 GetCurrentCoreId(KernelCore& kernel);
class KThread final : public KAutoObjectWithSlabHeapAndContainer<KThread, KWorkerTask>,
- public boost::intrusive::list_base_hook<> {
+ public boost::intrusive::list_base_hook<>,
+ public KTimerTask {
KERNEL_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject);
private:
@@ -840,4 +842,8 @@ private:
KernelCore& kernel;
};
+inline void KTimerTask::OnTimer() {
+ static_cast<KThread*>(this)->OnTimer();
+}
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/k_thread_queue.cpp b/src/core/hle/kernel/k_thread_queue.cpp
index 9f4e081ba..5f1dc97eb 100644
--- a/src/core/hle/kernel/k_thread_queue.cpp
+++ b/src/core/hle/kernel/k_thread_queue.cpp
@@ -1,9 +1,9 @@
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_thread_queue.h"
#include "core/hle/kernel/kernel.h"
-#include "core/hle/kernel/time_manager.h"
namespace Kernel {
@@ -22,7 +22,7 @@ void KThreadQueue::EndWait(KThread* waiting_thread, Result wait_result) {
waiting_thread->ClearWaitQueue();
// Cancel the thread task.
- kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
+ kernel.HardwareTimer().CancelTask(waiting_thread);
}
void KThreadQueue::CancelWait(KThread* waiting_thread, Result wait_result, bool cancel_timer_task) {
@@ -37,7 +37,7 @@ void KThreadQueue::CancelWait(KThread* waiting_thread, Result wait_result, bool
// Cancel the thread task.
if (cancel_timer_task) {
- kernel.TimeManager().UnscheduleTimeEvent(waiting_thread);
+ kernel.HardwareTimer().CancelTask(waiting_thread);
}
}
diff --git a/src/core/hle/kernel/k_timer_task.h b/src/core/hle/kernel/k_timer_task.h
new file mode 100644
index 000000000..66f0a5a90
--- /dev/null
+++ b/src/core/hle/kernel/k_timer_task.h
@@ -0,0 +1,40 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/intrusive_red_black_tree.h"
+
+namespace Kernel {
+
+class KTimerTask : public Common::IntrusiveRedBlackTreeBaseNode<KTimerTask> {
+public:
+ static constexpr int Compare(const KTimerTask& lhs, const KTimerTask& rhs) {
+ if (lhs.GetTime() < rhs.GetTime()) {
+ return -1;
+ } else {
+ return 1;
+ }
+ }
+
+ constexpr explicit KTimerTask() = default;
+
+ constexpr void SetTime(s64 t) {
+ m_time = t;
+ }
+
+ constexpr s64 GetTime() const {
+ return m_time;
+ }
+
+ // NOTE: This is virtual in Nintendo's kernel. Prior to 13.0.0, KWaitObject was also a
+ // TimerTask; this is no longer the case. Since this is now KThread exclusive, we have
+ // devirtualized (see inline declaration for this inside k_thread.h).
+ void OnTimer();
+
+private:
+ // Absolute time in nanoseconds
+ s64 m_time{};
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 288f97df5..1fb25f221 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -26,6 +26,7 @@
#include "core/hle/kernel/k_client_port.h"
#include "core/hle/kernel/k_dynamic_resource_manager.h"
#include "core/hle/kernel/k_handle_table.h"
+#include "core/hle/kernel/k_hardware_timer.h"
#include "core/hle/kernel/k_memory_layout.h"
#include "core/hle/kernel/k_memory_manager.h"
#include "core/hle/kernel/k_page_buffer.h"
@@ -39,7 +40,6 @@
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/physical_core.h"
#include "core/hle/kernel/service_thread.h"
-#include "core/hle/kernel/time_manager.h"
#include "core/hle/result.h"
#include "core/hle/service/sm/sm.h"
#include "core/memory.h"
@@ -55,7 +55,7 @@ struct KernelCore::Impl {
static constexpr size_t ReservedDynamicPageCount = 64;
explicit Impl(Core::System& system_, KernelCore& kernel_)
- : time_manager{system_}, service_threads_manager{1, "ServiceThreadsManager"},
+ : service_threads_manager{1, "ServiceThreadsManager"},
service_thread_barrier{2}, system{system_} {}
void SetMulticore(bool is_multi) {
@@ -63,6 +63,9 @@ struct KernelCore::Impl {
}
void Initialize(KernelCore& kernel) {
+ hardware_timer = std::make_unique<Kernel::KHardwareTimer>(kernel);
+ hardware_timer->Initialize();
+
global_object_list_container = std::make_unique<KAutoObjectWithListContainer>(kernel);
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel);
@@ -91,6 +94,7 @@ struct KernelCore::Impl {
pt_heap_region.GetSize());
}
+ InitializeHackSharedMemory();
RegisterHostThread(nullptr);
default_service_thread = &CreateServiceThread(kernel, "DefaultServiceThread");
@@ -104,12 +108,16 @@ struct KernelCore::Impl {
}
void CloseCurrentProcess() {
- (*current_process).Finalize();
- // current_process->Close();
- // TODO: The current process should be destroyed based on accurate ref counting after
+ KProcess* old_process = current_process.exchange(nullptr);
+ if (old_process == nullptr) {
+ return;
+ }
+
+ // old_process->Close();
+ // TODO: The process should be destroyed based on accurate ref counting after
// calling Close(). Adding a manual Destroy() call instead to avoid a memory leak.
- (*current_process).Destroy();
- current_process = nullptr;
+ old_process->Finalize();
+ old_process->Destroy();
}
void Shutdown() {
@@ -189,6 +197,9 @@ struct KernelCore::Impl {
// Ensure that the object list container is finalized and properly shutdown.
global_object_list_container->Finalize();
global_object_list_container.reset();
+
+ hardware_timer->Finalize();
+ hardware_timer.reset();
}
void CloseServices() {
@@ -716,14 +727,14 @@ struct KernelCore::Impl {
}
void InitializeMemoryLayout() {
- const auto system_pool = memory_layout->GetKernelSystemPoolRegionPhysicalExtents();
-
// Initialize the memory manager.
memory_manager = std::make_unique<KMemoryManager>(system);
const auto& management_region = memory_layout->GetPoolManagementRegion();
ASSERT(management_region.GetEndAddress() != 0);
memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize());
+ }
+ void InitializeHackSharedMemory() {
// Setup memory regions for emulated processes
// TODO(bunnei): These should not be hardcoded regions initialized within the kernel
constexpr std::size_t hid_size{0x40000};
@@ -732,39 +743,23 @@ struct KernelCore::Impl {
constexpr std::size_t time_size{0x1000};
constexpr std::size_t hidbus_size{0x1000};
- const PAddr hid_phys_addr{system_pool.GetAddress()};
- const PAddr font_phys_addr{system_pool.GetAddress() + hid_size};
- const PAddr irs_phys_addr{system_pool.GetAddress() + hid_size + font_size};
- const PAddr time_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size};
- const PAddr hidbus_phys_addr{system_pool.GetAddress() + hid_size + font_size + irs_size +
- time_size};
-
hid_shared_mem = KSharedMemory::Create(system.Kernel());
font_shared_mem = KSharedMemory::Create(system.Kernel());
irs_shared_mem = KSharedMemory::Create(system.Kernel());
time_shared_mem = KSharedMemory::Create(system.Kernel());
hidbus_shared_mem = KSharedMemory::Create(system.Kernel());
- hid_shared_mem->Initialize(system.DeviceMemory(), nullptr,
- {hid_phys_addr, hid_size / PageSize},
- Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
- hid_phys_addr, hid_size, "HID:SharedMemory");
- font_shared_mem->Initialize(system.DeviceMemory(), nullptr,
- {font_phys_addr, font_size / PageSize},
- Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
- font_phys_addr, font_size, "Font:SharedMemory");
- irs_shared_mem->Initialize(system.DeviceMemory(), nullptr,
- {irs_phys_addr, irs_size / PageSize},
- Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
- irs_phys_addr, irs_size, "IRS:SharedMemory");
- time_shared_mem->Initialize(system.DeviceMemory(), nullptr,
- {time_phys_addr, time_size / PageSize},
- Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
- time_phys_addr, time_size, "Time:SharedMemory");
- hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr,
- {hidbus_phys_addr, hidbus_size / PageSize},
- Svc::MemoryPermission::None, Svc::MemoryPermission::Read,
- hidbus_phys_addr, hidbus_size, "HidBus:SharedMemory");
+ hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
+ Svc::MemoryPermission::Read, hid_size, "HID:SharedMemory");
+ font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
+ Svc::MemoryPermission::Read, font_size, "Font:SharedMemory");
+ irs_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
+ Svc::MemoryPermission::Read, irs_size, "IRS:SharedMemory");
+ time_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
+ Svc::MemoryPermission::Read, time_size, "Time:SharedMemory");
+ hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
+ Svc::MemoryPermission::Read, hidbus_size,
+ "HidBus:SharedMemory");
}
KClientPort* CreateNamedServicePort(std::string name) {
@@ -828,7 +823,7 @@ struct KernelCore::Impl {
std::vector<KProcess*> process_list;
std::atomic<KProcess*> current_process{};
std::unique_ptr<Kernel::GlobalSchedulerContext> global_scheduler_context;
- Kernel::TimeManager time_manager;
+ std::unique_ptr<Kernel::KHardwareTimer> hardware_timer;
Init::KSlabResourceCounts slab_resource_counts{};
KResourceLimit* system_resource_limit{};
@@ -1015,12 +1010,8 @@ Kernel::KScheduler* KernelCore::CurrentScheduler() {
return impl->schedulers[core_id].get();
}
-Kernel::TimeManager& KernelCore::TimeManager() {
- return impl->time_manager;
-}
-
-const Kernel::TimeManager& KernelCore::TimeManager() const {
- return impl->time_manager;
+Kernel::KHardwareTimer& KernelCore::HardwareTimer() {
+ return *impl->hardware_timer;
}
Core::ExclusiveMonitor& KernelCore::GetExclusiveMonitor() {
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 2e22fe0f6..8d22f8d2c 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -39,6 +39,7 @@ class KDynamicPageManager;
class KEvent;
class KEventInfo;
class KHandleTable;
+class KHardwareTimer;
class KLinkedListNode;
class KMemoryLayout;
class KMemoryManager;
@@ -63,7 +64,6 @@ class KCodeMemory;
class PhysicalCore;
class ServiceThread;
class Synchronization;
-class TimeManager;
using ServiceInterfaceFactory =
std::function<KClientPort&(Service::SM::ServiceManager&, Core::System&)>;
@@ -175,11 +175,8 @@ public:
/// Gets the an instance of the current physical CPU core.
const Kernel::PhysicalCore& CurrentPhysicalCore() const;
- /// Gets the an instance of the TimeManager Interface.
- Kernel::TimeManager& TimeManager();
-
- /// Gets the an instance of the TimeManager Interface.
- const Kernel::TimeManager& TimeManager() const;
+ /// Gets the an instance of the hardware timer.
+ Kernel::KHardwareTimer& HardwareTimer();
/// Stops execution of 'id' core, in order to reschedule a new thread.
void PrepareReschedule(std::size_t id);
diff --git a/src/core/hle/kernel/memory_types.h b/src/core/hle/kernel/memory_types.h
index 3975507bd..92b8b37ac 100644
--- a/src/core/hle/kernel/memory_types.h
+++ b/src/core/hle/kernel/memory_types.h
@@ -14,4 +14,7 @@ constexpr std::size_t PageSize{1 << PageBits};
using Page = std::array<u8, PageSize>;
+using KPhysicalAddress = PAddr;
+using KProcessAddress = VAddr;
+
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index e520cab47..aca442196 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -784,63 +784,29 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
LOG_TRACE(Kernel_SVC, "called info_id=0x{:X}, info_sub_id=0x{:X}, handle=0x{:08X}", info_id,
info_sub_id, handle);
- enum class GetInfoType : u64 {
- // 1.0.0+
- AllowedCPUCoreMask = 0,
- AllowedThreadPriorityMask = 1,
- MapRegionBaseAddr = 2,
- MapRegionSize = 3,
- HeapRegionBaseAddr = 4,
- HeapRegionSize = 5,
- TotalPhysicalMemoryAvailable = 6,
- TotalPhysicalMemoryUsed = 7,
- IsCurrentProcessBeingDebugged = 8,
- RegisterResourceLimit = 9,
- IdleTickCount = 10,
- RandomEntropy = 11,
- ThreadTickCount = 0xF0000002,
- // 2.0.0+
- ASLRRegionBaseAddr = 12,
- ASLRRegionSize = 13,
- StackRegionBaseAddr = 14,
- StackRegionSize = 15,
- // 3.0.0+
- SystemResourceSize = 16,
- SystemResourceUsage = 17,
- TitleId = 18,
- // 4.0.0+
- PrivilegedProcessId = 19,
- // 5.0.0+
- UserExceptionContextAddr = 20,
- // 6.0.0+
- TotalPhysicalMemoryAvailableWithoutSystemResource = 21,
- TotalPhysicalMemoryUsedWithoutSystemResource = 22,
-
- // Homebrew only
- MesosphereCurrentProcess = 65001,
- };
-
- const auto info_id_type = static_cast<GetInfoType>(info_id);
+ const auto info_id_type = static_cast<InfoType>(info_id);
switch (info_id_type) {
- case GetInfoType::AllowedCPUCoreMask:
- case GetInfoType::AllowedThreadPriorityMask:
- case GetInfoType::MapRegionBaseAddr:
- case GetInfoType::MapRegionSize:
- case GetInfoType::HeapRegionBaseAddr:
- case GetInfoType::HeapRegionSize:
- case GetInfoType::ASLRRegionBaseAddr:
- case GetInfoType::ASLRRegionSize:
- case GetInfoType::StackRegionBaseAddr:
- case GetInfoType::StackRegionSize:
- case GetInfoType::TotalPhysicalMemoryAvailable:
- case GetInfoType::TotalPhysicalMemoryUsed:
- case GetInfoType::SystemResourceSize:
- case GetInfoType::SystemResourceUsage:
- case GetInfoType::TitleId:
- case GetInfoType::UserExceptionContextAddr:
- case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource:
- case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource: {
+ case InfoType::CoreMask:
+ case InfoType::PriorityMask:
+ case InfoType::AliasRegionAddress:
+ case InfoType::AliasRegionSize:
+ case InfoType::HeapRegionAddress:
+ case InfoType::HeapRegionSize:
+ case InfoType::AslrRegionAddress:
+ case InfoType::AslrRegionSize:
+ case InfoType::StackRegionAddress:
+ case InfoType::StackRegionSize:
+ case InfoType::TotalMemorySize:
+ case InfoType::UsedMemorySize:
+ case InfoType::SystemResourceSizeTotal:
+ case InfoType::SystemResourceSizeUsed:
+ case InfoType::ProgramId:
+ case InfoType::UserExceptionContextAddress:
+ case InfoType::TotalNonSystemMemorySize:
+ case InfoType::UsedNonSystemMemorySize:
+ case InfoType::IsApplication:
+ case InfoType::FreeThreadCount: {
if (info_sub_id != 0) {
LOG_ERROR(Kernel_SVC, "Info sub id is non zero! info_id={}, info_sub_id={}", info_id,
info_sub_id);
@@ -856,79 +822,83 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
}
switch (info_id_type) {
- case GetInfoType::AllowedCPUCoreMask:
+ case InfoType::CoreMask:
*result = process->GetCoreMask();
return ResultSuccess;
- case GetInfoType::AllowedThreadPriorityMask:
+ case InfoType::PriorityMask:
*result = process->GetPriorityMask();
return ResultSuccess;
- case GetInfoType::MapRegionBaseAddr:
+ case InfoType::AliasRegionAddress:
*result = process->PageTable().GetAliasRegionStart();
return ResultSuccess;
- case GetInfoType::MapRegionSize:
+ case InfoType::AliasRegionSize:
*result = process->PageTable().GetAliasRegionSize();
return ResultSuccess;
- case GetInfoType::HeapRegionBaseAddr:
+ case InfoType::HeapRegionAddress:
*result = process->PageTable().GetHeapRegionStart();
return ResultSuccess;
- case GetInfoType::HeapRegionSize:
+ case InfoType::HeapRegionSize:
*result = process->PageTable().GetHeapRegionSize();
return ResultSuccess;
- case GetInfoType::ASLRRegionBaseAddr:
+ case InfoType::AslrRegionAddress:
*result = process->PageTable().GetAliasCodeRegionStart();
return ResultSuccess;
- case GetInfoType::ASLRRegionSize:
+ case InfoType::AslrRegionSize:
*result = process->PageTable().GetAliasCodeRegionSize();
return ResultSuccess;
- case GetInfoType::StackRegionBaseAddr:
+ case InfoType::StackRegionAddress:
*result = process->PageTable().GetStackRegionStart();
return ResultSuccess;
- case GetInfoType::StackRegionSize:
+ case InfoType::StackRegionSize:
*result = process->PageTable().GetStackRegionSize();
return ResultSuccess;
- case GetInfoType::TotalPhysicalMemoryAvailable:
+ case InfoType::TotalMemorySize:
*result = process->GetTotalPhysicalMemoryAvailable();
return ResultSuccess;
- case GetInfoType::TotalPhysicalMemoryUsed:
+ case InfoType::UsedMemorySize:
*result = process->GetTotalPhysicalMemoryUsed();
return ResultSuccess;
- case GetInfoType::SystemResourceSize:
+ case InfoType::SystemResourceSizeTotal:
*result = process->GetSystemResourceSize();
return ResultSuccess;
- case GetInfoType::SystemResourceUsage:
+ case InfoType::SystemResourceSizeUsed:
LOG_WARNING(Kernel_SVC, "(STUBBED) Attempted to query system resource usage");
*result = process->GetSystemResourceUsage();
return ResultSuccess;
- case GetInfoType::TitleId:
+ case InfoType::ProgramId:
*result = process->GetProgramID();
return ResultSuccess;
- case GetInfoType::UserExceptionContextAddr:
+ case InfoType::UserExceptionContextAddress:
*result = process->GetProcessLocalRegionAddress();
return ResultSuccess;
- case GetInfoType::TotalPhysicalMemoryAvailableWithoutSystemResource:
+ case InfoType::TotalNonSystemMemorySize:
*result = process->GetTotalPhysicalMemoryAvailableWithoutSystemResource();
return ResultSuccess;
- case GetInfoType::TotalPhysicalMemoryUsedWithoutSystemResource:
+ case InfoType::UsedNonSystemMemorySize:
*result = process->GetTotalPhysicalMemoryUsedWithoutSystemResource();
return ResultSuccess;
+ case InfoType::FreeThreadCount:
+ *result = process->GetFreeThreadCount();
+ return ResultSuccess;
+
default:
break;
}
@@ -937,11 +907,11 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
return ResultInvalidEnumValue;
}
- case GetInfoType::IsCurrentProcessBeingDebugged:
+ case InfoType::DebuggerAttached:
*result = 0;
return ResultSuccess;
- case GetInfoType::RegisterResourceLimit: {
+ case InfoType::ResourceLimit: {
if (handle != 0) {
LOG_ERROR(Kernel, "Handle is non zero! handle={:08X}", handle);
return ResultInvalidHandle;
@@ -969,7 +939,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
return ResultSuccess;
}
- case GetInfoType::RandomEntropy:
+ case InfoType::RandomEntropy:
if (handle != 0) {
LOG_ERROR(Kernel_SVC, "Process Handle is non zero, expected 0 result but got {:016X}",
handle);
@@ -985,13 +955,13 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
*result = system.Kernel().CurrentProcess()->GetRandomEntropy(info_sub_id);
return ResultSuccess;
- case GetInfoType::PrivilegedProcessId:
+ case InfoType::InitialProcessIdRange:
LOG_WARNING(Kernel_SVC,
"(STUBBED) Attempted to query privileged process id bounds, returned 0");
*result = 0;
return ResultSuccess;
- case GetInfoType::ThreadTickCount: {
+ case InfoType::ThreadTickCount: {
constexpr u64 num_cpus = 4;
if (info_sub_id != 0xFFFFFFFFFFFFFFFF && info_sub_id >= num_cpus) {
LOG_ERROR(Kernel_SVC, "Core count is out of range, expected {} but got {}", num_cpus,
@@ -1026,7 +996,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
*result = out_ticks;
return ResultSuccess;
}
- case GetInfoType::IdleTickCount: {
+ case InfoType::IdleTickCount: {
// Verify the input handle is invalid.
R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
@@ -1040,7 +1010,7 @@ static Result GetInfo(Core::System& system, u64* result, u64 info_id, Handle han
*result = system.Kernel().CurrentScheduler()->GetIdleThread()->GetCpuTime();
return ResultSuccess;
}
- case GetInfoType::MesosphereCurrentProcess: {
+ case InfoType::MesosphereCurrentProcess: {
// Verify the input handle is invalid.
R_UNLESS(handle == InvalidHandle, ResultInvalidHandle);
@@ -1515,7 +1485,7 @@ static Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle p
ResultInvalidMemoryRegion);
// Create a new page group.
- KPageGroup pg;
+ KPageGroup pg{system.Kernel(), dst_pt.GetBlockInfoManager()};
R_TRY(src_pt.MakeAndOpenPageGroup(
std::addressof(pg), src_address, size / PageSize, KMemoryState::FlagCanMapProcess,
KMemoryState::FlagCanMapProcess, KMemoryPermission::None, KMemoryPermission::None,
diff --git a/src/core/hle/kernel/time_manager.cpp b/src/core/hle/kernel/time_manager.cpp
deleted file mode 100644
index 5ee72c432..000000000
--- a/src/core/hle/kernel/time_manager.cpp
+++ /dev/null
@@ -1,44 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/assert.h"
-#include "core/core.h"
-#include "core/core_timing.h"
-#include "core/hle/kernel/k_scheduler.h"
-#include "core/hle/kernel/k_thread.h"
-#include "core/hle/kernel/time_manager.h"
-
-namespace Kernel {
-
-TimeManager::TimeManager(Core::System& system_) : system{system_} {
- time_manager_event_type = Core::Timing::CreateEvent(
- "Kernel::TimeManagerCallback",
- [this](std::uintptr_t thread_handle, s64 time,
- std::chrono::nanoseconds) -> std::optional<std::chrono::nanoseconds> {
- KThread* thread = reinterpret_cast<KThread*>(thread_handle);
- {
- KScopedSchedulerLock sl(system.Kernel());
- thread->OnTimer();
- }
- return std::nullopt;
- });
-}
-
-void TimeManager::ScheduleTimeEvent(KThread* thread, s64 nanoseconds) {
- std::scoped_lock lock{mutex};
- if (nanoseconds > 0) {
- ASSERT(thread);
- ASSERT(thread->GetState() != ThreadState::Runnable);
- system.CoreTiming().ScheduleEvent(std::chrono::nanoseconds{nanoseconds},
- time_manager_event_type,
- reinterpret_cast<uintptr_t>(thread));
- }
-}
-
-void TimeManager::UnscheduleTimeEvent(KThread* thread) {
- std::scoped_lock lock{mutex};
- system.CoreTiming().UnscheduleEvent(time_manager_event_type,
- reinterpret_cast<uintptr_t>(thread));
-}
-
-} // namespace Kernel
diff --git a/src/core/hle/kernel/time_manager.h b/src/core/hle/kernel/time_manager.h
deleted file mode 100644
index 94d16b3b4..000000000
--- a/src/core/hle/kernel/time_manager.h
+++ /dev/null
@@ -1,41 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <memory>
-#include <mutex>
-
-namespace Core {
-class System;
-} // namespace Core
-
-namespace Core::Timing {
-struct EventType;
-} // namespace Core::Timing
-
-namespace Kernel {
-
-class KThread;
-
-/**
- * The `TimeManager` takes care of scheduling time events on threads and executes their TimeUp
- * method when the event is triggered.
- */
-class TimeManager {
-public:
- explicit TimeManager(Core::System& system);
-
- /// Schedule a time event on `timetask` thread that will expire in 'nanoseconds'
- void ScheduleTimeEvent(KThread* time_task, s64 nanoseconds);
-
- /// Unschedule an existing time event
- void UnscheduleTimeEvent(KThread* thread);
-
-private:
- Core::System& system;
- std::shared_ptr<Core::Timing::EventType> time_manager_event_type;
- std::mutex mutex;
-};
-
-} // namespace Kernel
diff --git a/src/core/hle/service/audio/audin_u.cpp b/src/core/hle/service/audio/audin_u.cpp
index 26dec7147..053e8f9dd 100644
--- a/src/core/hle/service/audio/audin_u.cpp
+++ b/src/core/hle/service/audio/audin_u.cpp
@@ -203,8 +203,9 @@ private:
};
AudInU::AudInU(Core::System& system_)
- : ServiceFramework{system_, "audin:u"}, service_context{system_, "AudInU"},
- impl{std::make_unique<AudioCore::AudioIn::Manager>(system_)} {
+ : ServiceFramework{system_, "audin:u", ServiceThreadType::CreateNew},
+ service_context{system_, "AudInU"}, impl{std::make_unique<AudioCore::AudioIn::Manager>(
+ system_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudInU::ListAudioIns, "ListAudioIns"},
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp
index 991e30ba1..29751f075 100644
--- a/src/core/hle/service/audio/audout_u.cpp
+++ b/src/core/hle/service/audio/audout_u.cpp
@@ -26,8 +26,9 @@ public:
explicit IAudioOut(Core::System& system_, AudioCore::AudioOut::Manager& manager,
size_t session_id, const std::string& device_name,
const AudioOutParameter& in_params, u32 handle, u64 applet_resource_user_id)
- : ServiceFramework{system_, "IAudioOut"}, service_context{system_, "IAudioOut"},
- event{service_context.CreateEvent("AudioOutEvent")},
+ : ServiceFramework{system_, "IAudioOut", ServiceThreadType::CreateNew},
+ service_context{system_, "IAudioOut"}, event{service_context.CreateEvent(
+ "AudioOutEvent")},
impl{std::make_shared<AudioCore::AudioOut::Out>(system_, manager, event, session_id)} {
// clang-format off
@@ -220,8 +221,9 @@ private:
};
AudOutU::AudOutU(Core::System& system_)
- : ServiceFramework{system_, "audout:u"}, service_context{system_, "AudOutU"},
- impl{std::make_unique<AudioCore::AudioOut::Manager>(system_)} {
+ : ServiceFramework{system_, "audout:u", ServiceThreadType::CreateNew},
+ service_context{system_, "AudOutU"}, impl{std::make_unique<AudioCore::AudioOut::Manager>(
+ system_)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &AudOutU::ListAudioOuts, "ListAudioOuts"},
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index ead16c321..3a1c231b6 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -35,9 +35,10 @@ public:
AudioCore::AudioRendererParameterInternal& params,
Kernel::KTransferMemory* transfer_memory, u64 transfer_memory_size,
u32 process_handle, u64 applet_resource_user_id, s32 session_id)
- : ServiceFramework{system_, "IAudioRenderer"}, service_context{system_, "IAudioRenderer"},
- rendered_event{service_context.CreateEvent("IAudioRendererEvent")}, manager{manager_},
- impl{std::make_unique<Renderer>(system_, manager, rendered_event)} {
+ : ServiceFramework{system_, "IAudioRenderer", ServiceThreadType::CreateNew},
+ service_context{system_, "IAudioRenderer"}, rendered_event{service_context.CreateEvent(
+ "IAudioRendererEvent")},
+ manager{manager_}, impl{std::make_unique<Renderer>(system_, manager, rendered_event)} {
// clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioRenderer::GetSampleRate, "GetSampleRate"},
@@ -242,8 +243,10 @@ class IAudioDevice final : public ServiceFramework<IAudioDevice> {
public:
explicit IAudioDevice(Core::System& system_, u64 applet_resource_user_id, u32 revision,
u32 device_num)
- : ServiceFramework{system_, "IAudioDevice"}, service_context{system_, "IAudioDevice"},
- impl{std::make_unique<AudioDevice>(system_, applet_resource_user_id, revision)},
+ : ServiceFramework{system_, "IAudioDevice", ServiceThreadType::CreateNew},
+ service_context{system_, "IAudioDevice"}, impl{std::make_unique<AudioDevice>(
+ system_, applet_resource_user_id,
+ revision)},
event{service_context.CreateEvent(fmt::format("IAudioDeviceEvent-{}", device_num))} {
static const FunctionInfo functions[] = {
{0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
@@ -418,7 +421,7 @@ private:
};
AudRenU::AudRenU(Core::System& system_)
- : ServiceFramework{system_, "audren:u"},
+ : ServiceFramework{system_, "audren:u", ServiceThreadType::CreateNew},
service_context{system_, "audren:u"}, impl{std::make_unique<Manager>(system_)} {
// clang-format off
static const FunctionInfo functions[] = {
diff --git a/src/core/hle/service/nfc/nfc_user.cpp b/src/core/hle/service/nfc/nfc_user.cpp
index 4615697e2..89aa6b3f5 100644
--- a/src/core/hle/service/nfc/nfc_user.cpp
+++ b/src/core/hle/service/nfc/nfc_user.cpp
@@ -97,7 +97,7 @@ void IUser::IsNfcEnabled(Kernel::HLERequestContext& ctx) {
}
void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
+ LOG_DEBUG(Service_NFC, "called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/nfp/nfp_user.cpp b/src/core/hle/service/nfp/nfp_user.cpp
index 49816b4c7..a4d3d1bc7 100644
--- a/src/core/hle/service/nfp/nfp_user.cpp
+++ b/src/core/hle/service/nfp/nfp_user.cpp
@@ -83,7 +83,7 @@ void IUser::Finalize(Kernel::HLERequestContext& ctx) {
}
void IUser::ListDevices(Kernel::HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
+ LOG_DEBUG(Service_NFP, "called");
if (state == State::NonInitialized) {
IPC::ResponseBuilder rb{ctx, 2};
diff --git a/src/core/hle/service/set/set.cpp b/src/core/hle/service/set/set.cpp
index 4f1a8d6b7..16c5eaf75 100644
--- a/src/core/hle/service/set/set.cpp
+++ b/src/core/hle/service/set/set.cpp
@@ -191,6 +191,13 @@ void SET::GetKeyCodeMap2(Kernel::HLERequestContext& ctx) {
GetKeyCodeMapImpl(ctx);
}
+void SET::GetDeviceNickName(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ ctx.WriteBuffer(Settings::values.device_name.GetValue());
+}
+
SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -205,7 +212,7 @@ SET::SET(Core::System& system_) : ServiceFramework{system_, "set"} {
{8, &SET::GetQuestFlag, "GetQuestFlag"},
{9, &SET::GetKeyCodeMap2, "GetKeyCodeMap2"},
{10, nullptr, "GetFirmwareVersionForDebug"},
- {11, nullptr, "GetDeviceNickName"},
+ {11, &SET::GetDeviceNickName, "GetDeviceNickName"},
};
// clang-format on
diff --git a/src/core/hle/service/set/set.h b/src/core/hle/service/set/set.h
index 60cad3e6f..375975711 100644
--- a/src/core/hle/service/set/set.h
+++ b/src/core/hle/service/set/set.h
@@ -50,6 +50,7 @@ private:
void GetRegionCode(Kernel::HLERequestContext& ctx);
void GetKeyCodeMap(Kernel::HLERequestContext& ctx);
void GetKeyCodeMap2(Kernel::HLERequestContext& ctx);
+ void GetDeviceNickName(Kernel::HLERequestContext& ctx);
};
} // namespace Service::Set
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index d7cea6aac..94c20edda 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -3,6 +3,7 @@
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/settings.h"
#include "core/file_sys/errors.h"
#include "core/file_sys/system_archive/system_version.h"
#include "core/hle/ipc_helpers.h"
@@ -176,6 +177,13 @@ void SET_SYS::GetSettingsItemValue(Kernel::HLERequestContext& ctx) {
rb.Push(response);
}
+void SET_SYS::GetDeviceNickName(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "called");
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+ ctx.WriteBuffer(::Settings::values.device_name.GetValue());
+}
+
SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -253,7 +261,7 @@ SET_SYS::SET_SYS(Core::System& system_) : ServiceFramework{system_, "set:sys"} {
{74, nullptr, "SetWirelessLanEnableFlag"},
{75, nullptr, "GetInitialLaunchSettings"},
{76, nullptr, "SetInitialLaunchSettings"},
- {77, nullptr, "GetDeviceNickName"},
+ {77, &SET_SYS::GetDeviceNickName, "GetDeviceNickName"},
{78, nullptr, "SetDeviceNickName"},
{79, nullptr, "GetProductModel"},
{80, nullptr, "GetLdnChannel"},
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index 258ef8c57..464ac3da1 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -29,6 +29,7 @@ private:
void GetFirmwareVersion2(Kernel::HLERequestContext& ctx);
void GetColorSetId(Kernel::HLERequestContext& ctx);
void SetColorSetId(Kernel::HLERequestContext& ctx);
+ void GetDeviceNickName(Kernel::HLERequestContext& ctx);
ColorSet color_set = ColorSet::BasicWhite;
};
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index ef070f32f..ed1eb5b2d 100644
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -49,6 +49,7 @@ struct SteadyClockContext {
static_assert(sizeof(SteadyClockContext) == 0x18, "SteadyClockContext is incorrect size");
static_assert(std::is_trivially_copyable_v<SteadyClockContext>,
"SteadyClockContext must be trivially copyable");
+using StandardSteadyClockTimePointType = SteadyClockContext;
struct SystemClockContext {
s64 offset;
diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp
index a3aa0e77f..ff53a7d6f 100644
--- a/src/core/hle/service/time/time_sharedmemory.cpp
+++ b/src/core/hle/service/time/time_sharedmemory.cpp
@@ -26,23 +26,24 @@ void SharedMemory::SetupStandardSteadyClock(const Common::UUID& clock_source_id,
const Clock::SteadyClockContext context{
static_cast<u64>(current_time_point.nanoseconds - ticks_time_span.nanoseconds),
clock_source_id};
- shared_memory_format.standard_steady_clock_timepoint.StoreData(
- system.Kernel().GetTimeSharedMem().GetPointer(), context);
+ StoreToLockFreeAtomicType(&GetFormat()->standard_steady_clock_timepoint, context);
}
void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) {
- shared_memory_format.standard_local_system_clock_context.StoreData(
- system.Kernel().GetTimeSharedMem().GetPointer(), context);
+ StoreToLockFreeAtomicType(&GetFormat()->standard_local_system_clock_context, context);
}
void SharedMemory::UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context) {
- shared_memory_format.standard_network_system_clock_context.StoreData(
- system.Kernel().GetTimeSharedMem().GetPointer(), context);
+ StoreToLockFreeAtomicType(&GetFormat()->standard_network_system_clock_context, context);
}
void SharedMemory::SetAutomaticCorrectionEnabled(bool is_enabled) {
- shared_memory_format.standard_user_system_clock_automatic_correction.StoreData(
- system.Kernel().GetTimeSharedMem().GetPointer(), is_enabled);
+ StoreToLockFreeAtomicType(
+ &GetFormat()->is_standard_user_system_clock_automatic_correction_enabled, is_enabled);
+}
+
+SharedMemory::Format* SharedMemory::GetFormat() {
+ return reinterpret_cast<SharedMemory::Format*>(system.Kernel().GetTimeSharedMem().GetPointer());
}
} // namespace Service::Time
diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h
index 561685acd..044a4d24e 100644
--- a/src/core/hle/service/time/time_sharedmemory.h
+++ b/src/core/hle/service/time/time_sharedmemory.h
@@ -10,45 +10,68 @@
namespace Service::Time {
+// Note: this type is not safe for concurrent writes.
+template <typename T>
+struct LockFreeAtomicType {
+ u32 counter_;
+ std::array<T, 2> value_;
+};
+
+template <typename T>
+static inline void StoreToLockFreeAtomicType(LockFreeAtomicType<T>* p, const T& value) {
+ // Get the current counter.
+ auto counter = p->counter_;
+
+ // Increment the counter.
+ ++counter;
+
+ // Store the updated value.
+ p->value_[counter % 2] = value;
+
+ // Fence memory.
+ std::atomic_thread_fence(std::memory_order_release);
+
+ // Set the updated counter.
+ p->counter_ = counter;
+}
+
+template <typename T>
+static inline T LoadFromLockFreeAtomicType(const LockFreeAtomicType<T>* p) {
+ while (true) {
+ // Get the counter.
+ auto counter = p->counter_;
+
+ // Get the value.
+ auto value = p->value_[counter % 2];
+
+ // Fence memory.
+ std::atomic_thread_fence(std::memory_order_acquire);
+
+ // Check that the counter matches.
+ if (counter == p->counter_) {
+ return value;
+ }
+ }
+}
+
class SharedMemory final {
public:
explicit SharedMemory(Core::System& system_);
~SharedMemory();
- // TODO(ogniK): We have to properly simulate memory barriers, how are we going to do this?
- template <typename T, std::size_t Offset>
- struct MemoryBarrier {
- static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
- u32_le read_attempt{};
- std::array<T, 2> data{};
-
- // These are not actually memory barriers at the moment as we don't have multicore and all
- // HLE is mutexed. This will need to properly be implemented when we start updating the time
- // points on threads. As of right now, we'll be updated both values synchronously and just
- // incrementing the read_attempt to indicate that we waited.
- void StoreData(u8* shared_memory, T data_to_store) {
- std::memcpy(this, shared_memory + Offset, sizeof(*this));
- read_attempt++;
- data[read_attempt & 1] = data_to_store;
- std::memcpy(shared_memory + Offset, this, sizeof(*this));
- }
-
- // For reading we're just going to read the last stored value. If there was no value stored
- // it will just end up reading an empty value as intended.
- T ReadData(u8* shared_memory) {
- std::memcpy(this, shared_memory + Offset, sizeof(*this));
- return data[(read_attempt - 1) & 1];
- }
- };
-
// Shared memory format
struct Format {
- MemoryBarrier<Clock::SteadyClockContext, 0x0> standard_steady_clock_timepoint;
- MemoryBarrier<Clock::SystemClockContext, 0x38> standard_local_system_clock_context;
- MemoryBarrier<Clock::SystemClockContext, 0x80> standard_network_system_clock_context;
- MemoryBarrier<bool, 0xc8> standard_user_system_clock_automatic_correction;
- u32_le format_version;
+ LockFreeAtomicType<Clock::StandardSteadyClockTimePointType> standard_steady_clock_timepoint;
+ LockFreeAtomicType<Clock::SystemClockContext> standard_local_system_clock_context;
+ LockFreeAtomicType<Clock::SystemClockContext> standard_network_system_clock_context;
+ LockFreeAtomicType<bool> is_standard_user_system_clock_automatic_correction_enabled;
+ u32 format_version;
};
+ static_assert(offsetof(Format, standard_steady_clock_timepoint) == 0x0);
+ static_assert(offsetof(Format, standard_local_system_clock_context) == 0x38);
+ static_assert(offsetof(Format, standard_network_system_clock_context) == 0x80);
+ static_assert(offsetof(Format, is_standard_user_system_clock_automatic_correction_enabled) ==
+ 0xc8);
static_assert(sizeof(Format) == 0xd8, "Format is an invalid size");
void SetupStandardSteadyClock(const Common::UUID& clock_source_id,
@@ -56,10 +79,10 @@ public:
void UpdateLocalSystemClockContext(const Clock::SystemClockContext& context);
void UpdateNetworkSystemClockContext(const Clock::SystemClockContext& context);
void SetAutomaticCorrectionEnabled(bool is_enabled);
+ Format* GetFormat();
private:
Core::System& system;
- Format shared_memory_format{};
};
} // namespace Service::Time
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 7932aaab0..f24c89b04 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -20,6 +20,8 @@ add_library(input_common STATIC
drivers/udp_client.h
drivers/virtual_amiibo.cpp
drivers/virtual_amiibo.h
+ drivers/virtual_gamepad.cpp
+ drivers/virtual_gamepad.h
helpers/stick_from_buttons.cpp
helpers/stick_from_buttons.h
helpers/touch_from_buttons.cpp
diff --git a/src/input_common/drivers/camera.cpp b/src/input_common/drivers/camera.cpp
index dceea67e0..fad9177dc 100644
--- a/src/input_common/drivers/camera.cpp
+++ b/src/input_common/drivers/camera.cpp
@@ -17,7 +17,7 @@ Camera::Camera(std::string input_engine_) : InputEngine(std::move(input_engine_)
PreSetController(identifier);
}
-void Camera::SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data) {
+void Camera::SetCameraData(std::size_t width, std::size_t height, std::span<const u32> data) {
const std::size_t desired_width = getImageWidth();
const std::size_t desired_height = getImageHeight();
status.data.resize(desired_width * desired_height);
diff --git a/src/input_common/drivers/camera.h b/src/input_common/drivers/camera.h
index b8a7c75e5..ead3e0fde 100644
--- a/src/input_common/drivers/camera.h
+++ b/src/input_common/drivers/camera.h
@@ -3,6 +3,8 @@
#pragma once
+#include <span>
+
#include "input_common/input_engine.h"
namespace InputCommon {
@@ -15,7 +17,7 @@ class Camera final : public InputEngine {
public:
explicit Camera(std::string input_engine_);
- void SetCameraData(std::size_t width, std::size_t height, std::vector<u32> data);
+ void SetCameraData(std::size_t width, std::size_t height, std::span<const u32> data);
std::size_t getImageWidth() const;
std::size_t getImageHeight() const;
@@ -23,6 +25,7 @@ public:
Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
Common::Input::CameraFormat camera_format) override;
+private:
Common::Input::CameraStatus status{};
};
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 8de86b61e..4818bb744 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -16,6 +16,8 @@ Common::UUID GetGUID(SDL_Joystick* joystick) {
const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
std::array<u8, 16> data{};
std::memcpy(data.data(), guid.data, sizeof(data));
+ // Clear controller name crc
+ std::memset(data.data() + 2, 0, sizeof(u16));
return Common::UUID{data};
}
} // Anonymous namespace
diff --git a/src/input_common/drivers/virtual_gamepad.cpp b/src/input_common/drivers/virtual_gamepad.cpp
new file mode 100644
index 000000000..7db945aa6
--- /dev/null
+++ b/src/input_common/drivers/virtual_gamepad.cpp
@@ -0,0 +1,78 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "input_common/drivers/virtual_gamepad.h"
+
+namespace InputCommon {
+constexpr std::size_t PlayerIndexCount = 10;
+
+VirtualGamepad::VirtualGamepad(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
+ for (std::size_t i = 0; i < PlayerIndexCount; i++) {
+ PreSetController(GetIdentifier(i));
+ }
+}
+
+void VirtualGamepad::SetButtonState(std::size_t player_index, int button_id, bool value) {
+ if (player_index > PlayerIndexCount) {
+ return;
+ }
+ const auto identifier = GetIdentifier(player_index);
+ SetButton(identifier, button_id, value);
+}
+
+void VirtualGamepad::SetButtonState(std::size_t player_index, VirtualButton button_id, bool value) {
+ SetButtonState(player_index, static_cast<int>(button_id), value);
+}
+
+void VirtualGamepad::SetStickPosition(std::size_t player_index, int axis_id, float x_value,
+ float y_value) {
+ if (player_index > PlayerIndexCount) {
+ return;
+ }
+ const auto identifier = GetIdentifier(player_index);
+ SetAxis(identifier, axis_id * 2, x_value);
+ SetAxis(identifier, (axis_id * 2) + 1, y_value);
+}
+
+void VirtualGamepad::SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value,
+ float y_value) {
+ SetStickPosition(player_index, static_cast<int>(axis_id), x_value, y_value);
+}
+
+void VirtualGamepad::ResetControllers() {
+ for (std::size_t i = 0; i < PlayerIndexCount; i++) {
+ SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f);
+ SetStickPosition(i, VirtualStick::Right, 0.0f, 0.0f);
+
+ SetButtonState(i, VirtualButton::ButtonA, false);
+ SetButtonState(i, VirtualButton::ButtonB, false);
+ SetButtonState(i, VirtualButton::ButtonX, false);
+ SetButtonState(i, VirtualButton::ButtonY, false);
+ SetButtonState(i, VirtualButton::StickL, false);
+ SetButtonState(i, VirtualButton::StickR, false);
+ SetButtonState(i, VirtualButton::TriggerL, false);
+ SetButtonState(i, VirtualButton::TriggerR, false);
+ SetButtonState(i, VirtualButton::TriggerZL, false);
+ SetButtonState(i, VirtualButton::TriggerZR, false);
+ SetButtonState(i, VirtualButton::ButtonPlus, false);
+ SetButtonState(i, VirtualButton::ButtonMinus, false);
+ SetButtonState(i, VirtualButton::ButtonLeft, false);
+ SetButtonState(i, VirtualButton::ButtonUp, false);
+ SetButtonState(i, VirtualButton::ButtonRight, false);
+ SetButtonState(i, VirtualButton::ButtonDown, false);
+ SetButtonState(i, VirtualButton::ButtonSL, false);
+ SetButtonState(i, VirtualButton::ButtonSR, false);
+ SetButtonState(i, VirtualButton::ButtonHome, false);
+ SetButtonState(i, VirtualButton::ButtonCapture, false);
+ }
+}
+
+PadIdentifier VirtualGamepad::GetIdentifier(std::size_t player_index) const {
+ return {
+ .guid = Common::UUID{},
+ .port = player_index,
+ .pad = 0,
+ };
+}
+
+} // namespace InputCommon
diff --git a/src/input_common/drivers/virtual_gamepad.h b/src/input_common/drivers/virtual_gamepad.h
new file mode 100644
index 000000000..3df91cc6f
--- /dev/null
+++ b/src/input_common/drivers/virtual_gamepad.h
@@ -0,0 +1,73 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "input_common/input_engine.h"
+
+namespace InputCommon {
+
+/**
+ * A virtual controller that is always assigned to the game input
+ */
+class VirtualGamepad final : public InputEngine {
+public:
+ enum class VirtualButton {
+ ButtonA,
+ ButtonB,
+ ButtonX,
+ ButtonY,
+ StickL,
+ StickR,
+ TriggerL,
+ TriggerR,
+ TriggerZL,
+ TriggerZR,
+ ButtonPlus,
+ ButtonMinus,
+ ButtonLeft,
+ ButtonUp,
+ ButtonRight,
+ ButtonDown,
+ ButtonSL,
+ ButtonSR,
+ ButtonHome,
+ ButtonCapture,
+ };
+
+ enum class VirtualStick {
+ Left = 0,
+ Right = 1,
+ };
+
+ explicit VirtualGamepad(std::string input_engine_);
+
+ /**
+ * Sets the status of all buttons bound with the key to pressed
+ * @param player_index the player number that will take this action
+ * @param button_id the id of the button
+ * @param value indicates if the button is pressed or not
+ */
+ void SetButtonState(std::size_t player_index, int button_id, bool value);
+ void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value);
+
+ /**
+ * Sets the status of all buttons bound with the key to released
+ * @param player_index the player number that will take this action
+ * @param axis_id the id of the axis to move
+ * @param x_value the position of the stick in the x axis
+ * @param y_value the position of the stick in the y axis
+ */
+ void SetStickPosition(std::size_t player_index, int axis_id, float x_value, float y_value);
+ void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value,
+ float y_value);
+
+ /// Restores all inputs into the neutral position
+ void ResetControllers();
+
+private:
+ /// Returns the correct identifier corresponding to the player index
+ PadIdentifier GetIdentifier(std::size_t player_index) const;
+};
+
+} // namespace InputCommon
diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp
index 0fa4b1ddb..edd5287c1 100644
--- a/src/input_common/input_mapping.cpp
+++ b/src/input_common/input_mapping.cpp
@@ -200,12 +200,6 @@ bool MappingFactory::IsDriverValid(const MappingData& data) const {
return false;
}
// The following drivers don't need to be mapped
- if (data.engine == "tas") {
- return false;
- }
- if (data.engine == "touch") {
- return false;
- }
if (data.engine == "touch_from_button") {
return false;
}
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 942a13535..86deb4c7c 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -12,6 +12,7 @@
#include "input_common/drivers/touch_screen.h"
#include "input_common/drivers/udp_client.h"
#include "input_common/drivers/virtual_amiibo.h"
+#include "input_common/drivers/virtual_gamepad.h"
#include "input_common/helpers/stick_from_buttons.h"
#include "input_common/helpers/touch_from_buttons.h"
#include "input_common/input_engine.h"
@@ -25,73 +26,33 @@
namespace InputCommon {
struct InputSubsystem::Impl {
- void Initialize() {
- mapping_factory = std::make_shared<MappingFactory>();
+ template <typename Engine>
+ void RegisterEngine(std::string name, std::shared_ptr<Engine>& engine) {
MappingCallback mapping_callback{[this](const MappingData& data) { RegisterInput(data); }};
- keyboard = std::make_shared<Keyboard>("keyboard");
- keyboard->SetMappingCallback(mapping_callback);
- keyboard_factory = std::make_shared<InputFactory>(keyboard);
- keyboard_output_factory = std::make_shared<OutputFactory>(keyboard);
- Common::Input::RegisterInputFactory(keyboard->GetEngineName(), keyboard_factory);
- Common::Input::RegisterOutputFactory(keyboard->GetEngineName(), keyboard_output_factory);
-
- mouse = std::make_shared<Mouse>("mouse");
- mouse->SetMappingCallback(mapping_callback);
- mouse_factory = std::make_shared<InputFactory>(mouse);
- mouse_output_factory = std::make_shared<OutputFactory>(mouse);
- Common::Input::RegisterInputFactory(mouse->GetEngineName(), mouse_factory);
- Common::Input::RegisterOutputFactory(mouse->GetEngineName(), mouse_output_factory);
-
- touch_screen = std::make_shared<TouchScreen>("touch");
- touch_screen_factory = std::make_shared<InputFactory>(touch_screen);
- Common::Input::RegisterInputFactory(touch_screen->GetEngineName(), touch_screen_factory);
-
- gcadapter = std::make_shared<GCAdapter>("gcpad");
- gcadapter->SetMappingCallback(mapping_callback);
- gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter);
- gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter);
- Common::Input::RegisterInputFactory(gcadapter->GetEngineName(), gcadapter_input_factory);
- Common::Input::RegisterOutputFactory(gcadapter->GetEngineName(), gcadapter_output_factory);
-
- udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp");
- udp_client->SetMappingCallback(mapping_callback);
- udp_client_input_factory = std::make_shared<InputFactory>(udp_client);
- udp_client_output_factory = std::make_shared<OutputFactory>(udp_client);
- Common::Input::RegisterInputFactory(udp_client->GetEngineName(), udp_client_input_factory);
- Common::Input::RegisterOutputFactory(udp_client->GetEngineName(),
- udp_client_output_factory);
-
- tas_input = std::make_shared<TasInput::Tas>("tas");
- tas_input->SetMappingCallback(mapping_callback);
- tas_input_factory = std::make_shared<InputFactory>(tas_input);
- tas_output_factory = std::make_shared<OutputFactory>(tas_input);
- Common::Input::RegisterInputFactory(tas_input->GetEngineName(), tas_input_factory);
- Common::Input::RegisterOutputFactory(tas_input->GetEngineName(), tas_output_factory);
-
- camera = std::make_shared<Camera>("camera");
- camera->SetMappingCallback(mapping_callback);
- camera_input_factory = std::make_shared<InputFactory>(camera);
- camera_output_factory = std::make_shared<OutputFactory>(camera);
- Common::Input::RegisterInputFactory(camera->GetEngineName(), camera_input_factory);
- Common::Input::RegisterOutputFactory(camera->GetEngineName(), camera_output_factory);
-
- virtual_amiibo = std::make_shared<VirtualAmiibo>("virtual_amiibo");
- virtual_amiibo->SetMappingCallback(mapping_callback);
- virtual_amiibo_input_factory = std::make_shared<InputFactory>(virtual_amiibo);
- virtual_amiibo_output_factory = std::make_shared<OutputFactory>(virtual_amiibo);
- Common::Input::RegisterInputFactory(virtual_amiibo->GetEngineName(),
- virtual_amiibo_input_factory);
- Common::Input::RegisterOutputFactory(virtual_amiibo->GetEngineName(),
- virtual_amiibo_output_factory);
+ engine = std::make_shared<Engine>(name);
+ engine->SetMappingCallback(mapping_callback);
+
+ std::shared_ptr<InputFactory> input_factory = std::make_shared<InputFactory>(engine);
+ std::shared_ptr<OutputFactory> output_factory = std::make_shared<OutputFactory>(engine);
+ Common::Input::RegisterInputFactory(engine->GetEngineName(), std::move(input_factory));
+ Common::Input::RegisterOutputFactory(engine->GetEngineName(), std::move(output_factory));
+ }
+
+ void Initialize() {
+ mapping_factory = std::make_shared<MappingFactory>();
+ RegisterEngine("keyboard", keyboard);
+ RegisterEngine("mouse", mouse);
+ RegisterEngine("touch", touch_screen);
+ RegisterEngine("gcpad", gcadapter);
+ RegisterEngine("cemuhookudp", udp_client);
+ RegisterEngine("tas", tas_input);
+ RegisterEngine("camera", camera);
+ RegisterEngine("virtual_amiibo", virtual_amiibo);
+ RegisterEngine("virtual_gamepad", virtual_gamepad);
#ifdef HAVE_SDL2
- sdl = std::make_shared<SDLDriver>("sdl");
- sdl->SetMappingCallback(mapping_callback);
- sdl_input_factory = std::make_shared<InputFactory>(sdl);
- sdl_output_factory = std::make_shared<OutputFactory>(sdl);
- Common::Input::RegisterInputFactory(sdl->GetEngineName(), sdl_input_factory);
- Common::Input::RegisterOutputFactory(sdl->GetEngineName(), sdl_output_factory);
+ RegisterEngine("sdl", sdl);
#endif
Common::Input::RegisterInputFactory("touch_from_button",
@@ -100,42 +61,25 @@ struct InputSubsystem::Impl {
std::make_shared<StickFromButton>());
}
- void Shutdown() {
- Common::Input::UnregisterInputFactory(keyboard->GetEngineName());
- Common::Input::UnregisterOutputFactory(keyboard->GetEngineName());
- keyboard.reset();
-
- Common::Input::UnregisterInputFactory(mouse->GetEngineName());
- Common::Input::UnregisterOutputFactory(mouse->GetEngineName());
- mouse.reset();
-
- Common::Input::UnregisterInputFactory(touch_screen->GetEngineName());
- touch_screen.reset();
-
- Common::Input::UnregisterInputFactory(gcadapter->GetEngineName());
- Common::Input::UnregisterOutputFactory(gcadapter->GetEngineName());
- gcadapter.reset();
-
- Common::Input::UnregisterInputFactory(udp_client->GetEngineName());
- Common::Input::UnregisterOutputFactory(udp_client->GetEngineName());
- udp_client.reset();
-
- Common::Input::UnregisterInputFactory(tas_input->GetEngineName());
- Common::Input::UnregisterOutputFactory(tas_input->GetEngineName());
- tas_input.reset();
-
- Common::Input::UnregisterInputFactory(camera->GetEngineName());
- Common::Input::UnregisterOutputFactory(camera->GetEngineName());
- camera.reset();
-
- Common::Input::UnregisterInputFactory(virtual_amiibo->GetEngineName());
- Common::Input::UnregisterOutputFactory(virtual_amiibo->GetEngineName());
- virtual_amiibo.reset();
+ template <typename Engine>
+ void UnregisterEngine(std::shared_ptr<Engine>& engine) {
+ Common::Input::UnregisterInputFactory(engine->GetEngineName());
+ Common::Input::UnregisterOutputFactory(engine->GetEngineName());
+ engine.reset();
+ }
+ void Shutdown() {
+ UnregisterEngine(keyboard);
+ UnregisterEngine(mouse);
+ UnregisterEngine(touch_screen);
+ UnregisterEngine(gcadapter);
+ UnregisterEngine(udp_client);
+ UnregisterEngine(tas_input);
+ UnregisterEngine(camera);
+ UnregisterEngine(virtual_amiibo);
+ UnregisterEngine(virtual_gamepad);
#ifdef HAVE_SDL2
- Common::Input::UnregisterInputFactory(sdl->GetEngineName());
- Common::Input::UnregisterOutputFactory(sdl->GetEngineName());
- sdl.reset();
+ UnregisterEngine(sdl);
#endif
Common::Input::UnregisterInputFactory("touch_from_button");
@@ -163,117 +107,86 @@ struct InputSubsystem::Impl {
return devices;
}
- [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
+ [[nodiscard]] std::shared_ptr<InputEngine> GetInputEngine(
const Common::ParamPackage& params) const {
if (!params.Has("engine") || params.Get("engine", "") == "any") {
- return {};
+ return nullptr;
}
const std::string engine = params.Get("engine", "");
+ if (engine == keyboard->GetEngineName()) {
+ return keyboard;
+ }
if (engine == mouse->GetEngineName()) {
- return mouse->GetAnalogMappingForDevice(params);
+ return mouse;
}
if (engine == gcadapter->GetEngineName()) {
- return gcadapter->GetAnalogMappingForDevice(params);
+ return gcadapter;
}
if (engine == udp_client->GetEngineName()) {
- return udp_client->GetAnalogMappingForDevice(params);
- }
- if (engine == tas_input->GetEngineName()) {
- return tas_input->GetAnalogMappingForDevice(params);
+ return udp_client;
}
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
- return sdl->GetAnalogMappingForDevice(params);
+ return sdl;
}
#endif
- return {};
+ return nullptr;
}
- [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
+ [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(
const Common::ParamPackage& params) const {
- if (!params.Has("engine") || params.Get("engine", "") == "any") {
+ const auto input_engine = GetInputEngine(params);
+
+ if (input_engine == nullptr) {
return {};
}
- const std::string engine = params.Get("engine", "");
- if (engine == gcadapter->GetEngineName()) {
- return gcadapter->GetButtonMappingForDevice(params);
- }
- if (engine == udp_client->GetEngineName()) {
- return udp_client->GetButtonMappingForDevice(params);
- }
- if (engine == tas_input->GetEngineName()) {
- return tas_input->GetButtonMappingForDevice(params);
- }
-#ifdef HAVE_SDL2
- if (engine == sdl->GetEngineName()) {
- return sdl->GetButtonMappingForDevice(params);
+
+ return input_engine->GetAnalogMappingForDevice(params);
+ }
+
+ [[nodiscard]] ButtonMapping GetButtonMappingForDevice(
+ const Common::ParamPackage& params) const {
+ const auto input_engine = GetInputEngine(params);
+
+ if (input_engine == nullptr) {
+ return {};
}
-#endif
- return {};
+
+ return input_engine->GetButtonMappingForDevice(params);
}
[[nodiscard]] MotionMapping GetMotionMappingForDevice(
const Common::ParamPackage& params) const {
- if (!params.Has("engine") || params.Get("engine", "") == "any") {
+ const auto input_engine = GetInputEngine(params);
+
+ if (input_engine == nullptr) {
return {};
}
- const std::string engine = params.Get("engine", "");
- if (engine == udp_client->GetEngineName()) {
- return udp_client->GetMotionMappingForDevice(params);
- }
-#ifdef HAVE_SDL2
- if (engine == sdl->GetEngineName()) {
- return sdl->GetMotionMappingForDevice(params);
- }
-#endif
- return {};
+
+ return input_engine->GetMotionMappingForDevice(params);
}
Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const {
if (!params.Has("engine") || params.Get("engine", "") == "any") {
return Common::Input::ButtonNames::Undefined;
}
- const std::string engine = params.Get("engine", "");
- if (engine == mouse->GetEngineName()) {
- return mouse->GetUIName(params);
- }
- if (engine == gcadapter->GetEngineName()) {
- return gcadapter->GetUIName(params);
- }
- if (engine == udp_client->GetEngineName()) {
- return udp_client->GetUIName(params);
- }
- if (engine == tas_input->GetEngineName()) {
- return tas_input->GetUIName(params);
- }
-#ifdef HAVE_SDL2
- if (engine == sdl->GetEngineName()) {
- return sdl->GetUIName(params);
+ const auto input_engine = GetInputEngine(params);
+
+ if (input_engine == nullptr) {
+ return Common::Input::ButtonNames::Invalid;
}
-#endif
- return Common::Input::ButtonNames::Invalid;
+
+ return input_engine->GetUIName(params);
}
bool IsStickInverted(const Common::ParamPackage& params) {
- const std::string engine = params.Get("engine", "");
- if (engine == mouse->GetEngineName()) {
- return mouse->IsStickInverted(params);
- }
- if (engine == gcadapter->GetEngineName()) {
- return gcadapter->IsStickInverted(params);
- }
- if (engine == udp_client->GetEngineName()) {
- return udp_client->IsStickInverted(params);
- }
- if (engine == tas_input->GetEngineName()) {
- return tas_input->IsStickInverted(params);
- }
-#ifdef HAVE_SDL2
- if (engine == sdl->GetEngineName()) {
- return sdl->IsStickInverted(params);
+ const auto input_engine = GetInputEngine(params);
+
+ if (input_engine == nullptr) {
+ return false;
}
-#endif
- return false;
+
+ return input_engine->IsStickInverted(params);
}
bool IsController(const Common::ParamPackage& params) {
@@ -290,6 +203,9 @@ struct InputSubsystem::Impl {
if (engine == tas_input->GetEngineName()) {
return true;
}
+ if (engine == virtual_gamepad->GetEngineName()) {
+ return true;
+ }
#ifdef HAVE_SDL2
if (engine == sdl->GetEngineName()) {
return true;
@@ -338,28 +254,10 @@ struct InputSubsystem::Impl {
std::shared_ptr<CemuhookUDP::UDPClient> udp_client;
std::shared_ptr<Camera> camera;
std::shared_ptr<VirtualAmiibo> virtual_amiibo;
-
- std::shared_ptr<InputFactory> keyboard_factory;
- std::shared_ptr<InputFactory> mouse_factory;
- std::shared_ptr<InputFactory> gcadapter_input_factory;
- std::shared_ptr<InputFactory> touch_screen_factory;
- std::shared_ptr<InputFactory> udp_client_input_factory;
- std::shared_ptr<InputFactory> tas_input_factory;
- std::shared_ptr<InputFactory> camera_input_factory;
- std::shared_ptr<InputFactory> virtual_amiibo_input_factory;
-
- std::shared_ptr<OutputFactory> keyboard_output_factory;
- std::shared_ptr<OutputFactory> mouse_output_factory;
- std::shared_ptr<OutputFactory> gcadapter_output_factory;
- std::shared_ptr<OutputFactory> udp_client_output_factory;
- std::shared_ptr<OutputFactory> tas_output_factory;
- std::shared_ptr<OutputFactory> camera_output_factory;
- std::shared_ptr<OutputFactory> virtual_amiibo_output_factory;
+ std::shared_ptr<VirtualGamepad> virtual_gamepad;
#ifdef HAVE_SDL2
std::shared_ptr<SDLDriver> sdl;
- std::shared_ptr<InputFactory> sdl_input_factory;
- std::shared_ptr<OutputFactory> sdl_output_factory;
#endif
};
@@ -423,6 +321,14 @@ const VirtualAmiibo* InputSubsystem::GetVirtualAmiibo() const {
return impl->virtual_amiibo.get();
}
+VirtualGamepad* InputSubsystem::GetVirtualGamepad() {
+ return impl->virtual_gamepad.get();
+}
+
+const VirtualGamepad* InputSubsystem::GetVirtualGamepad() const {
+ return impl->virtual_gamepad.get();
+}
+
std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const {
return impl->GetInputDevices();
}
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 6218c37f6..1207d786c 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -34,6 +34,7 @@ class Keyboard;
class Mouse;
class TouchScreen;
class VirtualAmiibo;
+class VirtualGamepad;
struct MappingData;
} // namespace InputCommon
@@ -108,6 +109,12 @@ public:
/// Retrieves the underlying virtual amiibo input device.
[[nodiscard]] const VirtualAmiibo* GetVirtualAmiibo() const;
+ /// Retrieves the underlying virtual gamepad input device.
+ [[nodiscard]] VirtualGamepad* GetVirtualGamepad();
+
+ /// Retrieves the underlying virtual gamepad input device.
+ [[nodiscard]] const VirtualGamepad* GetVirtualGamepad() const;
+
/**
* Returns all available input devices that this Factory can create a new device with.
* Each returned ParamPackage should have a `display` field used for display, a `engine` field
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
index 01f6ec9b5..73b67f0af 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp
@@ -461,7 +461,7 @@ void EmitSetSampleMask(EmitContext& ctx, Id value) {
}
void EmitSetFragDepth(EmitContext& ctx, Id value) {
- if (!ctx.runtime_info.convert_depth_mode) {
+ if (!ctx.runtime_info.convert_depth_mode || ctx.profile.support_native_ndc) {
ctx.OpStore(ctx.frag_depth, value);
return;
}
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp
index 00be1f127..9f7b6bb4b 100644
--- a/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp
+++ b/src/shader_recompiler/backend/spirv/emit_spirv_special.cpp
@@ -116,7 +116,8 @@ void EmitPrologue(EmitContext& ctx) {
}
void EmitEpilogue(EmitContext& ctx) {
- if (ctx.stage == Stage::VertexB && ctx.runtime_info.convert_depth_mode) {
+ if (ctx.stage == Stage::VertexB && ctx.runtime_info.convert_depth_mode &&
+ !ctx.profile.support_native_ndc) {
ConvertDepthMode(ctx);
}
if (ctx.stage == Stage::Fragment) {
@@ -125,7 +126,7 @@ void EmitEpilogue(EmitContext& ctx) {
}
void EmitEmitVertex(EmitContext& ctx, const IR::Value& stream) {
- if (ctx.runtime_info.convert_depth_mode) {
+ if (ctx.runtime_info.convert_depth_mode && !ctx.profile.support_native_ndc) {
ConvertDepthMode(ctx);
}
if (stream.IsImmediate()) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index 8e3e40cd5..41dc6d031 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -1345,8 +1345,10 @@ void EmitContext::DefineInputs(const IR::Program& program) {
if (info.uses_fswzadd || info.uses_subgroup_invocation_id || info.uses_subgroup_shuffles ||
(profile.warp_size_potentially_larger_than_guest &&
(info.uses_subgroup_vote || info.uses_subgroup_mask))) {
+ AddCapability(spv::Capability::GroupNonUniform);
subgroup_local_invocation_id =
DefineInput(*this, U32[1], false, spv::BuiltIn::SubgroupLocalInvocationId);
+ Decorate(subgroup_local_invocation_id, spv::Decoration::Flat);
}
if (info.uses_fswzadd) {
const Id f32_one{Const(1.0f)};
diff --git a/src/shader_recompiler/profile.h b/src/shader_recompiler/profile.h
index 21d3d236b..b8841a536 100644
--- a/src/shader_recompiler/profile.h
+++ b/src/shader_recompiler/profile.h
@@ -35,6 +35,7 @@ struct Profile {
bool support_int64_atomics{};
bool support_derivative_control{};
bool support_geometry_shader_passthrough{};
+ bool support_native_ndc{};
bool support_gl_nv_gpu_shader_5{};
bool support_gl_amd_gpu_shader_half_float{};
bool support_gl_texture_shadow_lod{};
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 348d1edf4..6a4022e45 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -8,6 +8,7 @@ add_executable(tests
common/host_memory.cpp
common/param_package.cpp
common/ring_buffer.cpp
+ common/scratch_buffer.cpp
common/unique_function.cpp
core/core_timing.cpp
core/internal_network/network.cpp
diff --git a/src/tests/common/scratch_buffer.cpp b/src/tests/common/scratch_buffer.cpp
new file mode 100644
index 000000000..f6e50da4a
--- /dev/null
+++ b/src/tests/common/scratch_buffer.cpp
@@ -0,0 +1,200 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <algorithm>
+#include <array>
+#include <cstring>
+#include <span>
+#include <catch2/catch.hpp>
+#include "common/common_types.h"
+#include "common/scratch_buffer.h"
+
+namespace Common {
+
+TEST_CASE("ScratchBuffer: Basic Test", "[common]") {
+ ScratchBuffer<u8> buf;
+
+ REQUIRE(buf.size() == 0U);
+ REQUIRE(buf.capacity() == 0U);
+
+ std::array<u8, 10> payload;
+ payload.fill(66);
+
+ buf.resize(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ std::memcpy(buf.data(), payload.data(), payload.size());
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+}
+
+TEST_CASE("ScratchBuffer: resize_destructive Grow", "[common]") {
+ std::array<u8, 10> payload;
+ payload.fill(66);
+
+ ScratchBuffer<u8> buf(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ // Increasing the size should reallocate the buffer
+ buf.resize_destructive(payload.size() * 2);
+ REQUIRE(buf.size() == payload.size() * 2);
+ REQUIRE(buf.capacity() == payload.size() * 2);
+
+ // Since the buffer is not value initialized, reading its data will be garbage
+}
+
+TEST_CASE("ScratchBuffer: resize_destructive Shrink", "[common]") {
+ std::array<u8, 10> payload;
+ payload.fill(66);
+
+ ScratchBuffer<u8> buf(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ std::memcpy(buf.data(), payload.data(), payload.size());
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+
+ // Decreasing the size should not cause a buffer reallocation
+ // This can be tested by ensuring the buffer capacity and data has not changed,
+ buf.resize_destructive(1U);
+ REQUIRE(buf.size() == 1U);
+ REQUIRE(buf.capacity() == payload.size());
+
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+}
+
+TEST_CASE("ScratchBuffer: resize Grow u8", "[common]") {
+ std::array<u8, 10> payload;
+ payload.fill(66);
+
+ ScratchBuffer<u8> buf(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ std::memcpy(buf.data(), payload.data(), payload.size());
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+
+ // Increasing the size should reallocate the buffer
+ buf.resize(payload.size() * 2);
+ REQUIRE(buf.size() == payload.size() * 2);
+ REQUIRE(buf.capacity() == payload.size() * 2);
+
+ // resize() keeps the previous data intact
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+}
+
+TEST_CASE("ScratchBuffer: resize Grow u64", "[common]") {
+ std::array<u64, 10> payload;
+ payload.fill(6666);
+
+ ScratchBuffer<u64> buf(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ std::memcpy(buf.data(), payload.data(), payload.size() * sizeof(u64));
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+
+ // Increasing the size should reallocate the buffer
+ buf.resize(payload.size() * 2);
+ REQUIRE(buf.size() == payload.size() * 2);
+ REQUIRE(buf.capacity() == payload.size() * 2);
+
+ // resize() keeps the previous data intact
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+}
+
+TEST_CASE("ScratchBuffer: resize Shrink", "[common]") {
+ std::array<u8, 10> payload;
+ payload.fill(66);
+
+ ScratchBuffer<u8> buf(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ std::memcpy(buf.data(), payload.data(), payload.size());
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+
+ // Decreasing the size should not cause a buffer reallocation
+ // This can be tested by ensuring the buffer capacity and data has not changed,
+ buf.resize(1U);
+ REQUIRE(buf.size() == 1U);
+ REQUIRE(buf.capacity() == payload.size());
+
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+}
+
+TEST_CASE("ScratchBuffer: Span Size", "[common]") {
+ std::array<u8, 10> payload;
+ payload.fill(66);
+
+ ScratchBuffer<u8> buf(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ std::memcpy(buf.data(), payload.data(), payload.size());
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+
+ buf.resize(3U);
+ REQUIRE(buf.size() == 3U);
+ REQUIRE(buf.capacity() == payload.size());
+
+ const auto buf_span = std::span<u8>(buf);
+ // The span size is the last requested size of the buffer, not its capacity
+ REQUIRE(buf_span.size() == buf.size());
+
+ for (size_t i = 0; i < buf_span.size(); ++i) {
+ REQUIRE(buf_span[i] == buf[i]);
+ REQUIRE(buf_span[i] == payload[i]);
+ }
+}
+
+TEST_CASE("ScratchBuffer: Span Writes", "[common]") {
+ std::array<u8, 10> payload;
+ payload.fill(66);
+
+ ScratchBuffer<u8> buf(payload.size());
+ REQUIRE(buf.size() == payload.size());
+ REQUIRE(buf.capacity() == payload.size());
+
+ std::memcpy(buf.data(), payload.data(), payload.size());
+ for (size_t i = 0; i < payload.size(); ++i) {
+ REQUIRE(buf[i] == payload[i]);
+ }
+
+ buf.resize(3U);
+ REQUIRE(buf.size() == 3U);
+ REQUIRE(buf.capacity() == payload.size());
+
+ const auto buf_span = std::span<u8>(buf);
+ REQUIRE(buf_span.size() == buf.size());
+
+ for (size_t i = 0; i < buf_span.size(); ++i) {
+ const auto new_value = static_cast<u8>(i + 1U);
+ // Writes to a span of the scratch buffer will propogate to the buffer itself
+ buf_span[i] = new_value;
+ REQUIRE(buf[i] == new_value);
+ }
+}
+
+} // namespace Common
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 158360830..f1c60d1f3 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -20,6 +20,7 @@
#include "common/lru_cache.h"
#include "common/microprofile.h"
#include "common/polyfill_ranges.h"
+#include "common/scratch_buffer.h"
#include "common/settings.h"
#include "core/memory.h"
#include "video_core/buffer_cache/buffer_base.h"
@@ -422,8 +423,7 @@ private:
IntervalSet common_ranges;
std::deque<IntervalSet> committed_ranges;
- size_t immediate_buffer_capacity = 0;
- std::unique_ptr<u8[]> immediate_buffer_alloc;
+ Common::ScratchBuffer<u8> immediate_buffer_alloc;
struct LRUItemParams {
using ObjectType = BufferId;
@@ -1927,11 +1927,8 @@ std::span<const u8> BufferCache<P>::ImmediateBufferWithData(VAddr cpu_addr, size
template <class P>
std::span<u8> BufferCache<P>::ImmediateBuffer(size_t wanted_capacity) {
- if (wanted_capacity > immediate_buffer_capacity) {
- immediate_buffer_capacity = wanted_capacity;
- immediate_buffer_alloc = std::make_unique<u8[]>(wanted_capacity);
- }
- return std::span<u8>(immediate_buffer_alloc.get(), wanted_capacity);
+ immediate_buffer_alloc.resize_destructive(wanted_capacity);
+ return std::span<u8>(immediate_buffer_alloc.data(), wanted_capacity);
}
template <class P>
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index 9835e3ac1..322de2606 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -56,7 +56,7 @@ bool DmaPusher::Step() {
if (command_list.prefetch_command_list.size()) {
// Prefetched command list from nvdrv, used for things like synchronization
- command_headers = std::move(command_list.prefetch_command_list);
+ ProcessCommands(command_list.prefetch_command_list);
dma_pushbuffer.pop();
} else {
const CommandListHeader command_list_header{
@@ -74,7 +74,7 @@ bool DmaPusher::Step() {
}
// Push buffer non-empty, read a word
- command_headers.resize(command_list_header.size);
+ command_headers.resize_destructive(command_list_header.size);
if (Settings::IsGPULevelHigh()) {
memory_manager.ReadBlock(dma_get, command_headers.data(),
command_list_header.size * sizeof(u32));
@@ -82,16 +82,21 @@ bool DmaPusher::Step() {
memory_manager.ReadBlockUnsafe(dma_get, command_headers.data(),
command_list_header.size * sizeof(u32));
}
+ ProcessCommands(command_headers);
}
- for (std::size_t index = 0; index < command_headers.size();) {
- const CommandHeader& command_header = command_headers[index];
+
+ return true;
+}
+
+void DmaPusher::ProcessCommands(std::span<const CommandHeader> commands) {
+ for (std::size_t index = 0; index < commands.size();) {
+ const CommandHeader& command_header = commands[index];
if (dma_state.method_count) {
// Data word of methods command
if (dma_state.non_incrementing) {
const u32 max_write = static_cast<u32>(
- std::min<std::size_t>(index + dma_state.method_count, command_headers.size()) -
- index);
+ std::min<std::size_t>(index + dma_state.method_count, commands.size()) - index);
CallMultiMethod(&command_header.argument, max_write);
dma_state.method_count -= max_write;
dma_state.is_last_call = true;
@@ -142,8 +147,6 @@ bool DmaPusher::Step() {
}
index++;
}
-
- return true;
}
void DmaPusher::SetState(const CommandHeader& command_header) {
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 938f0f11c..6f00de937 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -4,11 +4,13 @@
#pragma once
#include <array>
+#include <span>
#include <vector>
#include <queue>
#include "common/bit_field.h"
#include "common/common_types.h"
+#include "common/scratch_buffer.h"
#include "video_core/engines/engine_interface.h"
#include "video_core/engines/puller.h"
@@ -136,13 +138,15 @@ private:
static constexpr u32 non_puller_methods = 0x40;
static constexpr u32 max_subchannels = 8;
bool Step();
+ void ProcessCommands(std::span<const CommandHeader> commands);
void SetState(const CommandHeader& command_header);
void CallMethod(u32 argument) const;
void CallMultiMethod(const u32* base_start, u32 num_methods) const;
- std::vector<CommandHeader> command_headers; ///< Buffer for list of commands fetched at once
+ Common::ScratchBuffer<CommandHeader>
+ command_headers; ///< Buffer for list of commands fetched at once
std::queue<CommandList> dma_pushbuffer; ///< Queue of command lists to be processed
std::size_t dma_pushbuffer_subindex{}; ///< Index within a command list within the pushbuffer
@@ -159,7 +163,7 @@ private:
DmaState dma_state{};
bool dma_increment_once{};
- bool ib_enable{true}; ///< IB mode enabled
+ const bool ib_enable{true}; ///< IB mode enabled
std::array<Engines::EngineInterface*, max_subchannels> subchannels{};
diff --git a/src/video_core/engines/draw_manager.cpp b/src/video_core/engines/draw_manager.cpp
index b213c374f..3a78421f6 100644
--- a/src/video_core/engines/draw_manager.cpp
+++ b/src/video_core/engines/draw_manager.cpp
@@ -46,21 +46,26 @@ void DrawManager::ProcessMethodCall(u32 method, u32 argument) {
SetInlineIndexBuffer(regs.inline_index_4x8.index2);
SetInlineIndexBuffer(regs.inline_index_4x8.index3);
break;
- case MAXWELL3D_REG_INDEX(topology_override):
- use_topology_override = true;
+ case MAXWELL3D_REG_INDEX(vertex_array_instance_first):
+ case MAXWELL3D_REG_INDEX(vertex_array_instance_subsequent): {
+ LOG_WARNING(HW_GPU, "(STUBBED) called");
break;
+ }
default:
break;
}
}
void DrawManager::Clear(u32 layer_count) {
- maxwell3d->rasterizer->Clear(layer_count);
+ if (maxwell3d->ShouldExecute()) {
+ maxwell3d->rasterizer->Clear(layer_count);
+ }
}
void DrawManager::DrawDeferred() {
- if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0)
+ if (draw_state.draw_mode != DrawMode::Instance || draw_state.instance_count == 0) {
return;
+ }
DrawEnd(draw_state.instance_count + 1, true);
draw_state.instance_count = 0;
}
@@ -115,8 +120,9 @@ void DrawManager::DrawEnd(u32 instance_count, bool force_draw) {
const auto& regs{maxwell3d->regs};
switch (draw_state.draw_mode) {
case DrawMode::Instance:
- if (!force_draw)
+ if (!force_draw) {
break;
+ }
[[fallthrough]];
case DrawMode::General:
draw_state.base_instance = regs.global_base_instance_index;
@@ -156,25 +162,28 @@ void DrawManager::DrawIndexSmall(u32 argument) {
ProcessDraw(true, 1);
}
-void DrawManager::ProcessTopologyOverride() {
- if (!use_topology_override)
- return;
-
+void DrawManager::UpdateTopology() {
const auto& regs{maxwell3d->regs};
- switch (regs.topology_override) {
- case PrimitiveTopologyOverride::None:
- break;
- case PrimitiveTopologyOverride::Points:
- draw_state.topology = PrimitiveTopology::Points;
- break;
- case PrimitiveTopologyOverride::Lines:
- draw_state.topology = PrimitiveTopology::Lines;
- break;
- case PrimitiveTopologyOverride::LineStrip:
- draw_state.topology = PrimitiveTopology::LineStrip;
+ switch (regs.primitive_topology_control) {
+ case PrimitiveTopologyControl::UseInBeginMethods:
break;
- default:
- draw_state.topology = static_cast<PrimitiveTopology>(regs.topology_override);
+ case PrimitiveTopologyControl::UseSeparateState:
+ switch (regs.topology_override) {
+ case PrimitiveTopologyOverride::None:
+ break;
+ case PrimitiveTopologyOverride::Points:
+ draw_state.topology = PrimitiveTopology::Points;
+ break;
+ case PrimitiveTopologyOverride::Lines:
+ draw_state.topology = PrimitiveTopology::Lines;
+ break;
+ case PrimitiveTopologyOverride::LineStrip:
+ draw_state.topology = PrimitiveTopology::LineStrip;
+ break;
+ default:
+ draw_state.topology = static_cast<PrimitiveTopology>(regs.topology_override);
+ break;
+ }
break;
}
}
@@ -183,9 +192,10 @@ void DrawManager::ProcessDraw(bool draw_indexed, u32 instance_count) {
LOG_TRACE(HW_GPU, "called, topology={}, count={}", draw_state.topology,
draw_indexed ? draw_state.index_buffer.count : draw_state.vertex_buffer.count);
- ProcessTopologyOverride();
+ UpdateTopology();
- if (maxwell3d->ShouldExecute())
+ if (maxwell3d->ShouldExecute()) {
maxwell3d->rasterizer->Draw(draw_indexed, instance_count);
+ }
}
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/draw_manager.h b/src/video_core/engines/draw_manager.h
index 4f67027ca..0e6930a9c 100644
--- a/src/video_core/engines/draw_manager.h
+++ b/src/video_core/engines/draw_manager.h
@@ -10,6 +10,7 @@ class RasterizerInterface;
}
namespace Tegra::Engines {
+using PrimitiveTopologyControl = Maxwell3D::Regs::PrimitiveTopologyControl;
using PrimitiveTopology = Maxwell3D::Regs::PrimitiveTopology;
using PrimitiveTopologyOverride = Maxwell3D::Regs::PrimitiveTopologyOverride;
using IndexBuffer = Maxwell3D::Regs::IndexBuffer;
@@ -58,12 +59,11 @@ private:
void DrawIndexSmall(u32 argument);
- void ProcessTopologyOverride();
+ void UpdateTopology();
void ProcessDraw(bool draw_indexed, u32 instance_count);
Maxwell3D* maxwell3d{};
State draw_state{};
- bool use_topology_override{};
};
} // namespace Tegra::Engines
diff --git a/src/video_core/engines/engine_upload.cpp b/src/video_core/engines/engine_upload.cpp
index e4f8331ab..cea1dd8b0 100644
--- a/src/video_core/engines/engine_upload.cpp
+++ b/src/video_core/engines/engine_upload.cpp
@@ -24,7 +24,7 @@ void State::BindRasterizer(VideoCore::RasterizerInterface* rasterizer_) {
void State::ProcessExec(const bool is_linear_) {
write_offset = 0;
copy_size = regs.line_length_in * regs.line_count;
- inner_buffer.resize(copy_size);
+ inner_buffer.resize_destructive(copy_size);
is_linear = is_linear_;
}
@@ -70,7 +70,7 @@ void State::ProcessData(std::span<const u8> read_buffer) {
const std::size_t dst_size = Tegra::Texture::CalculateSize(
true, bytes_per_pixel, width, regs.dest.height, regs.dest.depth,
regs.dest.BlockHeight(), regs.dest.BlockDepth());
- tmp_buffer.resize(dst_size);
+ tmp_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(address, tmp_buffer.data(), dst_size);
Tegra::Texture::SwizzleSubrect(tmp_buffer, read_buffer, bytes_per_pixel, width,
regs.dest.height, regs.dest.depth, x_offset, regs.dest.y,
diff --git a/src/video_core/engines/engine_upload.h b/src/video_core/engines/engine_upload.h
index 94fafd9dc..7242d2529 100644
--- a/src/video_core/engines/engine_upload.h
+++ b/src/video_core/engines/engine_upload.h
@@ -4,9 +4,10 @@
#pragma once
#include <span>
-#include <vector>
+
#include "common/bit_field.h"
#include "common/common_types.h"
+#include "common/scratch_buffer.h"
namespace Tegra {
class MemoryManager;
@@ -73,8 +74,8 @@ private:
u32 write_offset = 0;
u32 copy_size = 0;
- std::vector<u8> inner_buffer;
- std::vector<u8> tmp_buffer;
+ Common::ScratchBuffer<u8> inner_buffer;
+ Common::ScratchBuffer<u8> tmp_buffer;
bool is_linear = false;
Registers& regs;
MemoryManager& memory_manager;
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index a189e60ae..f73d7bf0f 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -184,12 +184,8 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
const size_t src_size =
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
- if (read_buffer.size() < src_size) {
- read_buffer.resize(src_size);
- }
- if (write_buffer.size() < dst_size) {
- write_buffer.resize(dst_size);
- }
+ read_buffer.resize_destructive(src_size);
+ write_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
@@ -235,12 +231,8 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
CalculateSize(true, bytes_per_pixel, width, height, depth, block_height, block_depth);
const size_t src_size = static_cast<size_t>(regs.pitch_in) * regs.line_count;
- if (read_buffer.size() < src_size) {
- read_buffer.resize(src_size);
- }
- if (write_buffer.size() < dst_size) {
- write_buffer.resize(dst_size);
- }
+ read_buffer.resize_destructive(src_size);
+ write_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
if (Settings::IsGPULevelExtreme()) {
@@ -269,12 +261,8 @@ void MaxwellDMA::FastCopyBlockLinearToPitch() {
pos_x = pos_x % x_in_gob;
pos_y = pos_y % 8;
- if (read_buffer.size() < src_size) {
- read_buffer.resize(src_size);
- }
- if (write_buffer.size() < dst_size) {
- write_buffer.resize(dst_size);
- }
+ read_buffer.resize_destructive(src_size);
+ write_buffer.resize_destructive(dst_size);
if (Settings::IsGPULevelExtreme()) {
memory_manager.ReadBlock(regs.offset_in + offset, read_buffer.data(), src_size);
@@ -333,14 +321,10 @@ void MaxwellDMA::CopyBlockLinearToBlockLinear() {
const u32 pitch = x_elements * bytes_per_pixel;
const size_t mid_buffer_size = pitch * regs.line_count;
- if (read_buffer.size() < src_size) {
- read_buffer.resize(src_size);
- }
- if (write_buffer.size() < dst_size) {
- write_buffer.resize(dst_size);
- }
+ read_buffer.resize_destructive(src_size);
+ write_buffer.resize_destructive(dst_size);
- intermediate_buffer.resize(mid_buffer_size);
+ intermediate_buffer.resize_destructive(mid_buffer_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
diff --git a/src/video_core/engines/maxwell_dma.h b/src/video_core/engines/maxwell_dma.h
index d40d3d302..c88191a61 100644
--- a/src/video_core/engines/maxwell_dma.h
+++ b/src/video_core/engines/maxwell_dma.h
@@ -6,8 +6,10 @@
#include <array>
#include <cstddef>
#include <vector>
+
#include "common/bit_field.h"
#include "common/common_types.h"
+#include "common/scratch_buffer.h"
#include "video_core/engines/engine_interface.h"
namespace Core {
@@ -234,9 +236,9 @@ private:
MemoryManager& memory_manager;
VideoCore::RasterizerInterface* rasterizer = nullptr;
- std::vector<u8> read_buffer;
- std::vector<u8> write_buffer;
- std::vector<u8> intermediate_buffer;
+ Common::ScratchBuffer<u8> read_buffer;
+ Common::ScratchBuffer<u8> write_buffer;
+ Common::ScratchBuffer<u8> intermediate_buffer;
static constexpr std::size_t NUM_REGS = 0x800;
struct Regs {
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 28b38273e..c6d54be63 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -223,8 +223,6 @@ struct GPU::Impl {
/// core timing events.
void Start() {
gpu_thread.StartThread(*renderer, renderer->Context(), *scheduler);
- cpu_context = renderer->GetRenderWindow().CreateSharedContext();
- cpu_context->MakeCurrent();
}
void NotifyShutdown() {
@@ -235,6 +233,9 @@ struct GPU::Impl {
/// Obtain the CPU Context
void ObtainContext() {
+ if (!cpu_context) {
+ cpu_context = renderer->GetRenderWindow().CreateSharedContext();
+ }
cpu_context->MakeCurrent();
}
diff --git a/src/video_core/host1x/vic.cpp b/src/video_core/host1x/vic.cpp
index ac0b7d20e..36a04e4e0 100644
--- a/src/video_core/host1x/vic.cpp
+++ b/src/video_core/host1x/vic.cpp
@@ -155,7 +155,7 @@ void Vic::WriteRGBFrame(const AVFrame* frame, const VicConfig& config) {
// swizzle pitch linear to block linear
const u32 block_height = static_cast<u32>(config.block_linear_height_log2);
const auto size = Texture::CalculateSize(true, 4, width, height, 1, block_height, 0);
- luma_buffer.resize(size);
+ luma_buffer.resize_destructive(size);
std::span<const u8> frame_buff(converted_frame_buf_addr, 4 * width * height);
Texture::SwizzleSubrect(luma_buffer, frame_buff, 4, width, height, 1, 0, 0, width, height,
block_height, 0, width * 4);
@@ -181,8 +181,8 @@ void Vic::WriteYUVFrame(const AVFrame* frame, const VicConfig& config) {
const auto stride = static_cast<size_t>(frame->linesize[0]);
- luma_buffer.resize(aligned_width * surface_height);
- chroma_buffer.resize(aligned_width * surface_height / 2);
+ luma_buffer.resize_destructive(aligned_width * surface_height);
+ chroma_buffer.resize_destructive(aligned_width * surface_height / 2);
// Populate luma buffer
const u8* luma_src = frame->data[0];
diff --git a/src/video_core/host1x/vic.h b/src/video_core/host1x/vic.h
index 2b78786e8..3d9753047 100644
--- a/src/video_core/host1x/vic.h
+++ b/src/video_core/host1x/vic.h
@@ -4,8 +4,9 @@
#pragma once
#include <memory>
-#include <vector>
+
#include "common/common_types.h"
+#include "common/scratch_buffer.h"
struct SwsContext;
@@ -49,8 +50,8 @@ private:
/// size does not change during a stream
using AVMallocPtr = std::unique_ptr<u8, decltype(&av_free)>;
AVMallocPtr converted_frame_buffer;
- std::vector<u8> luma_buffer;
- std::vector<u8> chroma_buffer;
+ Common::ScratchBuffer<u8> luma_buffer;
+ Common::ScratchBuffer<u8> chroma_buffer;
GPUVAddr config_struct_address{};
GPUVAddr output_surface_luma_address{};
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index e2e3dac34..cee5c3247 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -112,7 +112,7 @@ bool IsASTCSupported() {
}
} // Anonymous namespace
-Device::Device() {
+Device::Device(Core::Frontend::EmuWindow& emu_window) {
if (!GLAD_GL_VERSION_4_6) {
LOG_ERROR(Render_OpenGL, "OpenGL 4.6 is not available");
throw std::runtime_error{"Insufficient version"};
@@ -126,9 +126,9 @@ Device::Device() {
const bool is_intel = vendor_name == "Intel";
#ifdef __unix__
- const bool is_linux = true;
+ constexpr bool is_linux = true;
#else
- const bool is_linux = false;
+ constexpr bool is_linux = false;
#endif
bool disable_fast_buffer_sub_data = false;
@@ -193,9 +193,11 @@ Device::Device() {
}
}
+ strict_context_required = emu_window.StrictContextRequired();
// Blocks AMD and Intel OpenGL drivers on Windows from using asynchronous shader compilation.
+ // Blocks EGL on Wayland from using asynchronous shader compilation.
use_asynchronous_shaders = Settings::values.use_asynchronous_shaders.GetValue() &&
- !(is_amd || (is_intel && !is_linux));
+ !(is_amd || (is_intel && !is_linux)) && !strict_context_required;
use_driver_cache = is_nvidia;
LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index 5ef51ebcf..2a72d84be 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -5,6 +5,7 @@
#include <cstddef>
#include "common/common_types.h"
+#include "core/frontend/emu_window.h"
#include "shader_recompiler/stage.h"
namespace Settings {
@@ -15,7 +16,7 @@ namespace OpenGL {
class Device {
public:
- explicit Device();
+ explicit Device(Core::Frontend::EmuWindow& emu_window);
[[nodiscard]] std::string GetVendorName() const;
@@ -173,6 +174,10 @@ public:
return can_report_memory;
}
+ bool StrictContextRequired() const {
+ return strict_context_required;
+ }
+
private:
static bool TestVariableAoffi();
static bool TestPreciseBug();
@@ -216,6 +221,7 @@ private:
bool has_cbuf_ftou_bug{};
bool has_bool_ref_bug{};
bool can_report_memory{};
+ bool strict_context_required{};
std::string vendor_name;
};
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 64ed6f628..a44b8c454 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -138,9 +138,6 @@ void RasterizerOpenGL::LoadDiskResources(u64 title_id, std::stop_token stop_load
void RasterizerOpenGL::Clear(u32 layer_count) {
MICROPROFILE_SCOPE(OpenGL_Clears);
- if (!maxwell3d->ShouldExecute()) {
- return;
- }
const auto& regs = maxwell3d->regs;
bool use_color{};
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index a59d0d24e..f8868a012 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -174,6 +174,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, program_manager{program_manager_},
state_tracker{state_tracker_}, shader_notify{shader_notify_},
use_asynchronous_shaders{device.UseAsynchronousShaders()},
+ strict_context_required{device.StrictContextRequired()},
profile{
.supported_spirv = 0x00010000,
@@ -203,6 +204,7 @@ ShaderCache::ShaderCache(RasterizerOpenGL& rasterizer_, Core::Frontend::EmuWindo
.support_int64_atomics = false,
.support_derivative_control = device.HasDerivativeControl(),
.support_geometry_shader_passthrough = device.HasGeometryShaderPassthrough(),
+ .support_native_ndc = true,
.support_gl_nv_gpu_shader_5 = device.HasNvGpuShader5(),
.support_gl_amd_gpu_shader_half_float = device.HasAmdShaderHalfFloat(),
.support_gl_texture_shadow_lod = device.HasTextureShadowLod(),
@@ -255,9 +257,14 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
}
shader_cache_filename = base_dir / "opengl.bin";
- if (!workers) {
+ if (!workers && !strict_context_required) {
workers = CreateWorkers();
}
+ std::optional<Context> strict_context;
+ if (strict_context_required) {
+ strict_context.emplace(emu_window);
+ }
+
struct {
std::mutex mutex;
size_t total{};
@@ -265,44 +272,49 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
bool has_loaded{};
} state;
+ const auto queue_work{[&](Common::UniqueFunction<void, Context*>&& work) {
+ if (strict_context_required) {
+ work(&strict_context.value());
+ } else {
+ workers->QueueWork(std::move(work));
+ }
+ }};
const auto load_compute{[&](std::ifstream& file, FileEnvironment env) {
ComputePipelineKey key;
file.read(reinterpret_cast<char*>(&key), sizeof(key));
- workers->QueueWork(
- [this, key, env = std::move(env), &state, &callback](Context* ctx) mutable {
- ctx->pools.ReleaseContents();
- auto pipeline{CreateComputePipeline(ctx->pools, key, env)};
- std::scoped_lock lock{state.mutex};
- if (pipeline) {
- compute_cache.emplace(key, std::move(pipeline));
- }
- ++state.built;
- if (state.has_loaded) {
- callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
- }
- });
+ queue_work([this, key, env = std::move(env), &state, &callback](Context* ctx) mutable {
+ ctx->pools.ReleaseContents();
+ auto pipeline{CreateComputePipeline(ctx->pools, key, env)};
+ std::scoped_lock lock{state.mutex};
+ if (pipeline) {
+ compute_cache.emplace(key, std::move(pipeline));
+ }
+ ++state.built;
+ if (state.has_loaded) {
+ callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
+ }
+ });
++state.total;
}};
const auto load_graphics{[&](std::ifstream& file, std::vector<FileEnvironment> envs) {
GraphicsPipelineKey key;
file.read(reinterpret_cast<char*>(&key), sizeof(key));
- workers->QueueWork(
- [this, key, envs = std::move(envs), &state, &callback](Context* ctx) mutable {
- boost::container::static_vector<Shader::Environment*, 5> env_ptrs;
- for (auto& env : envs) {
- env_ptrs.push_back(&env);
- }
- ctx->pools.ReleaseContents();
- auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false)};
- std::scoped_lock lock{state.mutex};
- if (pipeline) {
- graphics_cache.emplace(key, std::move(pipeline));
- }
- ++state.built;
- if (state.has_loaded) {
- callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
- }
- });
+ queue_work([this, key, envs = std::move(envs), &state, &callback](Context* ctx) mutable {
+ boost::container::static_vector<Shader::Environment*, 5> env_ptrs;
+ for (auto& env : envs) {
+ env_ptrs.push_back(&env);
+ }
+ ctx->pools.ReleaseContents();
+ auto pipeline{CreateGraphicsPipeline(ctx->pools, key, MakeSpan(env_ptrs), false)};
+ std::scoped_lock lock{state.mutex};
+ if (pipeline) {
+ graphics_cache.emplace(key, std::move(pipeline));
+ }
+ ++state.built;
+ if (state.has_loaded) {
+ callback(VideoCore::LoadCallbackStage::Build, state.built, state.total);
+ }
+ });
++state.total;
}};
LoadPipelines(stop_loading, shader_cache_filename, CACHE_VERSION, load_compute, load_graphics);
@@ -314,6 +326,9 @@ void ShaderCache::LoadDiskResources(u64 title_id, std::stop_token stop_loading,
state.has_loaded = true;
lock.unlock();
+ if (strict_context_required) {
+ return;
+ }
workers->WaitForRequests(stop_loading);
if (!use_asynchronous_shaders) {
workers.reset();
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 53ffea904..f82420592 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -69,6 +69,7 @@ private:
StateTracker& state_tracker;
VideoCore::ShaderNotify& shader_notify;
const bool use_asynchronous_shaders;
+ const bool strict_context_required;
GraphicsPipelineKey graphics_key{};
GraphicsPipeline* current_pipeline{};
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 5b5e178ad..bc75680f0 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -140,8 +140,8 @@ RendererOpenGL::RendererOpenGL(Core::TelemetrySession& telemetry_session_,
Core::Memory::Memory& cpu_memory_, Tegra::GPU& gpu_,
std::unique_ptr<Core::Frontend::GraphicsContext> context_)
: RendererBase{emu_window_, std::move(context_)}, telemetry_session{telemetry_session_},
- emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, state_tracker{},
- program_manager{device},
+ emu_window{emu_window_}, cpu_memory{cpu_memory_}, gpu{gpu_}, device{emu_window_},
+ state_tracker{}, program_manager{device},
rasterizer(emu_window, gpu, cpu_memory, device, screen_info, program_manager, state_tracker) {
if (Settings::values.renderer_debug && GLAD_GL_KHR_debug) {
glEnable(GL_DEBUG_OUTPUT);
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 18be54729..f502a7d09 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -139,23 +139,25 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
RenderScreenshot(*framebuffer, use_accelerated);
bool has_been_recreated = false;
- const auto recreate_swapchain = [&] {
+ const auto recreate_swapchain = [&](u32 width, u32 height) {
if (!has_been_recreated) {
has_been_recreated = true;
scheduler.Finish();
}
- const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
- swapchain.Create(layout.width, layout.height, is_srgb);
+ swapchain.Create(width, height, is_srgb);
};
- if (swapchain.NeedsRecreation(is_srgb)) {
- recreate_swapchain();
+
+ const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
+ if (swapchain.NeedsRecreation(is_srgb) || swapchain.GetWidth() != layout.width ||
+ swapchain.GetHeight() != layout.height) {
+ recreate_swapchain(layout.width, layout.height);
}
bool is_outdated;
do {
swapchain.AcquireNextImage();
is_outdated = swapchain.IsOutDated();
if (is_outdated) {
- recreate_swapchain();
+ recreate_swapchain(layout.width, layout.height);
}
} while (is_outdated);
if (has_been_recreated) {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 54a12b35f..6b54d7111 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -461,6 +461,9 @@ void BufferCacheRuntime::BindQuadIndexBuffer(PrimitiveTopology topology, u32 fir
void BufferCacheRuntime::BindVertexBuffer(u32 index, VkBuffer buffer, u32 offset, u32 size,
u32 stride) {
+ if (index >= device.GetMaxVertexInputBindings()) {
+ return;
+ }
if (device.IsExtExtendedDynamicStateSupported()) {
scheduler.Record([index, buffer, offset, size, stride](vk::CommandBuffer cmdbuf) {
const VkDeviceSize vk_offset = buffer != VK_NULL_HANDLE ? offset : 0;
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index 006128638..515d8d869 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -529,7 +529,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
static_vector<VkVertexInputBindingDivisorDescriptionEXT, 32> vertex_binding_divisors;
static_vector<VkVertexInputAttributeDescription, 32> vertex_attributes;
if (key.state.dynamic_vertex_input) {
- for (size_t index = 0; index < key.state.attributes.size(); ++index) {
+ const size_t num_vertex_arrays = std::min(
+ key.state.attributes.size(), static_cast<size_t>(device.GetMaxVertexInputBindings()));
+ for (size_t index = 0; index < num_vertex_arrays; ++index) {
const u32 type = key.state.DynamicAttributeType(index);
if (!stage_infos[0].loads.Generic(index) || type == 0) {
continue;
@@ -551,7 +553,9 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
});
}
} else {
- for (size_t index = 0; index < Maxwell::NumVertexArrays; ++index) {
+ const size_t num_vertex_arrays = std::min(
+ Maxwell::NumVertexArrays, static_cast<size_t>(device.GetMaxVertexInputBindings()));
+ for (size_t index = 0; index < num_vertex_arrays; ++index) {
const bool instanced = key.state.binding_divisors[index] != 0;
const auto rate =
instanced ? VK_VERTEX_INPUT_RATE_INSTANCE : VK_VERTEX_INPUT_RATE_VERTEX;
@@ -580,6 +584,8 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
});
}
}
+ ASSERT(vertex_attributes.size() <= device.GetMaxVertexInputAttributes());
+
VkPipelineVertexInputStateCreateInfo vertex_input_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.pNext = nullptr,
@@ -634,23 +640,33 @@ void GraphicsPipeline::MakePipeline(VkRenderPass render_pass) {
};
std::array<VkViewportSwizzleNV, Maxwell::NumViewports> swizzles;
std::ranges::transform(key.state.viewport_swizzles, swizzles.begin(), UnpackViewportSwizzle);
- const VkPipelineViewportSwizzleStateCreateInfoNV swizzle_ci{
+ VkPipelineViewportSwizzleStateCreateInfoNV swizzle_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_SWIZZLE_STATE_CREATE_INFO_NV,
.pNext = nullptr,
.flags = 0,
.viewportCount = Maxwell::NumViewports,
.pViewportSwizzles = swizzles.data(),
};
- const VkPipelineViewportStateCreateInfo viewport_ci{
+ VkPipelineViewportDepthClipControlCreateInfoEXT ndc_info{
+ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_DEPTH_CLIP_CONTROL_CREATE_INFO_EXT,
+ .pNext = nullptr,
+ .negativeOneToOne = key.state.ndc_minus_one_to_one.Value() != 0 ? VK_TRUE : VK_FALSE,
+ };
+ VkPipelineViewportStateCreateInfo viewport_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
- .pNext = device.IsNvViewportSwizzleSupported() ? &swizzle_ci : nullptr,
+ .pNext = nullptr,
.flags = 0,
.viewportCount = Maxwell::NumViewports,
.pViewports = nullptr,
.scissorCount = Maxwell::NumViewports,
.pScissors = nullptr,
};
-
+ if (device.IsNvViewportSwizzleSupported()) {
+ swizzle_ci.pNext = std::exchange(viewport_ci.pNext, &swizzle_ci);
+ }
+ if (device.IsExtDepthClipControlSupported()) {
+ ndc_info.pNext = std::exchange(viewport_ci.pNext, &ndc_info);
+ }
VkPipelineRasterizationStateCreateInfo rasterization_ci{
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.pNext = nullptr,
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 81f5f3e11..e7262420c 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -321,6 +321,7 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
.support_int64_atomics = device.IsExtShaderAtomicInt64Supported(),
.support_derivative_control = true,
.support_geometry_shader_passthrough = device.IsNvGeometryShaderPassthroughSupported(),
+ .support_native_ndc = device.IsExtDepthClipControlSupported(),
.warp_size_potentially_larger_than_guest = device.IsWarpSizePotentiallyBiggerThanGuest(),
@@ -341,6 +342,15 @@ PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device
.support_snorm_render_buffer = true,
.support_viewport_index_layer = device.IsExtShaderViewportIndexLayerSupported(),
};
+
+ if (device.GetMaxVertexInputAttributes() < Maxwell::NumVertexAttributes) {
+ LOG_WARNING(Render_Vulkan, "maxVertexInputAttributes is too low: {} < {}",
+ device.GetMaxVertexInputAttributes(), Maxwell::NumVertexAttributes);
+ }
+ if (device.GetMaxVertexInputBindings() < Maxwell::NumVertexArrays) {
+ LOG_WARNING(Render_Vulkan, "maxVertexInputBindings is too low: {} < {}",
+ device.GetMaxVertexInputBindings(), Maxwell::NumVertexArrays);
+ }
}
PipelineCache::~PipelineCache() = default;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 3774f303a..ac1eb9895 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -220,9 +220,6 @@ void RasterizerVulkan::Draw(bool is_indexed, u32 instance_count) {
void RasterizerVulkan::Clear(u32 layer_count) {
MICROPROFILE_SCOPE(Vulkan_Clearing);
- if (!maxwell3d->ShouldExecute()) {
- return;
- }
FlushWork();
query_cache.UpdateCounters();
@@ -665,8 +662,7 @@ void RasterizerVulkan::BeginTransformFeedback() {
return;
}
UNIMPLEMENTED_IF(regs.IsShaderConfigEnabled(Maxwell::ShaderType::TessellationInit) ||
- regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation) ||
- regs.IsShaderConfigEnabled(Maxwell::ShaderType::Geometry));
+ regs.IsShaderConfigEnabled(Maxwell::ShaderType::Tessellation));
scheduler.Record(
[](vk::CommandBuffer cmdbuf) { cmdbuf.BeginTransformFeedbackEXT(0, 0, nullptr, nullptr); });
}
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index d7be417f5..b6810eef9 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -67,17 +67,19 @@ VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 wi
} // Anonymous namespace
-Swapchain::Swapchain(VkSurfaceKHR surface_, const Device& device_, Scheduler& scheduler_, u32 width,
- u32 height, bool srgb)
+Swapchain::Swapchain(VkSurfaceKHR surface_, const Device& device_, Scheduler& scheduler_,
+ u32 width_, u32 height_, bool srgb)
: surface{surface_}, device{device_}, scheduler{scheduler_} {
- Create(width, height, srgb);
+ Create(width_, height_, srgb);
}
Swapchain::~Swapchain() = default;
-void Swapchain::Create(u32 width, u32 height, bool srgb) {
+void Swapchain::Create(u32 width_, u32 height_, bool srgb) {
is_outdated = false;
is_suboptimal = false;
+ width = width_;
+ height = height_;
const auto physical_device = device.GetPhysical();
const auto capabilities{physical_device.GetSurfaceCapabilitiesKHR(surface)};
@@ -88,7 +90,7 @@ void Swapchain::Create(u32 width, u32 height, bool srgb) {
device.GetLogical().WaitIdle();
Destroy();
- CreateSwapchain(capabilities, width, height, srgb);
+ CreateSwapchain(capabilities, srgb);
CreateSemaphores();
CreateImageViews();
@@ -148,8 +150,7 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
}
}
-void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height,
- bool srgb) {
+void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb) {
const auto physical_device{device.GetPhysical()};
const auto formats{physical_device.GetSurfaceFormatsKHR(surface)};
const auto present_modes{physical_device.GetSurfacePresentModesKHR(surface)};
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index 111b3902d..caf1ff32b 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -80,9 +80,16 @@ public:
return *present_semaphores[frame_index];
}
+ u32 GetWidth() const {
+ return width;
+ }
+
+ u32 GetHeight() const {
+ return height;
+ }
+
private:
- void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height,
- bool srgb);
+ void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb);
void CreateSemaphores();
void CreateImageViews();
@@ -105,6 +112,9 @@ private:
std::vector<u64> resource_ticks;
std::vector<vk::Semaphore> present_semaphores;
+ u32 width;
+ u32 height;
+
u32 image_index{};
u32 frame_index{};
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 8e68a2e53..27c82cd20 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -39,6 +39,12 @@ TextureCache<P>::TextureCache(Runtime& runtime_, VideoCore::RasterizerInterface&
sampler_descriptor.mipmap_filter.Assign(Tegra::Texture::TextureMipmapFilter::Linear);
sampler_descriptor.cubemap_anisotropy.Assign(1);
+ // These values were chosen based on typical peak swizzle data sizes seen in some titles
+ static constexpr size_t SWIZZLE_DATA_BUFFER_INITIAL_CAPACITY = 8_MiB;
+ static constexpr size_t UNSWIZZLE_DATA_BUFFER_INITIAL_CAPACITY = 1_MiB;
+ swizzle_data_buffer.resize_destructive(SWIZZLE_DATA_BUFFER_INITIAL_CAPACITY);
+ unswizzle_data_buffer.resize_destructive(UNSWIZZLE_DATA_BUFFER_INITIAL_CAPACITY);
+
// Make sure the first index is reserved for the null resources
// This way the null resource becomes a compile time constant
void(slot_images.insert(NullImageParams{}));
@@ -90,7 +96,8 @@ void TextureCache<P>::RunGarbageCollector() {
const auto copies = FullDownloadCopies(image.info);
image.DownloadMemory(map, copies);
runtime.Finish();
- SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span);
+ SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
+ swizzle_data_buffer);
}
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
@@ -461,7 +468,8 @@ void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) {
const auto copies = FullDownloadCopies(image.info);
image.DownloadMemory(map, copies);
runtime.Finish();
- SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span);
+ SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
+ swizzle_data_buffer);
}
}
@@ -672,7 +680,8 @@ void TextureCache<P>::PopAsyncFlushes() {
for (const ImageId image_id : download_ids) {
const ImageBase& image = slot_images[image_id];
const auto copies = FullDownloadCopies(image.info);
- SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span);
+ SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span,
+ swizzle_data_buffer);
download_map.offset += image.unswizzled_size_bytes;
download_span = download_span.subspan(image.unswizzled_size_bytes);
}
@@ -734,13 +743,21 @@ void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging)
gpu_memory->ReadBlockUnsafe(gpu_addr, mapped_span.data(), mapped_span.size_bytes());
const auto uploads = FullUploadSwizzles(image.info);
runtime.AccelerateImageUpload(image, staging, uploads);
- } else if (True(image.flags & ImageFlagBits::Converted)) {
- std::vector<u8> unswizzled_data(image.unswizzled_size_bytes);
- auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, unswizzled_data);
- ConvertImage(unswizzled_data, image.info, mapped_span, copies);
+ return;
+ }
+ const size_t guest_size_bytes = image.guest_size_bytes;
+ swizzle_data_buffer.resize_destructive(guest_size_bytes);
+ gpu_memory->ReadBlockUnsafe(gpu_addr, swizzle_data_buffer.data(), guest_size_bytes);
+
+ if (True(image.flags & ImageFlagBits::Converted)) {
+ unswizzle_data_buffer.resize_destructive(image.unswizzled_size_bytes);
+ auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data_buffer,
+ unswizzle_data_buffer);
+ ConvertImage(unswizzle_data_buffer, image.info, mapped_span, copies);
image.UploadMemory(staging, copies);
} else {
- const auto copies = UnswizzleImage(*gpu_memory, gpu_addr, image.info, mapped_span);
+ const auto copies =
+ UnswizzleImage(*gpu_memory, gpu_addr, image.info, swizzle_data_buffer, mapped_span);
image.UploadMemory(staging, copies);
}
}
@@ -910,7 +927,7 @@ void TextureCache<P>::InvalidateScale(Image& image) {
}
template <class P>
-u64 TextureCache<P>::GetScaledImageSizeBytes(ImageBase& image) {
+u64 TextureCache<P>::GetScaledImageSizeBytes(const ImageBase& image) {
const u64 scale_up = static_cast<u64>(Settings::values.resolution_info.up_scale *
Settings::values.resolution_info.up_scale);
const u64 down_shift = static_cast<u64>(Settings::values.resolution_info.down_shift +
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 587339a31..4fd677a80 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -17,6 +17,7 @@
#include "common/literals.h"
#include "common/lru_cache.h"
#include "common/polyfill_ranges.h"
+#include "common/scratch_buffer.h"
#include "video_core/compatible_formats.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/delayed_destruction_ring.h"
@@ -368,7 +369,7 @@ private:
void InvalidateScale(Image& image);
bool ScaleUp(Image& image);
bool ScaleDown(Image& image);
- u64 GetScaledImageSizeBytes(ImageBase& image);
+ u64 GetScaledImageSizeBytes(const ImageBase& image);
Runtime& runtime;
@@ -417,6 +418,9 @@ private:
std::unordered_map<GPUVAddr, ImageAllocId> image_allocs_table;
+ Common::ScratchBuffer<u8> swizzle_data_buffer;
+ Common::ScratchBuffer<u8> unswizzle_data_buffer;
+
u64 modification_tick = 0;
u64 frame_tick = 0;
};
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index e8c908b42..03acc68d9 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -505,7 +505,7 @@ void SwizzlePitchLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
const ImageInfo& info, const BufferImageCopy& copy,
- std::span<const u8> input) {
+ std::span<const u8> input, Common::ScratchBuffer<u8>& tmp_buffer) {
const Extent3D size = info.size;
const LevelInfo level_info = MakeLevelInfo(info);
const Extent2D tile_size = DefaultBlockSize(info.format);
@@ -534,8 +534,8 @@ void SwizzleBlockLinearImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr
tile_size.height, info.tile_width_spacing);
const size_t subresource_size = sizes[level];
- const auto dst_data = std::make_unique<u8[]>(subresource_size);
- const std::span<u8> dst(dst_data.get(), subresource_size);
+ tmp_buffer.resize_destructive(subresource_size);
+ const std::span<u8> dst(tmp_buffer);
for (s32 layer = 0; layer < info.resources.layers; ++layer) {
const std::span<const u8> src = input.subspan(host_offset);
@@ -765,8 +765,9 @@ bool IsValidEntry(const Tegra::MemoryManager& gpu_memory, const TICEntry& config
}
std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
- const ImageInfo& info, std::span<u8> output) {
- const size_t guest_size_bytes = CalculateGuestSizeInBytes(info);
+ const ImageInfo& info, std::span<const u8> input,
+ std::span<u8> output) {
+ const size_t guest_size_bytes = input.size_bytes();
const u32 bpp_log2 = BytesPerBlockLog2(info.format);
const Extent3D size = info.size;
@@ -789,10 +790,6 @@ std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory, GP
.image_extent = size,
}};
}
- const auto input_data = std::make_unique<u8[]>(guest_size_bytes);
- gpu_memory.ReadBlockUnsafe(gpu_addr, input_data.get(), guest_size_bytes);
- const std::span<const u8> input(input_data.get(), guest_size_bytes);
-
const LevelInfo level_info = MakeLevelInfo(info);
const s32 num_layers = info.resources.layers;
const s32 num_levels = info.resources.levels;
@@ -980,13 +977,14 @@ std::vector<SwizzleParameters> FullUploadSwizzles(const ImageInfo& info) {
}
void SwizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
- std::span<const BufferImageCopy> copies, std::span<const u8> memory) {
+ std::span<const BufferImageCopy> copies, std::span<const u8> memory,
+ Common::ScratchBuffer<u8>& tmp_buffer) {
const bool is_pitch_linear = info.type == ImageType::Linear;
for (const BufferImageCopy& copy : copies) {
if (is_pitch_linear) {
SwizzlePitchLinearImage(gpu_memory, gpu_addr, info, copy, memory);
} else {
- SwizzleBlockLinearImage(gpu_memory, gpu_addr, info, copy, memory);
+ SwizzleBlockLinearImage(gpu_memory, gpu_addr, info, copy, memory, tmp_buffer);
}
}
}
diff --git a/src/video_core/texture_cache/util.h b/src/video_core/texture_cache/util.h
index 5e28f4ab3..d103db8ae 100644
--- a/src/video_core/texture_cache/util.h
+++ b/src/video_core/texture_cache/util.h
@@ -7,6 +7,7 @@
#include <span>
#include "common/common_types.h"
+#include "common/scratch_buffer.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/image_base.h"
@@ -59,6 +60,7 @@ struct OverlapResult {
[[nodiscard]] std::vector<BufferImageCopy> UnswizzleImage(Tegra::MemoryManager& gpu_memory,
GPUVAddr gpu_addr, const ImageInfo& info,
+ std::span<const u8> input,
std::span<u8> output);
[[nodiscard]] BufferCopy UploadBufferCopy(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr,
@@ -76,7 +78,8 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8
[[nodiscard]] std::vector<SwizzleParameters> FullUploadSwizzles(const ImageInfo& info);
void SwizzleImage(Tegra::MemoryManager& gpu_memory, GPUVAddr gpu_addr, const ImageInfo& info,
- std::span<const BufferImageCopy> copies, std::span<const u8> memory);
+ std::span<const BufferImageCopy> copies, std::span<const u8> memory,
+ Common::ScratchBuffer<u8>& tmp_buffer);
[[nodiscard]] bool IsBlockLinearSizeCompatible(const ImageInfo& new_info,
const ImageInfo& overlap_info, u32 new_level,
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 6a2ad4b1d..c4d31681a 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -421,7 +421,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
VkPhysicalDevice8BitStorageFeatures bit8_storage{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_8BIT_STORAGE_FEATURES,
.pNext = nullptr,
- .storageBuffer8BitAccess = false,
+ .storageBuffer8BitAccess = true,
.uniformAndStorageBuffer8BitAccess = true,
.storagePushConstant8 = false,
};
@@ -660,6 +660,16 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
LOG_INFO(Render_Vulkan, "Device doesn't support depth range unrestricted");
}
+ VkPhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control_features;
+ if (ext_depth_clip_control) {
+ depth_clip_control_features = {
+ .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT,
+ .pNext = nullptr,
+ .depthClipControl = VK_TRUE,
+ };
+ SetNext(next, depth_clip_control_features);
+ }
+
VkDeviceDiagnosticsConfigCreateInfoNV diagnostics_nv;
if (Settings::values.enable_nsight_aftermath && nv_device_diagnostics_config) {
nsight_aftermath_tracker = std::make_unique<NsightAftermathTracker>();
@@ -1044,6 +1054,7 @@ void Device::CheckSuitability(bool requires_swapchain) const {
std::make_pair(bit16_storage.storageBuffer16BitAccess, "storageBuffer16BitAccess"),
std::make_pair(bit16_storage.uniformAndStorageBuffer16BitAccess,
"uniformAndStorageBuffer16BitAccess"),
+ std::make_pair(bit8_storage.storageBuffer8BitAccess, "storageBuffer8BitAccess"),
std::make_pair(bit8_storage.uniformAndStorageBuffer8BitAccess,
"uniformAndStorageBuffer8BitAccess"),
std::make_pair(host_query_reset.hostQueryReset, "hostQueryReset"),
@@ -1083,6 +1094,7 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
bool has_ext_vertex_input_dynamic_state{};
bool has_ext_line_rasterization{};
bool has_ext_primitive_topology_list_restart{};
+ bool has_ext_depth_clip_control{};
for (const std::string& extension : supported_extensions) {
const auto test = [&](std::optional<std::reference_wrapper<bool>> status, const char* name,
bool push) {
@@ -1116,6 +1128,7 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
test(ext_shader_stencil_export, VK_EXT_SHADER_STENCIL_EXPORT_EXTENSION_NAME, true);
test(ext_conservative_rasterization, VK_EXT_CONSERVATIVE_RASTERIZATION_EXTENSION_NAME,
true);
+ test(has_ext_depth_clip_control, VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME, false);
test(has_ext_transform_feedback, VK_EXT_TRANSFORM_FEEDBACK_EXTENSION_NAME, false);
test(has_ext_custom_border_color, VK_EXT_CUSTOM_BORDER_COLOR_EXTENSION_NAME, false);
test(has_ext_extended_dynamic_state, VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME, false);
@@ -1279,6 +1292,19 @@ std::vector<const char*> Device::LoadExtensions(bool requires_surface) {
ext_line_rasterization = true;
}
}
+ if (has_ext_depth_clip_control) {
+ VkPhysicalDeviceDepthClipControlFeaturesEXT depth_clip_control_features;
+ depth_clip_control_features.sType =
+ VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DEPTH_CLIP_CONTROL_FEATURES_EXT;
+ depth_clip_control_features.pNext = nullptr;
+ features.pNext = &depth_clip_control_features;
+ physical.GetFeatures2(features);
+
+ if (depth_clip_control_features.depthClipControl) {
+ extensions.push_back(VK_EXT_DEPTH_CLIP_CONTROL_EXTENSION_NAME);
+ ext_depth_clip_control = true;
+ }
+ }
if (has_khr_workgroup_memory_explicit_layout) {
VkPhysicalDeviceWorkgroupMemoryExplicitLayoutFeaturesKHR layout;
layout.sType =
@@ -1380,6 +1406,10 @@ void Device::SetupFeatures() {
is_shader_storage_image_multisample = features.shaderStorageImageMultisample;
is_blit_depth_stencil_supported = TestDepthStencilBlits();
is_optimal_astc_supported = IsOptimalAstcSupported(features);
+
+ const VkPhysicalDeviceLimits& limits{properties.limits};
+ max_vertex_input_attributes = limits.maxVertexInputAttributes;
+ max_vertex_input_bindings = limits.maxVertexInputBindings;
}
void Device::SetupProperties() {
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index db802437c..6a26c4e6e 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -256,6 +256,11 @@ public:
return ext_depth_range_unrestricted;
}
+ /// Returns true if the device supports VK_EXT_depth_clip_control.
+ bool IsExtDepthClipControlSupported() const {
+ return ext_depth_clip_control;
+ }
+
/// Returns true if the device supports VK_EXT_shader_viewport_index_layer.
bool IsExtShaderViewportIndexLayerSupported() const {
return ext_shader_viewport_index_layer;
@@ -368,6 +373,14 @@ public:
return must_emulate_bgr565;
}
+ u32 GetMaxVertexInputAttributes() const {
+ return max_vertex_input_attributes;
+ }
+
+ u32 GetMaxVertexInputBindings() const {
+ return max_vertex_input_bindings;
+ }
+
private:
/// Checks if the physical device is suitable.
void CheckSuitability(bool requires_swapchain) const;
@@ -446,6 +459,7 @@ private:
bool khr_swapchain_mutable_format{}; ///< Support for VK_KHR_swapchain_mutable_format.
bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8.
bool ext_sampler_filter_minmax{}; ///< Support for VK_EXT_sampler_filter_minmax.
+ bool ext_depth_clip_control{}; ///< Support for VK_EXT_depth_clip_control
bool ext_depth_range_unrestricted{}; ///< Support for VK_EXT_depth_range_unrestricted.
bool ext_shader_viewport_index_layer{}; ///< Support for VK_EXT_shader_viewport_index_layer.
bool ext_tooling_info{}; ///< Support for VK_EXT_tooling_info.
@@ -467,6 +481,8 @@ private:
bool supports_d24_depth{}; ///< Supports D24 depth buffers.
bool cant_blit_msaa{}; ///< Does not support MSAA<->MSAA blitting.
bool must_emulate_bgr565{}; ///< Emulates BGR565 by swizzling RGB565 format.
+ u32 max_vertex_input_attributes{}; ///< Max vertex input attributes in pipeline
+ u32 max_vertex_input_bindings{}; ///< Max vertex input buffers in pipeline
// Telemetry parameters
std::string vendor_name; ///< Device's driver name.
diff --git a/src/video_core/vulkan_common/vulkan_wrapper.cpp b/src/video_core/vulkan_common/vulkan_wrapper.cpp
index 483b534a0..7dca7341c 100644
--- a/src/video_core/vulkan_common/vulkan_wrapper.cpp
+++ b/src/video_core/vulkan_common/vulkan_wrapper.cpp
@@ -314,6 +314,18 @@ const char* ToString(VkResult result) noexcept {
return "VK_ERROR_VALIDATION_FAILED_EXT";
case VkResult::VK_ERROR_INVALID_SHADER_NV:
return "VK_ERROR_INVALID_SHADER_NV";
+ case VkResult::VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR:
+ return "VK_ERROR_IMAGE_USAGE_NOT_SUPPORTED_KHR";
+ case VkResult::VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR:
+ return "VK_ERROR_VIDEO_PICTURE_LAYOUT_NOT_SUPPORTED_KHR";
+ case VkResult::VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR:
+ return "VK_ERROR_VIDEO_PROFILE_OPERATION_NOT_SUPPORTED_KHR";
+ case VkResult::VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR:
+ return "VK_ERROR_VIDEO_PROFILE_FORMAT_NOT_SUPPORTED_KHR";
+ case VkResult::VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR:
+ return "VK_ERROR_VIDEO_PROFILE_CODEC_NOT_SUPPORTED_KHR";
+ case VkResult::VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR:
+ return "VK_ERROR_VIDEO_STD_VERSION_NOT_SUPPORTED_KHR";
case VkResult::VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT:
return "VK_ERROR_INVALID_DRM_FORMAT_MODIFIER_PLANE_LAYOUT_EXT";
case VkResult::VK_ERROR_FRAGMENTATION_EXT:
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 5b5b6fed8..3d560f303 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -44,32 +44,30 @@
#include "yuzu/bootmanager.h"
#include "yuzu/main.h"
-EmuThread::EmuThread(Core::System& system_) : system{system_} {}
+static Core::Frontend::WindowSystemType GetWindowSystemType();
+
+EmuThread::EmuThread(Core::System& system) : m_system{system} {}
EmuThread::~EmuThread() = default;
void EmuThread::run() {
- std::string name = "EmuControlThread";
- MicroProfileOnThreadCreate(name.c_str());
- Common::SetCurrentThreadName(name.c_str());
+ const char* name = "EmuControlThread";
+ MicroProfileOnThreadCreate(name);
+ Common::SetCurrentThreadName(name);
- auto& gpu = system.GPU();
- auto stop_token = stop_source.get_token();
- bool debugger_should_start = system.DebuggerEnabled();
+ auto& gpu = m_system.GPU();
+ auto stop_token = m_stop_source.get_token();
- system.RegisterHostThread();
+ m_system.RegisterHostThread();
// Main process has been loaded. Make the context current to this thread and begin GPU and CPU
// execution.
- gpu.Start();
-
gpu.ObtainContext();
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
-
if (Settings::values.use_disk_shader_cache.GetValue()) {
- system.Renderer().ReadRasterizer()->LoadDiskResources(
- system.GetCurrentProcessProgramID(), stop_token,
+ m_system.Renderer().ReadRasterizer()->LoadDiskResources(
+ m_system.GetCurrentProcessProgramID(), stop_token,
[this](VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total) {
emit LoadProgress(stage, value, total);
});
@@ -77,53 +75,36 @@ void EmuThread::run() {
emit LoadProgress(VideoCore::LoadCallbackStage::Complete, 0, 0);
gpu.ReleaseContext();
+ gpu.Start();
- system.GetCpuManager().OnGpuReady();
-
- // Holds whether the cpu was running during the last iteration,
- // so that the DebugModeLeft signal can be emitted before the
- // next execution step
- bool was_active = false;
- while (!stop_token.stop_requested()) {
- if (running) {
- if (was_active) {
- emit DebugModeLeft();
- }
-
- running_guard = true;
- Core::SystemResultStatus result = system.Run();
- if (result != Core::SystemResultStatus::Success) {
- running_guard = false;
- this->SetRunning(false);
- emit ErrorThrown(result, system.GetStatusDetails());
- }
+ m_system.GetCpuManager().OnGpuReady();
- if (debugger_should_start) {
- system.InitializeDebugger();
- debugger_should_start = false;
- }
+ if (m_system.DebuggerEnabled()) {
+ m_system.InitializeDebugger();
+ }
- running_wait.Wait();
- result = system.Pause();
- if (result != Core::SystemResultStatus::Success) {
- running_guard = false;
- this->SetRunning(false);
- emit ErrorThrown(result, system.GetStatusDetails());
- }
- running_guard = false;
+ while (!stop_token.stop_requested()) {
+ std::unique_lock lk{m_should_run_mutex};
+ if (m_should_run) {
+ m_system.Run();
+ m_is_running.store(true);
+ m_is_running.notify_all();
- if (!stop_token.stop_requested()) {
- was_active = true;
- emit DebugModeEntered();
- }
+ Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return !m_should_run; });
} else {
- std::unique_lock lock{running_mutex};
- Common::CondvarWait(running_cv, lock, stop_token, [&] { return IsRunning(); });
+ m_system.Pause();
+ m_is_running.store(false);
+ m_is_running.notify_all();
+
+ emit DebugModeEntered();
+ Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return m_should_run; });
+ emit DebugModeLeft();
}
}
// Shutdown the main emulated process
- system.ShutdownMainProcess();
+ m_system.DetachDebugger();
+ m_system.ShutdownMainProcess();
#if MICROPROFILE_ENABLED
MicroProfileOnThreadExit();
@@ -225,6 +206,9 @@ public:
explicit RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
+ if (GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) {
+ setAttribute(Qt::WA_DontCreateNativeAncestors);
+ }
}
virtual ~RenderWidget() = default;
@@ -269,12 +253,14 @@ static Core::Frontend::WindowSystemType GetWindowSystemType() {
return Core::Frontend::WindowSystemType::X11;
else if (platform_name == QStringLiteral("wayland"))
return Core::Frontend::WindowSystemType::Wayland;
+ else if (platform_name == QStringLiteral("wayland-egl"))
+ return Core::Frontend::WindowSystemType::Wayland;
else if (platform_name == QStringLiteral("cocoa"))
return Core::Frontend::WindowSystemType::Cocoa;
else if (platform_name == QStringLiteral("android"))
return Core::Frontend::WindowSystemType::Android;
- LOG_CRITICAL(Frontend, "Unknown Qt platform!");
+ LOG_CRITICAL(Frontend, "Unknown Qt platform {}!", platform_name.toStdString());
return Core::Frontend::WindowSystemType::Windows;
}
@@ -314,6 +300,9 @@ GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
input_subsystem->Initialize();
this->setMouseTracking(true);
+ strict_context_required = QGuiApplication::platformName() == QStringLiteral("wayland") ||
+ QGuiApplication::platformName() == QStringLiteral("wayland-egl");
+
connect(this, &GRenderWindow::FirstFrameDisplayed, parent, &GMainWindow::OnLoadComplete);
connect(this, &GRenderWindow::ExecuteProgramSignal, parent, &GMainWindow::OnExecuteProgram,
Qt::QueuedConnection);
@@ -750,6 +739,9 @@ void GRenderWindow::InitializeCamera() {
return;
}
+ const auto camera_width = input_subsystem->GetCamera()->getImageWidth();
+ const auto camera_height = input_subsystem->GetCamera()->getImageHeight();
+ camera_data.resize(camera_width * camera_height);
camera_capture->setCaptureDestination(QCameraImageCapture::CaptureDestination::CaptureToBuffer);
connect(camera_capture.get(), &QCameraImageCapture::imageCaptured, this,
&GRenderWindow::OnCameraCapture);
@@ -805,17 +797,22 @@ void GRenderWindow::RequestCameraCapture() {
}
void GRenderWindow::OnCameraCapture(int requestId, const QImage& img) {
- constexpr std::size_t camera_width = 320;
- constexpr std::size_t camera_height = 240;
+#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA
+ // TODO: Capture directly in the format and resolution needed
+ const auto camera_width = input_subsystem->GetCamera()->getImageWidth();
+ const auto camera_height = input_subsystem->GetCamera()->getImageHeight();
const auto converted =
- img.scaled(camera_width, camera_height, Qt::AspectRatioMode::IgnoreAspectRatio,
+ img.scaled(static_cast<int>(camera_width), static_cast<int>(camera_height),
+ Qt::AspectRatioMode::IgnoreAspectRatio,
Qt::TransformationMode::SmoothTransformation)
.mirrored(false, true);
- std::vector<u32> camera_data{};
- camera_data.resize(camera_width * camera_height);
+ if (camera_data.size() != camera_width * camera_height) {
+ camera_data.resize(camera_width * camera_height);
+ }
std::memcpy(camera_data.data(), converted.bits(), camera_width * camera_height * sizeof(u32));
input_subsystem->GetCamera()->SetCameraData(camera_width, camera_height, camera_data);
pending_camera_snapshots = 0;
+#endif
}
bool GRenderWindow::event(QEvent* event) {
@@ -952,6 +949,12 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(std::pair<u32, u32> minimal
bool GRenderWindow::InitializeOpenGL() {
#ifdef HAS_OPENGL
+ if (!QOpenGLContext::supportsThreadedOpenGL()) {
+ QMessageBox::warning(this, tr("OpenGL not available!"),
+ tr("OpenGL shared contexts are not supported."));
+ return false;
+ }
+
// TODO: One of these flags might be interesting: WA_OpaquePaintEvent, WA_NoBackground,
// WA_DontShowOnScreen, WA_DeleteOnClose
auto child = new OpenGLRenderWidget(this);
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index f4deae4ee..eca16b313 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -47,7 +47,7 @@ class EmuThread final : public QThread {
Q_OBJECT
public:
- explicit EmuThread(Core::System& system_);
+ explicit EmuThread(Core::System& system);
~EmuThread() override;
/**
@@ -57,48 +57,48 @@ public:
void run() override;
/**
- * Sets whether the emulation thread is running or not
- * @param running_ Boolean value, set the emulation thread to running if true
- * @note This function is thread-safe
+ * Sets whether the emulation thread should run or not
+ * @param should_run Boolean value, set the emulation thread to running if true
*/
- void SetRunning(bool running_) {
- std::unique_lock lock{running_mutex};
- running = running_;
- lock.unlock();
- running_cv.notify_all();
- if (!running) {
- running_wait.Set();
- /// Wait until effectively paused
- while (running_guard)
- ;
+ void SetRunning(bool should_run) {
+ // TODO: Prevent other threads from modifying the state until we finish.
+ {
+ // Notify the running thread to change state.
+ std::unique_lock run_lk{m_should_run_mutex};
+ m_should_run = should_run;
+ m_should_run_cv.notify_one();
+ }
+
+ // Wait until paused, if pausing.
+ if (!should_run) {
+ m_is_running.wait(true);
}
}
/**
* Check if the emulation thread is running or not
* @return True if the emulation thread is running, otherwise false
- * @note This function is thread-safe
*/
bool IsRunning() const {
- return running;
+ return m_is_running.load() || m_should_run;
}
/**
- * Requests for the emulation thread to stop running
+ * Requests for the emulation thread to immediately stop running
*/
- void RequestStop() {
- stop_source.request_stop();
- SetRunning(false);
+ void ForceStop() {
+ LOG_WARNING(Frontend, "Force stopping EmuThread");
+ m_stop_source.request_stop();
}
private:
- bool running = false;
- std::stop_source stop_source;
- std::mutex running_mutex;
- std::condition_variable_any running_cv;
- Common::Event running_wait{};
- std::atomic_bool running_guard{false};
- Core::System& system;
+ Core::System& m_system;
+
+ std::stop_source m_stop_source;
+ std::mutex m_should_run_mutex;
+ std::condition_variable_any m_should_run_cv;
+ std::atomic<bool> m_is_running{false};
+ bool m_should_run{true};
signals:
/**
@@ -119,8 +119,6 @@ signals:
*/
void DebugModeLeft();
- void ErrorThrown(Core::SystemResultStatus, std::string);
-
void LoadProgress(VideoCore::LoadCallbackStage stage, std::size_t value, std::size_t total);
};
@@ -241,13 +239,14 @@ private:
bool first_frame = false;
InputCommon::TasInput::TasState last_tas_state;
+#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA
bool is_virtual_camera;
int pending_camera_snapshots;
-#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA
+ std::vector<u32> camera_data;
std::unique_ptr<QCamera> camera;
std::unique_ptr<QCameraImageCapture> camera_capture;
-#endif
std::unique_ptr<QTimer> camera_timer;
+#endif
Core::System& system;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 722fc708e..2ea4f367b 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -697,7 +697,6 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.fsr_sharpening_slider);
ReadGlobalSetting(Settings::values.anti_aliasing);
ReadGlobalSetting(Settings::values.max_anisotropy);
- ReadGlobalSetting(Settings::values.use_speed_limit);
ReadGlobalSetting(Settings::values.speed_limit);
ReadGlobalSetting(Settings::values.use_disk_shader_cache);
ReadGlobalSetting(Settings::values.gpu_accuracy);
@@ -796,6 +795,7 @@ void Config::ReadSystemValues() {
} else {
Settings::values.custom_rtc = std::nullopt;
}
+ ReadBasicSetting(Settings::values.device_name);
}
ReadGlobalSetting(Settings::values.sound_index);
@@ -1328,7 +1328,6 @@ void Config::SaveRendererValues() {
static_cast<u32>(Settings::values.anti_aliasing.GetDefault()),
Settings::values.anti_aliasing.UsingGlobal());
WriteGlobalSetting(Settings::values.max_anisotropy);
- WriteGlobalSetting(Settings::values.use_speed_limit);
WriteGlobalSetting(Settings::values.speed_limit);
WriteGlobalSetting(Settings::values.use_disk_shader_cache);
WriteSetting(QString::fromStdString(Settings::values.gpu_accuracy.GetLabel()),
@@ -1415,6 +1414,7 @@ void Config::SaveSystemValues() {
false);
WriteSetting(QStringLiteral("custom_rtc"),
QVariant::fromValue<long long>(Settings::values.custom_rtc.value_or(0)), 0);
+ WriteBasicSetting(Settings::values.device_name);
}
WriteGlobalSetting(Settings::values.sound_index);
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index b1575b0d3..183cbe562 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -738,13 +738,10 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
connect(ui->comboDevices, qOverload<int>(&QComboBox::activated), this,
&ConfigureInputPlayer::UpdateMappingWithDefaults);
+ ui->comboDevices->installEventFilter(this);
ui->comboDevices->setCurrentIndex(-1);
- ui->buttonRefreshDevices->setIcon(QIcon::fromTheme(QStringLiteral("view-refresh")));
- connect(ui->buttonRefreshDevices, &QPushButton::clicked,
- [this] { emit RefreshInputDevices(); });
-
timeout_timer->setSingleShot(true);
connect(timeout_timer.get(), &QTimer::timeout, [this] { SetPollingResult({}, true); });
@@ -1479,6 +1476,13 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
}
}
+bool ConfigureInputPlayer::eventFilter(QObject* object, QEvent* event) {
+ if (object == ui->comboDevices && event->type() == QEvent::MouseButtonPress) {
+ RefreshInputDevices();
+ }
+ return object->eventFilter(object, event);
+}
+
void ConfigureInputPlayer::CreateProfile() {
const auto profile_name =
LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 30,
diff --git a/src/yuzu/configuration/configure_input_player.h b/src/yuzu/configuration/configure_input_player.h
index 26f60d121..6d1876f2b 100644
--- a/src/yuzu/configuration/configure_input_player.h
+++ b/src/yuzu/configuration/configure_input_player.h
@@ -119,6 +119,9 @@ private:
/// Handle key press events.
void keyPressEvent(QKeyEvent* event) override;
+ /// Handle combobox list refresh
+ bool eventFilter(QObject* object, QEvent* event) override;
+
/// Update UI to reflect current configuration.
void UpdateUI();
diff --git a/src/yuzu/configuration/configure_input_player.ui b/src/yuzu/configuration/configure_input_player.ui
index a62b57501..a9567c6ee 100644
--- a/src/yuzu/configuration/configure_input_player.ui
+++ b/src/yuzu/configuration/configure_input_player.ui
@@ -122,25 +122,6 @@
</property>
</widget>
</item>
- <item>
- <widget class="QPushButton" name="buttonRefreshDevices">
- <property name="minimumSize">
- <size>
- <width>21</width>
- <height>21</height>
- </size>
- </property>
- <property name="maximumSize">
- <size>
- <width>21</width>
- <height>21</height>
- </size>
- </property>
- <property name="styleSheet">
- <string notr="true"/>
- </property>
- </widget>
- </item>
</layout>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index bc9d9d77a..9b14e5903 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -72,6 +72,8 @@ void ConfigureSystem::SetConfiguration() {
ui->custom_rtc_checkbox->setChecked(Settings::values.custom_rtc.has_value());
ui->custom_rtc_edit->setEnabled(Settings::values.custom_rtc.has_value());
ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time));
+ ui->device_name_edit->setText(
+ QString::fromUtf8(Settings::values.device_name.GetValue().c_str()));
if (Settings::IsConfiguringGlobal()) {
ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
@@ -115,6 +117,8 @@ void ConfigureSystem::ApplyConfiguration() {
}
}
+ Settings::values.device_name = ui->device_name_edit->text().toStdString();
+
if (!enabled) {
return;
}
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index b234ea87b..46892f5c1 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -432,6 +432,13 @@
</property>
</widget>
</item>
+ <item row="7" column="0">
+ <widget class="QLabel" name="device_name_label">
+ <property name="text">
+ <string>Device Name</string>
+ </property>
+ </widget>
+ </item>
<item row="3" column="1">
<widget class="QComboBox" name="combo_sound">
<item>
@@ -476,6 +483,13 @@
</property>
</widget>
</item>
+ <item row="7" column="1">
+ <widget class="QLineEdit" name="device_name_edit">
+ <property name="maxLength">
+ <number>128</number>
+ </property>
+ </widget>
+ </item>
<item row="6" column="1">
<widget class="QLineEdit" name="rng_seed_edit">
<property name="sizePolicy">
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index 5c33c1b0f..22aa19c56 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -554,6 +554,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC"));
QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard"));
QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry"));
+#ifndef WIN32
+ QMenu* shortcut_menu = context_menu.addMenu(tr("Create Shortcut"));
+ QAction* create_desktop_shortcut = shortcut_menu->addAction(tr("Add to Desktop"));
+ QAction* create_applications_menu_shortcut =
+ shortcut_menu->addAction(tr("Add to Applications Menu"));
+#endif
context_menu.addSeparator();
QAction* properties = context_menu.addAction(tr("Properties"));
@@ -619,6 +625,14 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri
connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
});
+#ifndef WIN32
+ connect(create_desktop_shortcut, &QAction::triggered, [this, program_id, path]() {
+ emit CreateShortcut(program_id, path, GameListShortcutTarget::Desktop);
+ });
+ connect(create_applications_menu_shortcut, &QAction::triggered, [this, program_id, path]() {
+ emit CreateShortcut(program_id, path, GameListShortcutTarget::Applications);
+ });
+#endif
connect(properties, &QAction::triggered,
[this, path]() { emit OpenPerGameGeneralRequested(path); });
};
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index cdf085019..f7ff93ed9 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -52,6 +52,11 @@ enum class DumpRomFSTarget {
SDMC,
};
+enum class GameListShortcutTarget {
+ Desktop,
+ Applications,
+};
+
enum class InstalledEntryType {
Game,
Update,
@@ -108,6 +113,8 @@ signals:
const std::string& game_path);
void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target);
void CopyTIDRequested(u64 program_id);
+ void CreateShortcut(u64 program_id, const std::string& game_path,
+ GameListShortcutTarget target);
void NavigateToGamedbEntryRequested(u64 program_id,
const CompatibilityList& compatibility_list);
void OpenPerGameGeneralRequested(const std::string& file);
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index b11b26f7b..524650144 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -4,6 +4,8 @@
#include <cinttypes>
#include <clocale>
#include <cmath>
+#include <fstream>
+#include <iostream>
#include <memory>
#include <thread>
#ifdef __APPLE__
@@ -1249,6 +1251,7 @@ void GMainWindow::ConnectWidgetEvents() {
connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
&GMainWindow::OnGameListNavigateToGamedbEntry);
+ connect(game_list, &GameList::CreateShortcut, this, &GMainWindow::OnGameListCreateShortcut);
connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
&GMainWindow::OnGameListAddDirectory);
@@ -1495,7 +1498,7 @@ void GMainWindow::SetupSigInterrupts() {
void GMainWindow::HandleSigInterrupt(int sig) {
if (sig == SIGINT) {
- exit(1);
+ _exit(1);
}
// Calling into Qt directly from a signal handler is not safe,
@@ -1547,8 +1550,9 @@ void GMainWindow::AllowOSSleep() {
bool GMainWindow::LoadROM(const QString& filename, u64 program_id, std::size_t program_index) {
// Shutdown previous session if the emu thread is still active...
- if (emu_thread != nullptr)
+ if (emu_thread != nullptr) {
ShutdownGame();
+ }
if (!render_window->InitRenderTarget()) {
return false;
@@ -1707,8 +1711,10 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
system->RegisterExecuteProgramCallback(
[this](std::size_t program_index_) { render_window->ExecuteProgram(program_index_); });
- // Register an Exit callback such that Core can exit the currently running application.
- system->RegisterExitCallback([this]() { render_window->Exit(); });
+ system->RegisterExitCallback([this] {
+ emu_thread->ForceStop();
+ render_window->Exit();
+ });
connect(render_window, &GRenderWindow::Closed, this, &GMainWindow::OnStopGame);
connect(render_window, &GRenderWindow::MouseActivity, this, &GMainWindow::OnMouseActivity);
@@ -1779,9 +1785,9 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t
OnStartGame();
}
-void GMainWindow::ShutdownGame() {
+bool GMainWindow::OnShutdownBegin() {
if (!emulation_running) {
- return;
+ return false;
}
if (ui->action_Fullscreen->isChecked()) {
@@ -1790,17 +1796,58 @@ void GMainWindow::ShutdownGame() {
AllowOSSleep();
+ // Disable unlimited frame rate
+ Settings::values.use_speed_limit.SetValue(true);
+
+ if (system->IsShuttingDown()) {
+ return false;
+ }
+
system->SetShuttingDown(true);
- system->DetachDebugger();
discord_rpc->Pause();
- emu_thread->RequestStop();
+
+ RequestGameExit();
+ emu_thread->disconnect();
+ emu_thread->SetRunning(true);
emit EmulationStopping();
- // Wait for emulation thread to complete and delete it
+ shutdown_timer.setSingleShot(true);
+ shutdown_timer.start(system->DebuggerEnabled() ? 0 : 5000);
+ connect(&shutdown_timer, &QTimer::timeout, this, &GMainWindow::OnEmulationStopTimeExpired);
+ connect(emu_thread.get(), &QThread::finished, this, &GMainWindow::OnEmulationStopped);
+
+ // Disable everything to prevent anything from being triggered here
+ ui->action_Pause->setEnabled(false);
+ ui->action_Restart->setEnabled(false);
+ ui->action_Stop->setEnabled(false);
+
+ return true;
+}
+
+void GMainWindow::OnShutdownBeginDialog() {
+ shutdown_dialog = new OverlayDialog(this, *system, QString{}, tr("Closing software..."),
+ QString{}, QString{}, Qt::AlignHCenter | Qt::AlignVCenter);
+ shutdown_dialog->open();
+}
+
+void GMainWindow::OnEmulationStopTimeExpired() {
+ if (emu_thread) {
+ emu_thread->ForceStop();
+ }
+}
+
+void GMainWindow::OnEmulationStopped() {
+ shutdown_timer.stop();
+ emu_thread->disconnect();
emu_thread->wait();
emu_thread = nullptr;
+ if (shutdown_dialog) {
+ shutdown_dialog->deleteLater();
+ shutdown_dialog = nullptr;
+ }
+
emulation_running = false;
discord_rpc->Update();
@@ -1846,6 +1893,20 @@ void GMainWindow::ShutdownGame() {
// When closing the game, destroy the GLWindow to clear the context after the game is closed
render_window->ReleaseRenderTarget();
+
+ Settings::RestoreGlobalState(system->IsPoweredOn());
+ system->HIDCore().ReloadInputDevices();
+ UpdateStatusButtons();
+}
+
+void GMainWindow::ShutdownGame() {
+ if (!emulation_running) {
+ return;
+ }
+
+ OnShutdownBegin();
+ OnEmulationStopTimeExpired();
+ OnEmulationStopped();
}
void GMainWindow::StoreRecentFile(const QString& filename) {
@@ -2375,6 +2436,152 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
}
+void GMainWindow::OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
+ GameListShortcutTarget target) {
+ // Get path to yuzu executable
+ const QStringList args = QApplication::arguments();
+ std::filesystem::path yuzu_command = args[0].toStdString();
+
+#if defined(__linux__) || defined(__FreeBSD__)
+ // If relative path, make it an absolute path
+ if (yuzu_command.c_str()[0] == '.') {
+ yuzu_command = Common::FS::GetCurrentDir() / yuzu_command;
+ }
+
+#if defined(__linux__)
+ // Warn once if we are making a shortcut to a volatile AppImage
+ const std::string appimage_ending =
+ std::string(Common::g_scm_rev).substr(0, 9).append(".AppImage");
+ if (yuzu_command.string().ends_with(appimage_ending) &&
+ !UISettings::values.shortcut_already_warned) {
+ if (QMessageBox::warning(this, tr("Create Shortcut"),
+ tr("This will create a shortcut to the current AppImage. This may "
+ "not work well if you update. Continue?"),
+ QMessageBox::StandardButton::Ok |
+ QMessageBox::StandardButton::Cancel) ==
+ QMessageBox::StandardButton::Cancel) {
+ return;
+ }
+ UISettings::values.shortcut_already_warned = true;
+ }
+#endif // __linux__
+#endif // __linux__ || __FreeBSD__
+
+ std::filesystem::path target_directory{};
+ // Determine target directory for shortcut
+#if defined(__linux__) || defined(__FreeBSD__)
+ const char* home = std::getenv("HOME");
+ const std::filesystem::path home_path = (home == nullptr ? "~" : home);
+ const char* xdg_data_home = std::getenv("XDG_DATA_HOME");
+
+ if (target == GameListShortcutTarget::Desktop) {
+ target_directory = home_path / "Desktop";
+ if (!Common::FS::IsDir(target_directory)) {
+ QMessageBox::critical(
+ this, tr("Create Shortcut"),
+ tr("Cannot create shortcut on desktop. Path \"%1\" does not exist.")
+ .arg(QString::fromStdString(target_directory)),
+ QMessageBox::StandardButton::Ok);
+ return;
+ }
+ } else if (target == GameListShortcutTarget::Applications) {
+ target_directory = (xdg_data_home == nullptr ? home_path / ".local/share" : xdg_data_home) /
+ "applications";
+ if (!Common::FS::CreateDirs(target_directory)) {
+ QMessageBox::critical(this, tr("Create Shortcut"),
+ tr("Cannot create shortcut in applications menu. Path \"%1\" "
+ "does not exist and cannot be created.")
+ .arg(QString::fromStdString(target_directory)),
+ QMessageBox::StandardButton::Ok);
+ return;
+ }
+ }
+#endif
+
+ const std::string game_file_name = std::filesystem::path(game_path).filename().string();
+ // Determine full paths for icon and shortcut
+#if defined(__linux__) || defined(__FreeBSD__)
+ std::filesystem::path system_icons_path =
+ (xdg_data_home == nullptr ? home_path / ".local/share/" : xdg_data_home) /
+ "icons/hicolor/256x256";
+ if (!Common::FS::CreateDirs(system_icons_path)) {
+ QMessageBox::critical(
+ this, tr("Create Icon"),
+ tr("Cannot create icon file. Path \"%1\" does not exist and cannot be created.")
+ .arg(QString::fromStdString(system_icons_path)),
+ QMessageBox::StandardButton::Ok);
+ return;
+ }
+ std::filesystem::path icon_path =
+ system_icons_path / (program_id == 0 ? fmt::format("yuzu-{}.png", game_file_name)
+ : fmt::format("yuzu-{:016X}.png", program_id));
+ const std::filesystem::path shortcut_path =
+ target_directory / (program_id == 0 ? fmt::format("yuzu-{}.desktop", game_file_name)
+ : fmt::format("yuzu-{:016X}.desktop", program_id));
+#else
+ const std::filesystem::path icon_path{};
+ const std::filesystem::path shortcut_path{};
+#endif
+
+ // Get title from game file
+ const FileSys::PatchManager pm{program_id, system->GetFileSystemController(),
+ system->GetContentProvider()};
+ const auto control = pm.GetControlMetadata();
+ const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read));
+
+ std::string title{fmt::format("{:016X}", program_id)};
+
+ if (control.first != nullptr) {
+ title = control.first->GetApplicationName();
+ } else {
+ loader->ReadTitle(title);
+ }
+
+ // Get icon from game file
+ std::vector<u8> icon_image_file{};
+ if (control.second != nullptr) {
+ icon_image_file = control.second->ReadAllBytes();
+ } else if (loader->ReadIcon(icon_image_file) != Loader::ResultStatus::Success) {
+ LOG_WARNING(Frontend, "Could not read icon from {:s}", game_path);
+ }
+
+ QImage icon_jpeg =
+ QImage::fromData(icon_image_file.data(), static_cast<int>(icon_image_file.size()));
+#if defined(__linux__) || defined(__FreeBSD__)
+ // Convert and write the icon as a PNG
+ if (!icon_jpeg.save(QString::fromStdString(icon_path.string()))) {
+ LOG_ERROR(Frontend, "Could not write icon as PNG to file");
+ } else {
+ LOG_INFO(Frontend, "Wrote an icon to {}", icon_path.string());
+ }
+#endif // __linux__
+
+#if defined(__linux__) || defined(__FreeBSD__)
+ const std::string comment =
+ tr("Start %1 with the yuzu Emulator").arg(QString::fromStdString(title)).toStdString();
+ const std::string arguments = fmt::format("-g \"{:s}\"", game_path);
+ const std::string categories = "Game;Emulator;Qt;";
+ const std::string keywords = "Switch;Nintendo;";
+#else
+ const std::string comment{};
+ const std::string arguments{};
+ const std::string categories{};
+ const std::string keywords{};
+#endif
+ if (!CreateShortcut(shortcut_path.string(), title, comment, icon_path.string(),
+ yuzu_command.string(), arguments, categories, keywords)) {
+ QMessageBox::critical(this, tr("Create Shortcut"),
+ tr("Failed to create a shortcut at %1")
+ .arg(QString::fromStdString(shortcut_path.string())));
+ return;
+ }
+
+ LOG_INFO(Frontend, "Wrote a shortcut to {}", shortcut_path.string());
+ QMessageBox::information(
+ this, tr("Create Shortcut"),
+ tr("Successfully created a shortcut to %1").arg(QString::fromStdString(title)));
+}
+
void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
std::filesystem::path fs_path;
if (directory == QStringLiteral("SDMC")) {
@@ -2508,6 +2715,9 @@ void GMainWindow::OnMenuInstallToNAND() {
return;
}
+ // Save folder location of the first selected file
+ UISettings::values.roms_path = QFileInfo(filenames[0]).path();
+
int remaining = filenames.size();
// This would only overflow above 2^43 bytes (8.796 TB)
@@ -2763,8 +2973,6 @@ void GMainWindow::OnStartGame() {
emu_thread->SetRunning(true);
- connect(emu_thread.get(), &EmuThread::ErrorThrown, this, &GMainWindow::OnCoreError);
-
UpdateMenuState();
OnTasStateChanged();
@@ -2801,11 +3009,9 @@ void GMainWindow::OnStopGame() {
return;
}
- ShutdownGame();
-
- Settings::RestoreGlobalState(system->IsPoweredOn());
- system->HIDCore().ReloadInputDevices();
- UpdateStatusButtons();
+ if (OnShutdownBegin()) {
+ OnShutdownBeginDialog();
+ }
}
void GMainWindow::OnLoadComplete() {
@@ -2912,9 +3118,15 @@ static QScreen* GuessCurrentScreen(QWidget* window) {
});
}
+bool GMainWindow::UsingExclusiveFullscreen() {
+ return Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive ||
+ QGuiApplication::platformName() == QStringLiteral("wayland") ||
+ QGuiApplication::platformName() == QStringLiteral("wayland-egl");
+}
+
void GMainWindow::ShowFullscreen() {
- const auto show_fullscreen = [](QWidget* window) {
- if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
+ const auto show_fullscreen = [this](QWidget* window) {
+ if (UsingExclusiveFullscreen()) {
window->showFullScreen();
return;
}
@@ -2942,7 +3154,7 @@ void GMainWindow::ShowFullscreen() {
void GMainWindow::HideFullscreen() {
if (ui->action_Single_Window_Mode->isChecked()) {
- if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
+ if (UsingExclusiveFullscreen()) {
showNormal();
restoreGeometry(UISettings::values.geometry);
} else {
@@ -2956,7 +3168,7 @@ void GMainWindow::HideFullscreen() {
statusBar()->setVisible(ui->action_Show_Status_Bar->isChecked());
ui->menubar->show();
} else {
- if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Exclusive) {
+ if (UsingExclusiveFullscreen()) {
render_window->showNormal();
render_window->restoreGeometry(UISettings::values.renderwindow_geometry);
} else {
@@ -3293,6 +3505,38 @@ void GMainWindow::OpenPerGameConfiguration(u64 title_id, const std::string& file
}
}
+bool GMainWindow::CreateShortcut(const std::string& shortcut_path, const std::string& title,
+ const std::string& comment, const std::string& icon_path,
+ const std::string& command, const std::string& arguments,
+ const std::string& categories, const std::string& keywords) {
+#if defined(__linux__) || defined(__FreeBSD__)
+ // This desktop file template was writting referencing
+ // https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-1.0.html
+ std::string shortcut_contents{};
+ shortcut_contents.append("[Desktop Entry]\n");
+ shortcut_contents.append("Type=Application\n");
+ shortcut_contents.append("Version=1.0\n");
+ shortcut_contents.append(fmt::format("Name={:s}\n", title));
+ shortcut_contents.append(fmt::format("Comment={:s}\n", comment));
+ shortcut_contents.append(fmt::format("Icon={:s}\n", icon_path));
+ shortcut_contents.append(fmt::format("TryExec={:s}\n", command));
+ shortcut_contents.append(fmt::format("Exec={:s} {:s}\n", command, arguments));
+ shortcut_contents.append(fmt::format("Categories={:s}\n", categories));
+ shortcut_contents.append(fmt::format("Keywords={:s}\n", keywords));
+
+ std::ofstream shortcut_stream(shortcut_path);
+ if (!shortcut_stream.is_open()) {
+ LOG_WARNING(Common, "Failed to create file {:s}", shortcut_path);
+ return false;
+ }
+ shortcut_stream << shortcut_contents;
+ shortcut_stream.close();
+
+ return true;
+#endif
+ return false;
+}
+
void GMainWindow::OnLoadAmiibo() {
if (emu_thread == nullptr || !emu_thread->IsRunning()) {
return;
@@ -3710,79 +3954,6 @@ void GMainWindow::OnMouseActivity() {
mouse_center_timer.stop();
}
-void GMainWindow::OnCoreError(Core::SystemResultStatus result, std::string details) {
- QMessageBox::StandardButton answer;
- QString status_message;
- const QString common_message =
- tr("The game you are trying to load requires additional files from your Switch to be "
- "dumped "
- "before playing.<br/><br/>For more information on dumping these files, please see the "
- "following wiki page: <a "
- "href='https://yuzu-emu.org/wiki/"
- "dumping-system-archives-and-the-shared-fonts-from-a-switch-console/'>Dumping System "
- "Archives and the Shared Fonts from a Switch Console</a>.<br/><br/>Would you like to "
- "quit "
- "back to the game list? Continuing emulation may result in crashes, corrupted save "
- "data, or other bugs.");
- switch (result) {
- case Core::SystemResultStatus::ErrorSystemFiles: {
- QString message;
- if (details.empty()) {
- message =
- tr("yuzu was unable to locate a Switch system archive. %1").arg(common_message);
- } else {
- message = tr("yuzu was unable to locate a Switch system archive: %1. %2")
- .arg(QString::fromStdString(details), common_message);
- }
-
- answer = QMessageBox::question(this, tr("System Archive Not Found"), message,
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- status_message = tr("System Archive Missing");
- break;
- }
-
- case Core::SystemResultStatus::ErrorSharedFont: {
- const QString message =
- tr("yuzu was unable to locate the Switch shared fonts. %1").arg(common_message);
- answer = QMessageBox::question(this, tr("Shared Fonts Not Found"), message,
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- status_message = tr("Shared Font Missing");
- break;
- }
-
- default:
- answer = QMessageBox::question(
- this, tr("Fatal Error"),
- tr("yuzu has encountered a fatal error, please see the log for more details. "
- "For more information on accessing the log, please see the following page: "
- "<a href='https://community.citra-emu.org/t/how-to-upload-the-log-file/296'>How "
- "to "
- "Upload the Log File</a>.<br/><br/>Would you like to quit back to the game "
- "list? "
- "Continuing emulation may result in crashes, corrupted save data, or other "
- "bugs."),
- QMessageBox::Yes | QMessageBox::No, QMessageBox::No);
- status_message = tr("Fatal Error encountered");
- break;
- }
-
- if (answer == QMessageBox::Yes) {
- if (emu_thread) {
- ShutdownGame();
-
- Settings::RestoreGlobalState(system->IsPoweredOn());
- system->HIDCore().ReloadInputDevices();
- UpdateStatusButtons();
- }
- } else {
- // Only show the message if the game is still running.
- if (emu_thread) {
- emu_thread->SetRunning(true);
- message_label->setText(status_message);
- }
- }
-}
-
void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
if (behavior == ReinitializeKeyBehavior::Warning) {
const auto res = QMessageBox::information(
@@ -3927,10 +4098,6 @@ void GMainWindow::closeEvent(QCloseEvent* event) {
// Shutdown session if the emu thread is active...
if (emu_thread != nullptr) {
ShutdownGame();
-
- Settings::RestoreGlobalState(system->IsPoweredOn());
- system->HIDCore().ReloadInputDevices();
- UpdateStatusButtons();
}
render_window->close();
@@ -4023,6 +4190,10 @@ bool GMainWindow::ConfirmForceLockedExit() {
}
void GMainWindow::RequestGameExit() {
+ if (!system->IsPoweredOn()) {
+ return;
+ }
+
auto& sm{system->ServiceManager()};
auto applet_oe = sm.GetService<Service::AM::AppletOE>("appletOE");
auto applet_ae = sm.GetService<Service::AM::AppletAE>("appletAE");
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 62d629973..db318485d 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -29,6 +29,7 @@ class GImageInfo;
class GRenderWindow;
class LoadingScreen;
class MicroProfileDialog;
+class OverlayDialog;
class ProfilerWidget;
class ControllerDialog;
class QLabel;
@@ -38,6 +39,7 @@ class QProgressDialog;
class WaitTreeWidget;
enum class GameListOpenTarget;
enum class GameListRemoveTarget;
+enum class GameListShortcutTarget;
enum class DumpRomFSTarget;
enum class InstalledEntryType;
class GameListPlaceholder;
@@ -293,6 +295,8 @@ private slots:
void OnGameListCopyTID(u64 program_id);
void OnGameListNavigateToGamedbEntry(u64 program_id,
const CompatibilityList& compatibility_list);
+ void OnGameListCreateShortcut(u64 program_id, const std::string& game_path,
+ GameListShortcutTarget target);
void OnGameListOpenDirectory(const QString& directory);
void OnGameListAddDirectory();
void OnGameListShowList(bool show);
@@ -320,6 +324,7 @@ private slots:
void OnDisplayTitleBars(bool);
void InitializeHotkeys();
void ToggleFullscreen();
+ bool UsingExclusiveFullscreen();
void ShowFullscreen();
void HideFullscreen();
void ToggleWindowMode();
@@ -328,10 +333,13 @@ private slots:
void ResetWindowSize900();
void ResetWindowSize1080();
void OnCaptureScreenshot();
- void OnCoreError(Core::SystemResultStatus, std::string);
void OnReinitializeKeys(ReinitializeKeyBehavior behavior);
void OnLanguageChanged(const QString& locale);
void OnMouseActivity();
+ bool OnShutdownBegin();
+ void OnShutdownBeginDialog();
+ void OnEmulationStopped();
+ void OnEmulationStopTimeExpired();
private:
QString GetGameListErrorRemoving(InstalledEntryType type) const;
@@ -365,6 +373,10 @@ private:
bool CheckDarkMode();
QString GetTasStateDescription() const;
+ bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
+ const std::string& comment, const std::string& icon_path,
+ const std::string& command, const std::string& arguments,
+ const std::string& categories, const std::string& keywords);
std::unique_ptr<Ui::MainWindow> ui;
@@ -377,6 +389,8 @@ private:
GRenderWindow* render_window;
GameList* game_list;
LoadingScreen* loading_screen;
+ QTimer shutdown_timer;
+ OverlayDialog* shutdown_dialog{};
GameListPlaceholder* game_list_placeholder;
diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp
index 563818362..9f702fe95 100644
--- a/src/yuzu/startup_checks.cpp
+++ b/src/yuzu/startup_checks.cpp
@@ -186,7 +186,7 @@ pid_t SpawnChild(const char* arg0) {
return pid;
} else if (pid == 0) {
// child
- execl(arg0, arg0, nullptr);
+ execlp(arg0, arg0, nullptr);
const int err = errno;
fmt::print(stderr, "execl failed with error {}\n", err);
_exit(0);
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index 452038cd9..2006b883e 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -138,6 +138,7 @@ struct Values {
bool configuration_applied;
bool reset_to_defaults;
+ bool shortcut_already_warned{false};
Settings::Setting<bool> disable_web_applet{true, "disable_web_applet"};
};
diff --git a/src/yuzu/util/overlay_dialog.cpp b/src/yuzu/util/overlay_dialog.cpp
index b27954512..796f5bf41 100644
--- a/src/yuzu/util/overlay_dialog.cpp
+++ b/src/yuzu/util/overlay_dialog.cpp
@@ -3,6 +3,7 @@
#include <QKeyEvent>
#include <QScreen>
+#include <QWindow>
#include "core/core.h"
#include "core/hid/hid_types.h"
@@ -42,7 +43,7 @@ OverlayDialog::OverlayDialog(QWidget* parent, Core::System& system, const QStrin
MoveAndResizeWindow();
// TODO (Morph): Remove this when InputInterpreter no longer relies on the HID backend
- if (system.IsPoweredOn()) {
+ if (system.IsPoweredOn() && !ui->buttonsDialog->isHidden()) {
input_interpreter = std::make_unique<InputInterpreter>(system);
StartInputThread();
@@ -83,6 +84,11 @@ void OverlayDialog::InitializeRegularTextDialog(const QString& title_text, const
ui->button_ok_label->setEnabled(false);
}
+ if (ui->button_cancel->isHidden() && ui->button_ok_label->isHidden()) {
+ ui->buttonsDialog->hide();
+ return;
+ }
+
connect(
ui->button_cancel, &QPushButton::clicked, this,
[this](bool) {
@@ -130,6 +136,11 @@ void OverlayDialog::InitializeRichTextDialog(const QString& title_text, const QS
ui->button_ok_rich->setEnabled(false);
}
+ if (ui->button_cancel_rich->isHidden() && ui->button_ok_rich->isHidden()) {
+ ui->buttonsRichDialog->hide();
+ return;
+ }
+
connect(
ui->button_cancel_rich, &QPushButton::clicked, this,
[this](bool) {
@@ -152,7 +163,7 @@ void OverlayDialog::MoveAndResizeWindow() {
const auto height = static_cast<float>(parentWidget()->height());
// High DPI
- const float dpi_scale = qApp->screenAt(pos)->logicalDotsPerInch() / 96.0f;
+ const float dpi_scale = parentWidget()->windowHandle()->screen()->logicalDotsPerInch() / 96.0f;
const auto title_text_font_size = BASE_TITLE_FONT_SIZE * (height / BASE_HEIGHT) / dpi_scale;
const auto body_text_font_size =
@@ -249,3 +260,9 @@ void OverlayDialog::InputThread() {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
}
+
+void OverlayDialog::keyPressEvent(QKeyEvent* e) {
+ if (!ui->buttonsDialog->isHidden() || e->key() != Qt::Key_Escape) {
+ QDialog::keyPressEvent(e);
+ }
+}
diff --git a/src/yuzu/util/overlay_dialog.h b/src/yuzu/util/overlay_dialog.h
index 39c44393c..872283d61 100644
--- a/src/yuzu/util/overlay_dialog.h
+++ b/src/yuzu/util/overlay_dialog.h
@@ -94,6 +94,7 @@ private:
/// The thread where input is being polled and processed.
void InputThread();
+ void keyPressEvent(QKeyEvent* e) override;
std::unique_ptr<Ui::OverlayDialog> ui;
diff --git a/src/yuzu_cmd/CMakeLists.txt b/src/yuzu_cmd/CMakeLists.txt
index f6eeb9d8d..61b6cc4e0 100644
--- a/src/yuzu_cmd/CMakeLists.txt
+++ b/src/yuzu_cmd/CMakeLists.txt
@@ -49,6 +49,15 @@ if(UNIX AND NOT APPLE)
install(TARGETS yuzu-cmd)
endif()
+if(WIN32)
+ # compile as a win32 gui application instead of a console application
+ if(MSVC)
+ set_target_properties(yuzu-cmd PROPERTIES LINK_FLAGS_RELEASE "/SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup")
+ elseif(MINGW)
+ set_target_properties(yuzu-cmd PROPERTIES LINK_FLAGS_RELEASE "-Wl,--subsystem,windows")
+ endif()
+endif()
+
if (MSVC)
include(CopyYuzuSDLDeps)
copy_yuzu_SDL_deps(yuzu-cmd)
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 37dd1747c..31f28a507 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -115,7 +115,7 @@ bool EmuWindow_SDL2::IsShown() const {
void EmuWindow_SDL2::OnResize() {
int width, height;
- SDL_GetWindowSize(render_window, &width, &height);
+ SDL_GL_GetDrawableSize(render_window, &width, &height);
UpdateCurrentFramebufferLayout(width, height);
}
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
index 9b660c13c..ddcb048d6 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp
@@ -104,6 +104,8 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
exit(1);
}
+ strict_context_required = strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0;
+
SetWindowIcon();
if (fullscreen) {
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index a80649703..91133569d 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -174,6 +174,13 @@ static void OnStatusMessageReceived(const Network::StatusMessageEntry& msg) {
/// Application entry point
int main(int argc, char** argv) {
+#ifdef _WIN32
+ if (AttachConsole(ATTACH_PARENT_PROCESS)) {
+ freopen("CONOUT$", "wb", stdout);
+ freopen("CONOUT$", "wb", stderr);
+ }
+#endif
+
Common::Log::Initialize();
Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();