summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/CMakeLists.txt3
-rw-r--r--src/audio_core/renderer/system.cpp5
-rw-r--r--src/audio_core/sink/sdl2_sink.cpp11
-rw-r--r--src/common/address_space.inc4
-rw-r--r--src/common/input.h2
-rw-r--r--src/common/intrusive_list.h631
-rw-r--r--src/common/scratch_buffer.h9
-rw-r--r--src/common/settings.cpp11
-rw-r--r--src/common/settings.h19
-rw-r--r--src/common/vector_math.h14
-rw-r--r--src/core/CMakeLists.txt21
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h2
-rw-r--r--src/core/file_sys/savedata_factory.cpp4
-rw-r--r--src/core/file_sys/vfs_layered.cpp9
-rw-r--r--src/core/file_sys/vfs_vector.cpp19
-rw-r--r--src/core/file_sys/vfs_vector.h4
-rw-r--r--src/core/frontend/applets/cabinet.cpp2
-rw-r--r--src/core/frontend/applets/cabinet.h10
-rw-r--r--src/core/hid/emulated_controller.cpp45
-rw-r--r--src/core/hid/emulated_controller.h9
-rw-r--r--src/core/hid/input_converter.cpp10
-rw-r--r--src/core/hid/motion_input.cpp57
-rw-r--r--src/core/hid/motion_input.h13
-rw-r--r--src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp7
-rw-r--r--src/core/hle/kernel/k_auto_object.h4
-rw-r--r--src/core/hle/kernel/k_event_info.h5
-rw-r--r--src/core/hle/kernel/k_object_name.h8
-rw-r--r--src/core/hle/kernel/k_server_port.h4
-rw-r--r--src/core/hle/kernel/k_server_session.h7
-rw-r--r--src/core/hle/kernel/k_session_request.h4
-rw-r--r--src/core/hle/kernel/k_shared_memory_info.h4
-rw-r--r--src/core/hle/kernel/k_thread.h13
-rw-r--r--src/core/hle/kernel/kernel.cpp31
-rw-r--r--src/core/hle/service/am/am.cpp33
-rw-r--r--src/core/hle/service/am/am.h1
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.cpp24
-rw-r--r--src/core/hle/service/am/applets/applet_cabinet.h6
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp17
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h1
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp8
-rw-r--r--src/core/hle/service/hid/controllers/npad.h4
-rw-r--r--src/core/hle/service/hid/hid.cpp20
-rw-r--r--src/core/hle/service/ipc_helpers.h1
-rw-r--r--src/core/hle/service/kernel_helpers.cpp3
-rw-r--r--src/core/hle/service/mutex.cpp3
-rw-r--r--src/core/hle/service/nfc/common/amiibo_crypto.cpp (renamed from src/core/hle/service/nfp/amiibo_crypto.cpp)4
-rw-r--r--src/core/hle/service/nfc/common/amiibo_crypto.h (renamed from src/core/hle/service/nfp/amiibo_crypto.h)4
-rw-r--r--src/core/hle/service/nfc/common/device.cpp (renamed from src/core/hle/service/nfp/nfp_device.cpp)695
-rw-r--r--src/core/hle/service/nfc/common/device.h138
-rw-r--r--src/core/hle/service/nfc/common/device_manager.cpp695
-rw-r--r--src/core/hle/service/nfc/common/device_manager.h100
-rw-r--r--src/core/hle/service/nfc/mifare_result.h17
-rw-r--r--src/core/hle/service/nfc/mifare_types.h63
-rw-r--r--src/core/hle/service/nfc/mifare_user.cpp400
-rw-r--r--src/core/hle/service/nfc/mifare_user.h52
-rw-r--r--src/core/hle/service/nfc/nfc.cpp159
-rw-r--r--src/core/hle/service/nfc/nfc_device.cpp288
-rw-r--r--src/core/hle/service/nfc/nfc_device.h78
-rw-r--r--src/core/hle/service/nfc/nfc_interface.cpp382
-rw-r--r--src/core/hle/service/nfc/nfc_interface.h (renamed from src/core/hle/service/nfc/nfc_user.h)33
-rw-r--r--src/core/hle/service/nfc/nfc_result.h33
-rw-r--r--src/core/hle/service/nfc/nfc_types.h90
-rw-r--r--src/core/hle/service/nfc/nfc_user.cpp365
-rw-r--r--src/core/hle/service/nfp/nfp.cpp36
-rw-r--r--src/core/hle/service/nfp/nfp_device.h120
-rw-r--r--src/core/hle/service/nfp/nfp_interface.cpp839
-rw-r--r--src/core/hle/service/nfp/nfp_interface.h37
-rw-r--r--src/core/hle/service/nfp/nfp_result.h29
-rw-r--r--src/core/hle/service/nfp/nfp_types.h129
-rw-r--r--src/core/hle/service/nifm/nifm.cpp6
-rw-r--r--src/core/hle/service/nvnflinger/buffer_queue_producer.cpp5
-rw-r--r--src/core/hle/service/nvnflinger/parcel.h72
-rw-r--r--src/core/hle/service/server_manager.cpp6
-rw-r--r--src/core/hle/service/service.h20
-rw-r--r--src/core/hle/service/sm/sm.cpp3
-rw-r--r--src/core/hle/service/sm/sm_controller.cpp3
-rw-r--r--src/core/hle/service/time/clock_types.h12
-rw-r--r--src/core/hle/service/time/time_sharedmemory.cpp19
-rw-r--r--src/core/hle/service/time/time_sharedmemory.h5
-rw-r--r--src/core/hle/service/vi/vi.cpp12
-rw-r--r--src/core/internal_network/network.cpp2
-rw-r--r--src/core/internal_network/network_interface.cpp2
-rw-r--r--src/core/memory.cpp25
-rw-r--r--src/core/telemetry_session.cpp17
-rw-r--r--src/input_common/drivers/sdl_driver.cpp35
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.cpp7
-rw-r--r--src/input_common/helpers/joycon_protocol/common_protocol.h2
-rw-r--r--src/input_common/helpers/joycon_protocol/joycon_types.h8
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.cpp34
-rw-r--r--src/input_common/helpers/joycon_protocol/nfc.h6
-rw-r--r--src/input_common/input_mapping.cpp3
-rw-r--r--src/input_common/input_poller.cpp3
-rw-r--r--src/shader_recompiler/backend/spirv/emit_spirv_context_get_set.cpp11
-rw-r--r--src/shader_recompiler/backend/spirv/spirv_emit_context.cpp3
-rw-r--r--src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp7
-rw-r--r--src/tests/CMakeLists.txt2
-rw-r--r--src/tests/video_core/buffer_base.cpp549
-rw-r--r--src/tests/video_core/memory_tracker.cpp549
-rw-r--r--src/video_core/CMakeLists.txt7
-rw-r--r--src/video_core/buffer_cache/buffer_base.h527
-rw-r--r--src/video_core/buffer_cache/buffer_cache.cpp4
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h992
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h576
-rw-r--r--src/video_core/buffer_cache/memory_tracker_base.h299
-rw-r--r--src/video_core/buffer_cache/word_manager.h485
-rw-r--r--src/video_core/compatible_formats.cpp20
-rw-r--r--src/video_core/engines/maxwell_3d.cpp3
-rw-r--r--src/video_core/engines/maxwell_dma.cpp8
-rw-r--r--src/video_core/engines/sw_blitter/blitter.cpp25
-rw-r--r--src/video_core/fence_manager.h148
-rw-r--r--src/video_core/gpu.cpp19
-rw-r--r--src/video_core/gpu.h4
-rw-r--r--src/video_core/host1x/codecs/h264.cpp4
-rw-r--r--src/video_core/memory_manager.cpp13
-rw-r--r--src/video_core/memory_manager.h4
-rw-r--r--src/video_core/query_cache.h139
-rw-r--r--src/video_core/rasterizer_download_area.h16
-rw-r--r--src/video_core/rasterizer_interface.h3
-rw-r--r--src/video_core/renderer_null/null_rasterizer.cpp10
-rw-r--r--src/video_core/renderer_null/null_rasterizer.h1
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h4
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache_base.cpp9
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp3
-rw-r--r--src/video_core/renderer_opengl/gl_fence_manager.h12
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.cpp12
-rw-r--r--src/video_core/renderer_opengl/gl_query_cache.h6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp33
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h1
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp56
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h5
-rw-r--r--src/video_core/renderer_opengl/maxwell_to_gl.h3
-rw-r--r--src/video_core/renderer_vulkan/maxwell_to_vk.cpp3
-rw-r--r--src/video_core/renderer_vulkan/pipeline_helper.h6
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.cpp53
-rw-r--r--src/video_core/renderer_vulkan/renderer_vulkan.h2
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp224
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h34
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp18
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h19
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache_base.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.cpp39
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pass.h14
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.cpp12
-rw-r--r--src/video_core/renderer_vulkan/vk_compute_pipeline.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.cpp1
-rw-r--r--src/video_core/renderer_vulkan/vk_fence_manager.h11
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp10
-rw-r--r--src/video_core/renderer_vulkan/vk_graphics_pipeline.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.cpp16
-rw-r--r--src/video_core/renderer_vulkan/vk_pipeline_cache.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.cpp457
-rw-r--r--src/video_core/renderer_vulkan/vk_present_manager.h83
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.cpp15
-rw-r--r--src/video_core/renderer_vulkan/vk_query_cache.h5
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp52
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.cpp9
-rw-r--r--src/video_core/renderer_vulkan/vk_scheduler.h6
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.cpp124
-rw-r--r--src/video_core/renderer_vulkan/vk_swapchain.h37
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp73
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h13
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.cpp11
-rw-r--r--src/video_core/renderer_vulkan/vk_update_descriptor.h14
-rw-r--r--src/video_core/shader_cache.cpp4
-rw-r--r--src/video_core/shader_environment.cpp16
-rw-r--r--src/video_core/shader_environment.h6
-rw-r--r--src/video_core/surface.cpp5
-rw-r--r--src/video_core/surface.h12
-rw-r--r--src/video_core/texture_cache/format_lookup_table.cpp6
-rw-r--r--src/video_core/texture_cache/formatter.cpp22
-rw-r--r--src/video_core/texture_cache/formatter.h8
-rw-r--r--src/video_core/texture_cache/image_info.cpp12
-rw-r--r--src/video_core/texture_cache/image_info.h2
-rw-r--r--src/video_core/texture_cache/image_view_base.cpp12
-rw-r--r--src/video_core/texture_cache/image_view_base.h7
-rw-r--r--src/video_core/texture_cache/texture_cache.h189
-rw-r--r--src/video_core/texture_cache/texture_cache_base.h37
-rw-r--r--src/video_core/texture_cache/util.cpp10
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp6
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h3
-rw-r--r--src/video_core/vulkan_common/vulkan_memory_allocator.cpp2
-rw-r--r--src/video_core/vulkan_common/vulkan_surface.cpp6
-rw-r--r--src/video_core/vulkan_common/vulkan_surface.h9
-rw-r--r--src/yuzu/CMakeLists.txt2
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.cpp12
-rw-r--r--src/yuzu/applets/qt_amiibo_settings.h14
-rw-r--r--src/yuzu/applets/qt_profile_select.cpp1
-rw-r--r--src/yuzu/bootmanager.cpp90
-rw-r--r--src/yuzu/bootmanager.h25
-rw-r--r--src/yuzu/configuration/config.cpp24
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp3
-rw-r--r--src/yuzu/configuration/configure_dialog.h2
-rw-r--r--src/yuzu/configuration/configure_general.cpp9
-rw-r--r--src/yuzu/configuration/configure_general.h1
-rw-r--r--src/yuzu/configuration/configure_general.ui7
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp187
-rw-r--r--src/yuzu/configuration/configure_graphics.h29
-rw-r--r--src/yuzu/configuration/configure_graphics.ui40
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.cpp44
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.h6
-rw-r--r--src/yuzu/configuration/configure_graphics_advanced.ui36
-rw-r--r--src/yuzu/configuration/configure_input_advanced.cpp2
-rw-r--r--src/yuzu/configuration/configure_input_advanced.ui22
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp14
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp91
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.h5
-rw-r--r--src/yuzu/configuration/configure_per_game.cpp3
-rw-r--r--src/yuzu/configuration/configure_per_game.h2
-rw-r--r--src/yuzu/configuration/configure_system.cpp10
-rw-r--r--src/yuzu/configuration/configure_system.h1
-rw-r--r--src/yuzu/configuration/configure_system.ui7
-rw-r--r--src/yuzu/main.cpp39
-rw-r--r--src/yuzu/main.h9
-rw-r--r--src/yuzu/qt_common.cpp55
-rw-r--r--src/yuzu/qt_common.h15
-rw-r--r--src/yuzu_cmd/config.cpp20
-rw-r--r--src/yuzu_cmd/default_ini.h26
219 files changed, 8112 insertions, 5301 deletions
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 312a49f42..5e3a74c0f 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -113,6 +113,9 @@ else()
$<$<CXX_COMPILER_ID:Clang>:-Wno-braced-scalar-init>
$<$<CXX_COMPILER_ID:Clang>:-Wno-unused-private-field>
+ $<$<CXX_COMPILER_ID:Clang>:-Werror=shadow-uncaptured-local>
+ $<$<CXX_COMPILER_ID:Clang>:-Werror=implicit-fallthrough>
+ $<$<CXX_COMPILER_ID:Clang>:-Werror=type-limits>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-braced-scalar-init>
$<$<CXX_COMPILER_ID:AppleClang>:-Wno-unused-private-field>
)
diff --git a/src/audio_core/renderer/system.cpp b/src/audio_core/renderer/system.cpp
index ad869facb..53b258c4f 100644
--- a/src/audio_core/renderer/system.cpp
+++ b/src/audio_core/renderer/system.cpp
@@ -436,10 +436,7 @@ void System::Stop() {
}
if (execution_mode == ExecutionMode::Auto) {
- // Should wait for the system to terminate here, but core timing (should have) already
- // stopped, so this isn't needed. Find a way to make this definite.
-
- // terminate_event.Wait();
+ terminate_event.Wait();
}
}
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp
index ee1a0652f..c1529d1f9 100644
--- a/src/audio_core/sink/sdl2_sink.cpp
+++ b/src/audio_core/sink/sdl2_sink.cpp
@@ -3,6 +3,7 @@
#include <span>
#include <vector>
+#include <SDL.h>
#include "audio_core/common/common.h"
#include "audio_core/sink/sdl2_sink.h"
@@ -10,16 +11,6 @@
#include "common/logging/log.h"
#include "core/core.h"
-// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
-#endif
-#include <SDL.h>
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-
namespace AudioCore::Sink {
/**
* SDL sink stream, responsible for sinking samples to hardware.
diff --git a/src/common/address_space.inc b/src/common/address_space.inc
index 2195dabd5..1ee82df53 100644
--- a/src/common/address_space.inc
+++ b/src/common/address_space.inc
@@ -72,7 +72,7 @@ MAP_MEMBER(void)::MapLocked(VaType virt, PaType phys, VaType size, ExtraBlockInf
}
}()};
- if (block_end_predecessor->virt >= virt) {
+ if (block_end_predecessor != blocks.begin() && block_end_predecessor->virt >= virt) {
// If this block's start would be overlapped by the map then reuse it as a tail
// block
block_end_predecessor->virt = virt_end;
@@ -336,7 +336,7 @@ ALLOC_MEMBER(VaType)::Allocate(VaType size) {
ASSERT_MSG(false, "Unexpected allocator state!");
}
- auto search_predecessor{this->blocks.begin()};
+ auto search_predecessor{std::next(this->blocks.begin())};
auto search_successor{std::next(search_predecessor)};
while (search_successor != this->blocks.end() &&
diff --git a/src/common/input.h b/src/common/input.h
index 51b277c1f..66fb15f0a 100644
--- a/src/common/input.h
+++ b/src/common/input.h
@@ -111,6 +111,8 @@ struct AnalogProperties {
float offset{};
// Invert direction of the sensor data
bool inverted{};
+ // Invert the state if it's converted to a button
+ bool inverted_button{};
// Press once to activate, press again to release
bool toggle{};
};
diff --git a/src/common/intrusive_list.h b/src/common/intrusive_list.h
new file mode 100644
index 000000000..d330dc1c2
--- /dev/null
+++ b/src/common/intrusive_list.h
@@ -0,0 +1,631 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include "common/common_funcs.h"
+#include "common/parent_of_member.h"
+
+namespace Common {
+
+// Forward declare implementation class for Node.
+namespace impl {
+
+class IntrusiveListImpl;
+
+}
+
+class IntrusiveListNode {
+ YUZU_NON_COPYABLE(IntrusiveListNode);
+
+private:
+ friend class impl::IntrusiveListImpl;
+
+ IntrusiveListNode* m_prev;
+ IntrusiveListNode* m_next;
+
+public:
+ constexpr IntrusiveListNode() : m_prev(this), m_next(this) {}
+
+ constexpr bool IsLinked() const {
+ return m_next != this;
+ }
+
+private:
+ constexpr void LinkPrev(IntrusiveListNode* node) {
+ // We can't link an already linked node.
+ ASSERT(!node->IsLinked());
+ this->SplicePrev(node, node);
+ }
+
+ constexpr void SplicePrev(IntrusiveListNode* first, IntrusiveListNode* last) {
+ // Splice a range into the list.
+ auto last_prev = last->m_prev;
+ first->m_prev = m_prev;
+ last_prev->m_next = this;
+ m_prev->m_next = first;
+ m_prev = last_prev;
+ }
+
+ constexpr void LinkNext(IntrusiveListNode* node) {
+ // We can't link an already linked node.
+ ASSERT(!node->IsLinked());
+ return this->SpliceNext(node, node);
+ }
+
+ constexpr void SpliceNext(IntrusiveListNode* first, IntrusiveListNode* last) {
+ // Splice a range into the list.
+ auto last_prev = last->m_prev;
+ first->m_prev = this;
+ last_prev->m_next = m_next;
+ m_next->m_prev = last_prev;
+ m_next = first;
+ }
+
+ constexpr void Unlink() {
+ this->Unlink(m_next);
+ }
+
+ constexpr void Unlink(IntrusiveListNode* last) {
+ // Unlink a node from a next node.
+ auto last_prev = last->m_prev;
+ m_prev->m_next = last;
+ last->m_prev = m_prev;
+ last_prev->m_next = this;
+ m_prev = last_prev;
+ }
+
+ constexpr IntrusiveListNode* GetPrev() {
+ return m_prev;
+ }
+
+ constexpr const IntrusiveListNode* GetPrev() const {
+ return m_prev;
+ }
+
+ constexpr IntrusiveListNode* GetNext() {
+ return m_next;
+ }
+
+ constexpr const IntrusiveListNode* GetNext() const {
+ return m_next;
+ }
+};
+// DEPRECATED: static_assert(std::is_literal_type<IntrusiveListNode>::value);
+
+namespace impl {
+
+class IntrusiveListImpl {
+ YUZU_NON_COPYABLE(IntrusiveListImpl);
+
+private:
+ IntrusiveListNode m_root_node;
+
+public:
+ template <bool Const>
+ class Iterator;
+
+ using value_type = IntrusiveListNode;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = Iterator<false>;
+ using const_iterator = Iterator<true>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ template <bool Const>
+ class Iterator {
+ public:
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = typename IntrusiveListImpl::value_type;
+ using difference_type = typename IntrusiveListImpl::difference_type;
+ using pointer =
+ std::conditional_t<Const, IntrusiveListImpl::const_pointer, IntrusiveListImpl::pointer>;
+ using reference = std::conditional_t<Const, IntrusiveListImpl::const_reference,
+ IntrusiveListImpl::reference>;
+
+ private:
+ pointer m_node;
+
+ public:
+ constexpr explicit Iterator(pointer n) : m_node(n) {}
+
+ constexpr bool operator==(const Iterator& rhs) const {
+ return m_node == rhs.m_node;
+ }
+
+ constexpr pointer operator->() const {
+ return m_node;
+ }
+
+ constexpr reference operator*() const {
+ return *m_node;
+ }
+
+ constexpr Iterator& operator++() {
+ m_node = m_node->m_next;
+ return *this;
+ }
+
+ constexpr Iterator& operator--() {
+ m_node = m_node->m_prev;
+ return *this;
+ }
+
+ constexpr Iterator operator++(int) {
+ const Iterator it{*this};
+ ++(*this);
+ return it;
+ }
+
+ constexpr Iterator operator--(int) {
+ const Iterator it{*this};
+ --(*this);
+ return it;
+ }
+
+ constexpr operator Iterator<true>() const {
+ return Iterator<true>(m_node);
+ }
+
+ constexpr Iterator<false> GetNonConstIterator() const {
+ return Iterator<false>(const_cast<IntrusiveListImpl::pointer>(m_node));
+ }
+ };
+
+public:
+ constexpr IntrusiveListImpl() : m_root_node() {}
+
+ // Iterator accessors.
+ constexpr iterator begin() {
+ return iterator(m_root_node.GetNext());
+ }
+
+ constexpr const_iterator begin() const {
+ return const_iterator(m_root_node.GetNext());
+ }
+
+ constexpr iterator end() {
+ return iterator(std::addressof(m_root_node));
+ }
+
+ constexpr const_iterator end() const {
+ return const_iterator(std::addressof(m_root_node));
+ }
+
+ constexpr iterator iterator_to(reference v) {
+ // Only allow iterator_to for values in lists.
+ ASSERT(v.IsLinked());
+ return iterator(std::addressof(v));
+ }
+
+ constexpr const_iterator iterator_to(const_reference v) const {
+ // Only allow iterator_to for values in lists.
+ ASSERT(v.IsLinked());
+ return const_iterator(std::addressof(v));
+ }
+
+ // Content management.
+ constexpr bool empty() const {
+ return !m_root_node.IsLinked();
+ }
+
+ constexpr size_type size() const {
+ return static_cast<size_type>(std::distance(this->begin(), this->end()));
+ }
+
+ constexpr reference back() {
+ return *m_root_node.GetPrev();
+ }
+
+ constexpr const_reference back() const {
+ return *m_root_node.GetPrev();
+ }
+
+ constexpr reference front() {
+ return *m_root_node.GetNext();
+ }
+
+ constexpr const_reference front() const {
+ return *m_root_node.GetNext();
+ }
+
+ constexpr void push_back(reference node) {
+ m_root_node.LinkPrev(std::addressof(node));
+ }
+
+ constexpr void push_front(reference node) {
+ m_root_node.LinkNext(std::addressof(node));
+ }
+
+ constexpr void pop_back() {
+ m_root_node.GetPrev()->Unlink();
+ }
+
+ constexpr void pop_front() {
+ m_root_node.GetNext()->Unlink();
+ }
+
+ constexpr iterator insert(const_iterator pos, reference node) {
+ pos.GetNonConstIterator()->LinkPrev(std::addressof(node));
+ return iterator(std::addressof(node));
+ }
+
+ constexpr void splice(const_iterator pos, IntrusiveListImpl& o) {
+ splice_impl(pos, o.begin(), o.end());
+ }
+
+ constexpr void splice(const_iterator pos, IntrusiveListImpl& o, const_iterator first) {
+ const_iterator last(first);
+ std::advance(last, 1);
+ splice_impl(pos, first, last);
+ }
+
+ constexpr void splice(const_iterator pos, IntrusiveListImpl& o, const_iterator first,
+ const_iterator last) {
+ splice_impl(pos, first, last);
+ }
+
+ constexpr iterator erase(const_iterator pos) {
+ if (pos == this->end()) {
+ return this->end();
+ }
+ iterator it(pos.GetNonConstIterator());
+ (it++)->Unlink();
+ return it;
+ }
+
+ constexpr void clear() {
+ while (!this->empty()) {
+ this->pop_front();
+ }
+ }
+
+private:
+ constexpr void splice_impl(const_iterator _pos, const_iterator _first, const_iterator _last) {
+ if (_first == _last) {
+ return;
+ }
+ iterator pos(_pos.GetNonConstIterator());
+ iterator first(_first.GetNonConstIterator());
+ iterator last(_last.GetNonConstIterator());
+ first->Unlink(std::addressof(*last));
+ pos->SplicePrev(std::addressof(*first), std::addressof(*first));
+ }
+};
+
+} // namespace impl
+
+template <class T, class Traits>
+class IntrusiveList {
+ YUZU_NON_COPYABLE(IntrusiveList);
+
+private:
+ impl::IntrusiveListImpl m_impl;
+
+public:
+ template <bool Const>
+ class Iterator;
+
+ using value_type = T;
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using pointer = value_type*;
+ using const_pointer = const value_type*;
+ using reference = value_type&;
+ using const_reference = const value_type&;
+ using iterator = Iterator<false>;
+ using const_iterator = Iterator<true>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+
+ template <bool Const>
+ class Iterator {
+ public:
+ friend class Common::IntrusiveList<T, Traits>;
+
+ using ImplIterator =
+ std::conditional_t<Const, Common::impl::IntrusiveListImpl::const_iterator,
+ Common::impl::IntrusiveListImpl::iterator>;
+
+ using iterator_category = std::bidirectional_iterator_tag;
+ using value_type = typename IntrusiveList::value_type;
+ using difference_type = typename IntrusiveList::difference_type;
+ using pointer =
+ std::conditional_t<Const, IntrusiveList::const_pointer, IntrusiveList::pointer>;
+ using reference =
+ std::conditional_t<Const, IntrusiveList::const_reference, IntrusiveList::reference>;
+
+ private:
+ ImplIterator m_iterator;
+
+ private:
+ constexpr explicit Iterator(ImplIterator it) : m_iterator(it) {}
+
+ constexpr ImplIterator GetImplIterator() const {
+ return m_iterator;
+ }
+
+ public:
+ constexpr bool operator==(const Iterator& rhs) const {
+ return m_iterator == rhs.m_iterator;
+ }
+
+ constexpr pointer operator->() const {
+ return std::addressof(Traits::GetParent(*m_iterator));
+ }
+
+ constexpr reference operator*() const {
+ return Traits::GetParent(*m_iterator);
+ }
+
+ constexpr Iterator& operator++() {
+ ++m_iterator;
+ return *this;
+ }
+
+ constexpr Iterator& operator--() {
+ --m_iterator;
+ return *this;
+ }
+
+ constexpr Iterator operator++(int) {
+ const Iterator it{*this};
+ ++m_iterator;
+ return it;
+ }
+
+ constexpr Iterator operator--(int) {
+ const Iterator it{*this};
+ --m_iterator;
+ return it;
+ }
+
+ constexpr operator Iterator<true>() const {
+ return Iterator<true>(m_iterator);
+ }
+ };
+
+private:
+ static constexpr IntrusiveListNode& GetNode(reference ref) {
+ return Traits::GetNode(ref);
+ }
+
+ static constexpr IntrusiveListNode const& GetNode(const_reference ref) {
+ return Traits::GetNode(ref);
+ }
+
+ static constexpr reference GetParent(IntrusiveListNode& node) {
+ return Traits::GetParent(node);
+ }
+
+ static constexpr const_reference GetParent(IntrusiveListNode const& node) {
+ return Traits::GetParent(node);
+ }
+
+public:
+ constexpr IntrusiveList() : m_impl() {}
+
+ // Iterator accessors.
+ constexpr iterator begin() {
+ return iterator(m_impl.begin());
+ }
+
+ constexpr const_iterator begin() const {
+ return const_iterator(m_impl.begin());
+ }
+
+ constexpr iterator end() {
+ return iterator(m_impl.end());
+ }
+
+ constexpr const_iterator end() const {
+ return const_iterator(m_impl.end());
+ }
+
+ constexpr const_iterator cbegin() const {
+ return this->begin();
+ }
+
+ constexpr const_iterator cend() const {
+ return this->end();
+ }
+
+ constexpr reverse_iterator rbegin() {
+ return reverse_iterator(this->end());
+ }
+
+ constexpr const_reverse_iterator rbegin() const {
+ return const_reverse_iterator(this->end());
+ }
+
+ constexpr reverse_iterator rend() {
+ return reverse_iterator(this->begin());
+ }
+
+ constexpr const_reverse_iterator rend() const {
+ return const_reverse_iterator(this->begin());
+ }
+
+ constexpr const_reverse_iterator crbegin() const {
+ return this->rbegin();
+ }
+
+ constexpr const_reverse_iterator crend() const {
+ return this->rend();
+ }
+
+ constexpr iterator iterator_to(reference v) {
+ return iterator(m_impl.iterator_to(GetNode(v)));
+ }
+
+ constexpr const_iterator iterator_to(const_reference v) const {
+ return const_iterator(m_impl.iterator_to(GetNode(v)));
+ }
+
+ // Content management.
+ constexpr bool empty() const {
+ return m_impl.empty();
+ }
+
+ constexpr size_type size() const {
+ return m_impl.size();
+ }
+
+ constexpr reference back() {
+ return GetParent(m_impl.back());
+ }
+
+ constexpr const_reference back() const {
+ return GetParent(m_impl.back());
+ }
+
+ constexpr reference front() {
+ return GetParent(m_impl.front());
+ }
+
+ constexpr const_reference front() const {
+ return GetParent(m_impl.front());
+ }
+
+ constexpr void push_back(reference ref) {
+ m_impl.push_back(GetNode(ref));
+ }
+
+ constexpr void push_front(reference ref) {
+ m_impl.push_front(GetNode(ref));
+ }
+
+ constexpr void pop_back() {
+ m_impl.pop_back();
+ }
+
+ constexpr void pop_front() {
+ m_impl.pop_front();
+ }
+
+ constexpr iterator insert(const_iterator pos, reference ref) {
+ return iterator(m_impl.insert(pos.GetImplIterator(), GetNode(ref)));
+ }
+
+ constexpr void splice(const_iterator pos, IntrusiveList& o) {
+ m_impl.splice(pos.GetImplIterator(), o.m_impl);
+ }
+
+ constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first) {
+ m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator());
+ }
+
+ constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first,
+ const_iterator last) {
+ m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator(),
+ last.GetImplIterator());
+ }
+
+ constexpr iterator erase(const_iterator pos) {
+ return iterator(m_impl.erase(pos.GetImplIterator()));
+ }
+
+ constexpr void clear() {
+ m_impl.clear();
+ }
+};
+
+template <auto T, class Derived = Common::impl::GetParentType<T>>
+class IntrusiveListMemberTraits;
+
+template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
+class IntrusiveListMemberTraits<Member, Derived> {
+public:
+ using ListType = IntrusiveList<Derived, IntrusiveListMemberTraits>;
+
+private:
+ friend class IntrusiveList<Derived, IntrusiveListMemberTraits>;
+
+ static constexpr IntrusiveListNode& GetNode(Derived& parent) {
+ return parent.*Member;
+ }
+
+ static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
+ return parent.*Member;
+ }
+
+ static Derived& GetParent(IntrusiveListNode& node) {
+ return Common::GetParentReference<Member, Derived>(std::addressof(node));
+ }
+
+ static Derived const& GetParent(IntrusiveListNode const& node) {
+ return Common::GetParentReference<Member, Derived>(std::addressof(node));
+ }
+};
+
+template <auto T, class Derived = Common::impl::GetParentType<T>>
+class IntrusiveListMemberTraitsByNonConstexprOffsetOf;
+
+template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
+class IntrusiveListMemberTraitsByNonConstexprOffsetOf<Member, Derived> {
+public:
+ using ListType = IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
+
+private:
+ friend class IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
+
+ static constexpr IntrusiveListNode& GetNode(Derived& parent) {
+ return parent.*Member;
+ }
+
+ static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
+ return parent.*Member;
+ }
+
+ static Derived& GetParent(IntrusiveListNode& node) {
+ return *reinterpret_cast<Derived*>(reinterpret_cast<char*>(std::addressof(node)) -
+ GetOffset());
+ }
+
+ static Derived const& GetParent(IntrusiveListNode const& node) {
+ return *reinterpret_cast<const Derived*>(
+ reinterpret_cast<const char*>(std::addressof(node)) - GetOffset());
+ }
+
+ static uintptr_t GetOffset() {
+ return reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<Derived*>(0)->*Member));
+ }
+};
+
+template <class Derived>
+class IntrusiveListBaseNode : public IntrusiveListNode {};
+
+template <class Derived>
+class IntrusiveListBaseTraits {
+public:
+ using ListType = IntrusiveList<Derived, IntrusiveListBaseTraits>;
+
+private:
+ friend class IntrusiveList<Derived, IntrusiveListBaseTraits>;
+
+ static constexpr IntrusiveListNode& GetNode(Derived& parent) {
+ return static_cast<IntrusiveListNode&>(
+ static_cast<IntrusiveListBaseNode<Derived>&>(parent));
+ }
+
+ static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
+ return static_cast<const IntrusiveListNode&>(
+ static_cast<const IntrusiveListBaseNode<Derived>&>(parent));
+ }
+
+ static constexpr Derived& GetParent(IntrusiveListNode& node) {
+ return static_cast<Derived&>(static_cast<IntrusiveListBaseNode<Derived>&>(node));
+ }
+
+ static constexpr Derived const& GetParent(IntrusiveListNode const& node) {
+ return static_cast<const Derived&>(
+ static_cast<const IntrusiveListBaseNode<Derived>&>(node));
+ }
+};
+
+} // namespace Common
diff --git a/src/common/scratch_buffer.h b/src/common/scratch_buffer.h
index 26d4e76dc..a69a5a7af 100644
--- a/src/common/scratch_buffer.h
+++ b/src/common/scratch_buffer.h
@@ -23,7 +23,10 @@ public:
buffer{Common::make_unique_for_overwrite<T[]>(initial_capacity)} {}
~ScratchBuffer() = default;
+ ScratchBuffer(const ScratchBuffer&) = delete;
+ ScratchBuffer& operator=(const ScratchBuffer&) = delete;
ScratchBuffer(ScratchBuffer&&) = default;
+ ScratchBuffer& operator=(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.
@@ -87,6 +90,12 @@ public:
return buffer_capacity;
}
+ void swap(ScratchBuffer& other) noexcept {
+ std::swap(last_requested_size, other.last_requested_size);
+ std::swap(buffer_capacity, other.buffer_capacity);
+ std::swap(buffer, other.buffer);
+ }
+
private:
size_t last_requested_size{};
size_t buffer_capacity{};
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 84955030b..ba617aea1 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -45,6 +45,7 @@ void LogSettings() {
log_setting("System_LanguageIndex", values.language_index.GetValue());
log_setting("System_RegionIndex", values.region_index.GetValue());
log_setting("System_TimeZoneIndex", values.time_zone_index.GetValue());
+ log_setting("System_UnsafeMemoryLayout", values.use_unsafe_extended_memory_layout.GetValue());
log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue());
log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue());
@@ -60,7 +61,8 @@ void LogSettings() {
log_setting("Renderer_NvdecEmulation", values.nvdec_emulation.GetValue());
log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue());
log_setting("Renderer_AsyncASTC", values.async_astc.GetValue());
- log_setting("Renderer_UseVsync", values.use_vsync.GetValue());
+ log_setting("Renderer_UseVsync", values.vsync_mode.GetValue());
+ log_setting("Renderer_UseReactiveFlushing", values.use_reactive_flushing.GetValue());
log_setting("Renderer_ShaderBackend", values.shader_backend.GetValue());
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
log_setting("Renderer_AnisotropicFilteringLevel", values.max_anisotropy.GetValue());
@@ -191,7 +193,7 @@ void RestoreGlobalState(bool is_powered_on) {
// Core
values.use_multi_core.SetGlobal(true);
- values.use_extended_memory_layout.SetGlobal(true);
+ values.use_unsafe_extended_memory_layout.SetGlobal(true);
// CPU
values.cpu_accuracy.SetGlobal(true);
@@ -205,6 +207,7 @@ void RestoreGlobalState(bool is_powered_on) {
// Renderer
values.fsr_sharpening_slider.SetGlobal(true);
values.renderer_backend.SetGlobal(true);
+ values.async_presentation.SetGlobal(true);
values.renderer_force_max_clock.SetGlobal(true);
values.vulkan_device.SetGlobal(true);
values.fullscreen_mode.SetGlobal(true);
@@ -221,15 +224,15 @@ void RestoreGlobalState(bool is_powered_on) {
values.nvdec_emulation.SetGlobal(true);
values.accelerate_astc.SetGlobal(true);
values.async_astc.SetGlobal(true);
- values.use_vsync.SetGlobal(true);
+ values.use_reactive_flushing.SetGlobal(true);
values.shader_backend.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true);
values.use_fast_gpu_time.SetGlobal(true);
- values.use_pessimistic_flushes.SetGlobal(true);
values.use_vulkan_driver_pipeline_cache.SetGlobal(true);
values.bg_red.SetGlobal(true);
values.bg_green.SetGlobal(true);
values.bg_blue.SetGlobal(true);
+ values.enable_compute_pipelines.SetGlobal(true);
// System
values.language_index.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index b77a1580a..36ffcd693 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -16,6 +16,13 @@
namespace Settings {
+enum class VSyncMode : u32 {
+ Immediate = 0,
+ Mailbox = 1,
+ FIFO = 2,
+ FIFORelaxed = 3,
+};
+
enum class RendererBackend : u32 {
OpenGL = 0,
Vulkan = 1,
@@ -388,7 +395,8 @@ struct Values {
// Core
SwitchableSetting<bool> use_multi_core{true, "use_multi_core"};
- SwitchableSetting<bool> use_extended_memory_layout{false, "use_extended_memory_layout"};
+ SwitchableSetting<bool> use_unsafe_extended_memory_layout{false,
+ "use_unsafe_extended_memory_layout"};
// Cpu
SwitchableSetting<CPUAccuracy, true> cpu_accuracy{CPUAccuracy::Auto, CPUAccuracy::Auto,
@@ -422,6 +430,7 @@ struct Values {
// Renderer
SwitchableSetting<RendererBackend, true> renderer_backend{
RendererBackend::Vulkan, RendererBackend::OpenGL, RendererBackend::Null, "backend"};
+ SwitchableSetting<bool> async_presentation{false, "async_presentation"};
SwitchableSetting<bool> renderer_force_max_clock{false, "force_max_clock"};
Setting<bool> renderer_debug{false, "debug"};
Setting<bool> renderer_shader_feedback{false, "shader_feedback"};
@@ -454,14 +463,16 @@ struct Values {
SwitchableSetting<NvdecEmulation> nvdec_emulation{NvdecEmulation::GPU, "nvdec_emulation"};
SwitchableSetting<bool> accelerate_astc{true, "accelerate_astc"};
SwitchableSetting<bool> async_astc{false, "async_astc"};
- SwitchableSetting<bool> use_vsync{true, "use_vsync"};
+ Setting<VSyncMode, true> vsync_mode{VSyncMode::FIFO, VSyncMode::Immediate,
+ VSyncMode::FIFORelaxed, "use_vsync"};
+ SwitchableSetting<bool> use_reactive_flushing{true, "use_reactive_flushing"};
SwitchableSetting<ShaderBackend, true> shader_backend{ShaderBackend::GLSL, ShaderBackend::GLSL,
ShaderBackend::SPIRV, "shader_backend"};
SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"};
SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"};
- SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"};
SwitchableSetting<bool> use_vulkan_driver_pipeline_cache{true,
"use_vulkan_driver_pipeline_cache"};
+ SwitchableSetting<bool> enable_compute_pipelines{false, "enable_compute_pipelines"};
SwitchableSetting<u8> bg_red{0, "bg_red"};
SwitchableSetting<u8> bg_green{0, "bg_green"};
@@ -525,6 +536,8 @@ struct Values {
Setting<bool> enable_ir_sensor{false, "enable_ir_sensor"};
Setting<std::string> ir_sensor_device{"auto", "ir_sensor_device"};
+ Setting<bool> random_amiibo_id{false, "random_amiibo_id"};
+
// Data Storage
Setting<bool> use_virtual_sd{true, "use_virtual_sd"};
Setting<bool> gamecard_inserted{false, "gamecard_inserted"};
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index 0e2095c45..b4885835d 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -259,6 +259,20 @@ public:
return *this;
}
+ void RotateFromOrigin(float roll, float pitch, float yaw) {
+ float temp = y;
+ y = std::cos(roll) * y - std::sin(roll) * z;
+ z = std::sin(roll) * temp + std::cos(roll) * z;
+
+ temp = x;
+ x = std::cos(pitch) * x + std::sin(pitch) * z;
+ z = -std::sin(pitch) * temp + std::cos(pitch) * z;
+
+ temp = x;
+ x = std::cos(yaw) * x - std::sin(yaw) * y;
+ y = std::sin(yaw) * temp + std::cos(yaw) * y;
+ }
+
[[nodiscard]] constexpr T Length2() const {
return x * x + y * y + z * z;
}
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 8817a99c9..45328158f 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -555,21 +555,22 @@ add_library(core STATIC
hle/service/mnpp/mnpp_app.h
hle/service/ncm/ncm.cpp
hle/service/ncm/ncm.h
- hle/service/nfc/mifare_user.cpp
- hle/service/nfc/mifare_user.h
+ hle/service/nfc/common/amiibo_crypto.cpp
+ hle/service/nfc/common/amiibo_crypto.h
+ hle/service/nfc/common/device.cpp
+ hle/service/nfc/common/device.h
+ hle/service/nfc/common/device_manager.cpp
+ hle/service/nfc/common/device_manager.h
+ hle/service/nfc/mifare_result.h
+ hle/service/nfc/mifare_types.h
hle/service/nfc/nfc.cpp
hle/service/nfc/nfc.h
- hle/service/nfc/nfc_device.cpp
- hle/service/nfc/nfc_device.h
+ hle/service/nfc/nfc_interface.cpp
+ hle/service/nfc/nfc_interface.h
hle/service/nfc/nfc_result.h
- hle/service/nfc/nfc_user.cpp
- hle/service/nfc/nfc_user.h
- hle/service/nfp/amiibo_crypto.cpp
- hle/service/nfp/amiibo_crypto.h
+ hle/service/nfc/nfc_types.h
hle/service/nfp/nfp.cpp
hle/service/nfp/nfp.h
- hle/service/nfp/nfp_device.cpp
- hle/service/nfp/nfp_device.h
hle/service/nfp/nfp_interface.cpp
hle/service/nfp/nfp_interface.h
hle/service/nfp/nfp_result.h
diff --git a/src/core/core.cpp b/src/core/core.cpp
index caa6a77be..b5f62690e 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -137,7 +137,7 @@ struct System::Impl {
device_memory = std::make_unique<Core::DeviceMemory>();
is_multicore = Settings::values.use_multi_core.GetValue();
- extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
+ extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -169,7 +169,7 @@ struct System::Impl {
void ReinitializeIfNecessary(System& system) {
const bool must_reinitialize =
is_multicore != Settings::values.use_multi_core.GetValue() ||
- extended_memory_layout != Settings::values.use_extended_memory_layout.GetValue();
+ extended_memory_layout != Settings::values.use_unsafe_extended_memory_layout.GetValue();
if (!must_reinitialize) {
return;
@@ -178,7 +178,7 @@ struct System::Impl {
LOG_DEBUG(Kernel, "Re-initializing");
is_multicore = Settings::values.use_multi_core.GetValue();
- extended_memory_layout = Settings::values.use_extended_memory_layout.GetValue();
+ extended_memory_layout = Settings::values.use_unsafe_extended_memory_layout.GetValue();
Initialize(system);
}
@@ -293,6 +293,7 @@ struct System::Impl {
ASSERT(Kernel::KProcess::Initialize(main_process, system, "main",
Kernel::KProcess::ProcessType::Userland, resource_limit)
.IsSuccess());
+ Kernel::KProcess::Register(system.Kernel(), main_process);
kernel.MakeApplicationProcess(main_process);
const auto [load_result, load_parameters] = app_loader->Load(*main_process, system);
if (load_result != Loader::ResultStatus::Success) {
@@ -611,6 +612,10 @@ void System::PrepareReschedule(const u32 core_index) {
impl->kernel.PrepareReschedule(core_index);
}
+size_t System::GetCurrentHostThreadID() const {
+ return impl->kernel.GetCurrentHostThreadID();
+}
+
PerfStatsResults System::GetAndResetPerfStats() {
return impl->GetAndResetPerfStats();
}
diff --git a/src/core/core.h b/src/core/core.h
index 4a5aba032..4f153154f 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -222,6 +222,8 @@ public:
/// Prepare the core emulation for a reschedule
void PrepareReschedule(u32 core_index);
+ [[nodiscard]] size_t GetCurrentHostThreadID() const;
+
/// Gets and resets core performance statistics
[[nodiscard]] PerfStatsResults GetAndResetPerfStats();
diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp
index 769065b6f..70b36f170 100644
--- a/src/core/file_sys/savedata_factory.cpp
+++ b/src/core/file_sys/savedata_factory.cpp
@@ -82,9 +82,9 @@ std::string GetFutureSaveDataPath(SaveDataSpaceId space_id, SaveDataType type, u
// Only detect account/device saves from the future location.
switch (type) {
case SaveDataType::SaveData:
- return fmt::format("{}/account/{}/{:016X}/1", space_id_path, uuid.RawString(), title_id);
+ return fmt::format("{}/account/{}/{:016X}/0", space_id_path, uuid.RawString(), title_id);
case SaveDataType::DeviceSaveData:
- return fmt::format("{}/device/{:016X}/1", space_id_path, title_id);
+ return fmt::format("{}/device/{:016X}/0", space_id_path, title_id);
default:
return "";
}
diff --git a/src/core/file_sys/vfs_layered.cpp b/src/core/file_sys/vfs_layered.cpp
index da05dd395..3e6426afc 100644
--- a/src/core/file_sys/vfs_layered.cpp
+++ b/src/core/file_sys/vfs_layered.cpp
@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
+#include <set>
#include <utility>
#include "core/file_sys/vfs_layered.h"
@@ -58,11 +59,13 @@ std::string LayeredVfsDirectory::GetFullPath() const {
std::vector<VirtualFile> LayeredVfsDirectory::GetFiles() const {
std::vector<VirtualFile> out;
+ std::set<std::string, std::less<>> out_names;
+
for (const auto& layer : dirs) {
for (const auto& file : layer->GetFiles()) {
- if (std::find_if(out.begin(), out.end(), [&file](const VirtualFile& comp) {
- return comp->GetName() == file->GetName();
- }) == out.end()) {
+ auto file_name = file->GetName();
+ if (!out_names.contains(file_name)) {
+ out_names.emplace(std::move(file_name));
out.push_back(file);
}
}
diff --git a/src/core/file_sys/vfs_vector.cpp b/src/core/file_sys/vfs_vector.cpp
index 251d9d7c9..af1df4c51 100644
--- a/src/core/file_sys/vfs_vector.cpp
+++ b/src/core/file_sys/vfs_vector.cpp
@@ -67,6 +67,23 @@ VectorVfsDirectory::VectorVfsDirectory(std::vector<VirtualFile> files_,
VectorVfsDirectory::~VectorVfsDirectory() = default;
+VirtualFile VectorVfsDirectory::GetFile(std::string_view file_name) const {
+ if (!optimized_file_index_built) {
+ optimized_file_index.clear();
+ for (size_t i = 0; i < files.size(); i++) {
+ optimized_file_index.emplace(files[i]->GetName(), i);
+ }
+ optimized_file_index_built = true;
+ }
+
+ const auto it = optimized_file_index.find(file_name);
+ if (it != optimized_file_index.end()) {
+ return files[it->second];
+ }
+
+ return nullptr;
+}
+
std::vector<VirtualFile> VectorVfsDirectory::GetFiles() const {
return files;
}
@@ -107,6 +124,7 @@ bool VectorVfsDirectory::DeleteSubdirectory(std::string_view subdir_name) {
}
bool VectorVfsDirectory::DeleteFile(std::string_view file_name) {
+ optimized_file_index_built = false;
return FindAndRemoveVectorElement(files, file_name);
}
@@ -124,6 +142,7 @@ VirtualFile VectorVfsDirectory::CreateFile(std::string_view file_name) {
}
void VectorVfsDirectory::AddFile(VirtualFile file) {
+ optimized_file_index_built = false;
files.push_back(std::move(file));
}
diff --git a/src/core/file_sys/vfs_vector.h b/src/core/file_sys/vfs_vector.h
index bfedb6e42..c9955755b 100644
--- a/src/core/file_sys/vfs_vector.h
+++ b/src/core/file_sys/vfs_vector.h
@@ -105,6 +105,7 @@ public:
VirtualDir parent = nullptr);
~VectorVfsDirectory() override;
+ VirtualFile GetFile(std::string_view file_name) const override;
std::vector<VirtualFile> GetFiles() const override;
std::vector<VirtualDir> GetSubdirectories() const override;
bool IsWritable() const override;
@@ -126,6 +127,9 @@ private:
VirtualDir parent;
std::string name;
+
+ mutable std::map<std::string, size_t, std::less<>> optimized_file_index;
+ mutable bool optimized_file_index_built{};
};
} // namespace FileSys
diff --git a/src/core/frontend/applets/cabinet.cpp b/src/core/frontend/applets/cabinet.cpp
index 2d501eeae..c33ce248b 100644
--- a/src/core/frontend/applets/cabinet.cpp
+++ b/src/core/frontend/applets/cabinet.cpp
@@ -14,7 +14,7 @@ void DefaultCabinetApplet::Close() const {}
void DefaultCabinetApplet::ShowCabinetApplet(
const CabinetCallback& callback, const CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const {
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const {
LOG_WARNING(Service_AM, "(STUBBED) called");
callback(false, {});
}
diff --git a/src/core/frontend/applets/cabinet.h b/src/core/frontend/applets/cabinet.h
index 74dc5a4f6..af3fc6c3d 100644
--- a/src/core/frontend/applets/cabinet.h
+++ b/src/core/frontend/applets/cabinet.h
@@ -7,9 +7,9 @@
#include "core/frontend/applets/applet.h"
#include "core/hle/service/nfp/nfp_types.h"
-namespace Service::NFP {
-class NfpDevice;
-} // namespace Service::NFP
+namespace Service::NFC {
+class NfcDevice;
+} // namespace Service::NFC
namespace Core::Frontend {
@@ -26,14 +26,14 @@ public:
virtual ~CabinetApplet();
virtual void ShowCabinetApplet(const CabinetCallback& callback,
const CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const = 0;
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const = 0;
};
class DefaultCabinetApplet final : public CabinetApplet {
public:
void Close() const override;
void ShowCabinetApplet(const CabinetCallback& callback, const CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override;
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const override;
};
} // namespace Core::Frontend
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index a70f8807c..366880711 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -376,6 +376,7 @@ void EmulatedController::ReloadInput() {
motion.accel = emulated_motion.GetAcceleration();
motion.gyro = emulated_motion.GetGyroscope();
motion.rotation = emulated_motion.GetRotations();
+ motion.euler = emulated_motion.GetEulerAngles();
motion.orientation = emulated_motion.GetOrientation();
motion.is_at_rest = !emulated_motion.IsMoving(motion_sensitivity);
}
@@ -551,6 +552,8 @@ void EmulatedController::EnableSystemButtons() {
void EmulatedController::DisableSystemButtons() {
std::scoped_lock lock{mutex};
system_buttons_enabled = false;
+ controller.home_button_state.raw = 0;
+ controller.capture_button_state.raw = 0;
}
void EmulatedController::ResetSystemButtons() {
@@ -685,6 +688,12 @@ void EmulatedController::SetMotionParam(std::size_t index, Common::ParamPackage
ReloadInput();
}
+void EmulatedController::StartMotionCalibration() {
+ for (ControllerMotionInfo& motion : controller.motion_values) {
+ motion.emulated.Calibrate();
+ }
+}
+
void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback, std::size_t index,
Common::UUID uuid) {
if (index >= controller.button_values.size()) {
@@ -734,6 +743,8 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback
if (is_configuring) {
controller.npad_button_state.raw = NpadButton::None;
controller.debug_pad_button_state.raw = 0;
+ controller.home_button_state.raw = 0;
+ controller.capture_button_state.raw = 0;
lock.unlock();
TriggerOnChange(ControllerTriggerType::Button, false);
return;
@@ -974,16 +985,12 @@ void EmulatedController::SetMotion(const Common::Input::CallbackStatus& callback
emulated.SetUserGyroThreshold(raw_status.gyro.x.properties.threshold);
emulated.UpdateRotation(raw_status.delta_timestamp);
emulated.UpdateOrientation(raw_status.delta_timestamp);
- force_update_motion = raw_status.force_update;
-
- if (is_configuring) {
- return;
- }
auto& motion = controller.motion_state[index];
motion.accel = emulated.GetAcceleration();
motion.gyro = emulated.GetGyroscope();
motion.rotation = emulated.GetRotations();
+ motion.euler = emulated.GetEulerAngles();
motion.orientation = emulated.GetOrientation();
motion.is_at_rest = !emulated.IsMoving(motion_sensitivity);
}
@@ -1616,19 +1623,6 @@ NpadGcTriggerState EmulatedController::GetTriggers() const {
MotionState EmulatedController::GetMotions() const {
std::unique_lock lock{mutex};
-
- // Some drivers like mouse motion need constant refreshing
- if (force_update_motion) {
- for (auto& device : motion_devices) {
- if (!device) {
- continue;
- }
- lock.unlock();
- device->ForceUpdate();
- lock.lock();
- }
- }
-
return controller.motion_state;
}
@@ -1694,8 +1688,21 @@ void EmulatedController::DeleteCallback(int key) {
callback_list.erase(iterator);
}
-void EmulatedController::TurboButtonUpdate() {
+void EmulatedController::StatusUpdate() {
turbo_button_state = (turbo_button_state + 1) % (TURBO_BUTTON_DELAY * 2);
+
+ // Some drivers like key motion need constant refreshing
+ for (std::size_t index = 0; index < motion_devices.size(); ++index) {
+ const auto& raw_status = controller.motion_values[index].raw_status;
+ auto& device = motion_devices[index];
+ if (!raw_status.force_update) {
+ continue;
+ }
+ if (!device) {
+ continue;
+ }
+ device->ForceUpdate();
+ }
}
NpadButton EmulatedController::GetTurboButtonMask() const {
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index 429655355..88fad2f56 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -106,6 +106,7 @@ struct ControllerMotion {
Common::Vec3f accel{};
Common::Vec3f gyro{};
Common::Vec3f rotation{};
+ Common::Vec3f euler{};
std::array<Common::Vec3f, 3> orientation{};
bool is_at_rest{};
};
@@ -289,6 +290,9 @@ public:
*/
void SetMotionParam(std::size_t index, Common::ParamPackage param);
+ /// Auto calibrates the current motion devices
+ void StartMotionCalibration();
+
/// Returns the latest button status from the controller with parameters
ButtonValues GetButtonsValues() const;
@@ -414,8 +418,8 @@ public:
*/
void DeleteCallback(int key);
- /// Swaps the state of the turbo buttons
- void TurboButtonUpdate();
+ /// Swaps the state of the turbo buttons and updates motion input
+ void StatusUpdate();
private:
/// creates input devices from params
@@ -527,7 +531,6 @@ private:
bool is_configuring{false};
bool system_buttons_enabled{true};
f32 motion_sensitivity{Core::HID::MotionInput::IsAtRestStandard};
- bool force_update_motion{false};
u32 turbo_button_state{0};
// Temporary values to avoid doing changes while the controller is in configuring mode
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp
index 7cee39a53..4ccb1c596 100644
--- a/src/core/hid/input_converter.cpp
+++ b/src/core/hid/input_converter.cpp
@@ -54,6 +54,7 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu
case Common::Input::InputType::Analog:
status.value = TransformToTrigger(callback).pressed.value;
status.toggle = callback.analog_status.properties.toggle;
+ status.inverted = callback.analog_status.properties.inverted_button;
break;
case Common::Input::InputType::Trigger:
status.value = TransformToTrigger(callback).pressed.value;
@@ -61,6 +62,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu
case Common::Input::InputType::Button:
status = callback.button_status;
break;
+ case Common::Input::InputType::Motion:
+ status.value = std::abs(callback.motion_status.gyro.x.raw_value) > 1.0f;
+ break;
default:
LOG_ERROR(Input, "Conversion from type {} to button not implemented", callback.type);
break;
@@ -82,7 +86,7 @@ Common::Input::MotionStatus TransformToMotion(const Common::Input::CallbackStatu
.range = 1.0f,
.offset = 0.0f,
};
- status.delta_timestamp = 5000;
+ status.delta_timestamp = 1000;
status.force_update = true;
status.accel.x = {
.value = 0.0f,
@@ -226,6 +230,10 @@ Common::Input::TriggerStatus TransformToTrigger(const Common::Input::CallbackSta
status = callback.trigger_status;
calculate_button_value = false;
break;
+ case Common::Input::InputType::Motion:
+ status.analog.properties.range = 1.0f;
+ raw_value = callback.motion_status.accel.x.raw_value;
+ break;
default:
LOG_ERROR(Input, "Conversion from type {} to trigger not implemented", callback.type);
break;
diff --git a/src/core/hid/motion_input.cpp b/src/core/hid/motion_input.cpp
index 0dd66c1cc..f56f2ae1d 100644
--- a/src/core/hid/motion_input.cpp
+++ b/src/core/hid/motion_input.cpp
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <cmath>
+
#include "common/math_util.h"
#include "core/hid/motion_input.h"
@@ -35,11 +37,17 @@ void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) {
gyro.y = std::clamp(gyro.y, -GyroMaxValue, GyroMaxValue);
gyro.z = std::clamp(gyro.z, -GyroMaxValue, GyroMaxValue);
- // Auto adjust drift to minimize drift
+ // Auto adjust gyro_bias to minimize drift
if (!IsMoving(IsAtRestRelaxed)) {
gyro_bias = (gyro_bias * 0.9999f) + (gyroscope * 0.0001f);
}
+ // Adjust drift when calibration mode is enabled
+ if (calibration_mode) {
+ gyro_bias = (gyro_bias * 0.99f) + (gyroscope * 0.01f);
+ StopCalibration();
+ }
+
if (gyro.Length() < gyro_threshold * user_gyro_threshold) {
gyro = {};
} else {
@@ -51,6 +59,20 @@ void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) {
quat = quaternion;
}
+void MotionInput::SetEulerAngles(const Common::Vec3f& euler_angles) {
+ const float cr = std::cos(euler_angles.x * 0.5f);
+ const float sr = std::sin(euler_angles.x * 0.5f);
+ const float cp = std::cos(euler_angles.y * 0.5f);
+ const float sp = std::sin(euler_angles.y * 0.5f);
+ const float cy = std::cos(euler_angles.z * 0.5f);
+ const float sy = std::sin(euler_angles.z * 0.5f);
+
+ quat.w = cr * cp * cy + sr * sp * sy;
+ quat.xyz.x = sr * cp * cy - cr * sp * sy;
+ quat.xyz.y = cr * sp * cy + sr * cp * sy;
+ quat.xyz.z = cr * cp * sy - sr * sp * cy;
+}
+
void MotionInput::SetGyroBias(const Common::Vec3f& bias) {
gyro_bias = bias;
}
@@ -91,6 +113,19 @@ void MotionInput::UpdateRotation(u64 elapsed_time) {
rotations += gyro * sample_period;
}
+void MotionInput::Calibrate() {
+ calibration_mode = true;
+ calibration_counter = 0;
+}
+
+void MotionInput::StopCalibration() {
+ if (calibration_counter++ > CalibrationSamples) {
+ calibration_mode = false;
+ ResetQuaternion();
+ ResetRotations();
+ }
+}
+
// Based on Madgwick's implementation of Mayhony's AHRS algorithm.
// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs
void MotionInput::UpdateOrientation(u64 elapsed_time) {
@@ -222,6 +257,26 @@ Common::Vec3f MotionInput::GetRotations() const {
return rotations;
}
+Common::Vec3f MotionInput::GetEulerAngles() const {
+ // roll (x-axis rotation)
+ const float sinr_cosp = 2 * (quat.w * quat.xyz.x + quat.xyz.y * quat.xyz.z);
+ const float cosr_cosp = 1 - 2 * (quat.xyz.x * quat.xyz.x + quat.xyz.y * quat.xyz.y);
+
+ // pitch (y-axis rotation)
+ const float sinp = std::sqrt(1 + 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z));
+ const float cosp = std::sqrt(1 - 2 * (quat.w * quat.xyz.y - quat.xyz.x * quat.xyz.z));
+
+ // yaw (z-axis rotation)
+ const float siny_cosp = 2 * (quat.w * quat.xyz.z + quat.xyz.x * quat.xyz.y);
+ const float cosy_cosp = 1 - 2 * (quat.xyz.y * quat.xyz.y + quat.xyz.z * quat.xyz.z);
+
+ return {
+ std::atan2(sinr_cosp, cosr_cosp),
+ 2 * std::atan2(sinp, cosp) - Common::PI / 2,
+ std::atan2(siny_cosp, cosy_cosp),
+ };
+}
+
void MotionInput::ResetOrientation() {
if (!reset_enabled || only_accelerometer) {
return;
diff --git a/src/core/hid/motion_input.h b/src/core/hid/motion_input.h
index 9f3fc1cf7..11678983d 100644
--- a/src/core/hid/motion_input.h
+++ b/src/core/hid/motion_input.h
@@ -23,6 +23,8 @@ public:
static constexpr float GyroMaxValue = 5.0f;
static constexpr float AccelMaxValue = 7.0f;
+ static constexpr std::size_t CalibrationSamples = 300;
+
explicit MotionInput();
MotionInput(const MotionInput&) = default;
@@ -35,6 +37,7 @@ public:
void SetAcceleration(const Common::Vec3f& acceleration);
void SetGyroscope(const Common::Vec3f& gyroscope);
void SetQuaternion(const Common::Quaternion<f32>& quaternion);
+ void SetEulerAngles(const Common::Vec3f& euler_angles);
void SetGyroBias(const Common::Vec3f& bias);
void SetGyroThreshold(f32 threshold);
@@ -48,17 +51,21 @@ public:
void UpdateRotation(u64 elapsed_time);
void UpdateOrientation(u64 elapsed_time);
+ void Calibrate();
+
[[nodiscard]] std::array<Common::Vec3f, 3> GetOrientation() const;
[[nodiscard]] Common::Vec3f GetAcceleration() const;
[[nodiscard]] Common::Vec3f GetGyroscope() const;
[[nodiscard]] Common::Vec3f GetGyroBias() const;
[[nodiscard]] Common::Vec3f GetRotations() const;
[[nodiscard]] Common::Quaternion<f32> GetQuaternion() const;
+ [[nodiscard]] Common::Vec3f GetEulerAngles() const;
[[nodiscard]] bool IsMoving(f32 sensitivity) const;
[[nodiscard]] bool IsCalibrated(f32 sensitivity) const;
private:
+ void StopCalibration();
void ResetOrientation();
void SetOrientationFromAccelerometer();
@@ -101,6 +108,12 @@ private:
// Use accelerometer values to calculate position
bool only_accelerometer = true;
+
+ // When enabled it will aggressively adjust for gyro drift
+ bool calibration_mode = false;
+
+ // Used to auto disable calibration mode
+ std::size_t calibration_counter = 0;
};
} // namespace Core::HID
diff --git a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
index 36d0d20d2..49bdc671e 100644
--- a/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
+++ b/src/core/hle/kernel/board/nintendo/nx/k_system_control.cpp
@@ -35,12 +35,13 @@ namespace {
using namespace Common::Literals;
u32 GetMemorySizeForInit() {
- return Settings::values.use_extended_memory_layout ? Smc::MemorySize_8GB : Smc::MemorySize_4GB;
+ return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemorySize_8GB
+ : Smc::MemorySize_4GB;
}
Smc::MemoryArrangement GetMemoryArrangeForInit() {
- return Settings::values.use_extended_memory_layout ? Smc::MemoryArrangement_8GB
- : Smc::MemoryArrangement_4GB;
+ return Settings::values.use_unsafe_extended_memory_layout ? Smc::MemoryArrangement_8GB
+ : Smc::MemoryArrangement_4GB;
}
} // namespace
diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h
index 9b71fe371..f384b1568 100644
--- a/src/core/hle/kernel/k_auto_object.h
+++ b/src/core/hle/kernel/k_auto_object.h
@@ -182,8 +182,8 @@ public:
explicit KAutoObjectWithList(KernelCore& kernel) : KAutoObject(kernel) {}
static int Compare(const KAutoObjectWithList& lhs, const KAutoObjectWithList& rhs) {
- const u64 lid = lhs.GetId();
- const u64 rid = rhs.GetId();
+ const uintptr_t lid = reinterpret_cast<uintptr_t>(std::addressof(lhs));
+ const uintptr_t rid = reinterpret_cast<uintptr_t>(std::addressof(rhs));
if (lid < rid) {
return -1;
diff --git a/src/core/hle/kernel/k_event_info.h b/src/core/hle/kernel/k_event_info.h
index 25b3ff594..eacfa5dc6 100644
--- a/src/core/hle/kernel/k_event_info.h
+++ b/src/core/hle/kernel/k_event_info.h
@@ -5,14 +5,15 @@
#include <array>
-#include <boost/intrusive/list.hpp>
+#include "common/intrusive_list.h"
#include "core/hle/kernel/slab_helpers.h"
#include "core/hle/kernel/svc_types.h"
namespace Kernel {
-class KEventInfo : public KSlabAllocated<KEventInfo>, public boost::intrusive::list_base_hook<> {
+class KEventInfo : public KSlabAllocated<KEventInfo>,
+ public Common::IntrusiveListBaseNode<KEventInfo> {
public:
struct InfoCreateThread {
u32 thread_id{};
diff --git a/src/core/hle/kernel/k_object_name.h b/src/core/hle/kernel/k_object_name.h
index 2d97fc777..a8876fe37 100644
--- a/src/core/hle/kernel/k_object_name.h
+++ b/src/core/hle/kernel/k_object_name.h
@@ -5,7 +5,8 @@
#include <array>
#include <memory>
-#include <boost/intrusive/list.hpp>
+
+#include "common/intrusive_list.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/slab_helpers.h"
@@ -15,13 +16,14 @@ namespace Kernel {
class KObjectNameGlobalData;
-class KObjectName : public KSlabAllocated<KObjectName>, public boost::intrusive::list_base_hook<> {
+class KObjectName : public KSlabAllocated<KObjectName>,
+ public Common::IntrusiveListBaseNode<KObjectName> {
public:
explicit KObjectName(KernelCore&) {}
virtual ~KObjectName() = default;
static constexpr size_t NameLengthMax = 12;
- using List = boost::intrusive::list<KObjectName>;
+ using List = Common::IntrusiveListBaseTraits<KObjectName>::ListType;
static Result NewFromName(KernelCore& kernel, KAutoObject* obj, const char* name);
static Result Delete(KernelCore& kernel, KAutoObject* obj, const char* name);
diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h
index 21c040e62..625280290 100644
--- a/src/core/hle/kernel/k_server_port.h
+++ b/src/core/hle/kernel/k_server_port.h
@@ -7,7 +7,7 @@
#include <string>
#include <utility>
-#include <boost/intrusive/list.hpp>
+#include "common/intrusive_list.h"
#include "core/hle/kernel/k_server_session.h"
#include "core/hle/kernel/k_synchronization_object.h"
@@ -42,7 +42,7 @@ public:
bool IsSignaled() const override;
private:
- using SessionList = boost::intrusive::list<KServerSession>;
+ using SessionList = Common::IntrusiveListBaseTraits<KServerSession>::ListType;
void CleanupSessions();
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h
index 5ee02f556..403891919 100644
--- a/src/core/hle/kernel/k_server_session.h
+++ b/src/core/hle/kernel/k_server_session.h
@@ -8,7 +8,7 @@
#include <string>
#include <utility>
-#include <boost/intrusive/list.hpp>
+#include "common/intrusive_list.h"
#include "core/hle/kernel/k_light_lock.h"
#include "core/hle/kernel/k_session_request.h"
@@ -27,7 +27,7 @@ class KSession;
class KThread;
class KServerSession final : public KSynchronizationObject,
- public boost::intrusive::list_base_hook<> {
+ public Common::IntrusiveListBaseNode<KServerSession> {
KERNEL_AUTOOBJECT_TRAITS(KServerSession, KSynchronizationObject);
friend class ServiceThread;
@@ -67,7 +67,8 @@ private:
KSession* m_parent{};
/// List of threads which are pending a reply.
- boost::intrusive::list<KSessionRequest> m_request_list{};
+ using RequestList = Common::IntrusiveListBaseTraits<KSessionRequest>::ListType;
+ RequestList m_request_list{};
KSessionRequest* m_current_request{};
KLightLock m_lock;
diff --git a/src/core/hle/kernel/k_session_request.h b/src/core/hle/kernel/k_session_request.h
index b5f04907b..283669e0a 100644
--- a/src/core/hle/kernel/k_session_request.h
+++ b/src/core/hle/kernel/k_session_request.h
@@ -5,6 +5,8 @@
#include <array>
+#include "common/intrusive_list.h"
+
#include "core/hle/kernel/k_auto_object.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_memory_block.h"
@@ -16,7 +18,7 @@ namespace Kernel {
class KSessionRequest final : public KSlabAllocated<KSessionRequest>,
public KAutoObject,
- public boost::intrusive::list_base_hook<> {
+ public Common::IntrusiveListBaseNode<KSessionRequest> {
KERNEL_AUTOOBJECT_TRAITS(KSessionRequest, KAutoObject);
public:
diff --git a/src/core/hle/kernel/k_shared_memory_info.h b/src/core/hle/kernel/k_shared_memory_info.h
index 75b73ba39..2d8ff20d6 100644
--- a/src/core/hle/kernel/k_shared_memory_info.h
+++ b/src/core/hle/kernel/k_shared_memory_info.h
@@ -3,7 +3,7 @@
#pragma once
-#include <boost/intrusive/list.hpp>
+#include "common/intrusive_list.h"
#include "core/hle/kernel/slab_helpers.h"
@@ -12,7 +12,7 @@ namespace Kernel {
class KSharedMemory;
class KSharedMemoryInfo final : public KSlabAllocated<KSharedMemoryInfo>,
- public boost::intrusive::list_base_hook<> {
+ public Common::IntrusiveListBaseNode<KSharedMemoryInfo> {
public:
explicit KSharedMemoryInfo(KernelCore&) {}
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index 9c1a41128..f9814ac8f 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -12,7 +12,7 @@
#include <utility>
#include <vector>
-#include <boost/intrusive/list.hpp>
+#include "common/intrusive_list.h"
#include "common/intrusive_red_black_tree.h"
#include "common/spin_lock.h"
@@ -119,7 +119,7 @@ s32 GetCurrentCoreId(KernelCore& kernel);
Core::Memory::Memory& GetCurrentMemory(KernelCore& kernel);
class KThread final : public KAutoObjectWithSlabHeapAndContainer<KThread, KWorkerTask>,
- public boost::intrusive::list_base_hook<>,
+ public Common::IntrusiveListBaseNode<KThread>,
public KTimerTask {
KERNEL_AUTOOBJECT_TRAITS(KThread, KSynchronizationObject);
@@ -138,7 +138,7 @@ public:
public:
using ThreadContext32 = Core::ARM_Interface::ThreadContext32;
using ThreadContext64 = Core::ARM_Interface::ThreadContext64;
- using WaiterList = boost::intrusive::list<KThread>;
+ using WaiterList = Common::IntrusiveListBaseTraits<KThread>::ListType;
/**
* Gets the thread's current priority
@@ -750,8 +750,9 @@ private:
ConditionVariableThreadTreeTraits::TreeType<LockWithPriorityInheritanceComparator>;
public:
- class LockWithPriorityInheritanceInfo : public KSlabAllocated<LockWithPriorityInheritanceInfo>,
- public boost::intrusive::list_base_hook<> {
+ class LockWithPriorityInheritanceInfo
+ : public KSlabAllocated<LockWithPriorityInheritanceInfo>,
+ public Common::IntrusiveListBaseNode<LockWithPriorityInheritanceInfo> {
public:
explicit LockWithPriorityInheritanceInfo(KernelCore&) {}
@@ -839,7 +840,7 @@ public:
private:
using LockWithPriorityInheritanceInfoList =
- boost::intrusive::list<LockWithPriorityInheritanceInfo>;
+ Common::IntrusiveListBaseTraits<LockWithPriorityInheritanceInfo>::ListType;
ConditionVariableThreadTree* m_condvar_tree{};
u64 m_condvar_key{};
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 4f3366c9d..f33600ca5 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -95,7 +95,7 @@ struct KernelCore::Impl {
pt_heap_region.GetSize());
}
- InitializeHackSharedMemory();
+ InitializeHackSharedMemory(kernel);
RegisterHostThread(nullptr);
}
@@ -216,10 +216,12 @@ struct KernelCore::Impl {
auto* main_thread{Kernel::KThread::Create(system.Kernel())};
main_thread->SetCurrentCore(core);
ASSERT(Kernel::KThread::InitializeMainThread(system, main_thread, core).IsSuccess());
+ KThread::Register(system.Kernel(), main_thread);
auto* idle_thread{Kernel::KThread::Create(system.Kernel())};
idle_thread->SetCurrentCore(core);
ASSERT(Kernel::KThread::InitializeIdleThread(system, idle_thread, core).IsSuccess());
+ KThread::Register(system.Kernel(), idle_thread);
schedulers[i]->Initialize(main_thread, idle_thread, core);
}
@@ -230,6 +232,7 @@ struct KernelCore::Impl {
const Core::Timing::CoreTiming& core_timing) {
system_resource_limit = KResourceLimit::Create(system.Kernel());
system_resource_limit->Initialize(&core_timing);
+ KResourceLimit::Register(kernel, system_resource_limit);
const auto sizes{memory_layout->GetTotalAndKernelMemorySizes()};
const auto total_size{sizes.first};
@@ -355,6 +358,7 @@ struct KernelCore::Impl {
ASSERT(KThread::InitializeHighPriorityThread(system, shutdown_threads[core_id], {}, {},
core_id)
.IsSuccess());
+ KThread::Register(system.Kernel(), shutdown_threads[core_id]);
}
}
@@ -729,7 +733,7 @@ struct KernelCore::Impl {
memory_manager->Initialize(management_region.GetAddress(), management_region.GetSize());
}
- void InitializeHackSharedMemory() {
+ void InitializeHackSharedMemory(KernelCore& kernel) {
// 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};
@@ -746,14 +750,23 @@ struct KernelCore::Impl {
hid_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, hid_size);
+ KSharedMemory::Register(kernel, hid_shared_mem);
+
font_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, font_size);
+ KSharedMemory::Register(kernel, font_shared_mem);
+
irs_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, irs_size);
+ KSharedMemory::Register(kernel, irs_shared_mem);
+
time_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, time_size);
+ KSharedMemory::Register(kernel, time_shared_mem);
+
hidbus_shared_mem->Initialize(system.DeviceMemory(), nullptr, Svc::MemoryPermission::None,
Svc::MemoryPermission::Read, hidbus_size);
+ KSharedMemory::Register(kernel, hidbus_shared_mem);
}
std::mutex registered_objects_lock;
@@ -1072,12 +1085,15 @@ static std::jthread RunHostThreadFunc(KernelCore& kernel, KProcess* process,
// Commit the thread reservation.
thread_reservation.Commit();
+ // Register the thread.
+ KThread::Register(kernel, thread);
+
return std::jthread(
[&kernel, thread, thread_name{std::move(thread_name)}, func{std::move(func)}] {
// Set the thread name.
Common::SetCurrentThreadName(thread_name.c_str());
- // Register the thread.
+ // Set the thread as current.
kernel.RegisterHostThread(thread);
// Run the callback.
@@ -1099,6 +1115,9 @@ std::jthread KernelCore::RunOnHostCoreProcess(std::string&& process_name,
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
+ // Register the new process.
+ KProcess::Register(*this, process);
+
// Run the host thread.
return RunHostThreadFunc(*this, process, std::move(process_name), std::move(func));
}
@@ -1124,6 +1143,9 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
// Ensure that we don't hold onto any extra references.
SCOPE_EXIT({ process->Close(); });
+ // Register the new process.
+ KProcess::Register(*this, process);
+
// Reserve a new thread from the process resource limit.
KScopedResourceReservation thread_reservation(process, LimitableResource::ThreadCountMax);
ASSERT(thread_reservation.Succeeded());
@@ -1136,6 +1158,9 @@ void KernelCore::RunOnGuestCoreProcess(std::string&& process_name, std::function
// Commit the thread reservation.
thread_reservation.Commit();
+ // Register the new thread.
+ KThread::Register(*this, thread);
+
// Begin running the thread.
ASSERT(R_SUCCEEDED(thread->Run()));
}
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index e59de844c..a2375508a 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -13,6 +13,7 @@
#include "core/file_sys/savedata_factory.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/kernel/k_transfer_memory.h"
+#include "core/hle/result.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applet_ae.h"
@@ -1335,7 +1336,7 @@ IApplicationFunctions::IApplicationFunctions(Core::System& system_)
{24, nullptr, "GetLaunchStorageInfoForDebug"},
{25, &IApplicationFunctions::ExtendSaveData, "ExtendSaveData"},
{26, &IApplicationFunctions::GetSaveDataSize, "GetSaveDataSize"},
- {27, nullptr, "CreateCacheStorage"},
+ {27, &IApplicationFunctions::CreateCacheStorage, "CreateCacheStorage"},
{28, nullptr, "GetSaveDataSizeMax"},
{29, nullptr, "GetCacheStorageMax"},
{30, &IApplicationFunctions::BeginBlockingHomeButtonShortAndLongPressed, "BeginBlockingHomeButtonShortAndLongPressed"},
@@ -1738,6 +1739,36 @@ void IApplicationFunctions::GetSaveDataSize(HLERequestContext& ctx) {
rb.Push(size.journal);
}
+void IApplicationFunctions::CreateCacheStorage(HLERequestContext& ctx) {
+ struct InputParameters {
+ u16 index;
+ s64 size;
+ s64 journal_size;
+ };
+ static_assert(sizeof(InputParameters) == 24);
+
+ struct OutputParameters {
+ u32 storage_target;
+ u64 required_size;
+ };
+ static_assert(sizeof(OutputParameters) == 16);
+
+ IPC::RequestParser rp{ctx};
+ const auto params = rp.PopRaw<InputParameters>();
+
+ LOG_WARNING(Service_AM, "(STUBBED) called with index={}, size={:#x}, journal_size={:#x}",
+ params.index, params.size, params.journal_size);
+
+ const OutputParameters resp{
+ .storage_target = 1,
+ .required_size = 0,
+ };
+
+ IPC::ResponseBuilder rb{ctx, 6};
+ rb.Push(ResultSuccess);
+ rb.PushRaw(resp);
+}
+
void IApplicationFunctions::QueryApplicationPlayStatistics(HLERequestContext& ctx) {
LOG_WARNING(Service_AM, "(STUBBED) called");
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 0dbc6485e..d4fd163da 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -333,6 +333,7 @@ private:
void GetPseudoDeviceId(HLERequestContext& ctx);
void ExtendSaveData(HLERequestContext& ctx);
void GetSaveDataSize(HLERequestContext& ctx);
+ void CreateCacheStorage(HLERequestContext& ctx);
void BeginBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
void EndBlockingHomeButtonShortAndLongPressed(HLERequestContext& ctx);
void BeginBlockingHomeButton(HLERequestContext& ctx);
diff --git a/src/core/hle/service/am/applets/applet_cabinet.cpp b/src/core/hle/service/am/applets/applet_cabinet.cpp
index 93c9f2a55..8b754e9d4 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.cpp
+++ b/src/core/hle/service/am/applets/applet_cabinet.cpp
@@ -11,7 +11,7 @@
#include "core/hle/service/am/am.h"
#include "core/hle/service/am/applets/applet_cabinet.h"
#include "core/hle/service/mii/mii_manager.h"
-#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfc/common/device.h"
namespace Service::AM::Applets {
@@ -72,10 +72,10 @@ void Cabinet::Execute() {
// TODO: listen on all controllers
if (nfp_device == nullptr) {
- nfp_device = std::make_shared<Service::NFP::NfpDevice>(
+ nfp_device = std::make_shared<Service::NFC::NfcDevice>(
system.HIDCore().GetFirstNpadId(), system, service_context, availability_change_event);
nfp_device->Initialize();
- nfp_device->StartDetection(Service::NFP::TagProtocol::All);
+ nfp_device->StartDetection(Service::NFC::NfcProtocol::All);
}
const Core::Frontend::CabinetParameters parameters{
@@ -106,20 +106,22 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
Cancel();
}
- if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound &&
- nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) {
+ if (nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagFound &&
+ nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagMounted) {
Cancel();
}
- if (nfp_device->GetCurrentState() == Service::NFP::DeviceState::TagFound) {
- nfp_device->Mount(Service::NFP::MountTarget::All);
+ if (nfp_device->GetCurrentState() == Service::NFC::DeviceState::TagFound) {
+ nfp_device->Mount(Service::NFP::ModelType::Amiibo, Service::NFP::MountTarget::All);
}
switch (applet_input_common.applet_mode) {
case Service::NFP::CabinetMode::StartNicknameAndOwnerSettings: {
- Service::NFP::AmiiboName name{};
- std::memcpy(name.data(), amiibo_name.data(), std::min(amiibo_name.size(), name.size() - 1));
- nfp_device->SetRegisterInfoPrivate(name);
+ Service::NFP::RegisterInfoPrivate register_info{};
+ std::memcpy(register_info.amiibo_name.data(), amiibo_name.data(),
+ std::min(amiibo_name.size(), register_info.amiibo_name.size() - 1));
+
+ nfp_device->SetRegisterInfoPrivate(register_info);
break;
}
case Service::NFP::CabinetMode::StartGameDataEraser:
@@ -139,7 +141,7 @@ void Cabinet::DisplayCompleted(bool apply_changes, std::string_view amiibo_name)
applet_output.device_handle = applet_input_common.device_handle;
applet_output.result = CabinetResult::Cancel;
const auto reg_result = nfp_device->GetRegisterInfo(applet_output.register_info);
- const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info);
+ const auto tag_result = nfp_device->GetTagInfo(applet_output.tag_info, false);
nfp_device->Finalize();
if (reg_result.IsSuccess()) {
diff --git a/src/core/hle/service/am/applets/applet_cabinet.h b/src/core/hle/service/am/applets/applet_cabinet.h
index edd295a27..b56427021 100644
--- a/src/core/hle/service/am/applets/applet_cabinet.h
+++ b/src/core/hle/service/am/applets/applet_cabinet.h
@@ -19,8 +19,8 @@ namespace Core {
class System;
} // namespace Core
-namespace Service::NFP {
-class NfpDevice;
+namespace Service::NFC {
+class NfcDevice;
}
namespace Service::AM::Applets {
@@ -96,7 +96,7 @@ private:
Core::System& system;
bool is_complete{false};
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device;
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device;
Kernel::KEvent* availability_change_event;
KernelHelpers::ServiceContext service_context;
StartParamForAmiiboSettings applet_input_common{};
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 9e559d97e..f73a864c3 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -24,8 +24,10 @@
#include "core/file_sys/savedata_factory.h"
#include "core/file_sys/system_archive/system_archive.h"
#include "core/file_sys/vfs.h"
+#include "core/hle/result.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/filesystem/fsp_srv.h"
+#include "core/hle/service/hle_ipc.h"
#include "core/hle/service/ipc_helpers.h"
#include "core/reporter.h"
@@ -552,9 +554,9 @@ public:
// Write the data to memory
ctx.WriteBuffer(begin, range_size);
- IPC::ResponseBuilder rb{ctx, 3};
+ IPC::ResponseBuilder rb{ctx, 4};
rb.Push(ResultSuccess);
- rb.Push<u32>(static_cast<u32>(actual_entries));
+ rb.Push<u64>(actual_entries);
}
private:
@@ -712,7 +714,7 @@ FSP_SRV::FSP_SRV(Core::System& system_)
{59, nullptr, "WriteSaveDataFileSystemExtraData"},
{60, nullptr, "OpenSaveDataInfoReader"},
{61, &FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId, "OpenSaveDataInfoReaderBySaveDataSpaceId"},
- {62, nullptr, "OpenCacheStorageList"},
+ {62, &FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage, "OpenSaveDataInfoReaderOnlyCacheStorage"},
{64, nullptr, "OpenSaveDataInternalStorageFileSystem"},
{65, nullptr, "UpdateSaveDataMacForDebug"},
{66, nullptr, "WriteSaveDataFileSystemExtraData2"},
@@ -921,6 +923,15 @@ void FSP_SRV::OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx) {
std::make_shared<ISaveDataInfoReader>(system, space, fsc));
}
+void FSP_SRV::OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx) {
+ LOG_WARNING(Service_FS, "(STUBBED) called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 0, 1};
+ rb.Push(ResultSuccess);
+ rb.PushIpcInterface<ISaveDataInfoReader>(system, FileSys::SaveDataSpaceId::TemporaryStorage,
+ fsc);
+}
+
void FSP_SRV::WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx) {
LOG_WARNING(Service_FS, "(STUBBED) called.");
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 49f17c7c3..4f3c2f6de 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -42,6 +42,7 @@ private:
void OpenSaveDataFileSystem(HLERequestContext& ctx);
void OpenReadOnlySaveDataFileSystem(HLERequestContext& ctx);
void OpenSaveDataInfoReaderBySaveDataSpaceId(HLERequestContext& ctx);
+ void OpenSaveDataInfoReaderOnlyCacheStorage(HLERequestContext& ctx);
void WriteSaveDataFileSystemExtraDataBySaveDataAttribute(HLERequestContext& ctx);
void ReadSaveDataFileSystemExtraDataWithMaskBySaveDataAttribute(HLERequestContext& ctx);
void OpenDataStorageByCurrentProcess(HLERequestContext& ctx);
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 8abf71608..28818c813 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -423,8 +423,8 @@ void Controller_NPad::RequestPadStateUpdate(Core::HID::NpadIdType npad_id) {
return;
}
- // This function is unique to yuzu for the turbo buttons to work properly
- controller.device->TurboButtonUpdate();
+ // This function is unique to yuzu for the turbo buttons and motion to work properly
+ controller.device->StatusUpdate();
auto& pad_entry = controller.npad_pad_state;
auto& trigger_entry = controller.npad_trigger_state;
@@ -979,8 +979,8 @@ void Controller_NPad::VibrateController(
}
void Controller_NPad::VibrateControllers(
- const std::vector<Core::HID::VibrationDeviceHandle>& vibration_device_handles,
- const std::vector<Core::HID::VibrationValue>& vibration_values) {
+ std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles,
+ std::span<const Core::HID::VibrationValue> vibration_values) {
if (!Settings::values.vibration_enabled.GetValue() && !permit_vibration_session_enabled) {
return;
}
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 9cfe298f1..776411261 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -112,8 +112,8 @@ public:
const Core::HID::VibrationValue& vibration_value);
void VibrateControllers(
- const std::vector<Core::HID::VibrationDeviceHandle>& vibration_device_handles,
- const std::vector<Core::HID::VibrationValue>& vibration_values);
+ std::span<const Core::HID::VibrationDeviceHandle> vibration_device_handles,
+ std::span<const Core::HID::VibrationValue> vibration_values);
Core::HID::VibrationValue GetLastVibration(
const Core::HID::VibrationDeviceHandle& vibration_device_handle) const;
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 87e7b864a..2bf1d8a27 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -1601,16 +1601,16 @@ void Hid::SendVibrationValues(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto applet_resource_user_id{rp.Pop<u64>()};
- const auto handles = ctx.ReadBuffer(0);
- const auto vibrations = ctx.ReadBuffer(1);
-
- std::vector<Core::HID::VibrationDeviceHandle> vibration_device_handles(
- handles.size() / sizeof(Core::HID::VibrationDeviceHandle));
- std::vector<Core::HID::VibrationValue> vibration_values(vibrations.size() /
- sizeof(Core::HID::VibrationValue));
-
- std::memcpy(vibration_device_handles.data(), handles.data(), handles.size());
- std::memcpy(vibration_values.data(), vibrations.data(), vibrations.size());
+ const auto handle_data = ctx.ReadBuffer(0);
+ const auto handle_count = ctx.GetReadBufferNumElements<Core::HID::VibrationDeviceHandle>(0);
+ const auto vibration_data = ctx.ReadBuffer(1);
+ const auto vibration_count = ctx.GetReadBufferNumElements<Core::HID::VibrationValue>(1);
+
+ auto vibration_device_handles =
+ std::span(reinterpret_cast<const Core::HID::VibrationDeviceHandle*>(handle_data.data()),
+ handle_count);
+ auto vibration_values = std::span(
+ reinterpret_cast<const Core::HID::VibrationValue*>(vibration_data.data()), vibration_count);
applet_resource->GetController<Controller_NPad>(HidController::NPad)
.VibrateControllers(vibration_device_handles, vibration_values);
diff --git a/src/core/hle/service/ipc_helpers.h b/src/core/hle/service/ipc_helpers.h
index e4cb4e1f2..0e222362e 100644
--- a/src/core/hle/service/ipc_helpers.h
+++ b/src/core/hle/service/ipc_helpers.h
@@ -156,6 +156,7 @@ public:
auto* session = Kernel::KSession::Create(kernel);
session->Initialize(nullptr, 0);
+ Kernel::KSession::Register(kernel, session);
auto next_manager = std::make_shared<Service::SessionRequestManager>(
kernel, manager->GetServerManager());
diff --git a/src/core/hle/service/kernel_helpers.cpp b/src/core/hle/service/kernel_helpers.cpp
index a39ce5212..6a313a03b 100644
--- a/src/core/hle/service/kernel_helpers.cpp
+++ b/src/core/hle/service/kernel_helpers.cpp
@@ -25,6 +25,9 @@ ServiceContext::ServiceContext(Core::System& system_, std::string name_)
Kernel::KProcess::ProcessType::KernelInternal,
kernel.GetSystemResourceLimit())
.IsSuccess());
+
+ // Register the process.
+ Kernel::KProcess::Register(kernel, process);
process_created = true;
}
diff --git a/src/core/hle/service/mutex.cpp b/src/core/hle/service/mutex.cpp
index 07589a0f0..b0ff71d1b 100644
--- a/src/core/hle/service/mutex.cpp
+++ b/src/core/hle/service/mutex.cpp
@@ -12,6 +12,9 @@ Mutex::Mutex(Core::System& system) : m_system(system) {
m_event = Kernel::KEvent::Create(system.Kernel());
m_event->Initialize(nullptr);
+ // Register the event.
+ Kernel::KEvent::Register(system.Kernel(), m_event);
+
ASSERT(R_SUCCEEDED(m_event->Signal()));
}
diff --git a/src/core/hle/service/nfp/amiibo_crypto.cpp b/src/core/hle/service/nfc/common/amiibo_crypto.cpp
index a3622e792..f3901ee8d 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.cpp
+++ b/src/core/hle/service/nfc/common/amiibo_crypto.cpp
@@ -12,7 +12,7 @@
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
-#include "core/hle/service/nfp/amiibo_crypto.h"
+#include "core/hle/service/nfc/common/amiibo_crypto.h"
namespace Service::NFP::AmiiboCrypto {
@@ -55,7 +55,7 @@ bool IsAmiiboValid(const EncryptedNTAG215File& ntag_file) {
if (amiibo_data.constant_value != 0xA5) {
return false;
}
- if (amiibo_data.model_info.tag_type != PackedTagType::Type2) {
+ if (amiibo_data.model_info.tag_type != NFC::PackedTagType::Type2) {
return false;
}
if ((ntag_file.dynamic_lock & 0xFFFFFF) != 0x0F0001U) {
diff --git a/src/core/hle/service/nfp/amiibo_crypto.h b/src/core/hle/service/nfc/common/amiibo_crypto.h
index f6208ee6b..bf3044ed9 100644
--- a/src/core/hle/service/nfp/amiibo_crypto.h
+++ b/src/core/hle/service/nfc/common/amiibo_crypto.h
@@ -24,9 +24,9 @@ using DrgbOutput = std::array<u8, 0x20>;
struct HashSeed {
u16_be magic;
std::array<u8, 0xE> padding;
- UniqueSerialNumber uid_1;
+ NFC::UniqueSerialNumber uid_1;
u8 nintendo_id_1;
- UniqueSerialNumber uid_2;
+ NFC::UniqueSerialNumber uid_2;
u8 nintendo_id_2;
std::array<u8, 0x20> keygen_salt;
};
diff --git a/src/core/hle/service/nfp/nfp_device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 3f9af53c8..322bde2ed 100644
--- a/src/core/hle/service/nfp/nfp_device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -1,8 +1,6 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
-#include <array>
-
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4701) // Potentially uninitialized local variable 'result' used
@@ -26,21 +24,22 @@
#include "core/hle/service/ipc_helpers.h"
#include "core/hle/service/mii/mii_manager.h"
#include "core/hle/service/mii/types.h"
-#include "core/hle/service/nfp/amiibo_crypto.h"
-#include "core/hle/service/nfp/nfp_device.h"
-#include "core/hle/service/nfp/nfp_result.h"
+#include "core/hle/service/nfc/common/amiibo_crypto.h"
+#include "core/hle/service/nfc/common/device.h"
+#include "core/hle/service/nfc/mifare_result.h"
+#include "core/hle/service/nfc/nfc_result.h"
#include "core/hle/service/time/time_manager.h"
#include "core/hle/service/time/time_zone_content_manager.h"
#include "core/hle/service/time/time_zone_types.h"
-namespace Service::NFP {
-NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+namespace Service::NFC {
+NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
KernelHelpers::ServiceContext& service_context_,
Kernel::KEvent* availability_change_event_)
: npad_id{npad_id_}, system{system_}, service_context{service_context_},
availability_change_event{availability_change_event_} {
- activate_event = service_context.CreateEvent("IUser:NFPActivateEvent");
- deactivate_event = service_context.CreateEvent("IUser:NFPDeactivateEvent");
+ activate_event = service_context.CreateEvent("NFC:ActivateEvent");
+ deactivate_event = service_context.CreateEvent("NFC:DeactivateEvent");
npad_device = system.HIDCore().GetEmulatedController(npad_id);
Core::HID::ControllerUpdateCallback engine_callback{
@@ -49,14 +48,11 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
};
is_controller_set = true;
callback_key = npad_device->SetCallback(engine_callback);
-
- auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()};
- current_posix_time = standard_steady_clock.GetCurrentTimePoint(system).time_point;
}
-NfpDevice::~NfpDevice() {
- activate_event->Close();
- deactivate_event->Close();
+NfcDevice::~NfcDevice() {
+ service_context.CloseEvent(activate_event);
+ service_context.CloseEvent(deactivate_event);
if (!is_controller_set) {
return;
}
@@ -64,7 +60,7 @@ NfpDevice::~NfpDevice() {
is_controller_set = false;
};
-void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
+void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
if (!is_initalized) {
return;
}
@@ -92,14 +88,14 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
const auto nfc_status = npad_device->GetNfc();
switch (nfc_status.state) {
case Common::Input::NfcState::NewAmiibo:
- LoadAmiibo(nfc_status.data);
+ LoadNfcTag(nfc_status.data);
break;
case Common::Input::NfcState::AmiiboRemoved:
if (device_state == DeviceState::Initialized || device_state == DeviceState::TagRemoved) {
break;
}
if (device_state != DeviceState::SearchingForTag) {
- CloseAmiibo();
+ CloseNfcTag();
}
break;
default:
@@ -107,28 +103,29 @@ void NfpDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
}
}
-bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
+bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
if (device_state != DeviceState::SearchingForTag) {
- LOG_ERROR(Service_NFP, "Game is not looking for amiibos, current state {}", device_state);
+ LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
return false;
}
- if (data.size() != sizeof(EncryptedNTAG215File)) {
- LOG_ERROR(Service_NFP, "Not an amiibo, size={}", data.size());
+ if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
+ LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
return false;
}
- // TODO: Filter by allowed_protocols here
+ mifare_data.resize(data.size());
+ memcpy(mifare_data.data(), data.data(), data.size());
- memcpy(&tag_data, data.data(), sizeof(EncryptedNTAG215File));
- is_plain_amiibo = AmiiboCrypto::IsAmiiboValid(tag_data);
+ memcpy(&tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
+ is_plain_amiibo = NFP::AmiiboCrypto::IsAmiiboValid(tag_data);
if (is_plain_amiibo) {
- encrypted_tag_data = AmiiboCrypto::EncodedDataToNfcData(tag_data);
+ encrypted_tag_data = NFP::AmiiboCrypto::EncodedDataToNfcData(tag_data);
LOG_INFO(Service_NFP, "Using plain amiibo");
} else {
tag_data = {};
- memcpy(&encrypted_tag_data, data.data(), sizeof(EncryptedNTAG215File));
+ memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
}
device_state = DeviceState::TagFound;
@@ -137,8 +134,8 @@ bool NfpDevice::LoadAmiibo(std::span<const u8> data) {
return true;
}
-void NfpDevice::CloseAmiibo() {
- LOG_INFO(Service_NFP, "Remove amiibo");
+void NfcDevice::CloseNfcTag() {
+ LOG_INFO(Service_NFC, "Remove nfc tag");
if (device_state == DeviceState::TagMounted) {
Unmount();
@@ -147,26 +144,28 @@ void NfpDevice::CloseAmiibo() {
device_state = DeviceState::TagRemoved;
encrypted_tag_data = {};
tag_data = {};
+ mifare_data = {};
activate_event->GetReadableEvent().Clear();
deactivate_event->Signal();
}
-Kernel::KReadableEvent& NfpDevice::GetActivateEvent() const {
+Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const {
return activate_event->GetReadableEvent();
}
-Kernel::KReadableEvent& NfpDevice::GetDeactivateEvent() const {
+Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const {
return deactivate_event->GetReadableEvent();
}
-void NfpDevice::Initialize() {
+void NfcDevice::Initialize() {
device_state = npad_device->HasNfc() ? DeviceState::Initialized : DeviceState::Unavailable;
encrypted_tag_data = {};
tag_data = {};
+ mifare_data = {};
is_initalized = true;
}
-void NfpDevice::Finalize() {
+void NfcDevice::Finalize() {
if (device_state == DeviceState::TagMounted) {
Unmount();
}
@@ -177,17 +176,17 @@ void NfpDevice::Finalize() {
is_initalized = false;
}
-Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
+Result NfcDevice::StartDetection(NfcProtocol allowed_protocol) {
if (device_state != DeviceState::Initialized && device_state != DeviceState::TagRemoved) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return WrongDeviceState;
+ LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+ return ResultWrongDeviceState;
}
if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::NFC) !=
Common::Input::DriverResult::Success) {
- LOG_ERROR(Service_NFP, "Nfc not supported");
- return NfcDisabled;
+ LOG_ERROR(Service_NFC, "Nfc not supported");
+ return ResultNfcDisabled;
}
device_state = DeviceState::SearchingForTag;
@@ -195,7 +194,7 @@ Result NfpDevice::StartDetection(TagProtocol allowed_protocol) {
return ResultSuccess;
}
-Result NfpDevice::StopDetection() {
+Result NfcDevice::StopDetection() {
npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
Common::Input::PollingMode::Active);
@@ -204,7 +203,7 @@ Result NfpDevice::StopDetection() {
}
if (device_state == DeviceState::TagFound || device_state == DeviceState::TagMounted) {
- CloseAmiibo();
+ CloseNfcTag();
}
if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) {
@@ -212,94 +211,139 @@ Result NfpDevice::StopDetection() {
return ResultSuccess;
}
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return WrongDeviceState;
+ LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+ return ResultWrongDeviceState;
}
-Result NfpDevice::Flush() {
- if (device_state != DeviceState::TagMounted) {
- LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
+ if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
- LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
- }
+ UniqueSerialNumber uuid = encrypted_tag_data.uuid.uid;
- auto& settings = tag_data.settings;
+ // Generate random UUID to bypass amiibo load limits
+ if (Settings::values.random_amiibo_id) {
+ Common::TinyMT rng{};
+ rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
+ rng.GenerateRandomBytes(uuid.data(), sizeof(UniqueSerialNumber));
+ uuid[3] = 0x88 ^ uuid[0] ^ uuid[1] ^ uuid[2];
+ }
- const auto& current_date = GetAmiiboDate(current_posix_time);
- if (settings.write_date.raw_date != current_date.raw_date) {
- settings.write_date = current_date;
- UpdateSettingsCrc();
+ if (is_mifare) {
+ tag_info = {
+ .uuid = uuid,
+ .uuid_extension = {},
+ .uuid_length = static_cast<u8>(uuid.size()),
+ .protocol = NfcProtocol::TypeA,
+ .tag_type = TagType::Type4,
+ };
+ return ResultSuccess;
}
- tag_data.write_counter++;
+ // Protocol and tag type may change here
+ tag_info = {
+ .uuid = uuid,
+ .uuid_extension = {},
+ .uuid_length = static_cast<u8>(uuid.size()),
+ .protocol = NfcProtocol::TypeA,
+ .tag_type = TagType::Type2,
+ };
- FlushWithBreak(BreakType::Normal);
+ return ResultSuccess;
+}
- is_data_moddified = false;
+Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters,
+ std::span<MifareReadBlockData> read_block_data) const {
+ Result result = ResultSuccess;
- return ResultSuccess;
+ for (std::size_t i = 0; i < parameters.size(); i++) {
+ result = ReadMifare(parameters[i], read_block_data[i]);
+ if (result.IsError()) {
+ break;
+ }
+ }
+
+ return result;
}
-Result NfpDevice::FlushDebug() {
- if (device_state != DeviceState::TagMounted) {
+Result NfcDevice::ReadMifare(const MifareReadBlockParameter& parameter,
+ MifareReadBlockData& read_block_data) const {
+ const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
+ read_block_data.sector_number = parameter.sector_number;
+
+ if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
- LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
+ return Mifare::ResultReadError;
}
- tag_data.write_counter++;
-
- FlushWithBreak(BreakType::Normal);
-
- is_data_moddified = false;
+ // TODO: Use parameter.sector_key to read encrypted data
+ memcpy(read_block_data.data.data(), mifare_data.data() + sector_index, sizeof(DataBlock));
return ResultSuccess;
}
-Result NfpDevice::FlushWithBreak(BreakType break_type) {
- if (break_type != BreakType::Normal) {
- LOG_ERROR(Service_NFC, "Break type not implemented {}", break_type);
- return WrongDeviceState;
- }
+Result NfcDevice::WriteMifare(std::span<const MifareWriteBlockParameter> parameters) {
+ Result result = ResultSuccess;
- std::vector<u8> data(sizeof(EncryptedNTAG215File));
- if (is_plain_amiibo) {
- memcpy(data.data(), &tag_data, sizeof(tag_data));
- } else {
- if (!AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
- LOG_ERROR(Service_NFP, "Failed to encode data");
- return WriteAmiiboFailed;
+ for (std::size_t i = 0; i < parameters.size(); i++) {
+ result = WriteMifare(parameters[i]);
+ if (result.IsError()) {
+ break;
}
-
- memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
}
- if (!npad_device->WriteNfc(data)) {
+ if (!npad_device->WriteNfc(mifare_data)) {
LOG_ERROR(Service_NFP, "Error writing to file");
- return WriteAmiiboFailed;
+ return Mifare::ResultReadError;
+ }
+
+ return result;
+}
+
+Result NfcDevice::WriteMifare(const MifareWriteBlockParameter& parameter) {
+ const std::size_t sector_index = parameter.sector_number * sizeof(DataBlock);
+
+ if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return ResultTagRemoved;
+ }
+ return ResultWrongDeviceState;
+ }
+
+ if (mifare_data.size() < sector_index + sizeof(DataBlock)) {
+ return Mifare::ResultReadError;
}
+ // TODO: Use parameter.sector_key to encrypt the data
+ memcpy(mifare_data.data() + sector_index, parameter.data.data(), sizeof(DataBlock));
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
+ std::span<const u8> command_data,
+ std::span<u8> out_data) {
+ // Not implemented
return ResultSuccess;
}
-Result NfpDevice::Mount(MountTarget mount_target_) {
+Result NfcDevice::Mount(NFP::ModelType model_type, NFP::MountTarget mount_target_) {
if (device_state != DeviceState::TagFound) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
// The loaded amiibo is not encrypted
@@ -309,22 +353,22 @@ Result NfpDevice::Mount(MountTarget mount_target_) {
return ResultSuccess;
}
- if (!AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
+ if (!NFP::AmiiboCrypto::IsAmiiboValid(encrypted_tag_data)) {
LOG_ERROR(Service_NFP, "Not an amiibo");
- return NotAnAmiibo;
+ return ResultNotAnAmiibo;
}
// Mark amiibos as read only when keys are missing
- if (!AmiiboCrypto::IsKeyAvailable()) {
+ if (!NFP::AmiiboCrypto::IsKeyAvailable()) {
LOG_ERROR(Service_NFP, "No keys detected");
device_state = DeviceState::TagMounted;
- mount_target = MountTarget::Rom;
+ mount_target = NFP::MountTarget::Rom;
return ResultSuccess;
}
- if (!AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
+ if (!NFP::AmiiboCrypto::DecodeAmiibo(encrypted_tag_data, tag_data)) {
LOG_ERROR(Service_NFP, "Can't decode amiibo {}", device_state);
- return CorruptedData;
+ return ResultCorruptedData;
}
device_state = DeviceState::TagMounted;
@@ -332,13 +376,13 @@ Result NfpDevice::Mount(MountTarget mount_target_) {
return ResultSuccess;
}
-Result NfpDevice::Unmount() {
+Result NfcDevice::Unmount() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
// Save data before unloading the amiibo
@@ -347,43 +391,123 @@ Result NfpDevice::Unmount() {
}
device_state = DeviceState::TagFound;
- mount_target = MountTarget::None;
+ mount_target = NFP::MountTarget::None;
is_app_area_open = false;
return ResultSuccess;
}
-Result NfpDevice::GetTagInfo(TagInfo& tag_info) const {
- if (device_state != DeviceState::TagFound && device_state != DeviceState::TagMounted) {
+Result NfcDevice::Flush() {
+ if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- tag_info = {
- .uuid = encrypted_tag_data.uuid.uid,
- .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
- .protocol = TagProtocol::TypeA,
- .tag_type = TagType::Type2,
- };
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
+ LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
+ return ResultWrongDeviceState;
+ }
+
+ auto& settings = tag_data.settings;
+
+ const auto& current_date = GetAmiiboDate(GetCurrentPosixTime());
+ if (settings.write_date.raw_date != current_date.raw_date) {
+ settings.write_date = current_date;
+ UpdateSettingsCrc();
+ }
+
+ tag_data.write_counter++;
+
+ FlushWithBreak(NFP::BreakType::Normal);
+
+ is_data_moddified = false;
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::FlushDebug() {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return ResultTagRemoved;
+ }
+ return ResultWrongDeviceState;
+ }
+
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
+ LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
+ return ResultWrongDeviceState;
+ }
+
+ tag_data.write_counter++;
+
+ FlushWithBreak(NFP::BreakType::Normal);
+
+ is_data_moddified = false;
+
+ return ResultSuccess;
+}
+
+Result NfcDevice::FlushWithBreak(NFP::BreakType break_type) {
+ if (break_type != NFP::BreakType::Normal) {
+ LOG_ERROR(Service_NFC, "Break type not implemented {}", break_type);
+ return ResultWrongDeviceState;
+ }
+
+ std::vector<u8> data(sizeof(NFP::EncryptedNTAG215File));
+ if (is_plain_amiibo) {
+ memcpy(data.data(), &tag_data, sizeof(tag_data));
+ } else {
+ if (!NFP::AmiiboCrypto::EncodeAmiibo(tag_data, encrypted_tag_data)) {
+ LOG_ERROR(Service_NFP, "Failed to encode data");
+ return ResultWriteAmiiboFailed;
+ }
+
+ memcpy(data.data(), &encrypted_tag_data, sizeof(encrypted_tag_data));
+ }
+
+ if (!npad_device->WriteNfc(data)) {
+ LOG_ERROR(Service_NFP, "Error writing to file");
+ return ResultWriteAmiiboFailed;
+ }
return ResultSuccess;
}
-Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
+Result NfcDevice::Restore() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
+ LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
+ return ResultWrongDeviceState;
+ }
+
+ // TODO: Load amiibo from backup on system
+ LOG_ERROR(Service_NFP, "Not Implemented");
+ return ResultSuccess;
+}
+
+Result NfcDevice::GetCommonInfo(NFP::CommonInfo& common_info) const {
+ if (device_state != DeviceState::TagMounted) {
+ LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
+ if (device_state == DeviceState::TagRemoved) {
+ return ResultTagRemoved;
+ }
+ return ResultWrongDeviceState;
+ }
+
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
const auto& settings = tag_data.settings;
@@ -393,21 +517,22 @@ Result NfpDevice::GetCommonInfo(CommonInfo& common_info) const {
.last_write_date = settings.write_date.GetWriteDate(),
.write_counter = tag_data.write_counter,
.version = tag_data.amiibo_version,
- .application_area_size = sizeof(ApplicationArea),
+ .application_area_size = sizeof(NFP::ApplicationArea),
};
return ResultSuccess;
}
-Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
+Result NfcDevice::GetModelInfo(NFP::ModelInfo& model_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
const auto& model_info_data = encrypted_tag_data.user_memory.model_info;
+
model_info = {
.character_id = model_info_data.character_id,
.character_variant = model_info_data.character_variant,
@@ -418,22 +543,22 @@ Result NfpDevice::GetModelInfo(ModelInfo& model_info) const {
return ResultSuccess;
}
-Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
+Result NfcDevice::GetRegisterInfo(NFP::RegisterInfo& register_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.amiibo_initialized == 0) {
- return RegistrationIsNotInitialized;
+ return ResultRegistrationIsNotInitialized;
}
Service::Mii::MiiManager manager;
@@ -450,22 +575,22 @@ Result NfpDevice::GetRegisterInfo(RegisterInfo& register_info) const {
return ResultSuccess;
}
-Result NfpDevice::GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) const {
+Result NfcDevice::GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.amiibo_initialized == 0) {
- return RegistrationIsNotInitialized;
+ return ResultRegistrationIsNotInitialized;
}
Service::Mii::MiiManager manager;
@@ -482,18 +607,18 @@ Result NfpDevice::GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) con
return ResultSuccess;
}
-Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const {
+Result NfcDevice::GetAdminInfo(NFP::AdminInfo& admin_info) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
u8 flags = static_cast<u8>(tag_data.settings.settings.raw >> 0x4);
@@ -503,17 +628,18 @@ Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const {
u64 application_id = 0;
u32 application_area_id = 0;
- AppAreaVersion app_area_version = AppAreaVersion::NotSet;
+ NFP::AppAreaVersion app_area_version = NFP::AppAreaVersion::NotSet;
if (tag_data.settings.settings.appdata_initialized != 0) {
application_id = tag_data.application_id;
- app_area_version =
- static_cast<AppAreaVersion>(application_id >> application_id_version_offset & 0xf);
+ app_area_version = static_cast<NFP::AppAreaVersion>(
+ application_id >> NFP::application_id_version_offset & 0xf);
// Restore application id to original value
if (application_id >> 0x38 != 0) {
const u8 application_byte = tag_data.application_id_byte & 0xf;
- application_id = RemoveVersionByte(application_id) |
- (static_cast<u64>(application_byte) << application_id_version_offset);
+ application_id =
+ RemoveVersionByte(application_id) |
+ (static_cast<u64>(application_byte) << NFP::application_id_version_offset);
}
application_area_id = tag_data.application_area_id;
@@ -532,25 +658,26 @@ Result NfpDevice::GetAdminInfo(AdminInfo& admin_info) const {
return ResultSuccess;
}
-Result NfpDevice::DeleteRegisterInfo() {
+Result NfcDevice::DeleteRegisterInfo() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.amiibo_initialized == 0) {
- return RegistrationIsNotInitialized;
+ return ResultRegistrationIsNotInitialized;
}
Common::TinyMT rng{};
+ rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
rng.GenerateRandomBytes(&tag_data.owner_mii, sizeof(tag_data.owner_mii));
rng.GenerateRandomBytes(&tag_data.settings.amiibo_name, sizeof(tag_data.settings.amiibo_name));
rng.GenerateRandomBytes(&tag_data.unknown, sizeof(u8));
@@ -564,18 +691,18 @@ Result NfpDevice::DeleteRegisterInfo() {
return Flush();
}
-Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) {
+Result NfcDevice::SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
Service::Mii::MiiManager manager;
@@ -583,11 +710,11 @@ Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) {
auto& settings = tag_data.settings;
if (tag_data.settings.settings.amiibo_initialized == 0) {
- settings.init_date = GetAmiiboDate(current_posix_time);
+ settings.init_date = GetAmiiboDate(GetCurrentPosixTime());
settings.write_date.raw_date = 0;
}
- SetAmiiboName(settings, amiibo_name);
+ SetAmiiboName(settings, register_info.amiibo_name);
tag_data.owner_mii = manager.BuildFromStoreData(mii);
tag_data.mii_extension = manager.SetFromStoreData(mii);
tag_data.unknown = 0;
@@ -601,18 +728,18 @@ Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) {
return Flush();
}
-Result NfpDevice::RestoreAmiibo() {
+Result NfcDevice::RestoreAmiibo() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
// TODO: Load amiibo from backup on system
@@ -620,7 +747,7 @@ Result NfpDevice::RestoreAmiibo() {
return ResultSuccess;
}
-Result NfpDevice::Format() {
+Result NfcDevice::Format() {
auto result1 = DeleteApplicationArea();
auto result2 = DeleteRegisterInfo();
@@ -635,28 +762,28 @@ Result NfpDevice::Format() {
return Flush();
}
-Result NfpDevice::OpenApplicationArea(u32 access_id) {
+Result NfcDevice::OpenApplicationArea(u32 access_id) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
LOG_WARNING(Service_NFP, "Application area is not initialized");
- return ApplicationAreaIsNotInitialized;
+ return ResultApplicationAreaIsNotInitialized;
}
if (tag_data.application_area_id != access_id) {
LOG_WARNING(Service_NFP, "Wrong application area id");
- return WrongApplicationAreaId;
+ return ResultWrongApplicationAreaId;
}
is_app_area_open = true;
@@ -664,25 +791,25 @@ Result NfpDevice::OpenApplicationArea(u32 access_id) {
return ResultSuccess;
}
-Result NfpDevice::GetApplicationAreaId(u32& application_area_id) const {
+Result NfcDevice::GetApplicationAreaId(u32& application_area_id) const {
application_area_id = {};
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
LOG_WARNING(Service_NFP, "Application area is not initialized");
- return ApplicationAreaIsNotInitialized;
+ return ResultApplicationAreaIsNotInitialized;
}
application_area_id = tag_data.application_area_id;
@@ -690,75 +817,73 @@ Result NfpDevice::GetApplicationAreaId(u32& application_area_id) const {
return ResultSuccess;
}
-Result NfpDevice::GetApplicationArea(std::vector<u8>& data) const {
+Result NfcDevice::GetApplicationArea(std::span<u8> data) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (!is_app_area_open) {
LOG_ERROR(Service_NFP, "Application area is not open");
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
LOG_ERROR(Service_NFP, "Application area is not initialized");
- return ApplicationAreaIsNotInitialized;
- }
-
- if (data.size() > sizeof(ApplicationArea)) {
- data.resize(sizeof(ApplicationArea));
+ return ResultApplicationAreaIsNotInitialized;
}
- memcpy(data.data(), tag_data.application_area.data(), data.size());
+ memcpy(data.data(), tag_data.application_area.data(),
+ std::min(data.size(), sizeof(NFP::ApplicationArea)));
return ResultSuccess;
}
-Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
+Result NfcDevice::SetApplicationArea(std::span<const u8> data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (!is_app_area_open) {
LOG_ERROR(Service_NFP, "Application area is not open");
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.appdata_initialized.Value() == 0) {
LOG_ERROR(Service_NFP, "Application area is not initialized");
- return ApplicationAreaIsNotInitialized;
+ return ResultApplicationAreaIsNotInitialized;
}
- if (data.size() > sizeof(ApplicationArea)) {
+ if (data.size() > sizeof(NFP::ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
return ResultUnknown;
}
Common::TinyMT rng{};
+ rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
std::memcpy(tag_data.application_area.data(), data.data(), data.size());
// Fill remaining data with random numbers
rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
- sizeof(ApplicationArea) - data.size());
+ sizeof(NFP::ApplicationArea) - data.size());
- if (tag_data.application_write_counter != counter_limit) {
+ if (tag_data.application_write_counter != NFP::counter_limit) {
tag_data.application_write_counter++;
}
@@ -767,64 +892,65 @@ Result NfpDevice::SetApplicationArea(std::span<const u8> data) {
return ResultSuccess;
}
-Result NfpDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
+Result NfcDevice::CreateApplicationArea(u32 access_id, std::span<const u8> data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.appdata_initialized.Value() != 0) {
LOG_ERROR(Service_NFP, "Application area already exist");
- return ApplicationAreaExist;
+ return ResultApplicationAreaExist;
}
return RecreateApplicationArea(access_id, data);
}
-Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
+Result NfcDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (is_app_area_open) {
LOG_ERROR(Service_NFP, "Application area is open");
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (data.size() > sizeof(ApplicationArea)) {
+ if (data.size() > sizeof(NFP::ApplicationArea)) {
LOG_ERROR(Service_NFP, "Wrong data size {}", data.size());
- return WrongApplicationAreaSize;
+ return ResultWrongApplicationAreaSize;
}
Common::TinyMT rng{};
+ rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
std::memcpy(tag_data.application_area.data(), data.data(), data.size());
// Fill remaining data with random numbers
rng.GenerateRandomBytes(tag_data.application_area.data() + data.size(),
- sizeof(ApplicationArea) - data.size());
+ sizeof(NFP::ApplicationArea) - data.size());
- if (tag_data.application_write_counter != counter_limit) {
+ if (tag_data.application_write_counter != NFP::counter_limit) {
tag_data.application_write_counter++;
}
const u64 application_id = system.GetApplicationProcessProgramID();
tag_data.application_id_byte =
- static_cast<u8>(application_id >> application_id_version_offset & 0xf);
+ static_cast<u8>(application_id >> NFP::application_id_version_offset & 0xf);
tag_data.application_id =
- RemoveVersionByte(application_id) |
- (static_cast<u64>(AppAreaVersion::NintendoSwitch) << application_id_version_offset);
+ RemoveVersionByte(application_id) | (static_cast<u64>(NFP::AppAreaVersion::NintendoSwitch)
+ << NFP::application_id_version_offset);
tag_data.settings.settings.appdata_initialized.Assign(1);
tag_data.application_area_id = access_id;
tag_data.unknown = {};
@@ -835,30 +961,31 @@ Result NfpDevice::RecreateApplicationArea(u32 access_id, std::span<const u8> dat
return Flush();
}
-Result NfpDevice::DeleteApplicationArea() {
+Result NfcDevice::DeleteApplicationArea() {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFP, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFP, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
if (tag_data.settings.settings.appdata_initialized == 0) {
- return ApplicationAreaIsNotInitialized;
+ return ResultApplicationAreaIsNotInitialized;
}
- if (tag_data.application_write_counter != counter_limit) {
+ if (tag_data.application_write_counter != NFP::counter_limit) {
tag_data.application_write_counter++;
}
Common::TinyMT rng{};
- rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(ApplicationArea));
+ rng.Initialize(static_cast<u32>(GetCurrentPosixTime()));
+ rng.GenerateRandomBytes(tag_data.application_area.data(), sizeof(NFP::ApplicationArea));
rng.GenerateRandomBytes(&tag_data.application_id, sizeof(u64));
rng.GenerateRandomBytes(&tag_data.application_area_id, sizeof(u32));
rng.GenerateRandomBytes(&tag_data.application_id_byte, sizeof(u8));
@@ -872,18 +999,18 @@ Result NfpDevice::DeleteApplicationArea() {
return Flush();
}
-Result NfpDevice::ExistApplicationArea(bool& has_application_area) {
+Result NfcDevice::ExistsApplicationArea(bool& has_application_area) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
has_application_area = tag_data.settings.settings.appdata_initialized.Value() != 0;
@@ -891,21 +1018,21 @@ Result NfpDevice::ExistApplicationArea(bool& has_application_area) {
return ResultSuccess;
}
-Result NfpDevice::GetAll(NfpData& data) const {
+Result NfcDevice::GetAll(NFP::NfpData& data) const {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- CommonInfo common_info{};
+ NFP::CommonInfo common_info{};
Service::Mii::MiiManager manager;
const u64 application_id = tag_data.application_id;
@@ -930,8 +1057,8 @@ Result NfpDevice::GetAll(NfpData& data) const {
.settings_crc_counter = tag_data.settings.crc_counter,
.font_region = tag_data.settings.settings.font_region,
.tag_type = PackedTagType::Type2,
- .console_type =
- static_cast<AppAreaVersion>(application_id >> application_id_version_offset & 0xf),
+ .console_type = static_cast<NFP::AppAreaVersion>(
+ application_id >> NFP::application_id_version_offset & 0xf),
.application_id_byte = tag_data.application_id_byte,
.application_area = tag_data.application_area,
};
@@ -939,18 +1066,18 @@ Result NfpDevice::GetAll(NfpData& data) const {
return ResultSuccess;
}
-Result NfpDevice::SetAll(const NfpData& data) {
+Result NfcDevice::SetAll(const NFP::NfpData& data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
tag_data.constant_value = data.magic;
@@ -977,18 +1104,18 @@ Result NfpDevice::SetAll(const NfpData& data) {
return ResultSuccess;
}
-Result NfpDevice::BreakTag(BreakType break_type) {
+Result NfcDevice::BreakTag(NFP::BreakType break_type) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
// TODO: Complete this implementation
@@ -996,28 +1123,28 @@ Result NfpDevice::BreakTag(BreakType break_type) {
return FlushWithBreak(break_type);
}
-Result NfpDevice::ReadBackupData() {
+Result NfcDevice::ReadBackupData(std::span<u8> data) const {
// Not implemented
return ResultSuccess;
}
-Result NfpDevice::WriteBackupData() {
+Result NfcDevice::WriteBackupData(std::span<const u8> data) {
// Not implemented
return ResultSuccess;
}
-Result NfpDevice::WriteNtf() {
+Result NfcDevice::WriteNtf(std::span<const u8> data) {
if (device_state != DeviceState::TagMounted) {
LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
if (device_state == DeviceState::TagRemoved) {
- return TagRemoved;
+ return ResultTagRemoved;
}
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
- if (mount_target == MountTarget::None || mount_target == MountTarget::Rom) {
+ if (mount_target == NFP::MountTarget::None || mount_target == NFP::MountTarget::Rom) {
LOG_ERROR(Service_NFC, "Amiibo is read only", device_state);
- return WrongDeviceState;
+ return ResultWrongDeviceState;
}
// Not implemented
@@ -1025,29 +1152,12 @@ Result NfpDevice::WriteNtf() {
return ResultSuccess;
}
-u64 NfpDevice::GetHandle() const {
- // Generate a handle based of the npad id
- return static_cast<u64>(npad_id);
-}
-
-u32 NfpDevice::GetApplicationAreaSize() const {
- return sizeof(ApplicationArea);
-}
-
-DeviceState NfpDevice::GetCurrentState() const {
- return device_state;
-}
-
-Core::HID::NpadIdType NfpDevice::GetNpadId() const {
- return npad_id;
-}
-
-AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const {
- std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
- AmiiboName amiibo_name{};
+NFP::AmiiboName NfcDevice::GetAmiiboName(const NFP::AmiiboSettings& settings) const {
+ std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
+ NFP::AmiiboName amiibo_name{};
// Convert from big endian to little endian
- for (std::size_t i = 0; i < amiibo_name_length; i++) {
+ for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) {
settings_amiibo_name[i] = static_cast<u16>(settings.amiibo_name[i]);
}
@@ -1058,8 +1168,8 @@ AmiiboName NfpDevice::GetAmiiboName(const AmiiboSettings& settings) const {
return amiibo_name;
}
-void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name) {
- std::array<char16_t, amiibo_name_length> settings_amiibo_name{};
+void NfcDevice::SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name) {
+ std::array<char16_t, NFP::amiibo_name_length> settings_amiibo_name{};
// Convert from utf8 to utf16
const auto amiibo_name_utf16 = Common::UTF8ToUTF16(amiibo_name.data());
@@ -1067,16 +1177,16 @@ void NfpDevice::SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo
amiibo_name_utf16.size() * sizeof(char16_t));
// Convert from little endian to big endian
- for (std::size_t i = 0; i < amiibo_name_length; i++) {
+ for (std::size_t i = 0; i < NFP::amiibo_name_length; i++) {
settings.amiibo_name[i] = static_cast<u16_be>(settings_amiibo_name[i]);
}
}
-AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
+NFP::AmiiboDate NfcDevice::GetAmiiboDate(s64 posix_time) const {
const auto& time_zone_manager =
system.GetTimeManager().GetTimeZoneContentManager().GetTimeZoneManager();
Time::TimeZone::CalendarInfo calendar_info{};
- AmiiboDate amiibo_date{};
+ NFP::AmiiboDate amiibo_date{};
amiibo_date.SetYear(2000);
amiibo_date.SetMonth(1);
@@ -1091,14 +1201,19 @@ AmiiboDate NfpDevice::GetAmiiboDate(s64 posix_time) const {
return amiibo_date;
}
-u64 NfpDevice::RemoveVersionByte(u64 application_id) const {
- return application_id & ~(0xfULL << application_id_version_offset);
+u64 NfcDevice::GetCurrentPosixTime() const {
+ auto& standard_steady_clock{system.GetTimeManager().GetStandardSteadyClockCore()};
+ return standard_steady_clock.GetCurrentTimePoint(system).time_point;
}
-void NfpDevice::UpdateSettingsCrc() {
+u64 NfcDevice::RemoveVersionByte(u64 application_id) const {
+ return application_id & ~(0xfULL << NFP::application_id_version_offset);
+}
+
+void NfcDevice::UpdateSettingsCrc() {
auto& settings = tag_data.settings;
- if (settings.crc_counter != counter_limit) {
+ if (settings.crc_counter != NFP::counter_limit) {
settings.crc_counter++;
}
@@ -1109,7 +1224,7 @@ void NfpDevice::UpdateSettingsCrc() {
settings.crc = crc.checksum();
}
-void NfpDevice::UpdateRegisterInfoCrc() {
+void NfcDevice::UpdateRegisterInfoCrc() {
#pragma pack(push, 1)
struct CrcData {
Mii::Ver3StoreData mii;
@@ -1134,4 +1249,18 @@ void NfpDevice::UpdateRegisterInfoCrc() {
tag_data.register_info_crc = crc.checksum();
}
-} // namespace Service::NFP
+u64 NfcDevice::GetHandle() const {
+ // Generate a handle based of the npad id
+ return static_cast<u64>(npad_id);
+}
+
+DeviceState NfcDevice::GetCurrentState() const {
+ return device_state;
+}
+
+Result NfcDevice::GetNpadId(Core::HID::NpadIdType& out_npad_id) const {
+ out_npad_id = npad_id;
+ return ResultSuccess;
+}
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/common/device.h b/src/core/hle/service/nfc/common/device.h
new file mode 100644
index 000000000..98e1945c1
--- /dev/null
+++ b/src/core/hle/service/nfc/common/device.h
@@ -0,0 +1,138 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <span>
+
+#include "common/common_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nfc/mifare_types.h"
+#include "core/hle/service/nfc/nfc_types.h"
+#include "core/hle/service/nfp/nfp_types.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Kernel {
+class KEvent;
+class KReadableEvent;
+} // namespace Kernel
+
+namespace Core {
+class System;
+} // namespace Core
+
+namespace Core::HID {
+class EmulatedController;
+enum class ControllerTriggerType;
+enum class NpadIdType : u32;
+} // namespace Core::HID
+
+namespace Service::NFC {
+class NfcDevice {
+public:
+ NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
+ KernelHelpers::ServiceContext& service_context_,
+ Kernel::KEvent* availability_change_event_);
+ ~NfcDevice();
+
+ void Initialize();
+ void Finalize();
+
+ Result StartDetection(NfcProtocol allowed_protocol);
+ Result StopDetection();
+
+ Result GetTagInfo(TagInfo& tag_info, bool is_mifare) const;
+
+ Result ReadMifare(std::span<const MifareReadBlockParameter> parameters,
+ std::span<MifareReadBlockData> read_block_data) const;
+ Result ReadMifare(const MifareReadBlockParameter& parameter,
+ MifareReadBlockData& read_block_data) const;
+
+ Result WriteMifare(std::span<const MifareWriteBlockParameter> parameters);
+ Result WriteMifare(const MifareWriteBlockParameter& parameter);
+
+ Result SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout,
+ std::span<const u8> command_data, std::span<u8> out_data);
+
+ Result Mount(NFP::ModelType model_type, NFP::MountTarget mount_target);
+ Result Unmount();
+
+ Result Flush();
+ Result FlushDebug();
+ Result FlushWithBreak(NFP::BreakType break_type);
+ Result Restore();
+
+ Result GetCommonInfo(NFP::CommonInfo& common_info) const;
+ Result GetModelInfo(NFP::ModelInfo& model_info) const;
+ Result GetRegisterInfo(NFP::RegisterInfo& register_info) const;
+ Result GetRegisterInfoPrivate(NFP::RegisterInfoPrivate& register_info) const;
+ Result GetAdminInfo(NFP::AdminInfo& admin_info) const;
+
+ Result DeleteRegisterInfo();
+ Result SetRegisterInfoPrivate(const NFP::RegisterInfoPrivate& register_info);
+ Result RestoreAmiibo();
+ Result Format();
+
+ Result OpenApplicationArea(u32 access_id);
+ Result GetApplicationAreaId(u32& application_area_id) const;
+ Result GetApplicationArea(std::span<u8> data) const;
+ Result SetApplicationArea(std::span<const u8> data);
+ Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
+ Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
+ Result DeleteApplicationArea();
+ Result ExistsApplicationArea(bool& has_application_area) const;
+
+ Result GetAll(NFP::NfpData& data) const;
+ Result SetAll(const NFP::NfpData& data);
+ Result BreakTag(NFP::BreakType break_type);
+ Result ReadBackupData(std::span<u8> data) const;
+ Result WriteBackupData(std::span<const u8> data);
+ Result WriteNtf(std::span<const u8> data);
+
+ u64 GetHandle() const;
+ DeviceState GetCurrentState() const;
+ Result GetNpadId(Core::HID::NpadIdType& out_npad_id) const;
+
+ Kernel::KReadableEvent& GetActivateEvent() const;
+ Kernel::KReadableEvent& GetDeactivateEvent() const;
+
+private:
+ void NpadUpdate(Core::HID::ControllerTriggerType type);
+ bool LoadNfcTag(std::span<const u8> data);
+ void CloseNfcTag();
+
+ NFP::AmiiboName GetAmiiboName(const NFP::AmiiboSettings& settings) const;
+ void SetAmiiboName(NFP::AmiiboSettings& settings, const NFP::AmiiboName& amiibo_name);
+ NFP::AmiiboDate GetAmiiboDate(s64 posix_time) const;
+ u64 GetCurrentPosixTime() const;
+ u64 RemoveVersionByte(u64 application_id) const;
+ void UpdateSettingsCrc();
+ void UpdateRegisterInfoCrc();
+
+ bool is_controller_set{};
+ int callback_key;
+ const Core::HID::NpadIdType npad_id;
+ Core::System& system;
+ Core::HID::EmulatedController* npad_device = nullptr;
+ KernelHelpers::ServiceContext& service_context;
+ Kernel::KEvent* activate_event = nullptr;
+ Kernel::KEvent* deactivate_event = nullptr;
+ Kernel::KEvent* availability_change_event = nullptr;
+
+ bool is_initalized{};
+ NfcProtocol allowed_protocols{};
+ DeviceState device_state{DeviceState::Unavailable};
+
+ // NFP data
+ bool is_data_moddified{};
+ bool is_app_area_open{};
+ bool is_plain_amiibo{};
+ NFP::MountTarget mount_target{NFP::MountTarget::None};
+
+ NFP::NTAG215File tag_data{};
+ std::vector<u8> mifare_data{};
+ NFP::EncryptedNTAG215File encrypted_tag_data{};
+};
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/common/device_manager.cpp b/src/core/hle/service/nfc/common/device_manager.cpp
new file mode 100644
index 000000000..d5deaaf27
--- /dev/null
+++ b/src/core/hle/service/nfc/common/device_manager.cpp
@@ -0,0 +1,695 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/nfc/common/device.h"
+#include "core/hle/service/nfc/common/device_manager.h"
+#include "core/hle/service/nfc/nfc_result.h"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Service::NFC {
+
+DeviceManager::DeviceManager(Core::System& system_, KernelHelpers::ServiceContext& service_context_)
+ : system{system_}, service_context{service_context_} {
+
+ availability_change_event =
+ service_context.CreateEvent("Nfc:DeviceManager:AvailabilityChangeEvent");
+
+ for (u32 device_index = 0; device_index < devices.size(); device_index++) {
+ devices[device_index] =
+ std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
+ service_context, availability_change_event);
+ }
+
+ is_initialized = false;
+}
+
+DeviceManager ::~DeviceManager() {
+ service_context.CloseEvent(availability_change_event);
+}
+
+Result DeviceManager::Initialize() {
+ for (auto& device : devices) {
+ device->Initialize();
+ }
+ is_initialized = true;
+ return ResultSuccess;
+}
+
+Result DeviceManager::Finalize() {
+ for (auto& device : devices) {
+ device->Finalize();
+ }
+ is_initialized = false;
+ return ResultSuccess;
+}
+
+Result DeviceManager::ListDevices(std::vector<u64>& nfp_devices,
+ std::size_t max_allowed_devices) const {
+ for (auto& device : devices) {
+ if (nfp_devices.size() >= max_allowed_devices) {
+ continue;
+ }
+ if (device->GetCurrentState() != DeviceState::Unavailable) {
+ nfp_devices.push_back(device->GetHandle());
+ }
+ }
+
+ if (nfp_devices.empty()) {
+ return ResultDeviceNotFound;
+ }
+
+ return ResultSuccess;
+}
+
+DeviceState DeviceManager::GetDeviceState(u64 device_handle) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ const auto result = GetDeviceFromHandle(device_handle, device, false);
+
+ if (result.IsSuccess()) {
+ return device->GetCurrentState();
+ }
+
+ return DeviceState::Unavailable;
+}
+
+Result DeviceManager::GetNpadId(u64 device_handle, Core::HID::NpadIdType& npad_id) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetNpadId(npad_id);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Kernel::KReadableEvent& DeviceManager::AttachAvailabilityChangeEvent() const {
+ return availability_change_event->GetReadableEvent();
+}
+
+Result DeviceManager::StartDetection(u64 device_handle, NfcProtocol tag_protocol) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->StartDetection(tag_protocol);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::StopDetection(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->StopDetection();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetTagInfo(u64 device_handle, TagInfo& tag_info, bool is_mifare) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetTagInfo(tag_info, is_mifare);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Kernel::KReadableEvent& DeviceManager::AttachActivateEvent(u64 device_handle) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ GetDeviceFromHandle(device_handle, device, false);
+
+ // TODO: Return proper error code on failure
+ return device->GetActivateEvent();
+}
+
+Kernel::KReadableEvent& DeviceManager::AttachDeactivateEvent(u64 device_handle) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ GetDeviceFromHandle(device_handle, device, false);
+
+ // TODO: Return proper error code on failure
+ return device->GetDeactivateEvent();
+}
+
+Result DeviceManager::ReadMifare(u64 device_handle,
+ std::span<const MifareReadBlockParameter> read_parameters,
+ std::span<MifareReadBlockData> read_data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->ReadMifare(read_parameters, read_data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::WriteMifare(u64 device_handle,
+ std::span<const MifareWriteBlockParameter> write_parameters) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->WriteMifare(write_parameters);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::SendCommandByPassThrough(u64 device_handle,
+ const Time::Clock::TimeSpanType& timeout,
+ std::span<const u8> command_data,
+ std::span<u8> out_data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->SendCommandByPassThrough(timeout, command_data, out_data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::Mount(u64 device_handle, NFP::ModelType model_type,
+ NFP::MountTarget mount_target) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->Mount(model_type, mount_target);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::Unmount(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->Unmount();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::OpenApplicationArea(u64 device_handle, u32 access_id) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->OpenApplicationArea(access_id);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetApplicationArea(u64 device_handle, std::span<u8> data) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetApplicationArea(data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::SetApplicationArea(u64 device_handle, std::span<const u8> data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->SetApplicationArea(data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::Flush(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->Flush();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::Restore(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->Restore();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::CreateApplicationArea(u64 device_handle, u32 access_id,
+ std::span<const u8> data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->CreateApplicationArea(access_id, data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetRegisterInfo(u64 device_handle, NFP::RegisterInfo& register_info) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetRegisterInfo(register_info);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetCommonInfo(u64 device_handle, NFP::CommonInfo& common_info) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetCommonInfo(common_info);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetModelInfo(u64 device_handle, NFP::ModelInfo& model_info) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetModelInfo(model_info);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+u32 DeviceManager::GetApplicationAreaSize() const {
+ return sizeof(NFP::ApplicationArea);
+}
+
+Result DeviceManager::RecreateApplicationArea(u64 device_handle, u32 access_id,
+ std::span<const u8> data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->RecreateApplicationArea(access_id, data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::Format(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->Format();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetAdminInfo(u64 device_handle, NFP::AdminInfo& admin_info) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetAdminInfo(admin_info);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetRegisterInfoPrivate(u64 device_handle,
+ NFP::RegisterInfoPrivate& register_info) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetRegisterInfoPrivate(register_info);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::SetRegisterInfoPrivate(u64 device_handle,
+ const NFP::RegisterInfoPrivate& register_info) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->SetRegisterInfoPrivate(register_info);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::DeleteRegisterInfo(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->DeleteRegisterInfo();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::DeleteApplicationArea(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->DeleteApplicationArea();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::ExistsApplicationArea(u64 device_handle, bool& has_application_area) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->ExistsApplicationArea(has_application_area);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetAll(u64 device_handle, NFP::NfpData& nfp_data) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->GetAll(nfp_data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::SetAll(u64 device_handle, const NFP::NfpData& nfp_data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->SetAll(nfp_data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::FlushDebug(u64 device_handle) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->FlushDebug();
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::BreakTag(u64 device_handle, NFP::BreakType break_type) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->BreakTag(break_type);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::ReadBackupData(u64 device_handle, std::span<u8> data) const {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->ReadBackupData(data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::WriteBackupData(u64 device_handle, std::span<const u8> data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->WriteBackupData(data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::WriteNtf(u64 device_handle, NFP::WriteType, std::span<const u8> data) {
+ std::scoped_lock lock{mutex};
+
+ std::shared_ptr<NfcDevice> device = nullptr;
+ auto result = GetDeviceHandle(device_handle, device);
+
+ if (result.IsSuccess()) {
+ result = device->WriteNtf(data);
+ result = VerifyDeviceResult(device, result);
+ }
+
+ return result;
+}
+
+Result DeviceManager::GetDeviceFromHandle(u64 handle, std::shared_ptr<NfcDevice>& nfc_device,
+ bool check_state) const {
+ if (check_state) {
+ const Result is_parameter_set = IsNfcParameterSet();
+ if (is_parameter_set.IsError()) {
+ return is_parameter_set;
+ }
+ const Result is_enabled = IsNfcEnabled();
+ if (is_enabled.IsError()) {
+ return is_enabled;
+ }
+ const Result is_nfc_initialized = IsNfcInitialized();
+ if (is_nfc_initialized.IsError()) {
+ return is_nfc_initialized;
+ }
+ }
+
+ for (auto& device : devices) {
+ if (device->GetHandle() == handle) {
+ nfc_device = device;
+ return ResultSuccess;
+ }
+ }
+
+ return ResultDeviceNotFound;
+}
+
+std::optional<std::shared_ptr<NfcDevice>> DeviceManager::GetNfcDevice(u64 handle) {
+ for (auto& device : devices) {
+ if (device->GetHandle() == handle) {
+ return device;
+ }
+ }
+ return std::nullopt;
+}
+
+const std::optional<std::shared_ptr<NfcDevice>> DeviceManager::GetNfcDevice(u64 handle) const {
+ for (auto& device : devices) {
+ if (device->GetHandle() == handle) {
+ return device;
+ }
+ }
+ return std::nullopt;
+}
+
+Result DeviceManager::GetDeviceHandle(u64 handle, std::shared_ptr<NfcDevice>& device) const {
+ const auto result = GetDeviceFromHandle(handle, device, true);
+ if (result.IsError()) {
+ return result;
+ }
+ return CheckDeviceState(device);
+}
+
+Result DeviceManager::VerifyDeviceResult(std::shared_ptr<NfcDevice> device,
+ Result operation_result) const {
+ if (operation_result.IsSuccess()) {
+ return operation_result;
+ }
+
+ const Result is_parameter_set = IsNfcParameterSet();
+ if (is_parameter_set.IsError()) {
+ return is_parameter_set;
+ }
+ const Result is_enabled = IsNfcEnabled();
+ if (is_enabled.IsError()) {
+ return is_enabled;
+ }
+ const Result is_nfc_initialized = IsNfcInitialized();
+ if (is_nfc_initialized.IsError()) {
+ return is_nfc_initialized;
+ }
+ const Result device_state = CheckDeviceState(device);
+ if (device_state.IsError()) {
+ return device_state;
+ }
+
+ return operation_result;
+}
+
+Result DeviceManager::CheckDeviceState(std::shared_ptr<NfcDevice> device) const {
+ if (device == nullptr) {
+ return ResultInvalidArgument;
+ }
+
+ return ResultSuccess;
+}
+
+Result DeviceManager::IsNfcEnabled() const {
+ // TODO: This calls nn::settings::detail::GetNfcEnableFlag
+ const bool is_enabled = true;
+ if (!is_enabled) {
+ return ResultNfcDisabled;
+ }
+ return ResultSuccess;
+}
+
+Result DeviceManager::IsNfcParameterSet() const {
+ // TODO: This calls checks against a bool on offset 0x450
+ const bool is_set = true;
+ if (!is_set) {
+ return ResultUnknown76;
+ }
+ return ResultSuccess;
+}
+
+Result DeviceManager::IsNfcInitialized() const {
+ if (!is_initialized) {
+ return ResultNfcNotInitialized;
+ }
+ return ResultSuccess;
+}
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/common/device_manager.h b/src/core/hle/service/nfc/common/device_manager.h
new file mode 100644
index 000000000..2971e280f
--- /dev/null
+++ b/src/core/hle/service/nfc/common/device_manager.h
@@ -0,0 +1,100 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+#include <memory>
+#include <optional>
+#include <span>
+
+#include "core/hid/hid_types.h"
+#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nfc/mifare_types.h"
+#include "core/hle/service/nfc/nfc_types.h"
+#include "core/hle/service/nfp/nfp_types.h"
+#include "core/hle/service/service.h"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Service::NFC {
+class NfcDevice;
+
+class DeviceManager {
+public:
+ explicit DeviceManager(Core::System& system_, KernelHelpers::ServiceContext& service_context_);
+ ~DeviceManager();
+
+ // Nfc device manager
+ Result Initialize();
+ Result Finalize();
+ Result ListDevices(std::vector<u64>& nfp_devices, std::size_t max_allowed_devices) const;
+ DeviceState GetDeviceState(u64 device_handle) const;
+ Result GetNpadId(u64 device_handle, Core::HID::NpadIdType& npad_id) const;
+ Kernel::KReadableEvent& AttachAvailabilityChangeEvent() const;
+ Result StartDetection(u64 device_handle, NfcProtocol tag_protocol);
+ Result StopDetection(u64 device_handle);
+ Result GetTagInfo(u64 device_handle, NFP::TagInfo& tag_info, bool is_mifare) const;
+ Kernel::KReadableEvent& AttachActivateEvent(u64 device_handle) const;
+ Kernel::KReadableEvent& AttachDeactivateEvent(u64 device_handle) const;
+ Result ReadMifare(u64 device_handle,
+ const std::span<const MifareReadBlockParameter> read_parameters,
+ std::span<MifareReadBlockData> read_data);
+ Result WriteMifare(u64 device_handle,
+ std::span<const MifareWriteBlockParameter> write_parameters);
+ Result SendCommandByPassThrough(u64 device_handle, const Time::Clock::TimeSpanType& timeout,
+ std::span<const u8> command_data, std::span<u8> out_data);
+
+ // Nfp device manager
+ Result Mount(u64 device_handle, NFP::ModelType model_type, NFP::MountTarget mount_target);
+ Result Unmount(u64 device_handle);
+ Result OpenApplicationArea(u64 device_handle, u32 access_id);
+ Result GetApplicationArea(u64 device_handle, std::span<u8> data) const;
+ Result SetApplicationArea(u64 device_handle, std::span<const u8> data);
+ Result Flush(u64 device_handle);
+ Result Restore(u64 device_handle);
+ Result CreateApplicationArea(u64 device_handle, u32 access_id, std::span<const u8> data);
+ Result GetRegisterInfo(u64 device_handle, NFP::RegisterInfo& register_info) const;
+ Result GetCommonInfo(u64 device_handle, NFP::CommonInfo& common_info) const;
+ Result GetModelInfo(u64 device_handle, NFP::ModelInfo& model_info) const;
+ u32 GetApplicationAreaSize() const;
+ Result RecreateApplicationArea(u64 device_handle, u32 access_id, std::span<const u8> data);
+ Result Format(u64 device_handle);
+ Result GetAdminInfo(u64 device_handle, NFP::AdminInfo& admin_info) const;
+ Result GetRegisterInfoPrivate(u64 device_handle, NFP::RegisterInfoPrivate& register_info) const;
+ Result SetRegisterInfoPrivate(u64 device_handle, const NFP::RegisterInfoPrivate& register_info);
+ Result DeleteRegisterInfo(u64 device_handle);
+ Result DeleteApplicationArea(u64 device_handle);
+ Result ExistsApplicationArea(u64 device_handle, bool& has_application_area) const;
+ Result GetAll(u64 device_handle, NFP::NfpData& nfp_data) const;
+ Result SetAll(u64 device_handle, const NFP::NfpData& nfp_data);
+ Result FlushDebug(u64 device_handle);
+ Result BreakTag(u64 device_handle, NFP::BreakType break_type);
+ Result ReadBackupData(u64 device_handle, std::span<u8> data) const;
+ Result WriteBackupData(u64 device_handle, std::span<const u8> data);
+ Result WriteNtf(u64 device_handle, NFP::WriteType, std::span<const u8> data);
+
+private:
+ Result IsNfcEnabled() const;
+ Result IsNfcParameterSet() const;
+ Result IsNfcInitialized() const;
+
+ Result GetDeviceFromHandle(u64 handle, std::shared_ptr<NfcDevice>& device,
+ bool check_state) const;
+
+ Result GetDeviceHandle(u64 handle, std::shared_ptr<NfcDevice>& device) const;
+ Result VerifyDeviceResult(std::shared_ptr<NfcDevice> device, Result operation_result) const;
+ Result CheckDeviceState(std::shared_ptr<NfcDevice> device) const;
+
+ std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
+ const std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle) const;
+
+ bool is_initialized = false;
+ mutable std::mutex mutex;
+ std::array<std::shared_ptr<NfcDevice>, 10> devices{};
+
+ Core::System& system;
+ KernelHelpers::ServiceContext service_context;
+ Kernel::KEvent* availability_change_event;
+};
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/mifare_result.h b/src/core/hle/service/nfc/mifare_result.h
new file mode 100644
index 000000000..4b60048a5
--- /dev/null
+++ b/src/core/hle/service/nfc/mifare_result.h
@@ -0,0 +1,17 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "core/hle/result.h"
+
+namespace Service::NFC::Mifare {
+
+constexpr Result ResultDeviceNotFound(ErrorModule::NFCMifare, 64);
+constexpr Result ResultInvalidArgument(ErrorModule::NFCMifare, 65);
+constexpr Result ResultWrongDeviceState(ErrorModule::NFCMifare, 73);
+constexpr Result ResultNfcDisabled(ErrorModule::NFCMifare, 80);
+constexpr Result ResultTagRemoved(ErrorModule::NFCMifare, 97);
+constexpr Result ResultReadError(ErrorModule::NFCMifare, 288);
+
+} // namespace Service::NFC::Mifare
diff --git a/src/core/hle/service/nfc/mifare_types.h b/src/core/hle/service/nfc/mifare_types.h
new file mode 100644
index 000000000..75b59f021
--- /dev/null
+++ b/src/core/hle/service/nfc/mifare_types.h
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::NFC {
+
+enum class MifareCmd : u8 {
+ AuthA = 0x60,
+ AuthB = 0x61,
+ Read = 0x30,
+ Write = 0xA0,
+ Transfer = 0xB0,
+ Decrement = 0xC0,
+ Increment = 0xC1,
+ Store = 0xC2
+};
+
+using DataBlock = std::array<u8, 0x10>;
+using KeyData = std::array<u8, 0x6>;
+
+struct SectorKey {
+ MifareCmd command;
+ u8 unknown; // Usually 1
+ INSERT_PADDING_BYTES(0x6);
+ KeyData sector_key;
+ INSERT_PADDING_BYTES(0x2);
+};
+static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size");
+
+// This is nn::nfc::MifareReadBlockParameter
+struct MifareReadBlockParameter {
+ u8 sector_number;
+ INSERT_PADDING_BYTES(0x7);
+ SectorKey sector_key;
+};
+static_assert(sizeof(MifareReadBlockParameter) == 0x18,
+ "MifareReadBlockParameter is an invalid size");
+
+// This is nn::nfc::MifareReadBlockData
+struct MifareReadBlockData {
+ DataBlock data;
+ u8 sector_number;
+ INSERT_PADDING_BYTES(0x7);
+};
+static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size");
+
+// This is nn::nfc::MifareWriteBlockParameter
+struct MifareWriteBlockParameter {
+ DataBlock data;
+ u8 sector_number;
+ INSERT_PADDING_BYTES(0x7);
+ SectorKey sector_key;
+};
+static_assert(sizeof(MifareWriteBlockParameter) == 0x28,
+ "MifareWriteBlockParameter is an invalid size");
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/mifare_user.cpp b/src/core/hle/service/nfc/mifare_user.cpp
deleted file mode 100644
index e0bbd46e1..000000000
--- a/src/core/hle/service/nfc/mifare_user.cpp
+++ /dev/null
@@ -1,400 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hid/hid_types.h"
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/nfc/mifare_user.h"
-#include "core/hle/service/nfc/nfc_device.h"
-#include "core/hle/service/nfc/nfc_result.h"
-
-namespace Service::NFC {
-
-MFIUser::MFIUser(Core::System& system_)
- : ServiceFramework{system_, "NFC::MFIUser"}, service_context{system_, service_name} {
- static const FunctionInfo functions[] = {
- {0, &MFIUser::Initialize, "Initialize"},
- {1, &MFIUser::Finalize, "Finalize"},
- {2, &MFIUser::ListDevices, "ListDevices"},
- {3, &MFIUser::StartDetection, "StartDetection"},
- {4, &MFIUser::StopDetection, "StopDetection"},
- {5, &MFIUser::Read, "Read"},
- {6, &MFIUser::Write, "Write"},
- {7, &MFIUser::GetTagInfo, "GetTagInfo"},
- {8, &MFIUser::GetActivateEventHandle, "GetActivateEventHandle"},
- {9, &MFIUser::GetDeactivateEventHandle, "GetDeactivateEventHandle"},
- {10, &MFIUser::GetState, "GetState"},
- {11, &MFIUser::GetDeviceState, "GetDeviceState"},
- {12, &MFIUser::GetNpadId, "GetNpadId"},
- {13, &MFIUser::GetAvailabilityChangeEventHandle, "GetAvailabilityChangeEventHandle"},
- };
- RegisterHandlers(functions);
-
- availability_change_event = service_context.CreateEvent("MFIUser:AvailabilityChangeEvent");
-
- for (u32 device_index = 0; device_index < 10; device_index++) {
- devices[device_index] =
- std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
- service_context, availability_change_event);
- }
-}
-
-MFIUser ::~MFIUser() {
- availability_change_event->Close();
-}
-
-void MFIUser::Initialize(HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- state = State::Initialized;
-
- for (auto& device : devices) {
- device->Initialize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 0};
- rb.Push(ResultSuccess);
-}
-
-void MFIUser::Finalize(HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- state = State::NonInitialized;
-
- for (auto& device : devices) {
- device->Finalize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void MFIUser::ListDevices(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- if (!ctx.CanWriteBuffer()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareInvalidArgument);
- return;
- }
-
- if (ctx.GetWriteBufferSize() == 0) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareInvalidArgument);
- return;
- }
-
- std::vector<u64> nfp_devices;
- const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>();
-
- for (const auto& device : devices) {
- if (nfp_devices.size() >= max_allowed_devices) {
- continue;
- }
- if (device->GetCurrentState() != NFP::DeviceState::Unavailable) {
- nfp_devices.push_back(device->GetHandle());
- }
- }
-
- if (nfp_devices.empty()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- ctx.WriteBuffer(nfp_devices);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<s32>(nfp_devices.size()));
-}
-
-void MFIUser::StartDetection(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- const auto result = device.value()->StartDetection(NFP::TagProtocol::All);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void MFIUser::StopDetection(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- const auto result = device.value()->StopDetection();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void MFIUser::Read(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto buffer{ctx.ReadBuffer()};
- const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareReadBlockParameter>()};
- std::vector<NFP::MifareReadBlockParameter> read_commands(number_of_commands);
-
- memcpy(read_commands.data(), buffer.data(),
- number_of_commands * sizeof(NFP::MifareReadBlockParameter));
-
- LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}",
- device_handle, number_of_commands);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- Result result = ResultSuccess;
- std::vector<NFP::MifareReadBlockData> out_data(number_of_commands);
- for (std::size_t i = 0; i < number_of_commands; i++) {
- result = device.value()->MifareRead(read_commands[i], out_data[i]);
- if (result.IsError()) {
- break;
- }
- }
-
- ctx.WriteBuffer(out_data);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void MFIUser::Write(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto buffer{ctx.ReadBuffer()};
- const auto number_of_commands{ctx.GetReadBufferNumElements<NFP::MifareWriteBlockParameter>()};
- std::vector<NFP::MifareWriteBlockParameter> write_commands(number_of_commands);
-
- memcpy(write_commands.data(), buffer.data(),
- number_of_commands * sizeof(NFP::MifareWriteBlockParameter));
-
- LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, write_commands_size={}",
- device_handle, number_of_commands);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- Result result = ResultSuccess;
- std::vector<NFP::MifareReadBlockData> out_data(number_of_commands);
- for (std::size_t i = 0; i < number_of_commands; i++) {
- result = device.value()->MifareWrite(write_commands[i]);
- if (result.IsError()) {
- break;
- }
- }
-
- if (result.IsSuccess()) {
- result = device.value()->Flush();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void MFIUser::GetTagInfo(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- NFP::TagInfo tag_info{};
- const auto result = device.value()->GetTagInfo(tag_info, true);
- ctx.WriteBuffer(tag_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void MFIUser::GetActivateEventHandle(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(device.value()->GetActivateEvent());
-}
-
-void MFIUser::GetDeactivateEventHandle(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(device.value()->GetDeactivateEvent());
-}
-
-void MFIUser::GetState(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(state);
-}
-
-void MFIUser::GetDeviceState(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(device.value()->GetCurrentState());
-}
-
-void MFIUser::GetNpadId(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareDeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(device.value()->GetNpadId());
-}
-
-void MFIUser::GetAvailabilityChangeEventHandle(HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(MifareNfcDisabled);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(availability_change_event->GetReadableEvent());
-}
-
-std::optional<std::shared_ptr<NfcDevice>> MFIUser::GetNfcDevice(u64 handle) {
- for (auto& device : devices) {
- if (device->GetHandle() == handle) {
- return device;
- }
- }
- return std::nullopt;
-}
-
-} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/mifare_user.h b/src/core/hle/service/nfc/mifare_user.h
deleted file mode 100644
index 9701f1d7f..000000000
--- a/src/core/hle/service/nfc/mifare_user.h
+++ /dev/null
@@ -1,52 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <array>
-#include <memory>
-#include <optional>
-
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/service.h"
-
-namespace Service::NFC {
-class NfcDevice;
-
-class MFIUser final : public ServiceFramework<MFIUser> {
-public:
- explicit MFIUser(Core::System& system_);
- ~MFIUser();
-
-private:
- enum class State : u32 {
- NonInitialized,
- Initialized,
- };
-
- void Initialize(HLERequestContext& ctx);
- void Finalize(HLERequestContext& ctx);
- void ListDevices(HLERequestContext& ctx);
- void StartDetection(HLERequestContext& ctx);
- void StopDetection(HLERequestContext& ctx);
- void Read(HLERequestContext& ctx);
- void Write(HLERequestContext& ctx);
- void GetTagInfo(HLERequestContext& ctx);
- void GetActivateEventHandle(HLERequestContext& ctx);
- void GetDeactivateEventHandle(HLERequestContext& ctx);
- void GetState(HLERequestContext& ctx);
- void GetDeviceState(HLERequestContext& ctx);
- void GetNpadId(HLERequestContext& ctx);
- void GetAvailabilityChangeEventHandle(HLERequestContext& ctx);
-
- std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
-
- KernelHelpers::ServiceContext service_context;
-
- std::array<std::shared_ptr<NfcDevice>, 10> devices{};
-
- State state{State::NonInitialized};
- Kernel::KEvent* availability_change_event;
-};
-
-} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index 6595e34ed..30ae989b9 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -6,14 +6,115 @@
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/nfc/mifare_user.h"
#include "core/hle/service/nfc/nfc.h"
-#include "core/hle/service/nfc/nfc_user.h"
+#include "core/hle/service/nfc/nfc_interface.h"
#include "core/hle/service/server_manager.h"
#include "core/hle/service/service.h"
namespace Service::NFC {
+class IUser final : public NfcInterface {
+public:
+ explicit IUser(Core::System& system_) : NfcInterface(system_, "NFC::IUser", BackendType::Nfc) {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &NfcInterface::Initialize, "InitializeOld"},
+ {1, &NfcInterface::Finalize, "FinalizeOld"},
+ {2, &NfcInterface::GetState, "GetStateOld"},
+ {3, &NfcInterface::IsNfcEnabled, "IsNfcEnabledOld"},
+ {400, &NfcInterface::Initialize, "Initialize"},
+ {401, &NfcInterface::Finalize, "Finalize"},
+ {402, &NfcInterface::GetState, "GetState"},
+ {403, &NfcInterface::IsNfcEnabled, "IsNfcEnabled"},
+ {404, &NfcInterface::ListDevices, "ListDevices"},
+ {405, &NfcInterface::GetDeviceState, "GetDeviceState"},
+ {406, &NfcInterface::GetNpadId, "GetNpadId"},
+ {407, &NfcInterface::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+ {408, &NfcInterface::StartDetection, "StartDetection"},
+ {409, &NfcInterface::StopDetection, "StopDetection"},
+ {410, &NfcInterface::GetTagInfo, "GetTagInfo"},
+ {411, &NfcInterface::AttachActivateEvent, "AttachActivateEvent"},
+ {412, &NfcInterface::AttachDeactivateEvent, "AttachDeactivateEvent"},
+ {1000, &NfcInterface::ReadMifare, "ReadMifare"},
+ {1001, &NfcInterface::WriteMifare ,"WriteMifare"},
+ {1300, &NfcInterface::SendCommandByPassThrough, "SendCommandByPassThrough"},
+ {1301, nullptr, "KeepPassThroughSession"},
+ {1302, nullptr, "ReleasePassThroughSession"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+class ISystem final : public NfcInterface {
+public:
+ explicit ISystem(Core::System& system_)
+ : NfcInterface{system_, "NFC::ISystem", BackendType::Nfc} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, &NfcInterface::Initialize, "InitializeOld"},
+ {1, &NfcInterface::Finalize, "FinalizeOld"},
+ {2, &NfcInterface::GetState, "GetStateOld"},
+ {3, &NfcInterface::IsNfcEnabled, "IsNfcEnabledOld"},
+ {100, nullptr, "SetNfcEnabledOld"},
+ {400, &NfcInterface::Initialize, "Initialize"},
+ {401, &NfcInterface::Finalize, "Finalize"},
+ {402, &NfcInterface::GetState, "GetState"},
+ {403, &NfcInterface::IsNfcEnabled, "IsNfcEnabled"},
+ {404, &NfcInterface::ListDevices, "ListDevices"},
+ {405, &NfcInterface::GetDeviceState, "GetDeviceState"},
+ {406, &NfcInterface::GetNpadId, "GetNpadId"},
+ {407, &NfcInterface::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
+ {408, &NfcInterface::StartDetection, "StartDetection"},
+ {409, &NfcInterface::StopDetection, "StopDetection"},
+ {410, &NfcInterface::GetTagInfo, "GetTagInfo"},
+ {411, &NfcInterface::AttachActivateEvent, "AttachActivateEvent"},
+ {412, &NfcInterface::AttachDeactivateEvent, "AttachDeactivateEvent"},
+ {500, nullptr, "SetNfcEnabled"},
+ {510, nullptr, "OutputTestWave"},
+ {1000, &NfcInterface::ReadMifare, "ReadMifare"},
+ {1001, &NfcInterface::WriteMifare, "WriteMifare"},
+ {1300, &NfcInterface::SendCommandByPassThrough, "SendCommandByPassThrough"},
+ {1301, nullptr, "KeepPassThroughSession"},
+ {1302, nullptr, "ReleasePassThroughSession"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
+// MFInterface has an unique interface but it's identical to NfcInterface so we can keep the code
+// simpler
+using MFInterface = NfcInterface;
+class MFIUser final : public MFInterface {
+public:
+ explicit MFIUser(Core::System& system_)
+ : MFInterface{system_, "NFC::MFInterface", BackendType::Mifare} {
+ // clang-format off
+ static const FunctionInfoTyped<MFIUser> functions[] = {
+ {0, &MFIUser::Initialize, "Initialize"},
+ {1, &MFIUser::Finalize, "Finalize"},
+ {2, &MFIUser::ListDevices, "ListDevices"},
+ {3, &MFIUser::StartDetection, "StartDetection"},
+ {4, &MFIUser::StopDetection, "StopDetection"},
+ {5, &MFIUser::ReadMifare, "Read"},
+ {6, &MFIUser::WriteMifare, "Write"},
+ {7, &MFIUser::GetTagInfo, "GetTagInfo"},
+ {8, &MFIUser::AttachActivateEvent, "GetActivateEventHandle"},
+ {9, &MFIUser::AttachDeactivateEvent, "GetDeactivateEventHandle"},
+ {10, &MFIUser::GetState, "GetState"},
+ {11, &MFIUser::GetDeviceState, "GetDeviceState"},
+ {12, &MFIUser::GetNpadId, "GetNpadId"},
+ {13, &MFIUser::AttachAvailabilityChangeEvent, "GetAvailabilityChangeEventHandle"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+ }
+};
+
class IAm final : public ServiceFramework<IAm> {
public:
explicit IAm(Core::System& system_) : ServiceFramework{system_, "NFC::IAm"} {
@@ -34,7 +135,7 @@ public:
explicit NFC_AM(Core::System& system_) : ServiceFramework{system_, "nfc:am"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &NFC_AM::CreateAmInterface, "CreateAmInterface"},
+ {0, &NFC_AM::CreateAmNfcInterface, "CreateAmNfcInterface"},
};
// clang-format on
@@ -42,7 +143,7 @@ public:
}
private:
- void CreateAmInterface(HLERequestContext& ctx) {
+ void CreateAmNfcInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -56,7 +157,7 @@ public:
explicit NFC_MF_U(Core::System& system_) : ServiceFramework{system_, "nfc:mf:u"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &NFC_MF_U::CreateUserInterface, "CreateUserInterface"},
+ {0, &NFC_MF_U::CreateUserNfcInterface, "CreateUserNfcInterface"},
};
// clang-format on
@@ -64,7 +165,7 @@ public:
}
private:
- void CreateUserInterface(HLERequestContext& ctx) {
+ void CreateUserNfcInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -78,7 +179,7 @@ public:
explicit NFC_U(Core::System& system_) : ServiceFramework{system_, "nfc:user"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &NFC_U::CreateUserInterface, "CreateUserInterface"},
+ {0, &NFC_U::CreateUserNfcInterface, "CreateUserNfcInterface"},
};
// clang-format on
@@ -86,7 +187,7 @@ public:
}
private:
- void CreateUserInterface(HLERequestContext& ctx) {
+ void CreateUserNfcInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -95,49 +196,12 @@ private:
}
};
-class ISystem final : public ServiceFramework<ISystem> {
-public:
- explicit ISystem(Core::System& system_) : ServiceFramework{system_, "ISystem"} {
- // clang-format off
- static const FunctionInfo functions[] = {
- {0, nullptr, "Initialize"},
- {1, nullptr, "Finalize"},
- {2, nullptr, "GetStateOld"},
- {3, nullptr, "IsNfcEnabledOld"},
- {100, nullptr, "SetNfcEnabledOld"},
- {400, nullptr, "InitializeSystem"},
- {401, nullptr, "FinalizeSystem"},
- {402, nullptr, "GetState"},
- {403, nullptr, "IsNfcEnabled"},
- {404, nullptr, "ListDevices"},
- {405, nullptr, "GetDeviceState"},
- {406, nullptr, "GetNpadId"},
- {407, nullptr, "AttachAvailabilityChangeEvent"},
- {408, nullptr, "StartDetection"},
- {409, nullptr, "StopDetection"},
- {410, nullptr, "GetTagInfo"},
- {411, nullptr, "AttachActivateEvent"},
- {412, nullptr, "AttachDeactivateEvent"},
- {500, nullptr, "SetNfcEnabled"},
- {510, nullptr, "OutputTestWave"},
- {1000, nullptr, "ReadMifare"},
- {1001, nullptr, "WriteMifare"},
- {1300, nullptr, "SendCommandByPassThrough"},
- {1301, nullptr, "KeepPassThroughSession"},
- {1302, nullptr, "ReleasePassThroughSession"},
- };
- // clang-format on
-
- RegisterHandlers(functions);
- }
-};
-
class NFC_SYS final : public ServiceFramework<NFC_SYS> {
public:
explicit NFC_SYS(Core::System& system_) : ServiceFramework{system_, "nfc:sys"} {
// clang-format off
static const FunctionInfo functions[] = {
- {0, &NFC_SYS::CreateSystemInterface, "CreateSystemInterface"},
+ {0, &NFC_SYS::CreateSystemNfcInterface, "CreateSystemNfcInterface"},
};
// clang-format on
@@ -145,7 +209,7 @@ public:
}
private:
- void CreateSystemInterface(HLERequestContext& ctx) {
+ void CreateSystemNfcInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFC, "called");
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
@@ -161,6 +225,7 @@ void LoopProcess(Core::System& system) {
server_manager->RegisterNamedService("nfc:mf:u", std::make_shared<NFC_MF_U>(system));
server_manager->RegisterNamedService("nfc:user", std::make_shared<NFC_U>(system));
server_manager->RegisterNamedService("nfc:sys", std::make_shared<NFC_SYS>(system));
+
ServerManager::RunServer(std::move(server_manager));
}
diff --git a/src/core/hle/service/nfc/nfc_device.cpp b/src/core/hle/service/nfc/nfc_device.cpp
deleted file mode 100644
index c7db74d14..000000000
--- a/src/core/hle/service/nfc/nfc_device.cpp
+++ /dev/null
@@ -1,288 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/input.h"
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hid/emulated_controller.h"
-#include "core/hid/hid_core.h"
-#include "core/hid/hid_types.h"
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/nfc/nfc_device.h"
-#include "core/hle/service/nfc/nfc_result.h"
-#include "core/hle/service/nfc/nfc_user.h"
-
-namespace Service::NFC {
-NfcDevice::NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
- KernelHelpers::ServiceContext& service_context_,
- Kernel::KEvent* availability_change_event_)
- : npad_id{npad_id_}, system{system_}, service_context{service_context_},
- availability_change_event{availability_change_event_} {
- activate_event = service_context.CreateEvent("IUser:NFCActivateEvent");
- deactivate_event = service_context.CreateEvent("IUser:NFCDeactivateEvent");
- npad_device = system.HIDCore().GetEmulatedController(npad_id);
-
- Core::HID::ControllerUpdateCallback engine_callback{
- .on_change = [this](Core::HID::ControllerTriggerType type) { NpadUpdate(type); },
- .is_npad_service = false,
- };
- is_controller_set = true;
- callback_key = npad_device->SetCallback(engine_callback);
-}
-
-NfcDevice::~NfcDevice() {
- activate_event->Close();
- deactivate_event->Close();
- if (!is_controller_set) {
- return;
- }
- npad_device->DeleteCallback(callback_key);
- is_controller_set = false;
-};
-
-void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
- if (!is_initalized) {
- return;
- }
-
- if (type == Core::HID::ControllerTriggerType::Connected) {
- Initialize();
- availability_change_event->Signal();
- return;
- }
-
- if (type == Core::HID::ControllerTriggerType::Disconnected) {
- device_state = NFP::DeviceState::Unavailable;
- availability_change_event->Signal();
- return;
- }
-
- if (type != Core::HID::ControllerTriggerType::Nfc) {
- return;
- }
-
- if (!npad_device->IsConnected()) {
- return;
- }
-
- const auto nfc_status = npad_device->GetNfc();
- switch (nfc_status.state) {
- case Common::Input::NfcState::NewAmiibo:
- LoadNfcTag(nfc_status.data);
- break;
- case Common::Input::NfcState::AmiiboRemoved:
- if (device_state != NFP::DeviceState::SearchingForTag) {
- CloseNfcTag();
- }
- break;
- default:
- break;
- }
-}
-
-bool NfcDevice::LoadNfcTag(std::span<const u8> data) {
- if (device_state != NFP::DeviceState::SearchingForTag) {
- LOG_ERROR(Service_NFC, "Game is not looking for nfc tag, current state {}", device_state);
- return false;
- }
-
- if (data.size() < sizeof(NFP::EncryptedNTAG215File)) {
- LOG_ERROR(Service_NFC, "Not an amiibo, size={}", data.size());
- return false;
- }
-
- tag_data.resize(data.size());
- memcpy(tag_data.data(), data.data(), data.size());
- memcpy(&encrypted_tag_data, data.data(), sizeof(NFP::EncryptedNTAG215File));
-
- device_state = NFP::DeviceState::TagFound;
- deactivate_event->GetReadableEvent().Clear();
- activate_event->Signal();
- return true;
-}
-
-void NfcDevice::CloseNfcTag() {
- LOG_INFO(Service_NFC, "Remove nfc tag");
-
- device_state = NFP::DeviceState::TagRemoved;
- encrypted_tag_data = {};
- activate_event->GetReadableEvent().Clear();
- deactivate_event->Signal();
-}
-
-Kernel::KReadableEvent& NfcDevice::GetActivateEvent() const {
- return activate_event->GetReadableEvent();
-}
-
-Kernel::KReadableEvent& NfcDevice::GetDeactivateEvent() const {
- return deactivate_event->GetReadableEvent();
-}
-
-void NfcDevice::Initialize() {
- device_state =
- npad_device->HasNfc() ? NFP::DeviceState::Initialized : NFP::DeviceState::Unavailable;
- encrypted_tag_data = {};
- is_initalized = true;
-}
-
-void NfcDevice::Finalize() {
- if (device_state == NFP::DeviceState::SearchingForTag ||
- device_state == NFP::DeviceState::TagRemoved) {
- StopDetection();
- }
- device_state = NFP::DeviceState::Unavailable;
- is_initalized = false;
-}
-
-Result NfcDevice::StartDetection(NFP::TagProtocol allowed_protocol) {
- if (device_state != NFP::DeviceState::Initialized &&
- device_state != NFP::DeviceState::TagRemoved) {
- LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
- return WrongDeviceState;
- }
-
- if (npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
- Common::Input::PollingMode::NFC) !=
- Common::Input::DriverResult::Success) {
- LOG_ERROR(Service_NFC, "Nfc not supported");
- return NfcDisabled;
- }
-
- device_state = NFP::DeviceState::SearchingForTag;
- allowed_protocols = allowed_protocol;
- return ResultSuccess;
-}
-
-Result NfcDevice::StopDetection() {
- npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
- Common::Input::PollingMode::Active);
-
- if (device_state == NFP::DeviceState::Initialized) {
- return ResultSuccess;
- }
-
- if (device_state == NFP::DeviceState::TagFound ||
- device_state == NFP::DeviceState::TagMounted) {
- CloseNfcTag();
- return ResultSuccess;
- }
- if (device_state == NFP::DeviceState::SearchingForTag ||
- device_state == NFP::DeviceState::TagRemoved) {
- device_state = NFP::DeviceState::Initialized;
- return ResultSuccess;
- }
-
- LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
- return WrongDeviceState;
-}
-
-Result NfcDevice::Flush() {
- if (device_state != NFP::DeviceState::TagFound &&
- device_state != NFP::DeviceState::TagMounted) {
- LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
- if (device_state == NFP::DeviceState::TagRemoved) {
- return TagRemoved;
- }
- return WrongDeviceState;
- }
-
- if (!npad_device->WriteNfc(tag_data)) {
- LOG_ERROR(Service_NFP, "Error writing to file");
- return MifareReadError;
- }
-
- return ResultSuccess;
-}
-
-Result NfcDevice::GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const {
- if (device_state != NFP::DeviceState::TagFound &&
- device_state != NFP::DeviceState::TagMounted) {
- LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
- if (device_state == NFP::DeviceState::TagRemoved) {
- return TagRemoved;
- }
- return WrongDeviceState;
- }
-
- if (is_mifare) {
- tag_info = {
- .uuid = encrypted_tag_data.uuid.uid,
- .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
- .protocol = NFP::TagProtocol::TypeA,
- .tag_type = NFP::TagType::Type4,
- };
- return ResultSuccess;
- }
-
- // Protocol and tag type may change here
- tag_info = {
- .uuid = encrypted_tag_data.uuid.uid,
- .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()),
- .protocol = NFP::TagProtocol::TypeA,
- .tag_type = NFP::TagType::Type2,
- };
-
- return ResultSuccess;
-}
-
-Result NfcDevice::MifareRead(const NFP::MifareReadBlockParameter& parameter,
- NFP::MifareReadBlockData& read_block_data) {
- const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock);
- read_block_data.sector_number = parameter.sector_number;
-
- if (device_state != NFP::DeviceState::TagFound &&
- device_state != NFP::DeviceState::TagMounted) {
- LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
- if (device_state == NFP::DeviceState::TagRemoved) {
- return TagRemoved;
- }
- return WrongDeviceState;
- }
-
- if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) {
- return MifareReadError;
- }
-
- // TODO: Use parameter.sector_key to read encrypted data
- memcpy(read_block_data.data.data(), tag_data.data() + sector_index, sizeof(NFP::DataBlock));
-
- return ResultSuccess;
-}
-
-Result NfcDevice::MifareWrite(const NFP::MifareWriteBlockParameter& parameter) {
- const std::size_t sector_index = parameter.sector_number * sizeof(NFP::DataBlock);
-
- if (device_state != NFP::DeviceState::TagFound &&
- device_state != NFP::DeviceState::TagMounted) {
- LOG_ERROR(Service_NFC, "Wrong device state {}", device_state);
- if (device_state == NFP::DeviceState::TagRemoved) {
- return TagRemoved;
- }
- return WrongDeviceState;
- }
-
- if (tag_data.size() < sector_index + sizeof(NFP::DataBlock)) {
- return MifareReadError;
- }
-
- // TODO: Use parameter.sector_key to encrypt the data
- memcpy(tag_data.data() + sector_index, parameter.data.data(), sizeof(NFP::DataBlock));
-
- return ResultSuccess;
-}
-
-u64 NfcDevice::GetHandle() const {
- // Generate a handle based of the npad id
- return static_cast<u64>(npad_id);
-}
-
-NFP::DeviceState NfcDevice::GetCurrentState() const {
- return device_state;
-}
-
-Core::HID::NpadIdType NfcDevice::GetNpadId() const {
- return npad_id;
-}
-
-} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_device.h b/src/core/hle/service/nfc/nfc_device.h
deleted file mode 100644
index ea63f0537..000000000
--- a/src/core/hle/service/nfc/nfc_device.h
+++ /dev/null
@@ -1,78 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include "common/common_types.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nfp/nfp_types.h"
-#include "core/hle/service/service.h"
-
-namespace Kernel {
-class KEvent;
-class KReadableEvent;
-} // namespace Kernel
-
-namespace Core {
-class System;
-} // namespace Core
-
-namespace Core::HID {
-class EmulatedController;
-enum class ControllerTriggerType;
-enum class NpadIdType : u32;
-} // namespace Core::HID
-
-namespace Service::NFC {
-class NfcDevice {
-public:
- NfcDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
- KernelHelpers::ServiceContext& service_context_,
- Kernel::KEvent* availability_change_event_);
- ~NfcDevice();
-
- void Initialize();
- void Finalize();
-
- Result StartDetection(NFP::TagProtocol allowed_protocol);
- Result StopDetection();
- Result Flush();
-
- Result GetTagInfo(NFP::TagInfo& tag_info, bool is_mifare) const;
-
- Result MifareRead(const NFP::MifareReadBlockParameter& parameter,
- NFP::MifareReadBlockData& read_block_data);
-
- Result MifareWrite(const NFP::MifareWriteBlockParameter& parameter);
-
- u64 GetHandle() const;
- NFP::DeviceState GetCurrentState() const;
- Core::HID::NpadIdType GetNpadId() const;
-
- Kernel::KReadableEvent& GetActivateEvent() const;
- Kernel::KReadableEvent& GetDeactivateEvent() const;
-
-private:
- void NpadUpdate(Core::HID::ControllerTriggerType type);
- bool LoadNfcTag(std::span<const u8> data);
- void CloseNfcTag();
-
- bool is_controller_set{};
- int callback_key;
- const Core::HID::NpadIdType npad_id;
- Core::System& system;
- Core::HID::EmulatedController* npad_device = nullptr;
- KernelHelpers::ServiceContext& service_context;
- Kernel::KEvent* activate_event = nullptr;
- Kernel::KEvent* deactivate_event = nullptr;
- Kernel::KEvent* availability_change_event = nullptr;
-
- bool is_initalized{};
- NFP::TagProtocol allowed_protocols{};
- NFP::DeviceState device_state{NFP::DeviceState::Unavailable};
-
- NFP::EncryptedNTAG215File encrypted_tag_data{};
- std::vector<u8> tag_data{};
-};
-
-} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_interface.cpp b/src/core/hle/service/nfc/nfc_interface.cpp
new file mode 100644
index 000000000..0fa29d398
--- /dev/null
+++ b/src/core/hle/service/nfc/nfc_interface.cpp
@@ -0,0 +1,382 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/hid/hid_types.h"
+#include "core/hle/kernel/k_event.h"
+#include "core/hle/service/ipc_helpers.h"
+#include "core/hle/service/nfc/common/device.h"
+#include "core/hle/service/nfc/common/device_manager.h"
+#include "core/hle/service/nfc/mifare_result.h"
+#include "core/hle/service/nfc/mifare_types.h"
+#include "core/hle/service/nfc/nfc_interface.h"
+#include "core/hle/service/nfc/nfc_result.h"
+#include "core/hle/service/nfc/nfc_types.h"
+#include "core/hle/service/nfp/nfp_result.h"
+#include "core/hle/service/time/clock_types.h"
+
+namespace Service::NFC {
+
+NfcInterface::NfcInterface(Core::System& system_, const char* name, BackendType service_backend)
+ : ServiceFramework{system_, name}, service_context{system_, service_name},
+ backend_type{service_backend} {}
+
+NfcInterface ::~NfcInterface() = default;
+
+void NfcInterface::Initialize(HLERequestContext& ctx) {
+ LOG_INFO(Service_NFC, "called");
+
+ auto manager = GetManager();
+ auto result = manager->Initialize();
+
+ if (result.IsSuccess()) {
+ state = State::Initialized;
+ } else {
+ manager->Finalize();
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2, 0};
+ rb.Push(result);
+}
+
+void NfcInterface::Finalize(HLERequestContext& ctx) {
+ LOG_INFO(Service_NFC, "called");
+
+ if (state != State::NonInitialized) {
+ if (GetBackendType() != BackendType::None) {
+ GetManager()->Finalize();
+ }
+ device_manager = nullptr;
+ state = State::NonInitialized;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ResultSuccess);
+}
+
+void NfcInterface::GetState(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(state);
+}
+
+void NfcInterface::IsNfcEnabled(HLERequestContext& ctx) {
+ LOG_DEBUG(Service_NFC, "called");
+
+ // TODO: This calls nn::settings::detail::GetNfcEnableFlag
+ const bool is_enabled = true;
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(is_enabled);
+}
+
+void NfcInterface::ListDevices(HLERequestContext& ctx) {
+ std::vector<u64> nfp_devices;
+ const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>();
+ LOG_DEBUG(Service_NFC, "called");
+
+ auto result = GetManager()->ListDevices(nfp_devices, max_allowed_devices);
+ result = TranslateResultToServiceError(result);
+
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ ctx.WriteBuffer(nfp_devices);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<s32>(nfp_devices.size()));
+}
+
+void NfcInterface::GetDeviceState(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+ const auto device_state = GetManager()->GetDeviceState(device_handle);
+
+ if (device_state > DeviceState::Finalized) {
+ ASSERT_MSG(false, "Invalid device state");
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(device_state);
+}
+
+void NfcInterface::GetNpadId(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+ Core::HID::NpadIdType npad_id{};
+ auto result = GetManager()->GetNpadId(device_handle, npad_id);
+ result = TranslateResultToServiceError(result);
+
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.PushEnum(npad_id);
+}
+
+void NfcInterface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
+ LOG_INFO(Service_NFC, "called");
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(GetManager()->AttachAvailabilityChangeEvent());
+}
+
+void NfcInterface::StartDetection(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto tag_protocol{rp.PopEnum<NfcProtocol>()};
+ LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, tag_protocol);
+
+ auto result = GetManager()->StartDetection(device_handle, tag_protocol);
+ result = TranslateResultToServiceError(result);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void NfcInterface::StopDetection(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
+
+ auto result = GetManager()->StopDetection(device_handle);
+ result = TranslateResultToServiceError(result);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void NfcInterface::GetTagInfo(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
+
+ TagInfo tag_info{};
+ auto result =
+ GetManager()->GetTagInfo(device_handle, tag_info, backend_type == BackendType::Mifare);
+ result = TranslateResultToServiceError(result);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(tag_info);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void NfcInterface::AttachActivateEvent(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(GetManager()->AttachActivateEvent(device_handle));
+}
+
+void NfcInterface::AttachDeactivateEvent(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
+
+ IPC::ResponseBuilder rb{ctx, 2, 1};
+ rb.Push(ResultSuccess);
+ rb.PushCopyObjects(GetManager()->AttachDeactivateEvent(device_handle));
+}
+
+void NfcInterface::ReadMifare(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto buffer{ctx.ReadBuffer()};
+ const auto number_of_commands{ctx.GetReadBufferNumElements<MifareReadBlockParameter>()};
+ std::vector<MifareReadBlockParameter> read_commands(number_of_commands);
+
+ memcpy(read_commands.data(), buffer.data(),
+ number_of_commands * sizeof(MifareReadBlockParameter));
+
+ LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, read_commands_size={}",
+ device_handle, number_of_commands);
+
+ std::vector<MifareReadBlockData> out_data(number_of_commands);
+ auto result = GetManager()->ReadMifare(device_handle, read_commands, out_data);
+ result = TranslateResultToServiceError(result);
+
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(out_data);
+ }
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void NfcInterface::WriteMifare(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto buffer{ctx.ReadBuffer()};
+ const auto number_of_commands{ctx.GetReadBufferNumElements<MifareWriteBlockParameter>()};
+ std::vector<MifareWriteBlockParameter> write_commands(number_of_commands);
+
+ memcpy(write_commands.data(), buffer.data(),
+ number_of_commands * sizeof(MifareWriteBlockParameter));
+
+ LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, write_commands_size={}",
+ device_handle, number_of_commands);
+
+ auto result = GetManager()->WriteMifare(device_handle, write_commands);
+ result = TranslateResultToServiceError(result);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+}
+
+void NfcInterface::SendCommandByPassThrough(HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const auto device_handle{rp.Pop<u64>()};
+ const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()};
+ const auto command_data{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}",
+ device_handle, timeout.ToSeconds(), command_data.size());
+
+ std::vector<u8> out_data(1);
+ auto result =
+ GetManager()->SendCommandByPassThrough(device_handle, timeout, command_data, out_data);
+ result = TranslateResultToServiceError(result);
+
+ if (result.IsError()) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(result);
+ return;
+ }
+
+ ctx.WriteBuffer(out_data);
+
+ IPC::ResponseBuilder rb{ctx, 3};
+ rb.Push(ResultSuccess);
+ rb.Push(static_cast<u32>(out_data.size()));
+}
+
+std::shared_ptr<DeviceManager> NfcInterface::GetManager() {
+ if (device_manager == nullptr) {
+ device_manager = std::make_shared<DeviceManager>(system, service_context);
+ }
+ return device_manager;
+}
+
+BackendType NfcInterface::GetBackendType() const {
+ return backend_type;
+}
+
+Result NfcInterface::TranslateResultToServiceError(Result result) const {
+ const auto backend = GetBackendType();
+
+ if (result.IsSuccess()) {
+ return result;
+ }
+
+ if (result.module != ErrorModule::NFC) {
+ return result;
+ }
+
+ switch (backend) {
+ case BackendType::Mifare:
+ return TranslateResultToNfp(result);
+ case BackendType::Nfp: {
+ return TranslateResultToNfp(result);
+ }
+ default:
+ if (result != ResultUnknown216) {
+ return result;
+ }
+ return ResultUnknown74;
+ }
+}
+
+Result NfcInterface::TranslateResultToNfp(Result result) const {
+ if (result == ResultDeviceNotFound) {
+ return NFP::ResultDeviceNotFound;
+ }
+ if (result == ResultInvalidArgument) {
+ return NFP::ResultInvalidArgument;
+ }
+ if (result == ResultWrongApplicationAreaSize) {
+ return NFP::ResultWrongApplicationAreaSize;
+ }
+ if (result == ResultWrongDeviceState) {
+ return NFP::ResultWrongDeviceState;
+ }
+ if (result == ResultUnknown74) {
+ return NFP::ResultUnknown74;
+ }
+ if (result == ResultNfcDisabled) {
+ return NFP::ResultNfcDisabled;
+ }
+ if (result == ResultNfcNotInitialized) {
+ return NFP::ResultNfcDisabled;
+ }
+ if (result == ResultWriteAmiiboFailed) {
+ return NFP::ResultWriteAmiiboFailed;
+ }
+ if (result == ResultTagRemoved) {
+ return NFP::ResultTagRemoved;
+ }
+ if (result == ResultRegistrationIsNotInitialized) {
+ return NFP::ResultRegistrationIsNotInitialized;
+ }
+ if (result == ResultApplicationAreaIsNotInitialized) {
+ return NFP::ResultApplicationAreaIsNotInitialized;
+ }
+ if (result == ResultCorruptedData) {
+ return NFP::ResultCorruptedData;
+ }
+ if (result == ResultWrongApplicationAreaId) {
+ return NFP::ResultWrongApplicationAreaId;
+ }
+ if (result == ResultApplicationAreaExist) {
+ return NFP::ResultApplicationAreaExist;
+ }
+ if (result == ResultNotAnAmiibo) {
+ return NFP::ResultNotAnAmiibo;
+ }
+ LOG_WARNING(Service_NFC, "Result conversion not handled");
+ return result;
+}
+
+Result NfcInterface::TranslateResultToMifare(Result result) const {
+ if (result == ResultDeviceNotFound) {
+ return Mifare::ResultDeviceNotFound;
+ }
+ if (result == ResultInvalidArgument) {
+ return Mifare::ResultInvalidArgument;
+ }
+ if (result == ResultWrongDeviceState) {
+ return Mifare::ResultWrongDeviceState;
+ }
+ if (result == ResultNfcDisabled) {
+ return Mifare::ResultNfcDisabled;
+ }
+ if (result == ResultTagRemoved) {
+ return Mifare::ResultTagRemoved;
+ }
+ LOG_WARNING(Service_NFC, "Result conversion not handled");
+ return result;
+}
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_user.h b/src/core/hle/service/nfc/nfc_interface.h
index aee046ae8..08be174d8 100644
--- a/src/core/hle/service/nfc/nfc_user.h
+++ b/src/core/hle/service/nfc/nfc_interface.h
@@ -3,26 +3,17 @@
#pragma once
-#include <array>
-#include <memory>
-#include <optional>
-
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nfc/nfc_types.h"
#include "core/hle/service/service.h"
namespace Service::NFC {
-class NfcDevice;
+class DeviceManager;
-class IUser final : public ServiceFramework<IUser> {
+class NfcInterface : public ServiceFramework<NfcInterface> {
public:
- explicit IUser(Core::System& system_);
- ~IUser();
-
-private:
- enum class State : u32 {
- NonInitialized,
- Initialized,
- };
+ explicit NfcInterface(Core::System& system_, const char* name, BackendType service_backend);
+ ~NfcInterface();
void Initialize(HLERequestContext& ctx);
void Finalize(HLERequestContext& ctx);
@@ -37,16 +28,22 @@ private:
void GetTagInfo(HLERequestContext& ctx);
void AttachActivateEvent(HLERequestContext& ctx);
void AttachDeactivateEvent(HLERequestContext& ctx);
+ void ReadMifare(HLERequestContext& ctx);
+ void WriteMifare(HLERequestContext& ctx);
void SendCommandByPassThrough(HLERequestContext& ctx);
- std::optional<std::shared_ptr<NfcDevice>> GetNfcDevice(u64 handle);
+protected:
+ std::shared_ptr<DeviceManager> GetManager();
+ BackendType GetBackendType() const;
+ Result TranslateResultToServiceError(Result result) const;
+ Result TranslateResultToNfp(Result result) const;
+ Result TranslateResultToMifare(Result result) const;
KernelHelpers::ServiceContext service_context;
- std::array<std::shared_ptr<NfcDevice>, 10> devices{};
-
+ BackendType backend_type;
State state{State::NonInitialized};
- Kernel::KEvent* availability_change_event;
+ std::shared_ptr<DeviceManager> device_manager = nullptr;
};
} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_result.h b/src/core/hle/service/nfc/nfc_result.h
index 146b8ba61..917d79ef8 100644
--- a/src/core/hle/service/nfc/nfc_result.h
+++ b/src/core/hle/service/nfc/nfc_result.h
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -7,17 +7,22 @@
namespace Service::NFC {
-constexpr Result DeviceNotFound(ErrorModule::NFC, 64);
-constexpr Result InvalidArgument(ErrorModule::NFC, 65);
-constexpr Result WrongDeviceState(ErrorModule::NFC, 73);
-constexpr Result NfcDisabled(ErrorModule::NFC, 80);
-constexpr Result TagRemoved(ErrorModule::NFC, 97);
-
-constexpr Result MifareDeviceNotFound(ErrorModule::NFCMifare, 64);
-constexpr Result MifareInvalidArgument(ErrorModule::NFCMifare, 65);
-constexpr Result MifareWrongDeviceState(ErrorModule::NFCMifare, 73);
-constexpr Result MifareNfcDisabled(ErrorModule::NFCMifare, 80);
-constexpr Result MifareTagRemoved(ErrorModule::NFCMifare, 97);
-constexpr Result MifareReadError(ErrorModule::NFCMifare, 288);
+constexpr Result ResultDeviceNotFound(ErrorModule::NFC, 64);
+constexpr Result ResultInvalidArgument(ErrorModule::NFC, 65);
+constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68);
+constexpr Result ResultWrongDeviceState(ErrorModule::NFC, 73);
+constexpr Result ResultUnknown74(ErrorModule::NFC, 74);
+constexpr Result ResultUnknown76(ErrorModule::NFC, 76);
+constexpr Result ResultNfcNotInitialized(ErrorModule::NFC, 77);
+constexpr Result ResultNfcDisabled(ErrorModule::NFC, 80);
+constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
+constexpr Result ResultTagRemoved(ErrorModule::NFC, 97);
+constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
+constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
+constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
+constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
+constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
+constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
+constexpr Result ResultUnknown216(ErrorModule::NFC, 216);
} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_types.h b/src/core/hle/service/nfc/nfc_types.h
new file mode 100644
index 000000000..c7ebd1fdb
--- /dev/null
+++ b/src/core/hle/service/nfc/nfc_types.h
@@ -0,0 +1,90 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <array>
+
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+
+namespace Service::NFC {
+enum class BackendType : u32 {
+ None,
+ Nfc,
+ Nfp,
+ Mifare,
+};
+
+// This is nn::nfc::DeviceState
+enum class DeviceState : u32 {
+ Initialized,
+ SearchingForTag,
+ TagFound,
+ TagRemoved,
+ TagMounted,
+ Unavailable,
+ Finalized,
+};
+
+// This is nn::nfc::State
+enum class State : u32 {
+ NonInitialized,
+ Initialized,
+};
+
+// This is nn::nfc::TagType
+enum class TagType : u32 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+enum class PackedTagType : u8 {
+ None,
+ Type1, // ISO14443A RW 96-2k bytes 106kbit/s
+ Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
+ Type3, // Sony FeliCa RW/RO 2k bytes 212kbit/s
+ Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
+ Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
+};
+
+// This is nn::nfc::NfcProtocol
+// Verify this enum. It might be completely wrong default protocol is 0x48
+enum class NfcProtocol : u32 {
+ None,
+ TypeA = 1U << 0, // ISO14443A
+ TypeB = 1U << 1, // ISO14443B
+ TypeF = 1U << 2, // Sony FeliCa
+ Unknown1 = 1U << 3,
+ Unknown2 = 1U << 5,
+ All = 0xFFFFFFFFU,
+};
+
+// this is nn::nfc::TestWaveType
+enum class TestWaveType : u32 {
+ Unknown,
+};
+
+using UniqueSerialNumber = std::array<u8, 7>;
+using UniqueSerialNumberExtension = std::array<u8, 3>;
+
+// This is nn::nfc::DeviceHandle
+using DeviceHandle = u64;
+
+// This is nn::nfc::TagInfo
+struct TagInfo {
+ UniqueSerialNumber uuid;
+ UniqueSerialNumberExtension uuid_extension;
+ u8 uuid_length;
+ INSERT_PADDING_BYTES(0x15);
+ NfcProtocol protocol;
+ TagType tag_type;
+ INSERT_PADDING_BYTES(0x30);
+};
+static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
+
+} // namespace Service::NFC
diff --git a/src/core/hle/service/nfc/nfc_user.cpp b/src/core/hle/service/nfc/nfc_user.cpp
deleted file mode 100644
index 7c162a4f3..000000000
--- a/src/core/hle/service/nfc/nfc_user.cpp
+++ /dev/null
@@ -1,365 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include "common/logging/log.h"
-#include "core/core.h"
-#include "core/hid/hid_types.h"
-#include "core/hle/kernel/k_event.h"
-#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/nfc/nfc_device.h"
-#include "core/hle/service/nfc/nfc_result.h"
-#include "core/hle/service/nfc/nfc_user.h"
-#include "core/hle/service/time/clock_types.h"
-
-namespace Service::NFC {
-
-IUser::IUser(Core::System& system_)
- : ServiceFramework{system_, "NFC::IUser"}, service_context{system_, service_name} {
- static const FunctionInfo functions[] = {
- {0, &IUser::Initialize, "InitializeOld"},
- {1, &IUser::Finalize, "FinalizeOld"},
- {2, &IUser::GetState, "GetStateOld"},
- {3, &IUser::IsNfcEnabled, "IsNfcEnabledOld"},
- {400, &IUser::Initialize, "Initialize"},
- {401, &IUser::Finalize, "Finalize"},
- {402, &IUser::GetState, "GetState"},
- {403, &IUser::IsNfcEnabled, "IsNfcEnabled"},
- {404, &IUser::ListDevices, "ListDevices"},
- {405, &IUser::GetDeviceState, "GetDeviceState"},
- {406, &IUser::GetNpadId, "GetNpadId"},
- {407, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
- {408, &IUser::StartDetection, "StartDetection"},
- {409, &IUser::StopDetection, "StopDetection"},
- {410, &IUser::GetTagInfo, "GetTagInfo"},
- {411, &IUser::AttachActivateEvent, "AttachActivateEvent"},
- {412, &IUser::AttachDeactivateEvent, "AttachDeactivateEvent"},
- {1000, nullptr, "ReadMifare"},
- {1001, nullptr, "WriteMifare"},
- {1300, &IUser::SendCommandByPassThrough, "SendCommandByPassThrough"},
- {1301, nullptr, "KeepPassThroughSession"},
- {1302, nullptr, "ReleasePassThroughSession"},
- };
- RegisterHandlers(functions);
-
- availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
-
- for (u32 device_index = 0; device_index < 10; device_index++) {
- devices[device_index] =
- std::make_shared<NfcDevice>(Core::HID::IndexToNpadIdType(device_index), system,
- service_context, availability_change_event);
- }
-}
-
-IUser ::~IUser() {
- availability_change_event->Close();
-}
-
-void IUser::Initialize(HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- state = State::Initialized;
-
- for (auto& device : devices) {
- device->Initialize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 0};
- rb.Push(ResultSuccess);
-}
-
-void IUser::Finalize(HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- state = State::NonInitialized;
-
- for (auto& device : devices) {
- device->Finalize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void IUser::GetState(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(state);
-}
-
-void IUser::IsNfcEnabled(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(state != State::NonInitialized);
-}
-
-void IUser::ListDevices(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFC, "called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- if (!ctx.CanWriteBuffer()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InvalidArgument);
- return;
- }
-
- if (ctx.GetWriteBufferSize() == 0) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InvalidArgument);
- return;
- }
-
- std::vector<u64> nfp_devices;
- const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>();
-
- for (auto& device : devices) {
- if (nfp_devices.size() >= max_allowed_devices) {
- continue;
- }
- if (device->GetCurrentState() != NFP::DeviceState::Unavailable) {
- nfp_devices.push_back(device->GetHandle());
- }
- }
-
- if (nfp_devices.empty()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- ctx.WriteBuffer(nfp_devices);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<s32>(nfp_devices.size()));
-}
-
-void IUser::GetDeviceState(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(device.value()->GetCurrentState());
-}
-
-void IUser::GetNpadId(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(device.value()->GetNpadId());
-}
-
-void IUser::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
- LOG_INFO(Service_NFC, "called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(availability_change_event->GetReadableEvent());
-}
-
-void IUser::StartDetection(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto nfp_protocol{rp.PopEnum<NFP::TagProtocol>()};
- LOG_INFO(Service_NFC, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->StartDetection(nfp_protocol);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void IUser::StopDetection(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->StopDetection();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void IUser::GetTagInfo(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- NFP::TagInfo tag_info{};
- const auto result = device.value()->GetTagInfo(tag_info, false);
- ctx.WriteBuffer(tag_info);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void IUser::AttachActivateEvent(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(device.value()->GetActivateEvent());
-}
-
-void IUser::AttachDeactivateEvent(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFC, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(device.value()->GetDeactivateEvent());
-}
-
-void IUser::SendCommandByPassThrough(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto timeout{rp.PopRaw<Time::Clock::TimeSpanType>()};
- const auto command_data{ctx.ReadBuffer()};
-
- LOG_INFO(Service_NFC, "(STUBBED) called, device_handle={}, timeout={}, data_size={}",
- device_handle, timeout.ToSeconds(), command_data.size());
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfcDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- std::vector<u8> out_data(1);
- // TODO: Request data from nfc device
- ctx.WriteBuffer(out_data);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<u32>(out_data.size()));
-}
-
-std::optional<std::shared_ptr<NfcDevice>> IUser::GetNfcDevice(u64 handle) {
- for (auto& device : devices) {
- if (device->GetHandle() == handle) {
- return device;
- }
- }
- return std::nullopt;
-}
-
-} // namespace Service::NFC
diff --git a/src/core/hle/service/nfp/nfp.cpp b/src/core/hle/service/nfp/nfp.cpp
index 2714f4bea..2eeabc138 100644
--- a/src/core/hle/service/nfp/nfp.cpp
+++ b/src/core/hle/service/nfp/nfp.cpp
@@ -13,7 +13,7 @@ class IUser final : public Interface {
public:
explicit IUser(Core::System& system_) : Interface(system_, "NFP:IUser") {
// clang-format off
- static const FunctionInfo functions[] = {
+ static const FunctionInfoTyped<IUser> functions[] = {
{0, &IUser::Initialize, "Initialize"},
{1, &IUser::Finalize, "Finalize"},
{2, &IUser::ListDevices, "ListDevices"},
@@ -50,7 +50,7 @@ class ISystem final : public Interface {
public:
explicit ISystem(Core::System& system_) : Interface(system_, "NFP:ISystem") {
// clang-format off
- static const FunctionInfo functions[] = {
+ static const FunctionInfoTyped<ISystem> functions[] = {
{0, &ISystem::InitializeSystem, "InitializeSystem"},
{1, &ISystem::FinalizeSystem, "FinalizeSystem"},
{2, &ISystem::ListDevices, "ListDevices"},
@@ -89,7 +89,7 @@ class IDebug final : public Interface {
public:
explicit IDebug(Core::System& system_) : Interface(system_, "NFP:IDebug") {
// clang-format off
- static const FunctionInfo functions[] = {
+ static const FunctionInfoTyped<IDebug> functions[] = {
{0, &IDebug::InitializeDebug, "InitializeDebug"},
{1, &IDebug::FinalizeDebug, "FinalizeDebug"},
{2, &IDebug::ListDevices, "ListDevices"},
@@ -126,9 +126,9 @@ public:
{201, &IDebug::SetAll, "SetAll"},
{202, &IDebug::FlushDebug, "FlushDebug"},
{203, &IDebug::BreakTag, "BreakTag"},
- {204, nullptr, "ReadBackupData"},
- {205, nullptr, "WriteBackupData"},
- {206, nullptr, "WriteNtf"},
+ {204, &IDebug::ReadBackupData, "ReadBackupData"},
+ {205, &IDebug::WriteBackupData, "WriteBackupData"},
+ {206, &IDebug::WriteNtf, "WriteNtf"},
};
// clang-format on
@@ -152,16 +152,10 @@ private:
void CreateUserInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
- if (user_interface == nullptr) {
- user_interface = std::make_shared<IUser>(system);
- }
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IUser>(user_interface);
+ rb.PushIpcInterface<IUser>(system);
}
-
- std::shared_ptr<IUser> user_interface;
};
class ISystemManager final : public ServiceFramework<ISystemManager> {
@@ -180,16 +174,10 @@ private:
void CreateSystemInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
- if (system_interface == nullptr) {
- system_interface = std::make_shared<ISystem>(system);
- }
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<ISystem>(system_interface);
+ rb.PushIpcInterface<ISystem>(system);
}
-
- std::shared_ptr<ISystem> system_interface;
};
class IDebugManager final : public ServiceFramework<IDebugManager> {
@@ -208,16 +196,10 @@ private:
void CreateDebugInterface(HLERequestContext& ctx) {
LOG_DEBUG(Service_NFP, "called");
- if (system_interface == nullptr) {
- system_interface = std::make_shared<IDebug>(system);
- }
-
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(ResultSuccess);
- rb.PushIpcInterface<IDebug>(system_interface);
+ rb.PushIpcInterface<IDebug>(system);
}
-
- std::shared_ptr<IDebug> system_interface;
};
void LoopProcess(Core::System& system) {
diff --git a/src/core/hle/service/nfp/nfp_device.h b/src/core/hle/service/nfp/nfp_device.h
deleted file mode 100644
index bab05538a..000000000
--- a/src/core/hle/service/nfp/nfp_device.h
+++ /dev/null
@@ -1,120 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#pragma once
-
-#include <span>
-#include <vector>
-
-#include "common/common_types.h"
-#include "core/hle/service/kernel_helpers.h"
-#include "core/hle/service/nfp/nfp_types.h"
-#include "core/hle/service/service.h"
-
-namespace Kernel {
-class KEvent;
-class KReadableEvent;
-} // namespace Kernel
-
-namespace Core {
-class System;
-} // namespace Core
-
-namespace Core::HID {
-class EmulatedController;
-enum class ControllerTriggerType;
-enum class NpadIdType : u32;
-} // namespace Core::HID
-
-namespace Service::NFP {
-class NfpDevice {
-public:
- NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_,
- KernelHelpers::ServiceContext& service_context_,
- Kernel::KEvent* availability_change_event_);
- ~NfpDevice();
-
- void Initialize();
- void Finalize();
-
- Result StartDetection(TagProtocol allowed_protocol);
- Result StopDetection();
- Result Mount(MountTarget mount_target);
- Result Unmount();
-
- Result Flush();
- Result FlushDebug();
- Result FlushWithBreak(BreakType break_type);
-
- Result GetTagInfo(TagInfo& tag_info) const;
- Result GetCommonInfo(CommonInfo& common_info) const;
- Result GetModelInfo(ModelInfo& model_info) const;
- Result GetRegisterInfo(RegisterInfo& register_info) const;
- Result GetRegisterInfoPrivate(RegisterInfoPrivate& register_info) const;
- Result GetAdminInfo(AdminInfo& admin_info) const;
-
- Result DeleteRegisterInfo();
- Result SetRegisterInfoPrivate(const AmiiboName& amiibo_name);
- Result RestoreAmiibo();
- Result Format();
-
- Result OpenApplicationArea(u32 access_id);
- Result GetApplicationAreaId(u32& application_area_id) const;
- Result GetApplicationArea(std::vector<u8>& data) const;
- Result SetApplicationArea(std::span<const u8> data);
- Result CreateApplicationArea(u32 access_id, std::span<const u8> data);
- Result RecreateApplicationArea(u32 access_id, std::span<const u8> data);
- Result DeleteApplicationArea();
- Result ExistApplicationArea(bool& has_application_area);
-
- Result GetAll(NfpData& data) const;
- Result SetAll(const NfpData& data);
- Result BreakTag(BreakType break_type);
- Result ReadBackupData();
- Result WriteBackupData();
- Result WriteNtf();
-
- u64 GetHandle() const;
- u32 GetApplicationAreaSize() const;
- DeviceState GetCurrentState() const;
- Core::HID::NpadIdType GetNpadId() const;
-
- Kernel::KReadableEvent& GetActivateEvent() const;
- Kernel::KReadableEvent& GetDeactivateEvent() const;
-
-private:
- void NpadUpdate(Core::HID::ControllerTriggerType type);
- bool LoadAmiibo(std::span<const u8> data);
- void CloseAmiibo();
-
- AmiiboName GetAmiiboName(const AmiiboSettings& settings) const;
- void SetAmiiboName(AmiiboSettings& settings, const AmiiboName& amiibo_name);
- AmiiboDate GetAmiiboDate(s64 posix_time) const;
- u64 RemoveVersionByte(u64 application_id) const;
- void UpdateSettingsCrc();
- void UpdateRegisterInfoCrc();
-
- bool is_controller_set{};
- int callback_key;
- const Core::HID::NpadIdType npad_id;
- Core::System& system;
- Core::HID::EmulatedController* npad_device = nullptr;
- KernelHelpers::ServiceContext& service_context;
- Kernel::KEvent* activate_event = nullptr;
- Kernel::KEvent* deactivate_event = nullptr;
- Kernel::KEvent* availability_change_event = nullptr;
-
- bool is_initalized{};
- bool is_data_moddified{};
- bool is_app_area_open{};
- bool is_plain_amiibo{};
- TagProtocol allowed_protocols{};
- s64 current_posix_time{};
- MountTarget mount_target{MountTarget::None};
- DeviceState device_state{DeviceState::Unavailable};
-
- NTAG215File tag_data{};
- EncryptedNTAG215File encrypted_tag_data{};
-};
-
-} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_interface.cpp b/src/core/hle/service/nfp/nfp_interface.cpp
index 2ed8bb1ba..21d159154 100644
--- a/src/core/hle/service/nfp/nfp_interface.cpp
+++ b/src/core/hle/service/nfp/nfp_interface.cpp
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "common/logging/log.h"
@@ -6,198 +6,34 @@
#include "core/hid/hid_types.h"
#include "core/hle/kernel/k_event.h"
#include "core/hle/service/ipc_helpers.h"
-#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfc/common/device.h"
+#include "core/hle/service/nfc/common/device_manager.h"
+#include "core/hle/service/nfc/nfc_types.h"
#include "core/hle/service/nfp/nfp_interface.h"
#include "core/hle/service/nfp/nfp_result.h"
+#include "core/hle/service/nfp/nfp_types.h"
namespace Service::NFP {
Interface::Interface(Core::System& system_, const char* name)
- : ServiceFramework{system_, name}, service_context{system_, service_name} {
- availability_change_event = service_context.CreateEvent("IUser:AvailabilityChangeEvent");
+ : NfcInterface{system_, name, NFC::BackendType::Nfp} {}
- for (u32 device_index = 0; device_index < 10; device_index++) {
- devices[device_index] =
- std::make_shared<NfpDevice>(Core::HID::IndexToNpadIdType(device_index), system,
- service_context, availability_change_event);
- }
-}
-
-Interface::~Interface() {
- availability_change_event->Close();
-}
-
-void Interface::Initialize(HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::Initialized;
-
- for (auto& device : devices) {
- device->Initialize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
+Interface::~Interface() = default;
void Interface::InitializeSystem(HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::Initialized;
-
- for (auto& device : devices) {
- device->Initialize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ Initialize(ctx);
}
void Interface::InitializeDebug(HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::Initialized;
-
- for (auto& device : devices) {
- device->Initialize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Interface::Finalize(HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::NonInitialized;
-
- for (auto& device : devices) {
- device->Finalize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ Initialize(ctx);
}
void Interface::FinalizeSystem(HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::NonInitialized;
-
- for (auto& device : devices) {
- device->Finalize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
+ Finalize(ctx);
}
void Interface::FinalizeDebug(HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- state = State::NonInitialized;
-
- for (auto& device : devices) {
- device->Finalize();
- }
-
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(ResultSuccess);
-}
-
-void Interface::ListDevices(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- if (!ctx.CanWriteBuffer()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InvalidArgument);
- return;
- }
-
- if (ctx.GetWriteBufferSize() == 0) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InvalidArgument);
- return;
- }
-
- std::vector<u64> nfp_devices;
- const std::size_t max_allowed_devices = ctx.GetWriteBufferNumElements<u64>();
-
- for (const auto& device : devices) {
- if (nfp_devices.size() >= max_allowed_devices) {
- continue;
- }
- if (device->GetCurrentState() != DeviceState::Unavailable) {
- nfp_devices.push_back(device->GetHandle());
- }
- }
-
- if (nfp_devices.empty()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- ctx.WriteBuffer(nfp_devices);
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.Push(static_cast<s32>(nfp_devices.size()));
-}
-
-void Interface::StartDetection(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- const auto nfp_protocol{rp.PopEnum<TagProtocol>()};
- LOG_INFO(Service_NFP, "called, device_handle={}, nfp_protocol={}", device_handle, nfp_protocol);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->StartDetection(nfp_protocol);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Interface::StopDetection(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->StopDetection();
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
+ Finalize(ctx);
}
void Interface::Mount(HLERequestContext& ctx) {
@@ -208,21 +44,9 @@ void Interface::Mount(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called, device_handle={}, model_type={}, mount_target={}", device_handle,
model_type, mount_target);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ auto result = GetManager()->Mount(device_handle, model_type, mount_target);
+ result = TranslateResultToServiceError(result);
- const auto result = device.value()->Mount(mount_target);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -232,21 +56,9 @@ void Interface::Unmount(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ auto result = GetManager()->Unmount(device_handle);
+ result = TranslateResultToServiceError(result);
- const auto result = device.value()->Unmount();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -257,21 +69,9 @@ void Interface::OpenApplicationArea(HLERequestContext& ctx) {
const auto access_id{rp.Pop<u32>()};
LOG_INFO(Service_NFP, "called, device_handle={}, access_id={}", device_handle, access_id);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ auto result = GetManager()->OpenApplicationArea(device_handle, access_id);
+ result = TranslateResultToServiceError(result);
- const auto result = device.value()->OpenApplicationArea(access_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -282,28 +82,16 @@ void Interface::GetApplicationArea(HLERequestContext& ctx) {
const auto data_size = ctx.GetWriteBufferSize();
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- if (!ctx.CanWriteBuffer()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InvalidArgument);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ std::vector<u8> data(data_size);
+ auto result = GetManager()->GetApplicationArea(device_handle, data);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
+ if (result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
+ rb.Push(result);
return;
}
- std::vector<u8> data(data_size);
- const auto result = device.value()->GetApplicationArea(data);
ctx.WriteBuffer(data);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
@@ -316,27 +104,9 @@ void Interface::SetApplicationArea(HLERequestContext& ctx) {
const auto data{ctx.ReadBuffer()};
LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}", device_handle, data.size());
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- if (!ctx.CanReadBuffer()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InvalidArgument);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ auto result = GetManager()->SetApplicationArea(device_handle, data);
+ result = TranslateResultToServiceError(result);
- const auto result = device.value()->SetApplicationArea(data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -346,21 +116,9 @@ void Interface::Flush(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ auto result = GetManager()->Flush(device_handle);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->Flush();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -370,21 +128,9 @@ void Interface::Restore(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ auto result = GetManager()->Restore(device_handle);
+ result = TranslateResultToServiceError(result);
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->RestoreAmiibo();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -397,53 +143,9 @@ void Interface::CreateApplicationArea(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
access_id, data.size());
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- if (!ctx.CanReadBuffer()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(InvalidArgument);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->CreateApplicationArea(access_id, data);
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(result);
-}
-
-void Interface::GetTagInfo(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ auto result = GetManager()->CreateApplicationArea(device_handle, access_id, data);
+ result = TranslateResultToServiceError(result);
- TagInfo tag_info{};
- const auto result = device.value()->GetTagInfo(tag_info);
- ctx.WriteBuffer(tag_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -453,23 +155,14 @@ void Interface::GetRegisterInfo(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ RegisterInfo register_info{};
+ auto result = GetManager()->GetRegisterInfo(device_handle, register_info);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(register_info);
}
- RegisterInfo register_info{};
- const auto result = device.value()->GetRegisterInfo(register_info);
- ctx.WriteBuffer(register_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -479,23 +172,14 @@ void Interface::GetCommonInfo(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ CommonInfo common_info{};
+ auto result = GetManager()->GetCommonInfo(device_handle, common_info);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(common_info);
}
- CommonInfo common_info{};
- const auto result = device.value()->GetCommonInfo(common_info);
- ctx.WriteBuffer(common_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -505,155 +189,26 @@ void Interface::GetModelInfo(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ ModelInfo model_info{};
+ auto result = GetManager()->GetModelInfo(device_handle, model_info);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(model_info);
}
- ModelInfo model_info{};
- const auto result = device.value()->GetModelInfo(model_info);
- ctx.WriteBuffer(model_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
-void Interface::AttachActivateEvent(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(device.value()->GetActivateEvent());
-}
-
-void Interface::AttachDeactivateEvent(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(device.value()->GetDeactivateEvent());
-}
-
-void Interface::GetState(HLERequestContext& ctx) {
- LOG_DEBUG(Service_NFP, "called");
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(state);
-}
-
-void Interface::GetDeviceState(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(device.value()->GetCurrentState());
-}
-
-void Interface::GetNpadId(HLERequestContext& ctx) {
- IPC::RequestParser rp{ctx};
- const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 3};
- rb.Push(ResultSuccess);
- rb.PushEnum(device.value()->GetNpadId());
-}
-
void Interface::GetApplicationAreaSize(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
- rb.Push(device.value()->GetApplicationAreaSize());
-}
-
-void Interface::AttachAvailabilityChangeEvent(HLERequestContext& ctx) {
- LOG_INFO(Service_NFP, "called");
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- IPC::ResponseBuilder rb{ctx, 2, 1};
- rb.Push(ResultSuccess);
- rb.PushCopyObjects(availability_change_event->GetReadableEvent());
+ rb.Push(GetManager()->GetApplicationAreaSize());
}
void Interface::RecreateApplicationArea(HLERequestContext& ctx) {
@@ -664,21 +219,9 @@ void Interface::RecreateApplicationArea(HLERequestContext& ctx) {
LOG_INFO(Service_NFP, "called, device_handle={}, data_size={}, access_id={}", device_handle,
access_id, data.size());
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ auto result = GetManager()->RecreateApplicationArea(device_handle, access_id, data);
+ result = TranslateResultToServiceError(result);
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->RecreateApplicationArea(access_id, data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -686,23 +229,11 @@ void Interface::RecreateApplicationArea(HLERequestContext& ctx) {
void Interface::Format(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ auto result = GetManager()->Format(device_handle);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->Format();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -712,23 +243,14 @@ void Interface::GetAdminInfo(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ AdminInfo admin_info{};
+ auto result = GetManager()->GetAdminInfo(device_handle, admin_info);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(admin_info);
}
- AdminInfo admin_info{};
- const auto result = device.value()->GetAdminInfo(admin_info);
- ctx.WriteBuffer(admin_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -738,23 +260,14 @@ void Interface::GetRegisterInfoPrivate(HLERequestContext& ctx) {
const auto device_handle{rp.Pop<u64>()};
LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ RegisterInfoPrivate register_info{};
+ auto result = GetManager()->GetRegisterInfoPrivate(device_handle, register_info);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(register_info);
}
- RegisterInfoPrivate register_info{};
- const auto result = device.value()->GetRegisterInfoPrivate(register_info);
- ctx.WriteBuffer(register_info);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -762,25 +275,15 @@ void Interface::GetRegisterInfoPrivate(HLERequestContext& ctx) {
void Interface::SetRegisterInfoPrivate(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- const auto buffer{ctx.ReadBuffer()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}, buffer_size={}", device_handle,
- buffer.size());
+ const auto register_info_buffer{ctx.ReadBuffer()};
+ LOG_INFO(Service_NFP, "called, device_handle={}, buffer_size={}", device_handle,
+ register_info_buffer.size());
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ RegisterInfoPrivate register_info{};
+ memcpy(&register_info, register_info_buffer.data(), sizeof(RegisterInfoPrivate));
+ auto result = GetManager()->SetRegisterInfoPrivate(device_handle, register_info);
+ result = TranslateResultToServiceError(result);
- const auto result = device.value()->SetRegisterInfoPrivate({});
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -788,23 +291,11 @@ void Interface::SetRegisterInfoPrivate(HLERequestContext& ctx) {
void Interface::DeleteRegisterInfo(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ auto result = GetManager()->DeleteRegisterInfo(device_handle);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->DeleteRegisterInfo();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -812,23 +303,11 @@ void Interface::DeleteRegisterInfo(HLERequestContext& ctx) {
void Interface::DeleteApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ auto result = GetManager()->DeleteApplicationArea(device_handle);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->DeleteApplicationArea();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -836,24 +315,18 @@ void Interface::DeleteApplicationArea(HLERequestContext& ctx) {
void Interface::ExistsApplicationArea(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ bool has_application_area = false;
+ auto result = GetManager()->ExistsApplicationArea(device_handle, has_application_area);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
+ if (result.IsError()) {
IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
+ rb.Push(result);
return;
}
- bool has_application_area = false;
- const auto result = device.value()->ExistApplicationArea(has_application_area);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(result);
rb.Push(has_application_area);
@@ -862,27 +335,16 @@ void Interface::ExistsApplicationArea(HLERequestContext& ctx) {
void Interface::GetAll(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ NfpData nfp_data{};
+ auto result = GetManager()->GetAll(device_handle, nfp_data);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(nfp_data);
}
- NfpData data{};
- const auto result = device.value()->GetAll(data);
-
- ctx.WriteBuffer(data);
-
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -890,28 +352,15 @@ void Interface::GetAll(HLERequestContext& ctx) {
void Interface::SetAll(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- const auto nfp_data{ctx.ReadBuffer()};
-
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ const auto nfp_data_buffer{ctx.ReadBuffer()};
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- NfpData data{};
- memcpy(&data, nfp_data.data(), sizeof(NfpData));
+ NfpData nfp_data{};
+ memcpy(&nfp_data, nfp_data_buffer.data(), sizeof(NfpData));
+ auto result = GetManager()->SetAll(device_handle, nfp_data);
+ result = TranslateResultToServiceError(result);
- const auto result = device.value()->SetAll(data);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -919,23 +368,11 @@ void Interface::SetAll(HLERequestContext& ctx) {
void Interface::FlushDebug(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ LOG_INFO(Service_NFP, "called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ auto result = GetManager()->FlushDebug(device_handle);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->FlushDebug();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -944,23 +381,12 @@ void Interface::BreakTag(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
const auto break_type{rp.PopEnum<BreakType>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}, break_type={}", device_handle, break_type);
+ LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}, break_type={}", device_handle,
+ break_type);
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ auto result = GetManager()->BreakTag(device_handle, break_type);
+ result = TranslateResultToServiceError(result);
- auto device = GetNfpDevice(device_handle);
-
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->BreakTag(break_type);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -968,23 +394,16 @@ void Interface::BreakTag(HLERequestContext& ctx) {
void Interface::ReadBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ std::vector<u8> backup_data{};
+ auto result = GetManager()->ReadBackupData(device_handle, backup_data);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
+ if (result.IsSuccess()) {
+ ctx.WriteBuffer(backup_data);
}
- const auto result = device.value()->ReadBackupData();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -992,23 +411,12 @@ void Interface::ReadBackupData(HLERequestContext& ctx) {
void Interface::WriteBackupData(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
-
- auto device = GetNfpDevice(device_handle);
+ const auto backup_data_buffer{ctx.ReadBuffer()};
+ LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
+ auto result = GetManager()->WriteBackupData(device_handle, backup_data_buffer);
+ result = TranslateResultToServiceError(result);
- const auto result = device.value()->WriteBackupData();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
@@ -1016,34 +424,15 @@ void Interface::WriteBackupData(HLERequestContext& ctx) {
void Interface::WriteNtf(HLERequestContext& ctx) {
IPC::RequestParser rp{ctx};
const auto device_handle{rp.Pop<u64>()};
- LOG_DEBUG(Service_NFP, "called, device_handle={}", device_handle);
-
- if (state == State::NonInitialized) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(NfcDisabled);
- return;
- }
+ const auto write_type{rp.PopEnum<WriteType>()};
+ const auto ntf_data_buffer{ctx.ReadBuffer()};
+ LOG_WARNING(Service_NFP, "(STUBBED) called, device_handle={}", device_handle);
- auto device = GetNfpDevice(device_handle);
+ auto result = GetManager()->WriteNtf(device_handle, write_type, ntf_data_buffer);
+ result = TranslateResultToServiceError(result);
- if (!device.has_value()) {
- IPC::ResponseBuilder rb{ctx, 2};
- rb.Push(DeviceNotFound);
- return;
- }
-
- const auto result = device.value()->WriteNtf();
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(result);
}
-std::optional<std::shared_ptr<NfpDevice>> Interface::GetNfpDevice(u64 handle) {
- for (auto& device : devices) {
- if (device->GetHandle() == handle) {
- return device;
- }
- }
- return std::nullopt;
-}
-
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_interface.h b/src/core/hle/service/nfp/nfp_interface.h
index 616c94b06..fa985b068 100644
--- a/src/core/hle/service/nfp/nfp_interface.h
+++ b/src/core/hle/service/nfp/nfp_interface.h
@@ -1,32 +1,23 @@
-// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
-#include <array>
-#include <memory>
-#include <optional>
-
#include "core/hle/service/kernel_helpers.h"
+#include "core/hle/service/nfc/nfc_interface.h"
#include "core/hle/service/service.h"
namespace Service::NFP {
-class NfpDevice;
-class Interface : public ServiceFramework<Interface> {
+class Interface : public NFC::NfcInterface {
public:
explicit Interface(Core::System& system_, const char* name);
~Interface() override;
- void Initialize(HLERequestContext& ctx);
void InitializeSystem(HLERequestContext& ctx);
void InitializeDebug(HLERequestContext& ctx);
- void Finalize(HLERequestContext& ctx);
void FinalizeSystem(HLERequestContext& ctx);
void FinalizeDebug(HLERequestContext& ctx);
- void ListDevices(HLERequestContext& ctx);
- void StartDetection(HLERequestContext& ctx);
- void StopDetection(HLERequestContext& ctx);
void Mount(HLERequestContext& ctx);
void Unmount(HLERequestContext& ctx);
void OpenApplicationArea(HLERequestContext& ctx);
@@ -35,17 +26,10 @@ public:
void Flush(HLERequestContext& ctx);
void Restore(HLERequestContext& ctx);
void CreateApplicationArea(HLERequestContext& ctx);
- void GetTagInfo(HLERequestContext& ctx);
void GetRegisterInfo(HLERequestContext& ctx);
void GetCommonInfo(HLERequestContext& ctx);
void GetModelInfo(HLERequestContext& ctx);
- void AttachActivateEvent(HLERequestContext& ctx);
- void AttachDeactivateEvent(HLERequestContext& ctx);
- void GetState(HLERequestContext& ctx);
- void GetDeviceState(HLERequestContext& ctx);
- void GetNpadId(HLERequestContext& ctx);
void GetApplicationAreaSize(HLERequestContext& ctx);
- void AttachAvailabilityChangeEvent(HLERequestContext& ctx);
void RecreateApplicationArea(HLERequestContext& ctx);
void Format(HLERequestContext& ctx);
void GetAdminInfo(HLERequestContext& ctx);
@@ -61,21 +45,6 @@ public:
void ReadBackupData(HLERequestContext& ctx);
void WriteBackupData(HLERequestContext& ctx);
void WriteNtf(HLERequestContext& ctx);
-
-private:
- enum class State : u32 {
- NonInitialized,
- Initialized,
- };
-
- std::optional<std::shared_ptr<NfpDevice>> GetNfpDevice(u64 handle);
-
- KernelHelpers::ServiceContext service_context;
-
- std::array<std::shared_ptr<NfpDevice>, 10> devices{};
-
- State state{State::NonInitialized};
- Kernel::KEvent* availability_change_event;
};
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_result.h b/src/core/hle/service/nfp/nfp_result.h
index d8e4cf094..4c126cd81 100644
--- a/src/core/hle/service/nfp/nfp_result.h
+++ b/src/core/hle/service/nfp/nfp_result.h
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-3.0-or-later
+// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
@@ -7,18 +7,19 @@
namespace Service::NFP {
-constexpr Result DeviceNotFound(ErrorModule::NFP, 64);
-constexpr Result InvalidArgument(ErrorModule::NFP, 65);
-constexpr Result WrongApplicationAreaSize(ErrorModule::NFP, 68);
-constexpr Result WrongDeviceState(ErrorModule::NFP, 73);
-constexpr Result NfcDisabled(ErrorModule::NFP, 80);
-constexpr Result WriteAmiiboFailed(ErrorModule::NFP, 88);
-constexpr Result TagRemoved(ErrorModule::NFP, 97);
-constexpr Result RegistrationIsNotInitialized(ErrorModule::NFP, 120);
-constexpr Result ApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
-constexpr Result CorruptedData(ErrorModule::NFP, 144);
-constexpr Result WrongApplicationAreaId(ErrorModule::NFP, 152);
-constexpr Result ApplicationAreaExist(ErrorModule::NFP, 168);
-constexpr Result NotAnAmiibo(ErrorModule::NFP, 178);
+constexpr Result ResultDeviceNotFound(ErrorModule::NFP, 64);
+constexpr Result ResultInvalidArgument(ErrorModule::NFP, 65);
+constexpr Result ResultWrongApplicationAreaSize(ErrorModule::NFP, 68);
+constexpr Result ResultWrongDeviceState(ErrorModule::NFP, 73);
+constexpr Result ResultUnknown74(ErrorModule::NFC, 74);
+constexpr Result ResultNfcDisabled(ErrorModule::NFP, 80);
+constexpr Result ResultWriteAmiiboFailed(ErrorModule::NFP, 88);
+constexpr Result ResultTagRemoved(ErrorModule::NFP, 97);
+constexpr Result ResultRegistrationIsNotInitialized(ErrorModule::NFP, 120);
+constexpr Result ResultApplicationAreaIsNotInitialized(ErrorModule::NFP, 128);
+constexpr Result ResultCorruptedData(ErrorModule::NFP, 144);
+constexpr Result ResultWrongApplicationAreaId(ErrorModule::NFP, 152);
+constexpr Result ResultApplicationAreaExist(ErrorModule::NFP, 168);
+constexpr Result ResultNotAnAmiibo(ErrorModule::NFP, 178);
} // namespace Service::NFP
diff --git a/src/core/hle/service/nfp/nfp_types.h b/src/core/hle/service/nfp/nfp_types.h
index 1ef047cee..7d36d5ee6 100644
--- a/src/core/hle/service/nfp/nfp_types.h
+++ b/src/core/hle/service/nfp/nfp_types.h
@@ -7,32 +7,19 @@
#include "common/swap.h"
#include "core/hle/service/mii/types.h"
+#include "core/hle/service/nfc/nfc_types.h"
namespace Service::NFP {
static constexpr std::size_t amiibo_name_length = 0xA;
static constexpr std::size_t application_id_version_offset = 0x1c;
static constexpr std::size_t counter_limit = 0xffff;
-enum class ServiceType : u32 {
- User,
- Debug,
- System,
-};
-
-enum class DeviceState : u32 {
- Initialized,
- SearchingForTag,
- TagFound,
- TagRemoved,
- TagMounted,
- Unavailable,
- Finalized,
-};
-
+// This is nn::nfp::ModelType
enum class ModelType : u32 {
Amiibo,
};
+// This is nn::nfp::MountTarget
enum class MountTarget : u32 {
None,
Rom,
@@ -72,35 +59,6 @@ enum class AmiiboSeries : u8 {
Diablo,
};
-enum class TagType : u32 {
- None,
- Type1, // ISO14443A RW 96-2k bytes 106kbit/s
- Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
- Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
- Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
- Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
-};
-
-enum class PackedTagType : u8 {
- None,
- Type1, // ISO14443A RW 96-2k bytes 106kbit/s
- Type2, // ISO14443A RW/RO 540 bytes 106kbit/s
- Type3, // Sony Felica RW/RO 2k bytes 212kbit/s
- Type4, // ISO14443A RW/RO 4k-32k bytes 424kbit/s
- Type5, // ISO15693 RW/RO 540 bytes 106kbit/s
-};
-
-// Verify this enum. It might be completely wrong default protocol is 0x48
-enum class TagProtocol : u32 {
- None,
- TypeA = 1U << 0, // ISO14443A
- TypeB = 1U << 1, // ISO14443B
- TypeF = 1U << 2, // Sony Felica
- Unknown1 = 1U << 3,
- Unknown2 = 1U << 5,
- All = 0xFFFFFFFFU,
-};
-
enum class AppAreaVersion : u8 {
Nintendo3DS = 0,
NintendoWiiU = 1,
@@ -115,6 +73,11 @@ enum class BreakType : u32 {
Unknown2,
};
+enum class WriteType : u32 {
+ Unknown0,
+ Unknown1,
+};
+
enum class CabinetMode : u8 {
StartNicknameAndOwnerSettings,
StartGameDataEraser,
@@ -122,27 +85,16 @@ enum class CabinetMode : u8 {
StartFormatter,
};
-enum class MifareCmd : u8 {
- AuthA = 0x60,
- AuthB = 0x61,
- Read = 0x30,
- Write = 0xA0,
- Transfer = 0xB0,
- Decrement = 0xC0,
- Increment = 0xC1,
- Store = 0xC2
-};
-
-using UniqueSerialNumber = std::array<u8, 7>;
using LockBytes = std::array<u8, 2>;
using HashData = std::array<u8, 0x20>;
using ApplicationArea = std::array<u8, 0xD8>;
using AmiiboName = std::array<char, (amiibo_name_length * 4) + 1>;
-using DataBlock = std::array<u8, 0x10>;
-using KeyData = std::array<u8, 0x6>;
+
+// This is nn::nfp::TagInfo
+using TagInfo = NFC::TagInfo;
struct TagUuid {
- UniqueSerialNumber uid;
+ NFC::UniqueSerialNumber uid;
u8 nintendo_id;
LockBytes lock_bytes;
};
@@ -243,7 +195,7 @@ struct AmiiboModelInfo {
AmiiboType amiibo_type;
u16_be model_number;
AmiiboSeries series;
- PackedTagType tag_type;
+ NFC::PackedTagType tag_type;
INSERT_PADDING_BYTES(0x4); // Unknown
};
static_assert(sizeof(AmiiboModelInfo) == 0xC, "AmiiboModelInfo is an invalid size");
@@ -298,7 +250,7 @@ struct NTAG215File {
u32_be register_info_crc;
ApplicationArea application_area; // Encrypted Game data
HashData hmac_tag; // Hash
- UniqueSerialNumber uid; // Unique serial number
+ NFC::UniqueSerialNumber uid; // Unique serial number
u8 nintendo_id; // Tag UUID
AmiiboModelInfo model_info;
HashData keygen_salt; // Salt
@@ -326,17 +278,7 @@ static_assert(sizeof(EncryptedNTAG215File) == sizeof(NTAG215File),
static_assert(std::is_trivially_copyable_v<EncryptedNTAG215File>,
"EncryptedNTAG215File must be trivially copyable.");
-struct TagInfo {
- UniqueSerialNumber uuid;
- INSERT_PADDING_BYTES(0x3);
- u8 uuid_length;
- INSERT_PADDING_BYTES(0x15);
- TagProtocol protocol;
- TagType tag_type;
- INSERT_PADDING_BYTES(0x30);
-};
-static_assert(sizeof(TagInfo) == 0x58, "TagInfo is an invalid size");
-
+// This is nn::nfp::CommonInfo
struct CommonInfo {
WriteDate last_write_date;
u16 write_counter;
@@ -347,6 +289,7 @@ struct CommonInfo {
};
static_assert(sizeof(CommonInfo) == 0x40, "CommonInfo is an invalid size");
+// This is nn::nfp::ModelInfo
struct ModelInfo {
u16 character_id;
u8 character_variant;
@@ -357,6 +300,7 @@ struct ModelInfo {
};
static_assert(sizeof(ModelInfo) == 0x40, "ModelInfo is an invalid size");
+// This is nn::nfp::RegisterInfo
struct RegisterInfo {
Service::Mii::CharInfo mii_char_info;
WriteDate creation_date;
@@ -366,6 +310,7 @@ struct RegisterInfo {
};
static_assert(sizeof(RegisterInfo) == 0x100, "RegisterInfo is an invalid size");
+// This is nn::nfp::RegisterInfoPrivate
struct RegisterInfoPrivate {
Service::Mii::MiiStoreData mii_store_data;
WriteDate creation_date;
@@ -375,12 +320,13 @@ struct RegisterInfoPrivate {
};
static_assert(sizeof(RegisterInfoPrivate) == 0x100, "RegisterInfoPrivate is an invalid size");
+// This is nn::nfp::AdminInfo
struct AdminInfo {
u64 application_id;
u32 application_area_id;
u16 crc_change_counter;
u8 flags;
- PackedTagType tag_type;
+ NFC::PackedTagType tag_type;
AppAreaVersion app_area_version;
INSERT_PADDING_BYTES(0x7);
INSERT_PADDING_BYTES(0x28);
@@ -411,7 +357,7 @@ struct NfpData {
u32 access_id;
u16 settings_crc_counter;
u8 font_region;
- PackedTagType tag_type;
+ NFC::PackedTagType tag_type;
AppAreaVersion console_type;
u8 application_id_byte;
INSERT_PADDING_BYTES(0x2E);
@@ -420,37 +366,4 @@ struct NfpData {
static_assert(sizeof(NfpData) == 0x298, "NfpData is an invalid size");
#pragma pack()
-struct SectorKey {
- MifareCmd command;
- u8 unknown; // Usually 1
- INSERT_PADDING_BYTES(0x6);
- KeyData sector_key;
- INSERT_PADDING_BYTES(0x2);
-};
-static_assert(sizeof(SectorKey) == 0x10, "SectorKey is an invalid size");
-
-struct MifareReadBlockParameter {
- u8 sector_number;
- INSERT_PADDING_BYTES(0x7);
- SectorKey sector_key;
-};
-static_assert(sizeof(MifareReadBlockParameter) == 0x18,
- "MifareReadBlockParameter is an invalid size");
-
-struct MifareReadBlockData {
- DataBlock data;
- u8 sector_number;
- INSERT_PADDING_BYTES(0x7);
-};
-static_assert(sizeof(MifareReadBlockData) == 0x18, "MifareReadBlockData is an invalid size");
-
-struct MifareWriteBlockParameter {
- DataBlock data;
- u8 sector_number;
- INSERT_PADDING_BYTES(0x7);
- SectorKey sector_key;
-};
-static_assert(sizeof(MifareWriteBlockParameter) == 0x28,
- "MifareWriteBlockParameter is an invalid size");
-
} // namespace Service::NFP
diff --git a/src/core/hle/service/nifm/nifm.cpp b/src/core/hle/service/nifm/nifm.cpp
index 0c042f412..91d42853e 100644
--- a/src/core/hle/service/nifm/nifm.cpp
+++ b/src/core/hle/service/nifm/nifm.cpp
@@ -218,7 +218,7 @@ public:
private:
void Submit(HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ LOG_DEBUG(Service_NIFM, "(STUBBED) called");
if (state == RequestState::NotSubmitted) {
UpdateState(RequestState::OnHold);
@@ -229,7 +229,7 @@ private:
}
void GetRequestState(HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ LOG_DEBUG(Service_NIFM, "(STUBBED) called");
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(ResultSuccess);
@@ -237,7 +237,7 @@ private:
}
void GetResult(HLERequestContext& ctx) {
- LOG_WARNING(Service_NIFM, "(STUBBED) called");
+ LOG_DEBUG(Service_NIFM, "(STUBBED) called");
const auto result = [this] {
const auto has_connection = Network::GetHostIPv4Address().has_value();
diff --git a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
index cd0a13094..b16f9933f 100644
--- a/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
+++ b/src/core/hle/service/nvnflinger/buffer_queue_producer.cpp
@@ -793,6 +793,7 @@ Status BufferQueueProducer::SetPreallocatedBuffer(s32 slot,
std::scoped_lock lock{core->mutex};
slots[slot] = {};
+ slots[slot].fence = Fence::NoFence();
slots[slot].graphic_buffer = buffer;
slots[slot].frame_number = 0;
@@ -854,7 +855,7 @@ void BufferQueueProducer::Transact(HLERequestContext& ctx, TransactionId code, u
status = DequeueBuffer(&slot, &fence, is_async, width, height, pixel_format, usage);
parcel_out.Write(slot);
- parcel_out.WriteObject(&fence);
+ parcel_out.WriteFlattenedObject(&fence);
break;
}
case TransactionId::RequestBuffer: {
@@ -864,7 +865,7 @@ void BufferQueueProducer::Transact(HLERequestContext& ctx, TransactionId code, u
status = RequestBuffer(slot, &buf);
- parcel_out.WriteObject(buf);
+ parcel_out.WriteFlattenedObject(buf);
break;
}
case TransactionId::QueueBuffer: {
diff --git a/src/core/hle/service/nvnflinger/parcel.h b/src/core/hle/service/nvnflinger/parcel.h
index d1b6201e0..fb56d75d7 100644
--- a/src/core/hle/service/nvnflinger/parcel.h
+++ b/src/core/hle/service/nvnflinger/parcel.h
@@ -117,61 +117,67 @@ private:
class OutputParcel final {
public:
- static constexpr std::size_t DefaultBufferSize = 0x40;
-
- OutputParcel() : buffer(DefaultBufferSize) {}
-
- template <typename T>
- explicit OutputParcel(const T& out_data) : buffer(DefaultBufferSize) {
- Write(out_data);
- }
+ OutputParcel() = default;
template <typename T>
void Write(const T& val) {
- static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
-
- if (buffer.size() < write_index + sizeof(T)) {
- buffer.resize(buffer.size() + sizeof(T) + DefaultBufferSize);
- }
-
- std::memcpy(buffer.data() + write_index, &val, sizeof(T));
- write_index += sizeof(T);
- write_index = Common::AlignUp(write_index, 4);
+ this->WriteImpl(val, m_data_buffer);
}
template <typename T>
- void WriteObject(const T* ptr) {
- static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable.");
-
+ void WriteFlattenedObject(const T* ptr) {
if (!ptr) {
- Write<u32>(0);
+ this->Write<u32>(0);
return;
}
- Write<u32>(1);
- Write<s64>(sizeof(T));
- Write(*ptr);
+ this->Write<u32>(1);
+ this->Write<s64>(sizeof(T));
+ this->Write(*ptr);
}
template <typename T>
- void WriteObject(const std::shared_ptr<T> ptr) {
- WriteObject(ptr.get());
+ void WriteFlattenedObject(const std::shared_ptr<T> ptr) {
+ this->WriteFlattenedObject(ptr.get());
+ }
+
+ template <typename T>
+ void WriteInterface(const T& val) {
+ this->WriteImpl(val, m_data_buffer);
+ this->WriteImpl(0U, m_object_buffer);
}
std::vector<u8> Serialize() const {
+ std::vector<u8> output_buffer(sizeof(ParcelHeader) + m_data_buffer.size() +
+ m_object_buffer.size());
+
ParcelHeader header{};
- header.data_size = static_cast<u32>(write_index - sizeof(ParcelHeader));
+ header.data_size = static_cast<u32>(m_data_buffer.size());
header.data_offset = sizeof(ParcelHeader);
- header.objects_size = 4;
- header.objects_offset = static_cast<u32>(sizeof(ParcelHeader) + header.data_size);
- std::memcpy(buffer.data(), &header, sizeof(ParcelHeader));
+ header.objects_size = static_cast<u32>(m_object_buffer.size());
+ header.objects_offset = header.data_offset + header.data_size;
+
+ std::memcpy(output_buffer.data(), &header, sizeof(header));
+ std::ranges::copy(m_data_buffer, output_buffer.data() + header.data_offset);
+ std::ranges::copy(m_object_buffer, output_buffer.data() + header.objects_offset);
+
+ return output_buffer;
+ }
+
+private:
+ template <typename T>
+ requires(std::is_trivially_copyable_v<T>)
+ void WriteImpl(const T& val, std::vector<u8>& buffer) {
+ const size_t aligned_size = Common::AlignUp(sizeof(T), 4);
+ const size_t old_size = buffer.size();
+ buffer.resize(old_size + aligned_size);
- return buffer;
+ std::memcpy(buffer.data() + old_size, &val, sizeof(T));
}
private:
- mutable std::vector<u8> buffer;
- std::size_t write_index = sizeof(ParcelHeader);
+ std::vector<u8> m_data_buffer;
+ std::vector<u8> m_object_buffer;
};
} // namespace Service::android
diff --git a/src/core/hle/service/server_manager.cpp b/src/core/hle/service/server_manager.cpp
index 6b4a1291e..156bc27d8 100644
--- a/src/core/hle/service/server_manager.cpp
+++ b/src/core/hle/service/server_manager.cpp
@@ -33,6 +33,9 @@ ServerManager::ServerManager(Core::System& system) : m_system{system}, m_serve_m
// Initialize event.
m_event = Kernel::KEvent::Create(system.Kernel());
m_event->Initialize(nullptr);
+
+ // Register event.
+ Kernel::KEvent::Register(system.Kernel(), m_event);
}
ServerManager::~ServerManager() {
@@ -160,6 +163,9 @@ Result ServerManager::ManageDeferral(Kernel::KEvent** out_event) {
// Initialize the event.
m_deferral_event->Initialize(nullptr);
+ // Register the event.
+ Kernel::KEvent::Register(m_system.Kernel(), m_deferral_event);
+
// Set the output.
*out_event = m_deferral_event;
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h
index 0f79a1b7e..45b2c43b7 100644
--- a/src/core/hle/service/service.h
+++ b/src/core/hle/service/service.h
@@ -142,7 +142,8 @@ template <typename Self>
class ServiceFramework : public ServiceFrameworkBase {
protected:
/// Contains information about a request type which is handled by the service.
- struct FunctionInfo : FunctionInfoBase {
+ template <typename T>
+ struct FunctionInfoTyped : FunctionInfoBase {
// TODO(yuriks): This function could be constexpr, but clang is the only compiler that
// doesn't emit an ICE or a wrong diagnostic because of the static_cast.
@@ -155,12 +156,13 @@ protected:
* the request
* @param name_ human-friendly name for the request. Used mostly for logging purposes.
*/
- FunctionInfo(u32 expected_header_, HandlerFnP<Self> handler_callback_, const char* name_)
+ FunctionInfoTyped(u32 expected_header_, HandlerFnP<T> handler_callback_, const char* name_)
: FunctionInfoBase{
expected_header_,
// Type-erase member function pointer by casting it down to the base class.
static_cast<HandlerFnP<ServiceFrameworkBase>>(handler_callback_), name_} {}
};
+ using FunctionInfo = FunctionInfoTyped<Self>;
/**
* Initializes the handler with no functions installed.
@@ -175,8 +177,8 @@ protected:
: ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {}
/// Registers handlers in the service.
- template <std::size_t N>
- void RegisterHandlers(const FunctionInfo (&functions)[N]) {
+ template <typename T = Self, std::size_t N>
+ void RegisterHandlers(const FunctionInfoTyped<T> (&functions)[N]) {
RegisterHandlers(functions, N);
}
@@ -184,13 +186,14 @@ protected:
* Registers handlers in the service. Usually prefer using the other RegisterHandlers
* overload in order to avoid needing to specify the array size.
*/
- void RegisterHandlers(const FunctionInfo* functions, std::size_t n) {
+ template <typename T = Self>
+ void RegisterHandlers(const FunctionInfoTyped<T>* functions, std::size_t n) {
RegisterHandlersBase(functions, n);
}
/// Registers handlers in the service.
- template <std::size_t N>
- void RegisterHandlersTipc(const FunctionInfo (&functions)[N]) {
+ template <typename T = Self, std::size_t N>
+ void RegisterHandlersTipc(const FunctionInfoTyped<T> (&functions)[N]) {
RegisterHandlersTipc(functions, N);
}
@@ -198,7 +201,8 @@ protected:
* Registers handlers in the service. Usually prefer using the other RegisterHandlers
* overload in order to avoid needing to specify the array size.
*/
- void RegisterHandlersTipc(const FunctionInfo* functions, std::size_t n) {
+ template <typename T = Self>
+ void RegisterHandlersTipc(const FunctionInfoTyped<T>* functions, std::size_t n) {
RegisterHandlersBaseTipc(functions, n);
}
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp
index c45be5726..1608fa24c 100644
--- a/src/core/hle/service/sm/sm.cpp
+++ b/src/core/hle/service/sm/sm.cpp
@@ -64,6 +64,9 @@ Result ServiceManager::RegisterService(std::string name, u32 max_sessions,
auto* port = Kernel::KPort::Create(kernel);
port->Initialize(ServerSessionCountMax, false, 0);
+ // Register the port.
+ Kernel::KPort::Register(kernel, port);
+
service_ports.emplace(name, port);
registered_services.emplace(name, handler);
if (deferral_event) {
diff --git a/src/core/hle/service/sm/sm_controller.cpp b/src/core/hle/service/sm/sm_controller.cpp
index 419c1df2b..7dce28fe0 100644
--- a/src/core/hle/service/sm/sm_controller.cpp
+++ b/src/core/hle/service/sm/sm_controller.cpp
@@ -49,6 +49,9 @@ void Controller::CloneCurrentObject(HLERequestContext& ctx) {
// Commit the session reservation.
session_reservation.Commit();
+ // Register the session.
+ Kernel::KSession::Register(system.Kernel(), session);
+
// Register with server manager.
session_manager->GetServerManager().RegisterSession(&session->GetServerSession(),
session_manager);
diff --git a/src/core/hle/service/time/clock_types.h b/src/core/hle/service/time/clock_types.h
index ed1eb5b2d..e6293ffb9 100644
--- a/src/core/hle/service/time/clock_types.h
+++ b/src/core/hle/service/time/clock_types.h
@@ -59,6 +59,18 @@ static_assert(sizeof(SystemClockContext) == 0x20, "SystemClockContext is incorre
static_assert(std::is_trivially_copyable_v<SystemClockContext>,
"SystemClockContext must be trivially copyable");
+struct ContinuousAdjustmentTimePoint {
+ s64 measurement_offset;
+ s64 diff_scale;
+ u32 shift_amount;
+ s64 lower;
+ s64 upper;
+ Common::UUID clock_source_id;
+};
+static_assert(sizeof(ContinuousAdjustmentTimePoint) == 0x38);
+static_assert(std::is_trivially_copyable_v<ContinuousAdjustmentTimePoint>,
+ "ContinuousAdjustmentTimePoint must be trivially copyable");
+
/// https://switchbrew.org/wiki/Glue_services#TimeSpanType
struct TimeSpanType {
s64 nanoseconds{};
diff --git a/src/core/hle/service/time/time_sharedmemory.cpp b/src/core/hle/service/time/time_sharedmemory.cpp
index ff53a7d6f..ce1c85bcc 100644
--- a/src/core/hle/service/time/time_sharedmemory.cpp
+++ b/src/core/hle/service/time/time_sharedmemory.cpp
@@ -30,6 +30,25 @@ void SharedMemory::SetupStandardSteadyClock(const Common::UUID& clock_source_id,
}
void SharedMemory::UpdateLocalSystemClockContext(const Clock::SystemClockContext& context) {
+ // lower and upper are related to the measurement point for the steady time point,
+ // and compare equal on boot
+ const s64 time_point_ns = context.steady_time_point.time_point * 1'000'000'000LL;
+
+ // This adjusts for some sort of time skew
+ // Both 0 on boot
+ const s64 diff_scale = 0;
+ const u32 shift_amount = 0;
+
+ const Clock::ContinuousAdjustmentTimePoint adjustment{
+ .measurement_offset = system.CoreTiming().GetGlobalTimeNs().count(),
+ .diff_scale = diff_scale,
+ .shift_amount = shift_amount,
+ .lower = time_point_ns,
+ .upper = time_point_ns,
+ .clock_source_id = context.steady_time_point.clock_source_id,
+ };
+
+ StoreToLockFreeAtomicType(&GetFormat()->continuous_adjustment_timepoint, adjustment);
StoreToLockFreeAtomicType(&GetFormat()->standard_local_system_clock_context, context);
}
diff --git a/src/core/hle/service/time/time_sharedmemory.h b/src/core/hle/service/time/time_sharedmemory.h
index 044a4d24e..c89be9765 100644
--- a/src/core/hle/service/time/time_sharedmemory.h
+++ b/src/core/hle/service/time/time_sharedmemory.h
@@ -65,14 +65,15 @@ public:
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;
+ LockFreeAtomicType<Clock::ContinuousAdjustmentTimePoint> continuous_adjustment_timepoint;
};
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");
+ static_assert(offsetof(Format, continuous_adjustment_timepoint) == 0xd0);
+ static_assert(sizeof(Format) == 0x148, "Format is an invalid size");
void SetupStandardSteadyClock(const Common::UUID& clock_source_id,
Clock::TimeSpanType current_time_point);
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index 68eab5133..1b193f00c 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -64,8 +64,8 @@ public:
private:
const u32 magic = 2;
const u32 process_id = 1;
- const u32 id;
- INSERT_PADDING_WORDS(3);
+ const u64 id;
+ INSERT_PADDING_WORDS(2);
std::array<u8, 8> dispdrv = {'d', 'i', 's', 'p', 'd', 'r', 'v', '\0'};
INSERT_PADDING_WORDS(2);
};
@@ -608,7 +608,9 @@ private:
return;
}
- const auto parcel = android::OutputParcel{NativeWindow{*buffer_queue_id}};
+ android::OutputParcel parcel;
+ parcel.WriteInterface(NativeWindow{*buffer_queue_id});
+
const auto buffer_size = ctx.WriteBuffer(parcel.Serialize());
IPC::ResponseBuilder rb{ctx, 4};
@@ -654,7 +656,9 @@ private:
return;
}
- const auto parcel = android::OutputParcel{NativeWindow{*buffer_queue_id}};
+ android::OutputParcel parcel;
+ parcel.WriteInterface(NativeWindow{*buffer_queue_id});
+
const auto buffer_size = ctx.WriteBuffer(parcel.Serialize());
IPC::ResponseBuilder rb{ctx, 6};
diff --git a/src/core/internal_network/network.cpp b/src/core/internal_network/network.cpp
index bf97b0ebc..75ac10a9c 100644
--- a/src/core/internal_network/network.cpp
+++ b/src/core/internal_network/network.cpp
@@ -356,7 +356,7 @@ NetworkInstance::~NetworkInstance() {
std::optional<IPv4Address> GetHostIPv4Address() {
const auto network_interface = Network::GetSelectedNetworkInterface();
if (!network_interface.has_value()) {
- LOG_ERROR(Network, "GetSelectedNetworkInterface returned no interface");
+ LOG_DEBUG(Network, "GetSelectedNetworkInterface returned no interface");
return {};
}
diff --git a/src/core/internal_network/network_interface.cpp b/src/core/internal_network/network_interface.cpp
index 7b8e510a2..4c909a6d3 100644
--- a/src/core/internal_network/network_interface.cpp
+++ b/src/core/internal_network/network_interface.cpp
@@ -200,7 +200,7 @@ std::optional<NetworkInterface> GetSelectedNetworkInterface() {
});
if (res == network_interfaces.end()) {
- LOG_ERROR(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
+ LOG_DEBUG(Network, "Couldn't find selected interface \"{}\"", selected_network_interface);
return std::nullopt;
}
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 432310632..514ba0d66 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -13,10 +13,12 @@
#include "common/swap.h"
#include "core/core.h"
#include "core/device_memory.h"
+#include "core/hardware_properties.h"
#include "core/hle/kernel/k_page_table.h"
#include "core/hle/kernel/k_process.h"
#include "core/memory.h"
#include "video_core/gpu.h"
+#include "video_core/rasterizer_download_area.h"
namespace Core::Memory {
@@ -243,7 +245,7 @@ struct Memory::Impl {
[&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount,
const u8* const host_ptr) {
if constexpr (!UNSAFE) {
- system.GPU().FlushRegion(GetInteger(current_vaddr), copy_amount);
+ HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount);
}
std::memcpy(dest_buffer, host_ptr, copy_amount);
},
@@ -334,7 +336,7 @@ struct Memory::Impl {
},
[&](const Common::ProcessAddress current_vaddr, const std::size_t copy_amount,
u8* const host_ptr) {
- system.GPU().FlushRegion(GetInteger(current_vaddr), copy_amount);
+ HandleRasterizerDownload(GetInteger(current_vaddr), copy_amount);
WriteBlockImpl<false>(process, dest_addr, host_ptr, copy_amount);
},
[&](const std::size_t copy_amount) {
@@ -373,7 +375,7 @@ struct Memory::Impl {
const std::size_t block_size) {
// dc ivac: Invalidate to point of coherency
// GPU flush -> CPU invalidate
- system.GPU().FlushRegion(GetInteger(current_vaddr), block_size);
+ HandleRasterizerDownload(GetInteger(current_vaddr), block_size);
};
return PerformCacheOperation(process, dest_addr, size, on_rasterizer);
}
@@ -462,7 +464,8 @@ struct Memory::Impl {
}
if (Settings::IsFastmemEnabled()) {
- const bool is_read_enable = Settings::IsGPULevelHigh() || !cached;
+ const bool is_read_enable =
+ !Settings::values.use_reactive_flushing.GetValue() || !cached;
system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached);
}
@@ -651,7 +654,7 @@ struct Memory::Impl {
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:016X}", sizeof(T) * 8,
GetInteger(vaddr));
},
- [&]() { system.GPU().FlushRegion(GetInteger(vaddr), sizeof(T)); });
+ [&]() { HandleRasterizerDownload(GetInteger(vaddr), sizeof(T)); });
if (ptr) {
std::memcpy(&result, ptr, sizeof(T));
}
@@ -712,7 +715,19 @@ struct Memory::Impl {
return true;
}
+ void HandleRasterizerDownload(VAddr address, size_t size) {
+ const size_t core = system.GetCurrentHostThreadID();
+ auto& current_area = rasterizer_areas[core];
+ const VAddr end_address = address + size;
+ if (current_area.start_address <= address && end_address <= current_area.end_address)
+ [[likely]] {
+ return;
+ }
+ current_area = system.GPU().OnCPURead(address, size);
+ }
+
Common::PageTable* current_page_table = nullptr;
+ std::array<VideoCore::RasterizerDownloadArea, Core::Hardware::NUM_CPU_CORES> rasterizer_areas{};
Core::System& system;
};
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp
index 9178b00ca..7a2f3c90a 100644
--- a/src/core/telemetry_session.cpp
+++ b/src/core/telemetry_session.cpp
@@ -85,6 +85,20 @@ static const char* TranslateNvdecEmulation(Settings::NvdecEmulation backend) {
return "Unknown";
}
+static constexpr const char* TranslateVSyncMode(Settings::VSyncMode mode) {
+ switch (mode) {
+ case Settings::VSyncMode::Immediate:
+ return "Immediate";
+ case Settings::VSyncMode::Mailbox:
+ return "Mailbox";
+ case Settings::VSyncMode::FIFO:
+ return "FIFO";
+ case Settings::VSyncMode::FIFORelaxed:
+ return "FIFO Relaxed";
+ }
+ return "Unknown";
+}
+
u64 GetTelemetryId() {
u64 telemetry_id{};
const auto filename = Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "telemetry_id";
@@ -241,7 +255,8 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
AddField(field_type, "Renderer_NvdecEmulation",
TranslateNvdecEmulation(Settings::values.nvdec_emulation.GetValue()));
AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue());
- AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());
+ AddField(field_type, "Renderer_UseVsync",
+ TranslateVSyncMode(Settings::values.vsync_mode.GetValue()));
AddField(field_type, "Renderer_ShaderBackend",
static_cast<u32>(Settings::values.shader_backend.GetValue()));
AddField(field_type, "Renderer_UseAsynchronousShaders",
diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp
index 7f9e8dbb9..9a0439bb5 100644
--- a/src/input_common/drivers/sdl_driver.cpp
+++ b/src/input_common/drivers/sdl_driver.cpp
@@ -109,14 +109,37 @@ public:
}
bool RumblePlay(const Common::Input::VibrationStatus vibration) {
- constexpr u32 rumble_max_duration_ms = 1000;
+ constexpr u32 rumble_max_duration_ms = 2000;
+ constexpr f32 low_start_sensitivity_limit = 140.0;
+ constexpr f32 low_width_sensitivity_limit = 400.0;
+ constexpr f32 high_start_sensitivity_limit = 200.0;
+ constexpr f32 high_width_sensitivity_limit = 700.0;
+ // Try to provide some feeling of the frequency by reducing the amplitude depending on it.
+ f32 low_frequency_scale = 1.0;
+ if (vibration.low_frequency > low_start_sensitivity_limit) {
+ low_frequency_scale =
+ std::max(1.0f - (vibration.low_frequency - low_start_sensitivity_limit) /
+ low_width_sensitivity_limit,
+ 0.3f);
+ }
+ f32 low_amplitude = vibration.low_amplitude * low_frequency_scale;
+
+ f32 high_frequency_scale = 1.0;
+ if (vibration.high_frequency > high_start_sensitivity_limit) {
+ high_frequency_scale =
+ std::max(1.0f - (vibration.high_frequency - high_start_sensitivity_limit) /
+ high_width_sensitivity_limit,
+ 0.3f);
+ }
+ f32 high_amplitude = vibration.high_amplitude * high_frequency_scale;
+
if (sdl_controller) {
- return SDL_GameControllerRumble(
- sdl_controller.get(), static_cast<u16>(vibration.low_amplitude),
- static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1;
+ return SDL_GameControllerRumble(sdl_controller.get(), static_cast<u16>(low_amplitude),
+ static_cast<u16>(high_amplitude),
+ rumble_max_duration_ms) != -1;
} else if (sdl_joystick) {
- return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude),
- static_cast<u16>(vibration.high_amplitude),
+ return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(low_amplitude),
+ static_cast<u16>(high_amplitude),
rumble_max_duration_ms) != -1;
}
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.cpp b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
index 2b42a4555..077d72cd0 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.cpp
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.cpp
@@ -236,13 +236,13 @@ DriverResult JoyconCommonProtocol::GetMCUDataResponse(ReportMode report_mode,
return DriverResult::Success;
}
-DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, SubCommand sc,
+DriverResult JoyconCommonProtocol::SendMCUData(ReportMode report_mode, MCUSubCommand sc,
std::span<const u8> buffer,
MCUCommandResponse& output) {
SubCommandPacket packet{
.output_report = OutputReport::MCU_DATA,
.packet_counter = GetCounter(),
- .sub_command = sc,
+ .mcu_sub_command = sc,
.command_data = {},
};
@@ -269,8 +269,7 @@ DriverResult JoyconCommonProtocol::WaitSetMCUMode(ReportMode report_mode, MCUMod
std::size_t tries{};
do {
- const std::vector<u8> mcu_data{static_cast<u8>(MCUMode::Standby)};
- const auto result = SendMCUData(report_mode, SubCommand::STATE, mcu_data, output);
+ const auto result = SendMCUData(report_mode, MCUSubCommand::SetDeviceMode, {}, output);
if (result != DriverResult::Success) {
return result;
diff --git a/src/input_common/helpers/joycon_protocol/common_protocol.h b/src/input_common/helpers/joycon_protocol/common_protocol.h
index 62cae739a..411ec018a 100644
--- a/src/input_common/helpers/joycon_protocol/common_protocol.h
+++ b/src/input_common/helpers/joycon_protocol/common_protocol.h
@@ -156,7 +156,7 @@ public:
* @param buffer data to be send
* @returns output buffer containing the response
*/
- DriverResult SendMCUData(ReportMode report_mode, SubCommand sc, std::span<const u8> buffer,
+ DriverResult SendMCUData(ReportMode report_mode, MCUSubCommand sc, std::span<const u8> buffer,
MCUCommandResponse& output);
/**
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h
index dcac0e422..1c8d294b0 100644
--- a/src/input_common/helpers/joycon_protocol/joycon_types.h
+++ b/src/input_common/helpers/joycon_protocol/joycon_types.h
@@ -394,6 +394,7 @@ enum class DriverResult {
InvalidHandle,
NotSupported,
Disabled,
+ Delayed,
Unknown,
};
@@ -575,7 +576,6 @@ struct NFCPollingCommandData {
static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
struct NFCRequestState {
- MCUSubCommand sub_command;
NFCReadCommand command_argument;
u8 packet_id;
INSERT_PADDING_BYTES(0x1);
@@ -587,6 +587,7 @@ struct NFCRequestState {
NFCPollingCommandData nfc_polling;
};
u8 crc;
+ INSERT_PADDING_BYTES(0x1);
};
static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
@@ -659,7 +660,10 @@ struct SubCommandPacket {
OutputReport output_report;
u8 packet_counter;
INSERT_PADDING_BYTES(0x8); // This contains vibration data
- SubCommand sub_command;
+ union {
+ SubCommand sub_command;
+ MCUSubCommand mcu_sub_command;
+ };
std::array<u8, 0x26> command_data;
};
static_assert(sizeof(SubCommandPacket) == 0x31, "SubCommandPacket is an invalid size");
diff --git a/src/input_common/helpers/joycon_protocol/nfc.cpp b/src/input_common/helpers/joycon_protocol/nfc.cpp
index eeba82986..14818ae33 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.cpp
+++ b/src/input_common/helpers/joycon_protocol/nfc.cpp
@@ -72,6 +72,11 @@ DriverResult NfcProtocol::StartNFCPollingMode() {
}
DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
+ if (update_counter++ < AMIIBO_UPDATE_DELAY) {
+ return DriverResult::Delayed;
+ }
+ update_counter = 0;
+
LOG_DEBUG(Input, "Start NFC pooling Mode");
ScopedSetBlocking sb(this);
DriverResult result{DriverResult::Success};
@@ -87,7 +92,7 @@ DriverResult NfcProtocol::ScanAmiibo(std::vector<u8>& data) {
result = WaitUntilNfcIsReady();
}
if (result == DriverResult::Success) {
- result = StartPolling(tag_data);
+ result = StartPolling(tag_data, 7);
}
if (result == DriverResult::Success) {
result = GetAmiiboData(data);
@@ -129,9 +134,8 @@ DriverResult NfcProtocol::WaitUntilNfcIsReady() {
return DriverResult::Success;
}
-DriverResult NfcProtocol::StartPolling(TagFoundData& data) {
+DriverResult NfcProtocol::StartPolling(TagFoundData& data, std::size_t timeout_limit) {
LOG_DEBUG(Input, "Start Polling for tag");
- constexpr std::size_t timeout_limit = 7;
MCUCommandResponse output{};
std::size_t tries = 0;
@@ -278,7 +282,6 @@ DriverResult NfcProtocol::GetAmiiboData(std::vector<u8>& ntag_data) {
DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) {
NFCRequestState request{
- .sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::StartPolling,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
@@ -296,13 +299,13 @@ DriverResult NfcProtocol::SendStartPollingRequest(MCUCommandResponse& output) {
std::array<u8, sizeof(NFCRequestState)> request_data{};
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
- request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
- return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+ request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
+ return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
+ output);
}
DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) {
NFCRequestState request{
- .sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::StopPolling,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
@@ -313,13 +316,13 @@ DriverResult NfcProtocol::SendStopPollingRequest(MCUCommandResponse& output) {
std::array<u8, sizeof(NFCRequestState)> request_data{};
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
- request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
- return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+ request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
+ return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
+ output);
}
DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& output) {
NFCRequestState request{
- .sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::StartWaitingRecieve,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
@@ -330,13 +333,13 @@ DriverResult NfcProtocol::SendStartWaitingRecieveRequest(MCUCommandResponse& out
std::vector<u8> request_data(sizeof(NFCRequestState));
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
- request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
- return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+ request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
+ return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
+ output);
}
DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCPages ntag_pages) {
NFCRequestState request{
- .sub_command = MCUSubCommand::ReadDeviceMode,
.command_argument = NFCReadCommand::Ntag,
.packet_id = 0x0,
.packet_flag = MCUPacketFlag::LastCommandPacket,
@@ -355,8 +358,9 @@ DriverResult NfcProtocol::SendReadAmiiboRequest(MCUCommandResponse& output, NFCP
std::array<u8, sizeof(NFCRequestState)> request_data{};
memcpy(request_data.data(), &request, sizeof(NFCRequestState));
- request_data[37] = CalculateMCU_CRC8(request_data.data() + 1, 36);
- return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, SubCommand::STATE, request_data, output);
+ request_data[36] = CalculateMCU_CRC8(request_data.data(), 36);
+ return SendMCUData(ReportMode::NFC_IR_MODE_60HZ, MCUSubCommand::ReadDeviceMode, request_data,
+ output);
}
NFCReadBlockCommand NfcProtocol::GetReadBlockCommand(NFCPages pages) const {
diff --git a/src/input_common/helpers/joycon_protocol/nfc.h b/src/input_common/helpers/joycon_protocol/nfc.h
index 11e263e07..4cb992d1d 100644
--- a/src/input_common/helpers/joycon_protocol/nfc.h
+++ b/src/input_common/helpers/joycon_protocol/nfc.h
@@ -32,6 +32,9 @@ public:
bool IsEnabled() const;
private:
+ // Number of times the function will be delayed until it outputs valid data
+ static constexpr std::size_t AMIIBO_UPDATE_DELAY = 15;
+
struct TagFoundData {
u8 type;
std::vector<u8> uuid;
@@ -39,7 +42,7 @@ private:
DriverResult WaitUntilNfcIsReady();
- DriverResult StartPolling(TagFoundData& data);
+ DriverResult StartPolling(TagFoundData& data, std::size_t timeout_limit = 1);
DriverResult ReadTag(const TagFoundData& data);
@@ -56,6 +59,7 @@ private:
NFCReadBlockCommand GetReadBlockCommand(NFCPages pages) const;
bool is_enabled{};
+ std::size_t update_counter{};
};
} // namespace InputCommon::Joycon
diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp
index 9361b00c5..8c2ee4eb3 100644
--- a/src/input_common/input_mapping.cpp
+++ b/src/input_common/input_mapping.cpp
@@ -82,6 +82,9 @@ void MappingFactory::RegisterButton(const MappingData& data) {
new_input.Set("axis", data.index);
new_input.Set("threshold", 0.5f);
break;
+ case EngineInputType::Motion:
+ new_input.Set("motion", data.index);
+ break;
default:
return;
}
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp
index 8c6a6521a..380a01542 100644
--- a/src/input_common/input_poller.cpp
+++ b/src/input_common/input_poller.cpp
@@ -667,7 +667,7 @@ public:
.raw_value = input_engine->GetAxis(identifier, axis_z),
.properties = properties_z,
};
- status.delta_timestamp = 5000;
+ status.delta_timestamp = 1000;
status.force_update = true;
return status;
}
@@ -939,6 +939,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice(
.threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f),
.offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f),
.inverted = params.Get("invert", "+") == "-",
+ .inverted_button = params.Get("inverted", false) != 0,
.toggle = params.Get("toggle", false) != 0,
};
input_engine->PreSetController(identifier);
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 0cd87a48f..07c2b7b8a 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
@@ -339,9 +339,7 @@ Id EmitGetAttribute(EmitContext& ctx, IR::Attribute attr, Id vertex) {
if (ctx.profile.support_vertex_instance_id) {
return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.vertex_id));
} else {
- const Id index{ctx.OpLoad(ctx.U32[1], ctx.vertex_index)};
- const Id base{ctx.OpLoad(ctx.U32[1], ctx.base_vertex)};
- return ctx.OpBitcast(ctx.F32[1], ctx.OpISub(ctx.U32[1], index, base));
+ return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.vertex_index));
}
case IR::Attribute::BaseInstance:
return ctx.OpBitcast(ctx.F32[1], ctx.OpLoad(ctx.U32[1], ctx.base_instance));
@@ -386,9 +384,7 @@ Id EmitGetAttributeU32(EmitContext& ctx, IR::Attribute attr, Id) {
if (ctx.profile.support_vertex_instance_id) {
return ctx.OpLoad(ctx.U32[1], ctx.vertex_id);
} else {
- const Id index{ctx.OpLoad(ctx.U32[1], ctx.vertex_index)};
- const Id base{ctx.OpLoad(ctx.U32[1], ctx.base_vertex)};
- return ctx.OpISub(ctx.U32[1], index, base);
+ return ctx.OpLoad(ctx.U32[1], ctx.vertex_index);
}
case IR::Attribute::BaseInstance:
return ctx.OpLoad(ctx.U32[1], ctx.base_instance);
@@ -473,7 +469,8 @@ void EmitSetFragColor(EmitContext& ctx, u32 index, u32 component, Id value) {
}
void EmitSetSampleMask(EmitContext& ctx, Id value) {
- ctx.OpStore(ctx.sample_mask, value);
+ const Id pointer{ctx.OpAccessChain(ctx.output_u32, ctx.sample_mask, ctx.u32_zero_value)};
+ ctx.OpStore(pointer, value);
}
void EmitSetFragDepth(EmitContext& ctx, Id value) {
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
index d48d4860e..47739794f 100644
--- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
+++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp
@@ -1572,7 +1572,8 @@ void EmitContext::DefineOutputs(const IR::Program& program) {
Decorate(frag_depth, spv::Decoration::BuiltIn, spv::BuiltIn::FragDepth);
}
if (info.stores_sample_mask) {
- sample_mask = DefineOutput(*this, U32[1], std::nullopt);
+ const Id array_type{TypeArray(U32[1], Const(1U))};
+ sample_mask = DefineOutput(*this, array_type, std::nullopt);
Decorate(sample_mask, spv::Decoration::BuiltIn, spv::BuiltIn::SampleMask);
}
break;
diff --git a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp
index 639da1e9c..eeb49444f 100644
--- a/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp
+++ b/src/shader_recompiler/frontend/maxwell/translate/impl/texture_mipmap_level.cpp
@@ -102,12 +102,7 @@ void Impl(TranslatorVisitor& v, u64 insn, bool is_bindless) {
}
IR::F32 value{v.ir.CompositeExtract(sample, element)};
if (element < 2) {
- IR::U32 casted_value;
- if (element == 0) {
- casted_value = v.ir.ConvertFToU(32, value);
- } else {
- casted_value = v.ir.ConvertFToS(16, value);
- }
+ IR::U32 casted_value = v.ir.ConvertFToU(32, value);
v.X(dest_reg, v.ir.ShiftLeftLogical(casted_value, v.ir.Imm32(8)));
} else {
v.F(dest_reg, value);
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 39b774c98..1e158f375 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -15,7 +15,7 @@ add_executable(tests
core/core_timing.cpp
core/internal_network/network.cpp
precompiled_headers.h
- video_core/buffer_base.cpp
+ video_core/memory_tracker.cpp
input_common/calibration_configuration_job.cpp
)
diff --git a/src/tests/video_core/buffer_base.cpp b/src/tests/video_core/buffer_base.cpp
deleted file mode 100644
index 734dbf4b6..000000000
--- a/src/tests/video_core/buffer_base.cpp
+++ /dev/null
@@ -1,549 +0,0 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
-
-#include <stdexcept>
-#include <unordered_map>
-
-#include <catch2/catch_test_macros.hpp>
-
-#include "common/alignment.h"
-#include "common/common_types.h"
-#include "video_core/buffer_cache/buffer_base.h"
-
-namespace {
-using VideoCommon::BufferBase;
-using Range = std::pair<u64, u64>;
-
-constexpr u64 PAGE = 4096;
-constexpr u64 WORD = 4096 * 64;
-
-constexpr VAddr c = 0x1328914000;
-
-class RasterizerInterface {
-public:
- void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
- const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
- const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
- Core::Memory::YUZU_PAGEBITS};
- for (u64 page = page_start; page < page_end; ++page) {
- int& value = page_table[page];
- value += delta;
- if (value < 0) {
- throw std::logic_error{"negative page"};
- }
- if (value == 0) {
- page_table.erase(page);
- }
- }
- }
-
- [[nodiscard]] int Count(VAddr addr) const noexcept {
- const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
- return it == page_table.end() ? 0 : it->second;
- }
-
- [[nodiscard]] unsigned Count() const noexcept {
- unsigned count = 0;
- for (const auto& [index, value] : page_table) {
- count += value;
- }
- return count;
- }
-
-private:
- std::unordered_map<u64, int> page_table;
-};
-} // Anonymous namespace
-
-TEST_CASE("BufferBase: Small buffer", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- REQUIRE(rasterizer.Count() == 0);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == WORD / PAGE);
- REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{0, 0});
-
- buffer.MarkRegionAsCpuModified(c + PAGE, 1);
- REQUIRE(buffer.ModifiedCpuRegion(c, WORD) == Range{PAGE * 1, PAGE * 2});
-}
-
-TEST_CASE("BufferBase: Large buffer", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 32);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
- buffer.MarkRegionAsCpuModified(c + 4096, WORD * 4);
- REQUIRE(buffer.ModifiedCpuRegion(c, WORD + PAGE * 2) == Range{PAGE, WORD + PAGE * 2});
- REQUIRE(buffer.ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) == Range{PAGE * 2, PAGE * 8});
- REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 4 + PAGE});
- REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 4, PAGE) == Range{WORD * 4, WORD * 4 + PAGE});
- REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) ==
- Range{WORD * 3 + PAGE * 63, WORD * 4});
-
- buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE);
- buffer.MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
- REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
- Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 9});
-
- buffer.UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
- REQUIRE(buffer.ModifiedCpuRegion(c + WORD * 5, WORD) ==
- Range{WORD * 5 + PAGE * 6, WORD * 5 + PAGE * 7});
-
- buffer.MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63);
- REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{PAGE, WORD * 32});
-
- buffer.UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE);
- buffer.UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE);
-
- buffer.UnmarkRegionAsCpuModified(c, WORD * 32);
- REQUIRE(buffer.ModifiedCpuRegion(c, WORD * 32) == Range{0, 0});
-}
-
-TEST_CASE("BufferBase: Rasterizer counting", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, PAGE * 2);
- REQUIRE(rasterizer.Count() == 0);
- buffer.UnmarkRegionAsCpuModified(c, PAGE);
- REQUIRE(rasterizer.Count() == 1);
- buffer.MarkRegionAsCpuModified(c, PAGE * 2);
- REQUIRE(rasterizer.Count() == 0);
- buffer.UnmarkRegionAsCpuModified(c, PAGE);
- buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
- REQUIRE(rasterizer.Count() == 2);
- buffer.MarkRegionAsCpuModified(c, PAGE * 2);
- REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Basic range", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.MarkRegionAsCpuModified(c, PAGE);
- int num = 0;
- buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
- REQUIRE(offset == 0U);
- REQUIRE(size == PAGE);
- ++num;
- });
- REQUIRE(num == 1U);
-}
-
-TEST_CASE("BufferBase: Border upload", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 2);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
- buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
- buffer.ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD - PAGE);
- REQUIRE(size == PAGE * 2);
- });
-}
-
-TEST_CASE("BufferBase: Border upload range", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 2);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
- buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
- buffer.ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD - PAGE);
- REQUIRE(size == PAGE * 2);
- });
- buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
- buffer.ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD - PAGE);
- REQUIRE(size == PAGE);
- });
- buffer.ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD);
- REQUIRE(size == PAGE);
- });
-}
-
-TEST_CASE("BufferBase: Border upload partial range", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 2);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
- buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
- buffer.ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD - PAGE);
- REQUIRE(size == PAGE * 2);
- });
- buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
- buffer.ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD - PAGE);
- REQUIRE(size == PAGE);
- });
- buffer.ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD);
- REQUIRE(size == PAGE);
- });
-}
-
-TEST_CASE("BufferBase: Partial word uploads", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, 0x9d000);
- int num = 0;
- buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
- REQUIRE(offset == 0U);
- REQUIRE(size == WORD);
- ++num;
- });
- REQUIRE(num == 1);
- buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) {
- REQUIRE(offset == WORD);
- REQUIRE(size == WORD);
- ++num;
- });
- REQUIRE(num == 2);
- buffer.ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) {
- REQUIRE(offset == WORD * 2);
- REQUIRE(size == PAGE * 0x1d);
- ++num;
- });
- REQUIRE(num == 3);
-}
-
-TEST_CASE("BufferBase: Partial page upload", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- int num = 0;
- buffer.MarkRegionAsCpuModified(c + PAGE * 2, PAGE);
- buffer.MarkRegionAsCpuModified(c + PAGE * 9, PAGE);
- buffer.ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) {
- REQUIRE(offset == PAGE * 2);
- REQUIRE(size == PAGE);
- ++num;
- });
- REQUIRE(num == 1);
- buffer.ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) {
- REQUIRE(offset == PAGE * 9);
- REQUIRE(size == PAGE);
- ++num;
- });
- REQUIRE(num == 2);
-}
-
-TEST_CASE("BufferBase: Partial page upload with multiple words on the right") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 8);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
- buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
- int num = 0;
- buffer.ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) {
- REQUIRE(offset == PAGE * 13);
- REQUIRE(size == WORD * 7 - PAGE * 3);
- ++num;
- });
- REQUIRE(num == 1);
- buffer.ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) {
- REQUIRE(offset == WORD * 7 + PAGE * 10);
- REQUIRE(size == PAGE * 3);
- ++num;
- });
- REQUIRE(num == 2);
-}
-
-TEST_CASE("BufferBase: Partial page upload with multiple words on the left", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 8);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
- buffer.MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
- int num = 0;
- buffer.ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) {
- REQUIRE(offset == PAGE * 16);
- REQUIRE(size == WORD * 7 - PAGE * 3);
- ++num;
- });
- REQUIRE(num == 1);
- buffer.ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) {
- REQUIRE(offset == PAGE * 13);
- REQUIRE(size == PAGE * 3);
- ++num;
- });
- REQUIRE(num == 2);
-}
-
-TEST_CASE("BufferBase: Partial page upload with multiple words in the middle", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 8);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 8);
- buffer.MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140);
- int num = 0;
- buffer.ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) {
- REQUIRE(offset == PAGE * 16);
- REQUIRE(size == WORD);
- ++num;
- });
- REQUIRE(num == 1);
- buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
- REQUIRE(offset == PAGE * 13);
- REQUIRE(size == PAGE * 3);
- ++num;
- });
- REQUIRE(num == 2);
- buffer.ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) {
- REQUIRE(offset == WORD + PAGE * 16);
- REQUIRE(size == PAGE * 73);
- ++num;
- });
- REQUIRE(num == 3);
-}
-
-TEST_CASE("BufferBase: Empty right bits", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 2048);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 2048);
- buffer.MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
- buffer.ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) {
- REQUIRE(offset == WORD - PAGE);
- REQUIRE(size == PAGE * 2);
- });
-}
-
-TEST_CASE("BufferBase: Out of bound ranges 1", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.MarkRegionAsCpuModified(c, PAGE);
- int num = 0;
- buffer.ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; });
- buffer.ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; });
- buffer.ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; });
- REQUIRE(num == 0);
- buffer.ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; });
- REQUIRE(num == 1);
- buffer.MarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Out of bound ranges 2", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, 0x22000);
- REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x22000, PAGE));
- REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x28000, PAGE));
- REQUIRE(rasterizer.Count() == 0);
- REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100));
- REQUIRE(rasterizer.Count() == 1);
- REQUIRE_NOTHROW(buffer.UnmarkRegionAsCpuModified(c - 0x1000, PAGE * 2));
- buffer.UnmarkRegionAsCpuModified(c - 0x3000, PAGE * 2);
- buffer.UnmarkRegionAsCpuModified(c - 0x2000, PAGE * 2);
- REQUIRE(rasterizer.Count() == 2);
-}
-
-TEST_CASE("BufferBase: Out of bound ranges 3", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, 0x310720);
- buffer.UnmarkRegionAsCpuModified(c, 0x310720);
- REQUIRE(rasterizer.Count(c) == 1);
- REQUIRE(rasterizer.Count(c + PAGE) == 1);
- REQUIRE(rasterizer.Count(c + WORD) == 1);
- REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1);
-}
-
-TEST_CASE("BufferBase: Sparse regions 1", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.MarkRegionAsCpuModified(c + PAGE * 1, PAGE);
- buffer.MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4);
- buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
- static constexpr std::array<u64, 2> offsets{PAGE, PAGE * 3};
- static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4};
- REQUIRE(offset == offsets.at(i));
- REQUIRE(size == sizes.at(i));
- ++i;
- });
-}
-
-TEST_CASE("BufferBase: Sparse regions 2", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, 0x22000);
- buffer.UnmarkRegionAsCpuModified(c, 0x22000);
- REQUIRE(rasterizer.Count() == 0x22);
- buffer.MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE);
- buffer.MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE);
- buffer.ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
- static constexpr std::array<u64, 2> offsets{PAGE * 0x1B, PAGE * 0x21};
- static constexpr std::array<u64, 2> sizes{PAGE, PAGE};
- REQUIRE(offset == offsets.at(i));
- REQUIRE(size == sizes.at(i));
- ++i;
- });
-}
-
-TEST_CASE("BufferBase: Single page modified range", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, PAGE);
- REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
- buffer.UnmarkRegionAsCpuModified(c, PAGE);
- REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
-}
-
-TEST_CASE("BufferBase: Two page modified range", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, PAGE * 2);
- REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c, PAGE * 2));
- buffer.UnmarkRegionAsCpuModified(c, PAGE);
- REQUIRE(!buffer.IsRegionCpuModified(c, PAGE));
-}
-
-TEST_CASE("BufferBase: Multi word modified ranges", "[video_core]") {
- for (int offset = 0; offset < 4; ++offset) {
- const VAddr address = c + WORD * offset;
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, address, WORD * 4);
- REQUIRE(buffer.IsRegionCpuModified(address, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 48, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 56, PAGE));
-
- buffer.UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE);
- REQUIRE(buffer.IsRegionCpuModified(address + PAGE, WORD));
- REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE));
- REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 33, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 31, PAGE * 2));
- REQUIRE(buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
-
- buffer.UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE);
- REQUIRE(!buffer.IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
- }
-}
-
-TEST_CASE("BufferBase: Single page in large buffer", "[video_core]") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 16);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 16);
- REQUIRE(!buffer.IsRegionCpuModified(c, WORD * 16));
-
- buffer.MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE);
- REQUIRE(buffer.IsRegionCpuModified(c, WORD * 16));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 10, WORD * 2));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 11, WORD * 2));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12, WORD * 2));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8));
- REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2));
-}
-
-TEST_CASE("BufferBase: Out of bounds region query") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 16);
- REQUIRE(!buffer.IsRegionCpuModified(c - PAGE, PAGE));
- REQUIRE(!buffer.IsRegionCpuModified(c - PAGE * 2, PAGE));
- REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + WORD * 16 - PAGE, WORD * 64));
- REQUIRE(!buffer.IsRegionCpuModified(c + WORD * 16, WORD * 64));
-}
-
-TEST_CASE("BufferBase: Wrap word regions") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD * 2);
- buffer.UnmarkRegionAsCpuModified(c, WORD * 2);
- buffer.MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2);
- REQUIRE(buffer.IsRegionCpuModified(c, WORD * 2));
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 62, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 64, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 2));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 63, PAGE * 8));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 60, PAGE * 8));
-
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
- buffer.MarkRegionAsCpuModified(c + PAGE * 127, PAGE);
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, WORD * 16));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 127, PAGE));
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 126, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 126, PAGE * 2));
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 128, WORD * 16));
-}
-
-TEST_CASE("BufferBase: Unaligned page region query") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.MarkRegionAsCpuModified(c + 4000, 1000);
- REQUIRE(buffer.IsRegionCpuModified(c, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1000));
- REQUIRE(buffer.IsRegionCpuModified(c + 4000, 1));
-}
-
-TEST_CASE("BufferBase: Cached write") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.CachedCpuWrite(c + PAGE, PAGE);
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
- buffer.FlushCachedWrites();
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
- buffer.MarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Multiple cached write") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.CachedCpuWrite(c + PAGE, PAGE);
- buffer.CachedCpuWrite(c + PAGE * 3, PAGE);
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
- buffer.FlushCachedWrites();
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE * 3, PAGE));
- buffer.MarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Cached write unmarked") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.CachedCpuWrite(c + PAGE, PAGE);
- buffer.UnmarkRegionAsCpuModified(c + PAGE, PAGE);
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
- buffer.FlushCachedWrites();
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
- buffer.MarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Cached write iterated") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- buffer.CachedCpuWrite(c + PAGE, PAGE);
- int num = 0;
- buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
- REQUIRE(num == 0);
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
- buffer.FlushCachedWrites();
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
- buffer.MarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == 0);
-}
-
-TEST_CASE("BufferBase: Cached write downloads") {
- RasterizerInterface rasterizer;
- BufferBase buffer(rasterizer, c, WORD);
- buffer.UnmarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == 64);
- buffer.CachedCpuWrite(c + PAGE, PAGE);
- REQUIRE(rasterizer.Count() == 63);
- buffer.MarkRegionAsGpuModified(c + PAGE, PAGE);
- int num = 0;
- buffer.ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; });
- buffer.ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
- REQUIRE(num == 0);
- REQUIRE(!buffer.IsRegionCpuModified(c + PAGE, PAGE));
- REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
- buffer.FlushCachedWrites();
- REQUIRE(buffer.IsRegionCpuModified(c + PAGE, PAGE));
- REQUIRE(!buffer.IsRegionGpuModified(c + PAGE, PAGE));
- buffer.MarkRegionAsCpuModified(c, WORD);
- REQUIRE(rasterizer.Count() == 0);
-}
diff --git a/src/tests/video_core/memory_tracker.cpp b/src/tests/video_core/memory_tracker.cpp
new file mode 100644
index 000000000..618793668
--- /dev/null
+++ b/src/tests/video_core/memory_tracker.cpp
@@ -0,0 +1,549 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include <memory>
+#include <stdexcept>
+#include <unordered_map>
+
+#include <catch2/catch_test_macros.hpp>
+
+#include "common/alignment.h"
+#include "common/common_types.h"
+#include "video_core/buffer_cache/memory_tracker_base.h"
+
+namespace {
+using Range = std::pair<u64, u64>;
+
+constexpr u64 PAGE = 4096;
+constexpr u64 WORD = 4096 * 64;
+constexpr u64 HIGH_PAGE_BITS = 22;
+constexpr u64 HIGH_PAGE_SIZE = 1ULL << HIGH_PAGE_BITS;
+
+constexpr VAddr c = 16 * HIGH_PAGE_SIZE;
+
+class RasterizerInterface {
+public:
+ void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
+ const u64 page_start{addr >> Core::Memory::YUZU_PAGEBITS};
+ const u64 page_end{(addr + size + Core::Memory::YUZU_PAGESIZE - 1) >>
+ Core::Memory::YUZU_PAGEBITS};
+ for (u64 page = page_start; page < page_end; ++page) {
+ int& value = page_table[page];
+ value += delta;
+ if (value < 0) {
+ throw std::logic_error{"negative page"};
+ }
+ if (value == 0) {
+ page_table.erase(page);
+ }
+ }
+ }
+
+ [[nodiscard]] int Count(VAddr addr) const noexcept {
+ const auto it = page_table.find(addr >> Core::Memory::YUZU_PAGEBITS);
+ return it == page_table.end() ? 0 : it->second;
+ }
+
+ [[nodiscard]] unsigned Count() const noexcept {
+ unsigned count = 0;
+ for (const auto& [index, value] : page_table) {
+ count += value;
+ }
+ return count;
+ }
+
+private:
+ std::unordered_map<u64, int> page_table;
+};
+} // Anonymous namespace
+
+using MemoryTracker = VideoCommon::MemoryTrackerBase<RasterizerInterface>;
+
+TEST_CASE("MemoryTracker: Small region", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ REQUIRE(rasterizer.Count() == 0);
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == WORD / PAGE);
+ REQUIRE(memory_track->ModifiedCpuRegion(c, WORD) == Range{0, 0});
+
+ memory_track->MarkRegionAsCpuModified(c + PAGE, 1);
+ REQUIRE(memory_track->ModifiedCpuRegion(c, WORD) == Range{c + PAGE * 1, c + PAGE * 2});
+}
+
+TEST_CASE("MemoryTracker: Large region", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
+ memory_track->MarkRegionAsCpuModified(c + 4096, WORD * 4);
+ REQUIRE(memory_track->ModifiedCpuRegion(c, WORD + PAGE * 2) ==
+ Range{c + PAGE, c + WORD + PAGE * 2});
+ REQUIRE(memory_track->ModifiedCpuRegion(c + PAGE * 2, PAGE * 6) ==
+ Range{c + PAGE * 2, c + PAGE * 8});
+ REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 4 + PAGE});
+ REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 4, PAGE) ==
+ Range{c + WORD * 4, c + WORD * 4 + PAGE});
+ REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 3 + PAGE * 63, PAGE) ==
+ Range{c + WORD * 3 + PAGE * 63, c + WORD * 4});
+
+ memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 6, PAGE);
+ memory_track->MarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
+ REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) ==
+ Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 9});
+
+ memory_track->UnmarkRegionAsCpuModified(c + WORD * 5 + PAGE * 8, PAGE);
+ REQUIRE(memory_track->ModifiedCpuRegion(c + WORD * 5, WORD) ==
+ Range{c + WORD * 5 + PAGE * 6, c + WORD * 5 + PAGE * 7});
+
+ memory_track->MarkRegionAsCpuModified(c + PAGE, WORD * 31 + PAGE * 63);
+ REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{c + PAGE, c + WORD * 32});
+
+ memory_track->UnmarkRegionAsCpuModified(c + PAGE * 4, PAGE);
+ memory_track->UnmarkRegionAsCpuModified(c + PAGE * 6, PAGE);
+
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
+ REQUIRE(memory_track->ModifiedCpuRegion(c, WORD * 32) == Range{0, 0});
+}
+
+TEST_CASE("MemoryTracker: Rasterizer counting", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ REQUIRE(rasterizer.Count() == 0);
+ memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+ REQUIRE(rasterizer.Count() == 1);
+ memory_track->MarkRegionAsCpuModified(c, PAGE * 2);
+ REQUIRE(rasterizer.Count() == 0);
+ memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+ memory_track->UnmarkRegionAsCpuModified(c + PAGE, PAGE);
+ REQUIRE(rasterizer.Count() == 2);
+ memory_track->MarkRegionAsCpuModified(c, PAGE * 2);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Basic range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ memory_track->MarkRegionAsCpuModified(c, PAGE);
+ int num = 0;
+ memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c);
+ REQUIRE(size == PAGE);
+ ++num;
+ });
+ REQUIRE(num == 1U);
+}
+
+TEST_CASE("MemoryTracker: Border upload", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
+ memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ memory_track->ForEachUploadRange(c, WORD * 2, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+}
+
+TEST_CASE("MemoryTracker: Border upload range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
+ memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ memory_track->ForEachUploadRange(c + WORD - PAGE, PAGE * 2, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+ memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ memory_track->ForEachUploadRange(c + WORD - PAGE, PAGE, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD - PAGE);
+ REQUIRE(size == PAGE);
+ });
+ memory_track->ForEachUploadRange(c + WORD, PAGE, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD);
+ REQUIRE(size == PAGE);
+ });
+}
+
+TEST_CASE("MemoryTracker: Border upload partial range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 2);
+ memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ memory_track->ForEachUploadRange(c + WORD - 1, 2, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+ memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ memory_track->ForEachUploadRange(c + WORD - 1, 1, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD - PAGE);
+ REQUIRE(size == PAGE);
+ });
+ memory_track->ForEachUploadRange(c + WORD + 50, 1, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD);
+ REQUIRE(size == PAGE);
+ });
+}
+
+TEST_CASE("MemoryTracker: Partial word uploads", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ int num = 0;
+ memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c);
+ REQUIRE(size == WORD);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ memory_track->ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD);
+ REQUIRE(size == WORD);
+ ++num;
+ });
+ REQUIRE(num == 2);
+ memory_track->ForEachUploadRange(c + 0x79000, 0x24000, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD * 2);
+ REQUIRE(size == PAGE * 0x1d);
+ ++num;
+ });
+ REQUIRE(num == 3);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ int num = 0;
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 2, PAGE);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 9, PAGE);
+ memory_track->ForEachUploadRange(c, PAGE * 3, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + PAGE * 2);
+ REQUIRE(size == PAGE);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ memory_track->ForEachUploadRange(c + PAGE * 7, PAGE * 3, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + PAGE * 9);
+ REQUIRE(size == PAGE);
+ ++num;
+ });
+ REQUIRE(num == 2);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload with multiple words on the right") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 9);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
+ int num = 0;
+ memory_track->ForEachUploadRange(c + PAGE * 10, WORD * 7, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + PAGE * 13);
+ REQUIRE(size == WORD * 7 - PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ memory_track->ForEachUploadRange(c + PAGE, WORD * 8, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD * 7 + PAGE * 10);
+ REQUIRE(size == PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 2);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload with multiple words on the left", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 8);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 13, WORD * 7);
+ int num = 0;
+ memory_track->ForEachUploadRange(c + PAGE * 16, WORD * 7, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + PAGE * 16);
+ REQUIRE(size == WORD * 7 - PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ memory_track->ForEachUploadRange(c + PAGE, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + PAGE * 13);
+ REQUIRE(size == PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 2);
+}
+
+TEST_CASE("MemoryTracker: Partial page upload with multiple words in the middle", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 8);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 13, PAGE * 140);
+ int num = 0;
+ memory_track->ForEachUploadRange(c + PAGE * 16, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + PAGE * 16);
+ REQUIRE(size == WORD);
+ ++num;
+ });
+ REQUIRE(num == 1);
+ memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + PAGE * 13);
+ REQUIRE(size == PAGE * 3);
+ ++num;
+ });
+ REQUIRE(num == 2);
+ memory_track->ForEachUploadRange(c, WORD * 8, [&](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD + PAGE * 16);
+ REQUIRE(size == PAGE * 73);
+ ++num;
+ });
+ REQUIRE(num == 3);
+}
+
+TEST_CASE("MemoryTracker: Empty right bits", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 2048);
+ memory_track->MarkRegionAsCpuModified(c + WORD - PAGE, PAGE * 2);
+ memory_track->ForEachUploadRange(c, WORD * 2048, [](u64 offset, u64 size) {
+ REQUIRE(offset == c + WORD - PAGE);
+ REQUIRE(size == PAGE * 2);
+ });
+}
+
+TEST_CASE("MemoryTracker: Out of bound ranges 1", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c - WORD, 3 * WORD);
+ memory_track->MarkRegionAsCpuModified(c, PAGE);
+ REQUIRE(rasterizer.Count() == (3 * WORD - PAGE) / PAGE);
+ int num = 0;
+ memory_track->ForEachUploadRange(c - WORD, WORD, [&](u64 offset, u64 size) { ++num; });
+ memory_track->ForEachUploadRange(c + WORD, WORD, [&](u64 offset, u64 size) { ++num; });
+ memory_track->ForEachUploadRange(c - PAGE, PAGE, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 0);
+ memory_track->ForEachUploadRange(c - PAGE, PAGE * 2, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 1);
+ memory_track->MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 2 * WORD / PAGE);
+}
+
+TEST_CASE("MemoryTracker: Out of bound ranges 2", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x22000, PAGE));
+ REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x28000, PAGE));
+ REQUIRE(rasterizer.Count() == 2);
+ REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c + 0x21100, PAGE - 0x100));
+ REQUIRE(rasterizer.Count() == 3);
+ REQUIRE_NOTHROW(memory_track->UnmarkRegionAsCpuModified(c - PAGE, PAGE * 2));
+ memory_track->UnmarkRegionAsCpuModified(c - PAGE * 3, PAGE * 2);
+ memory_track->UnmarkRegionAsCpuModified(c - PAGE * 2, PAGE * 2);
+ REQUIRE(rasterizer.Count() == 7);
+}
+
+TEST_CASE("MemoryTracker: Out of bound ranges 3", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, 0x310720);
+ REQUIRE(rasterizer.Count(c) == 1);
+ REQUIRE(rasterizer.Count(c + PAGE) == 1);
+ REQUIRE(rasterizer.Count(c + WORD) == 1);
+ REQUIRE(rasterizer.Count(c + WORD + PAGE) == 1);
+}
+
+TEST_CASE("MemoryTracker: Sparse regions 1", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 1, PAGE);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 3, PAGE * 4);
+ memory_track->ForEachUploadRange(c, WORD, [i = 0](u64 offset, u64 size) mutable {
+ static constexpr std::array<u64, 2> offsets{c + PAGE, c + PAGE * 3};
+ static constexpr std::array<u64, 2> sizes{PAGE, PAGE * 4};
+ REQUIRE(offset == offsets.at(i));
+ REQUIRE(size == sizes.at(i));
+ ++i;
+ });
+}
+
+TEST_CASE("MemoryTracker: Sparse regions 2", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, PAGE * 0x23);
+ REQUIRE(rasterizer.Count() == 0x23);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 0x1B, PAGE);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 0x21, PAGE);
+ memory_track->ForEachUploadRange(c, PAGE * 0x23, [i = 0](u64 offset, u64 size) mutable {
+ static constexpr std::array<u64, 3> offsets{c + PAGE * 0x1B, c + PAGE * 0x21};
+ static constexpr std::array<u64, 3> sizes{PAGE, PAGE};
+ REQUIRE(offset == offsets.at(i));
+ REQUIRE(size == sizes.at(i));
+ ++i;
+ });
+}
+
+TEST_CASE("MemoryTracker: Single page modified range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
+ memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+ REQUIRE(!memory_track->IsRegionCpuModified(c, PAGE));
+}
+
+TEST_CASE("MemoryTracker: Two page modified range", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c, PAGE * 2));
+ memory_track->UnmarkRegionAsCpuModified(c, PAGE);
+ REQUIRE(!memory_track->IsRegionCpuModified(c, PAGE));
+}
+
+TEST_CASE("MemoryTracker: Multi word modified ranges", "[video_core]") {
+ for (int offset = 0; offset < 4; ++offset) {
+ const VAddr address = c + WORD * offset;
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ REQUIRE(memory_track->IsRegionCpuModified(address, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 48, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 56, PAGE));
+
+ memory_track->UnmarkRegionAsCpuModified(address + PAGE * 32, PAGE);
+ REQUIRE(memory_track->IsRegionCpuModified(address + PAGE, WORD));
+ REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 31, PAGE));
+ REQUIRE(!memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 33, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 31, PAGE * 2));
+ REQUIRE(memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
+
+ memory_track->UnmarkRegionAsCpuModified(address + PAGE * 33, PAGE);
+ REQUIRE(!memory_track->IsRegionCpuModified(address + PAGE * 32, PAGE * 2));
+ }
+}
+
+TEST_CASE("MemoryTracker: Single page in large region", "[video_core]") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 16);
+ REQUIRE(!memory_track->IsRegionCpuModified(c, WORD * 16));
+
+ memory_track->MarkRegionAsCpuModified(c + WORD * 12 + PAGE * 8, PAGE);
+ REQUIRE(memory_track->IsRegionCpuModified(c, WORD * 16));
+ REQUIRE(!memory_track->IsRegionCpuModified(c + WORD * 10, WORD * 2));
+ REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 11, WORD * 2));
+ REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12, WORD * 2));
+ REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 4, PAGE * 8));
+ REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE * 8));
+ REQUIRE(!memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 6, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 7, PAGE * 2));
+ REQUIRE(memory_track->IsRegionCpuModified(c + WORD * 12 + PAGE * 8, PAGE * 2));
+}
+
+TEST_CASE("MemoryTracker: Wrap word regions") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD * 32);
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 63, PAGE * 2);
+ REQUIRE(memory_track->IsRegionCpuModified(c, WORD * 2));
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 62, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 64, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE * 2));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 63, PAGE * 8));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 60, PAGE * 8));
+
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 127, WORD * 16));
+ memory_track->MarkRegionAsCpuModified(c + PAGE * 127, PAGE);
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 127, WORD * 16));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 127, PAGE));
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 126, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 126, PAGE * 2));
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 128, WORD * 16));
+}
+
+TEST_CASE("MemoryTracker: Unaligned page region query") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ memory_track->MarkRegionAsCpuModified(c + 4000, 1000);
+ REQUIRE(memory_track->IsRegionCpuModified(c, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + 4000, 1000));
+ REQUIRE(memory_track->IsRegionCpuModified(c + 4000, 1));
+}
+
+TEST_CASE("MemoryTracker: Cached write") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ memory_track->CachedCpuWrite(c + PAGE, c + PAGE);
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ memory_track->FlushCachedWrites();
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ memory_track->MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Multiple cached write") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ memory_track->CachedCpuWrite(c + PAGE, PAGE);
+ memory_track->CachedCpuWrite(c + PAGE * 3, PAGE);
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE * 3, PAGE));
+ memory_track->FlushCachedWrites();
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE * 3, PAGE));
+ memory_track->MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Cached write unmarked") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ memory_track->CachedCpuWrite(c + PAGE, PAGE);
+ memory_track->UnmarkRegionAsCpuModified(c + PAGE, PAGE);
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ memory_track->FlushCachedWrites();
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ memory_track->MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Cached write iterated") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ memory_track->CachedCpuWrite(c + PAGE, PAGE);
+ int num = 0;
+ memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 0);
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ memory_track->FlushCachedWrites();
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ memory_track->MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+}
+
+TEST_CASE("MemoryTracker: Cached write downloads") {
+ RasterizerInterface rasterizer;
+ std::unique_ptr<MemoryTracker> memory_track(std::make_unique<MemoryTracker>(rasterizer));
+ memory_track->UnmarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 64);
+ memory_track->CachedCpuWrite(c + PAGE, PAGE);
+ REQUIRE(rasterizer.Count() == 63);
+ memory_track->MarkRegionAsGpuModified(c + PAGE, PAGE);
+ int num = 0;
+ memory_track->ForEachDownloadRangeAndClear(c, WORD, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 0);
+ num = 0;
+ memory_track->ForEachUploadRange(c, WORD, [&](u64 offset, u64 size) { ++num; });
+ REQUIRE(num == 0);
+ REQUIRE(!memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(memory_track->IsRegionGpuModified(c + PAGE, PAGE));
+ memory_track->FlushCachedWrites();
+ REQUIRE(memory_track->IsRegionCpuModified(c + PAGE, PAGE));
+ REQUIRE(!memory_track->IsRegionGpuModified(c + PAGE, PAGE));
+ memory_track->MarkRegionAsCpuModified(c, WORD);
+ REQUIRE(rasterizer.Count() == 0);
+} \ No newline at end of file
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index e904573d7..a0009a36f 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -11,8 +11,11 @@ endif()
add_library(video_core STATIC
buffer_cache/buffer_base.h
+ buffer_cache/buffer_cache_base.h
buffer_cache/buffer_cache.cpp
buffer_cache/buffer_cache.h
+ buffer_cache/memory_tracker_base.h
+ buffer_cache/word_manager.h
cache_types.h
cdma_pusher.cpp
cdma_pusher.h
@@ -104,6 +107,7 @@ add_library(video_core STATIC
renderer_null/renderer_null.h
renderer_opengl/blit_image.cpp
renderer_opengl/blit_image.h
+ renderer_opengl/gl_buffer_cache_base.cpp
renderer_opengl/gl_buffer_cache.cpp
renderer_opengl/gl_buffer_cache.h
renderer_opengl/gl_compute_pipeline.cpp
@@ -154,6 +158,7 @@ add_library(video_core STATIC
renderer_vulkan/renderer_vulkan.cpp
renderer_vulkan/vk_blit_screen.cpp
renderer_vulkan/vk_blit_screen.h
+ renderer_vulkan/vk_buffer_cache_base.cpp
renderer_vulkan/vk_buffer_cache.cpp
renderer_vulkan/vk_buffer_cache.h
renderer_vulkan/vk_command_pool.cpp
@@ -174,6 +179,8 @@ add_library(video_core STATIC
renderer_vulkan/vk_master_semaphore.h
renderer_vulkan/vk_pipeline_cache.cpp
renderer_vulkan/vk_pipeline_cache.h
+ renderer_vulkan/vk_present_manager.cpp
+ renderer_vulkan/vk_present_manager.h
renderer_vulkan/vk_query_cache.cpp
renderer_vulkan/vk_query_cache.h
renderer_vulkan/vk_rasterizer.cpp
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h
index 1b4d63616..0bb3bf8ae 100644
--- a/src/video_core/buffer_cache/buffer_base.h
+++ b/src/video_core/buffer_cache/buffer_base.h
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -11,15 +11,14 @@
#include "common/alignment.h"
#include "common/common_funcs.h"
#include "common/common_types.h"
-#include "common/div_ceil.h"
-#include "common/settings.h"
-#include "core/memory.h"
+#include "video_core/buffer_cache/word_manager.h"
namespace VideoCommon {
enum class BufferFlagBits {
Picked = 1 << 0,
CachedWrites = 1 << 1,
+ PreemtiveDownload = 1 << 2,
};
DECLARE_ENUM_FLAG_OPERATORS(BufferFlagBits)
@@ -36,116 +35,12 @@ struct NullBufferParams {};
*/
template <class RasterizerInterface>
class BufferBase {
- static constexpr u64 PAGES_PER_WORD = 64;
- static constexpr u64 BYTES_PER_PAGE = Core::Memory::YUZU_PAGESIZE;
- static constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
-
- /// Vector tracking modified pages tightly packed with small vector optimization
- union WordsArray {
- /// Returns the pointer to the words state
- [[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
- return is_short ? &stack : heap;
- }
-
- /// Returns the pointer to the words state
- [[nodiscard]] u64* Pointer(bool is_short) noexcept {
- return is_short ? &stack : heap;
- }
-
- u64 stack = 0; ///< Small buffers storage
- u64* heap; ///< Not-small buffers pointer to the storage
- };
-
- struct Words {
- explicit Words() = default;
- explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
- if (IsShort()) {
- cpu.stack = ~u64{0};
- gpu.stack = 0;
- cached_cpu.stack = 0;
- untracked.stack = ~u64{0};
- } else {
- // Share allocation between CPU and GPU pages and set their default values
- const size_t num_words = NumWords();
- u64* const alloc = new u64[num_words * 4];
- cpu.heap = alloc;
- gpu.heap = alloc + num_words;
- cached_cpu.heap = alloc + num_words * 2;
- untracked.heap = alloc + num_words * 3;
- std::fill_n(cpu.heap, num_words, ~u64{0});
- std::fill_n(gpu.heap, num_words, 0);
- std::fill_n(cached_cpu.heap, num_words, 0);
- std::fill_n(untracked.heap, num_words, ~u64{0});
- }
- // Clean up tailing bits
- const u64 last_word_size = size_bytes % BYTES_PER_WORD;
- const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
- const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
- const u64 last_word = (~u64{0} << shift) >> shift;
- cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
- untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
- }
-
- ~Words() {
- Release();
- }
-
- Words& operator=(Words&& rhs) noexcept {
- Release();
- size_bytes = rhs.size_bytes;
- cpu = rhs.cpu;
- gpu = rhs.gpu;
- cached_cpu = rhs.cached_cpu;
- untracked = rhs.untracked;
- rhs.cpu.heap = nullptr;
- return *this;
- }
-
- Words(Words&& rhs) noexcept
- : size_bytes{rhs.size_bytes}, cpu{rhs.cpu}, gpu{rhs.gpu},
- cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked} {
- rhs.cpu.heap = nullptr;
- }
-
- Words& operator=(const Words&) = delete;
- Words(const Words&) = delete;
-
- /// Returns true when the buffer fits in the small vector optimization
- [[nodiscard]] bool IsShort() const noexcept {
- return size_bytes <= BYTES_PER_WORD;
- }
-
- /// Returns the number of words of the buffer
- [[nodiscard]] size_t NumWords() const noexcept {
- return Common::DivCeil(size_bytes, BYTES_PER_WORD);
- }
-
- /// Release buffer resources
- void Release() {
- if (!IsShort()) {
- // CPU written words is the base for the heap allocation
- delete[] cpu.heap;
- }
- }
-
- u64 size_bytes = 0;
- WordsArray cpu;
- WordsArray gpu;
- WordsArray cached_cpu;
- WordsArray untracked;
- };
-
- enum class Type {
- CPU,
- GPU,
- CachedCPU,
- Untracked,
- };
-
public:
- explicit BufferBase(RasterizerInterface& rasterizer_, VAddr cpu_addr_, u64 size_bytes)
- : rasterizer{&rasterizer_}, cpu_addr{Common::AlignDown(cpu_addr_, BYTES_PER_PAGE)},
- words(Common::AlignUp(size_bytes + (cpu_addr_ - cpu_addr), BYTES_PER_PAGE)) {}
+ static constexpr u64 BASE_PAGE_BITS = 16;
+ static constexpr u64 BASE_PAGE_SIZE = 1ULL << BASE_PAGE_BITS;
+
+ explicit BufferBase(RasterizerInterface& rasterizer_, VAddr cpu_addr_, u64 size_bytes_)
+ : cpu_addr{cpu_addr_}, size_bytes{size_bytes_} {}
explicit BufferBase(NullBufferParams) {}
@@ -155,105 +50,15 @@ public:
BufferBase& operator=(BufferBase&&) = default;
BufferBase(BufferBase&&) = default;
- /// Returns the inclusive CPU modified range in a begin end pair
- [[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
- u64 query_size) const noexcept {
- const u64 offset = query_cpu_addr - cpu_addr;
- return ModifiedRegion<Type::CPU>(offset, query_size);
- }
-
- /// Returns the inclusive GPU modified range in a begin end pair
- [[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
- u64 query_size) const noexcept {
- const u64 offset = query_cpu_addr - cpu_addr;
- return ModifiedRegion<Type::GPU>(offset, query_size);
- }
-
- /// Returns true if a region has been modified from the CPU
- [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
- const u64 offset = query_cpu_addr - cpu_addr;
- return IsRegionModified<Type::CPU>(offset, query_size);
- }
-
- /// Returns true if a region has been modified from the GPU
- [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) const noexcept {
- const u64 offset = query_cpu_addr - cpu_addr;
- return IsRegionModified<Type::GPU>(offset, query_size);
- }
-
- /// Mark region as CPU modified, notifying the rasterizer about this change
- void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
- ChangeRegionState<Type::CPU, true>(dirty_cpu_addr, size);
- }
-
- /// Unmark region as CPU modified, notifying the rasterizer about this change
- void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 size) {
- ChangeRegionState<Type::CPU, false>(dirty_cpu_addr, size);
- }
-
- /// Mark region as modified from the host GPU
- void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
- ChangeRegionState<Type::GPU, true>(dirty_cpu_addr, size);
- }
-
- /// Unmark region as modified from the host GPU
- void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 size) noexcept {
- ChangeRegionState<Type::GPU, false>(dirty_cpu_addr, size);
- }
-
- /// Mark region as modified from the CPU
- /// but don't mark it as modified until FlusHCachedWrites is called.
- void CachedCpuWrite(VAddr dirty_cpu_addr, u64 size) {
- flags |= BufferFlagBits::CachedWrites;
- ChangeRegionState<Type::CachedCPU, true>(dirty_cpu_addr, size);
- }
-
- /// Flushes cached CPU writes, and notify the rasterizer about the deltas
- void FlushCachedWrites() noexcept {
- flags &= ~BufferFlagBits::CachedWrites;
- const u64 num_words = NumWords();
- u64* const cached_words = Array<Type::CachedCPU>();
- u64* const untracked_words = Array<Type::Untracked>();
- u64* const cpu_words = Array<Type::CPU>();
- for (u64 word_index = 0; word_index < num_words; ++word_index) {
- const u64 cached_bits = cached_words[word_index];
- NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
- untracked_words[word_index] |= cached_bits;
- cpu_words[word_index] |= cached_bits;
- if (!Settings::values.use_pessimistic_flushes) {
- cached_words[word_index] = 0;
- }
- }
- }
-
- /// Call 'func' for each CPU modified range and unmark those pages as CPU modified
- template <typename Func>
- void ForEachUploadRange(VAddr query_cpu_range, u64 size, Func&& func) {
- ForEachModifiedRange<Type::CPU>(query_cpu_range, size, true, func);
- }
-
- /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
- template <typename Func>
- void ForEachDownloadRange(VAddr query_cpu_range, u64 size, bool clear, Func&& func) {
- ForEachModifiedRange<Type::GPU>(query_cpu_range, size, clear, func);
- }
-
- template <typename Func>
- void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 size, Func&& func) {
- ForEachModifiedRange<Type::GPU>(query_cpu_range, size, true, func);
- }
-
- /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
- template <typename Func>
- void ForEachDownloadRange(Func&& func) {
- ForEachModifiedRange<Type::GPU>(cpu_addr, SizeBytes(), true, func);
- }
-
/// Mark buffer as picked
void Pick() noexcept {
flags |= BufferFlagBits::Picked;
}
+ void MarkPreemtiveDownload() noexcept {
+ flags |= BufferFlagBits::PreemtiveDownload;
+ }
+
/// Unmark buffer as picked
void Unpick() noexcept {
flags &= ~BufferFlagBits::Picked;
@@ -284,6 +89,10 @@ public:
return True(flags & BufferFlagBits::CachedWrites);
}
+ bool IsPreemtiveDownload() const noexcept {
+ return True(flags & BufferFlagBits::PreemtiveDownload);
+ }
+
/// Returns the base CPU address of the buffer
[[nodiscard]] VAddr CpuAddr() const noexcept {
return cpu_addr;
@@ -295,11 +104,6 @@ public:
return static_cast<u32>(other_cpu_addr - cpu_addr);
}
- /// Returns the size in bytes of the buffer
- [[nodiscard]] u64 SizeBytes() const noexcept {
- return words.size_bytes;
- }
-
size_t getLRUID() const noexcept {
return lru_id;
}
@@ -308,305 +112,16 @@ public:
lru_id = lru_id_;
}
-private:
- template <Type type>
- u64* Array() noexcept {
- if constexpr (type == Type::CPU) {
- return words.cpu.Pointer(IsShort());
- } else if constexpr (type == Type::GPU) {
- return words.gpu.Pointer(IsShort());
- } else if constexpr (type == Type::CachedCPU) {
- return words.cached_cpu.Pointer(IsShort());
- } else if constexpr (type == Type::Untracked) {
- return words.untracked.Pointer(IsShort());
- }
- }
-
- template <Type type>
- const u64* Array() const noexcept {
- if constexpr (type == Type::CPU) {
- return words.cpu.Pointer(IsShort());
- } else if constexpr (type == Type::GPU) {
- return words.gpu.Pointer(IsShort());
- } else if constexpr (type == Type::CachedCPU) {
- return words.cached_cpu.Pointer(IsShort());
- } else if constexpr (type == Type::Untracked) {
- return words.untracked.Pointer(IsShort());
- }
- }
-
- /**
- * Change the state of a range of pages
- *
- * @param dirty_addr Base address to mark or unmark as modified
- * @param size Size in bytes to mark or unmark as modified
- */
- template <Type type, bool enable>
- void ChangeRegionState(u64 dirty_addr, s64 size) noexcept(type == Type::GPU) {
- const s64 difference = dirty_addr - cpu_addr;
- const u64 offset = std::max<s64>(difference, 0);
- size += std::min<s64>(difference, 0);
- if (offset >= SizeBytes() || size < 0) {
- return;
- }
- u64* const untracked_words = Array<Type::Untracked>();
- u64* const state_words = Array<type>();
- const u64 offset_end = std::min(offset + size, SizeBytes());
- const u64 begin_page_index = offset / BYTES_PER_PAGE;
- const u64 begin_word_index = begin_page_index / PAGES_PER_WORD;
- const u64 end_page_index = Common::DivCeil(offset_end, BYTES_PER_PAGE);
- const u64 end_word_index = Common::DivCeil(end_page_index, PAGES_PER_WORD);
- u64 page_index = begin_page_index % PAGES_PER_WORD;
- u64 word_index = begin_word_index;
- while (word_index < end_word_index) {
- const u64 next_word_first_page = (word_index + 1) * PAGES_PER_WORD;
- const u64 left_offset =
- std::min(next_word_first_page - end_page_index, PAGES_PER_WORD) % PAGES_PER_WORD;
- const u64 right_offset = page_index;
- u64 bits = ~u64{0};
- bits = (bits >> right_offset) << right_offset;
- bits = (bits << left_offset) >> left_offset;
- if constexpr (type == Type::CPU || type == Type::CachedCPU) {
- NotifyRasterizer<!enable>(word_index, untracked_words[word_index], bits);
- }
- if constexpr (enable) {
- state_words[word_index] |= bits;
- if constexpr (type == Type::CPU || type == Type::CachedCPU) {
- untracked_words[word_index] |= bits;
- }
- } else {
- state_words[word_index] &= ~bits;
- if constexpr (type == Type::CPU || type == Type::CachedCPU) {
- untracked_words[word_index] &= ~bits;
- }
- }
- page_index = 0;
- ++word_index;
- }
- }
-
- /**
- * Notify rasterizer about changes in the CPU tracking state of a word in the buffer
- *
- * @param word_index Index to the word to notify to the rasterizer
- * @param current_bits Current state of the word
- * @param new_bits New state of the word
- *
- * @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages
- */
- template <bool add_to_rasterizer>
- void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
- u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits;
- VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
- while (changed_bits != 0) {
- const int empty_bits = std::countr_zero(changed_bits);
- addr += empty_bits * BYTES_PER_PAGE;
- changed_bits >>= empty_bits;
-
- const u32 continuous_bits = std::countr_one(changed_bits);
- const u64 size = continuous_bits * BYTES_PER_PAGE;
- const VAddr begin_addr = addr;
- addr += size;
- changed_bits = continuous_bits < PAGES_PER_WORD ? (changed_bits >> continuous_bits) : 0;
- rasterizer->UpdatePagesCachedCount(begin_addr, size, add_to_rasterizer ? 1 : -1);
- }
- }
-
- /**
- * Loop over each page in the given range, turn off those bits and notify the rasterizer if
- * needed. Call the given function on each turned off range.
- *
- * @param query_cpu_range Base CPU address to loop over
- * @param size Size in bytes of the CPU range to loop over
- * @param func Function to call for each turned off region
- */
- template <Type type, typename Func>
- void ForEachModifiedRange(VAddr query_cpu_range, s64 size, bool clear, Func&& func) {
- static_assert(type != Type::Untracked);
-
- const s64 difference = query_cpu_range - cpu_addr;
- const u64 query_begin = std::max<s64>(difference, 0);
- size += std::min<s64>(difference, 0);
- if (query_begin >= SizeBytes() || size < 0) {
- return;
- }
- u64* const untracked_words = Array<Type::Untracked>();
- u64* const state_words = Array<type>();
- const u64 query_end = query_begin + std::min(static_cast<u64>(size), SizeBytes());
- u64* const words_begin = state_words + query_begin / BYTES_PER_WORD;
- u64* const words_end = state_words + Common::DivCeil(query_end, BYTES_PER_WORD);
-
- const auto modified = [](u64 word) { return word != 0; };
- const auto first_modified_word = std::find_if(words_begin, words_end, modified);
- if (first_modified_word == words_end) {
- // Exit early when the buffer is not modified
- return;
- }
- const auto last_modified_word = std::find_if_not(first_modified_word, words_end, modified);
-
- const u64 word_index_begin = std::distance(state_words, first_modified_word);
- const u64 word_index_end = std::distance(state_words, last_modified_word);
-
- const unsigned local_page_begin = std::countr_zero(*first_modified_word);
- const unsigned local_page_end =
- static_cast<unsigned>(PAGES_PER_WORD) - std::countl_zero(last_modified_word[-1]);
- const u64 word_page_begin = word_index_begin * PAGES_PER_WORD;
- const u64 word_page_end = (word_index_end - 1) * PAGES_PER_WORD;
- const u64 query_page_begin = query_begin / BYTES_PER_PAGE;
- const u64 query_page_end = Common::DivCeil(query_end, BYTES_PER_PAGE);
- const u64 page_index_begin = std::max(word_page_begin + local_page_begin, query_page_begin);
- const u64 page_index_end = std::min(word_page_end + local_page_end, query_page_end);
- const u64 first_word_page_begin = page_index_begin % PAGES_PER_WORD;
- const u64 last_word_page_end = (page_index_end - 1) % PAGES_PER_WORD + 1;
-
- u64 page_begin = first_word_page_begin;
- u64 current_base = 0;
- u64 current_size = 0;
- bool on_going = false;
- for (u64 word_index = word_index_begin; word_index < word_index_end; ++word_index) {
- const bool is_last_word = word_index + 1 == word_index_end;
- const u64 page_end = is_last_word ? last_word_page_end : PAGES_PER_WORD;
- const u64 right_offset = page_begin;
- const u64 left_offset = PAGES_PER_WORD - page_end;
- u64 bits = ~u64{0};
- bits = (bits >> right_offset) << right_offset;
- bits = (bits << left_offset) >> left_offset;
-
- const u64 current_word = state_words[word_index] & bits;
- if (clear) {
- state_words[word_index] &= ~bits;
- }
-
- if constexpr (type == Type::CPU) {
- const u64 current_bits = untracked_words[word_index] & bits;
- untracked_words[word_index] &= ~bits;
- NotifyRasterizer<true>(word_index, current_bits, ~u64{0});
- }
- // Exclude CPU modified pages when visiting GPU pages
- const u64 word = current_word & ~(type == Type::GPU ? untracked_words[word_index] : 0);
- u64 page = page_begin;
- page_begin = 0;
-
- while (page < page_end) {
- const int empty_bits = std::countr_zero(word >> page);
- if (on_going && empty_bits != 0) {
- InvokeModifiedRange(func, current_size, current_base);
- current_size = 0;
- on_going = false;
- }
- if (empty_bits == PAGES_PER_WORD) {
- break;
- }
- page += empty_bits;
-
- const int continuous_bits = std::countr_one(word >> page);
- if (!on_going && continuous_bits != 0) {
- current_base = word_index * PAGES_PER_WORD + page;
- on_going = true;
- }
- current_size += continuous_bits;
- page += continuous_bits;
- }
- }
- if (on_going && current_size > 0) {
- InvokeModifiedRange(func, current_size, current_base);
- }
- }
-
- template <typename Func>
- void InvokeModifiedRange(Func&& func, u64 current_size, u64 current_base) {
- const u64 current_size_bytes = current_size * BYTES_PER_PAGE;
- const u64 offset_begin = current_base * BYTES_PER_PAGE;
- const u64 offset_end = std::min(offset_begin + current_size_bytes, SizeBytes());
- func(offset_begin, offset_end - offset_begin);
- }
-
- /**
- * Returns true when a region has been modified
- *
- * @param offset Offset in bytes from the start of the buffer
- * @param size Size in bytes of the region to query for modifications
- */
- template <Type type>
- [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
- static_assert(type != Type::Untracked);
-
- const u64* const untracked_words = Array<Type::Untracked>();
- const u64* const state_words = Array<type>();
- const u64 num_query_words = size / BYTES_PER_WORD + 1;
- const u64 word_begin = offset / BYTES_PER_WORD;
- const u64 word_end = std::min<u64>(word_begin + num_query_words, NumWords());
- const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
- u64 page_index = (offset / BYTES_PER_PAGE) % PAGES_PER_WORD;
- for (u64 word_index = word_begin; word_index < word_end; ++word_index, page_index = 0) {
- const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
- const u64 word = state_words[word_index] & ~off_word;
- if (word == 0) {
- continue;
- }
- const u64 page_end = std::min((word_index + 1) * PAGES_PER_WORD, page_limit);
- const u64 local_page_end = page_end % PAGES_PER_WORD;
- const u64 page_end_shift = (PAGES_PER_WORD - local_page_end) % PAGES_PER_WORD;
- if (((word >> page_index) << page_index) << page_end_shift != 0) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Returns a begin end pair with the inclusive modified region
- *
- * @param offset Offset in bytes from the start of the buffer
- * @param size Size in bytes of the region to query for modifications
- */
- template <Type type>
- [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
- static_assert(type != Type::Untracked);
-
- const u64* const untracked_words = Array<Type::Untracked>();
- const u64* const state_words = Array<type>();
- const u64 num_query_words = size / BYTES_PER_WORD + 1;
- const u64 word_begin = offset / BYTES_PER_WORD;
- const u64 word_end = std::min<u64>(word_begin + num_query_words, NumWords());
- const u64 page_base = offset / BYTES_PER_PAGE;
- const u64 page_limit = Common::DivCeil(offset + size, BYTES_PER_PAGE);
- u64 begin = std::numeric_limits<u64>::max();
- u64 end = 0;
- for (u64 word_index = word_begin; word_index < word_end; ++word_index) {
- const u64 off_word = type == Type::GPU ? untracked_words[word_index] : 0;
- const u64 word = state_words[word_index] & ~off_word;
- if (word == 0) {
- continue;
- }
- const u64 local_page_begin = std::countr_zero(word);
- const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
- const u64 page_index = word_index * PAGES_PER_WORD;
- const u64 page_begin = std::max(page_index + local_page_begin, page_base);
- const u64 page_end = std::min(page_index + local_page_end, page_limit);
- begin = std::min(begin, page_begin);
- end = std::max(end, page_end);
- }
- static constexpr std::pair<u64, u64> EMPTY{0, 0};
- return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
- }
-
- /// Returns the number of words of the buffer
- [[nodiscard]] size_t NumWords() const noexcept {
- return words.NumWords();
+ size_t SizeBytes() const {
+ return size_bytes;
}
- /// Returns true when the buffer fits in the small vector optimization
- [[nodiscard]] bool IsShort() const noexcept {
- return words.IsShort();
- }
-
- RasterizerInterface* rasterizer = nullptr;
+private:
VAddr cpu_addr = 0;
- Words words;
BufferFlagBits flags{};
int stream_score = 0;
size_t lru_id = SIZE_MAX;
+ size_t size_bytes = 0;
};
} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/buffer_cache.cpp b/src/video_core/buffer_cache/buffer_cache.cpp
index a16308b60..40db243d2 100644
--- a/src/video_core/buffer_cache/buffer_cache.cpp
+++ b/src/video_core/buffer_cache/buffer_cache.cpp
@@ -1,5 +1,5 @@
-// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#include "common/microprofile.h"
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index abdc593df..98756e4da 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -1,485 +1,27 @@
-// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <algorithm>
-#include <array>
#include <memory>
-#include <mutex>
#include <numeric>
-#include <span>
-#include <vector>
-
-#include <boost/container/small_vector.hpp>
-#include <boost/icl/interval_set.hpp>
-
-#include "common/common_types.h"
-#include "common/div_ceil.h"
-#include "common/literals.h"
-#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"
-#include "video_core/control/channel_state_cache.h"
-#include "video_core/delayed_destruction_ring.h"
-#include "video_core/dirty_flags.h"
-#include "video_core/engines/draw_manager.h"
-#include "video_core/engines/kepler_compute.h"
-#include "video_core/engines/maxwell_3d.h"
-#include "video_core/memory_manager.h"
-#include "video_core/rasterizer_interface.h"
-#include "video_core/surface.h"
-#include "video_core/texture_cache/slot_vector.h"
-#include "video_core/texture_cache/types.h"
-namespace VideoCommon {
-
-MICROPROFILE_DECLARE(GPU_PrepareBuffers);
-MICROPROFILE_DECLARE(GPU_BindUploadBuffers);
-MICROPROFILE_DECLARE(GPU_DownloadMemory);
-
-using BufferId = SlotId;
-
-using VideoCore::Surface::PixelFormat;
-using namespace Common::Literals;
-
-constexpr u32 NUM_VERTEX_BUFFERS = 32;
-constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
-constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
-constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
-constexpr u32 NUM_STORAGE_BUFFERS = 16;
-constexpr u32 NUM_TEXTURE_BUFFERS = 16;
-constexpr u32 NUM_STAGES = 5;
-
-enum class ObtainBufferSynchronize : u32 {
- NoSynchronize = 0,
- FullSynchronize = 1,
- SynchronizeNoDirty = 2,
-};
-
-enum class ObtainBufferOperation : u32 {
- DoNothing = 0,
- MarkAsWritten = 1,
- DiscardWrite = 2,
- MarkQuery = 3,
-};
-
-using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>;
-using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
-
-template <typename P>
-class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
-
- // Page size for caching purposes.
- // This is unrelated to the CPU page size and it can be changed as it seems optimal.
- static constexpr u32 YUZU_PAGEBITS = 16;
- static constexpr u64 YUZU_PAGESIZE = u64{1} << YUZU_PAGEBITS;
-
- static constexpr bool IS_OPENGL = P::IS_OPENGL;
- static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS =
- P::HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS;
- static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT =
- P::HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT;
- static constexpr bool NEEDS_BIND_UNIFORM_INDEX = P::NEEDS_BIND_UNIFORM_INDEX;
- static constexpr bool NEEDS_BIND_STORAGE_INDEX = P::NEEDS_BIND_STORAGE_INDEX;
- static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
- static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
-
- static constexpr BufferId NULL_BUFFER_ID{0};
-
- static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
- static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
- static constexpr s64 TARGET_THRESHOLD = 4_GiB;
-
- using Maxwell = Tegra::Engines::Maxwell3D::Regs;
-
- using Runtime = typename P::Runtime;
- using Buffer = typename P::Buffer;
-
- using IntervalSet = boost::icl::interval_set<VAddr>;
- using IntervalType = typename IntervalSet::interval_type;
-
- struct Empty {};
-
- struct OverlapResult {
- std::vector<BufferId> ids;
- VAddr begin;
- VAddr end;
- bool has_stream_leap = false;
- };
-
- struct Binding {
- VAddr cpu_addr{};
- u32 size{};
- BufferId buffer_id;
- };
-
- struct TextureBufferBinding : Binding {
- PixelFormat format;
- };
-
- static constexpr Binding NULL_BINDING{
- .cpu_addr = 0,
- .size = 0,
- .buffer_id = NULL_BUFFER_ID,
- };
-
-public:
- static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
-
- explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
- Core::Memory::Memory& cpu_memory_, Runtime& runtime_);
-
- void TickFrame();
-
- void WriteMemory(VAddr cpu_addr, u64 size);
-
- void CachedWriteMemory(VAddr cpu_addr, u64 size);
-
- void DownloadMemory(VAddr cpu_addr, u64 size);
-
- bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<const u8> inlined_buffer);
-
- void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
-
- void DisableGraphicsUniformBuffer(size_t stage, u32 index);
-
- void UpdateGraphicsBuffers(bool is_indexed);
-
- void UpdateComputeBuffers();
-
- void BindHostGeometryBuffers(bool is_indexed);
-
- void BindHostStageBuffers(size_t stage);
-
- void BindHostComputeBuffers();
-
- void SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
- const UniformBufferSizes* sizes);
-
- void SetComputeUniformBufferState(u32 mask, const ComputeUniformBufferSizes* sizes);
-
- void UnbindGraphicsStorageBuffers(size_t stage);
-
- void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
- bool is_written);
-
- void UnbindGraphicsTextureBuffers(size_t stage);
-
- void BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, u32 size,
- PixelFormat format, bool is_written, bool is_image);
-
- void UnbindComputeStorageBuffers();
-
- void BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
- bool is_written);
-
- void UnbindComputeTextureBuffers();
-
- void BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size, PixelFormat format,
- bool is_written, bool is_image);
-
- void FlushCachedWrites();
-
- /// Return true when there are uncommitted buffers to be downloaded
- [[nodiscard]] bool HasUncommittedFlushes() const noexcept;
-
- void AccumulateFlushes();
-
- /// Return true when the caller should wait for async downloads
- [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
-
- /// Commit asynchronous downloads
- void CommitAsyncFlushes();
- void CommitAsyncFlushesHigh();
-
- /// Pop asynchronous downloads
- void PopAsyncFlushes();
-
- bool DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount);
-
- bool DMAClear(GPUVAddr src_address, u64 amount, u32 value);
-
- [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
- ObtainBufferSynchronize sync_info,
- ObtainBufferOperation post_op);
-
- /// Return true when a CPU region is modified from the GPU
- [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
-
- /// Return true when a region is registered on the cache
- [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size);
-
- /// Return true when a CPU region is modified from the CPU
- [[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size);
-
- void SetDrawIndirect(
- const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect_) {
- current_draw_indirect = current_draw_indirect_;
- }
-
- [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectCount();
-
- [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
-
- std::recursive_mutex mutex;
- Runtime& runtime;
-
-private:
- template <typename Func>
- static void ForEachEnabledBit(u32 enabled_mask, Func&& func) {
- for (u32 index = 0; enabled_mask != 0; ++index, enabled_mask >>= 1) {
- const int disabled_bits = std::countr_zero(enabled_mask);
- index += disabled_bits;
- enabled_mask >>= disabled_bits;
- func(index);
- }
- }
-
- template <typename Func>
- void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) {
- const u64 page_end = Common::DivCeil(cpu_addr + size, YUZU_PAGESIZE);
- for (u64 page = cpu_addr >> YUZU_PAGEBITS; page < page_end;) {
- const BufferId buffer_id = page_table[page];
- if (!buffer_id) {
- ++page;
- continue;
- }
- Buffer& buffer = slot_buffers[buffer_id];
- func(buffer_id, buffer);
-
- const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
- page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
- }
- }
-
- template <typename Func>
- void ForEachWrittenRange(VAddr cpu_addr, u64 size, Func&& func) {
- const VAddr start_address = cpu_addr;
- const VAddr end_address = start_address + size;
- const VAddr search_base =
- static_cast<VAddr>(std::min<s64>(0LL, static_cast<s64>(start_address - size)));
- const IntervalType search_interval{search_base, search_base + 1};
- auto it = common_ranges.lower_bound(search_interval);
- if (it == common_ranges.end()) {
- it = common_ranges.begin();
- }
- for (; it != common_ranges.end(); it++) {
- VAddr inter_addr_end = it->upper();
- VAddr inter_addr = it->lower();
- if (inter_addr >= end_address) {
- break;
- }
- if (inter_addr_end <= start_address) {
- continue;
- }
- if (inter_addr_end > end_address) {
- inter_addr_end = end_address;
- }
- if (inter_addr < start_address) {
- inter_addr = start_address;
- }
- func(inter_addr, inter_addr_end);
- }
- }
-
- static bool IsRangeGranular(VAddr cpu_addr, size_t size) {
- return (cpu_addr & ~Core::Memory::YUZU_PAGEMASK) ==
- ((cpu_addr + size) & ~Core::Memory::YUZU_PAGEMASK);
- }
-
- void RunGarbageCollector();
-
- void BindHostIndexBuffer();
-
- void BindHostVertexBuffers();
-
- void BindHostDrawIndirectBuffers();
-
- void BindHostGraphicsUniformBuffers(size_t stage);
-
- void BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind);
-
- void BindHostGraphicsStorageBuffers(size_t stage);
-
- void BindHostGraphicsTextureBuffers(size_t stage);
-
- void BindHostTransformFeedbackBuffers();
-
- void BindHostComputeUniformBuffers();
-
- void BindHostComputeStorageBuffers();
-
- void BindHostComputeTextureBuffers();
-
- void DoUpdateGraphicsBuffers(bool is_indexed);
-
- void DoUpdateComputeBuffers();
-
- void UpdateIndexBuffer();
-
- void UpdateVertexBuffers();
-
- void UpdateVertexBuffer(u32 index);
-
- void UpdateDrawIndirect();
-
- void UpdateUniformBuffers(size_t stage);
-
- void UpdateStorageBuffers(size_t stage);
-
- void UpdateTextureBuffers(size_t stage);
-
- void UpdateTransformFeedbackBuffers();
-
- void UpdateTransformFeedbackBuffer(u32 index);
-
- void UpdateComputeUniformBuffers();
-
- void UpdateComputeStorageBuffers();
-
- void UpdateComputeTextureBuffers();
-
- void MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size);
-
- [[nodiscard]] BufferId FindBuffer(VAddr cpu_addr, u32 size);
-
- [[nodiscard]] OverlapResult ResolveOverlaps(VAddr cpu_addr, u32 wanted_size);
-
- void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score);
-
- [[nodiscard]] BufferId CreateBuffer(VAddr cpu_addr, u32 wanted_size);
-
- void Register(BufferId buffer_id);
-
- void Unregister(BufferId buffer_id);
-
- template <bool insert>
- void ChangeRegister(BufferId buffer_id);
-
- void TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept;
-
- bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
-
- bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
-
- void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
- std::span<BufferCopy> copies);
-
- void ImmediateUploadMemory(Buffer& buffer, u64 largest_copy,
- std::span<const BufferCopy> copies);
-
- void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
-
- void DownloadBufferMemory(Buffer& buffer_id);
-
- void DownloadBufferMemory(Buffer& buffer_id, VAddr cpu_addr, u64 size);
-
- void DeleteBuffer(BufferId buffer_id);
-
- void NotifyBufferDeletion();
-
- [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
- bool is_written = false) const;
-
- [[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
- PixelFormat format);
-
- [[nodiscard]] std::span<const u8> ImmediateBufferWithData(VAddr cpu_addr, size_t size);
-
- [[nodiscard]] std::span<u8> ImmediateBuffer(size_t wanted_capacity);
-
- [[nodiscard]] bool HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept;
-
- void ClearDownload(IntervalType subtract_interval);
-
- VideoCore::RasterizerInterface& rasterizer;
- Core::Memory::Memory& cpu_memory;
-
- SlotVector<Buffer> slot_buffers;
- DelayedDestructionRing<Buffer, 8> delayed_destruction_ring;
-
- const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect{};
-
- u32 last_index_count = 0;
-
- Binding index_buffer;
- std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers;
- std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers;
- std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers;
- std::array<std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS>, NUM_STAGES> texture_buffers;
- std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers;
- Binding count_buffer_binding;
- Binding indirect_buffer_binding;
-
- std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers;
- std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers;
- std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS> compute_texture_buffers;
-
- std::array<u32, NUM_STAGES> enabled_uniform_buffer_masks{};
- u32 enabled_compute_uniform_buffer_mask = 0;
-
- const UniformBufferSizes* uniform_buffer_sizes{};
- const ComputeUniformBufferSizes* compute_uniform_buffer_sizes{};
-
- std::array<u32, NUM_STAGES> enabled_storage_buffers{};
- std::array<u32, NUM_STAGES> written_storage_buffers{};
- u32 enabled_compute_storage_buffers = 0;
- u32 written_compute_storage_buffers = 0;
-
- std::array<u32, NUM_STAGES> enabled_texture_buffers{};
- std::array<u32, NUM_STAGES> written_texture_buffers{};
- std::array<u32, NUM_STAGES> image_texture_buffers{};
- u32 enabled_compute_texture_buffers = 0;
- u32 written_compute_texture_buffers = 0;
- u32 image_compute_texture_buffers = 0;
-
- std::array<u32, 16> uniform_cache_hits{};
- std::array<u32, 16> uniform_cache_shots{};
+#include "video_core/buffer_cache/buffer_cache_base.h"
- u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE;
-
- bool has_deleted_buffers = false;
-
- std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS, std::array<u32, NUM_STAGES>, Empty>
- dirty_uniform_buffers{};
- std::conditional_t<IS_OPENGL, std::array<u32, NUM_STAGES>, Empty> fast_bound_uniform_buffers{};
- std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS,
- std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>, Empty>
- uniform_buffer_binding_sizes{};
-
- std::vector<BufferId> cached_write_buffer_ids;
-
- IntervalSet uncommitted_ranges;
- IntervalSet common_ranges;
- std::deque<IntervalSet> committed_ranges;
-
- Common::ScratchBuffer<u8> immediate_buffer_alloc;
-
- struct LRUItemParams {
- using ObjectType = BufferId;
- using TickType = u64;
- };
- Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
- u64 frame_tick = 0;
- u64 total_used_memory = 0;
- u64 minimum_memory = 0;
- u64 critical_memory = 0;
+namespace VideoCommon {
- std::array<BufferId, ((1ULL << 39) >> YUZU_PAGEBITS)> page_table;
-};
+using Core::Memory::YUZU_PAGESIZE;
template <class P>
BufferCache<P>::BufferCache(VideoCore::RasterizerInterface& rasterizer_,
Core::Memory::Memory& cpu_memory_, Runtime& runtime_)
- : runtime{runtime_}, rasterizer{rasterizer_}, cpu_memory{cpu_memory_} {
+ : runtime{runtime_}, rasterizer{rasterizer_}, cpu_memory{cpu_memory_}, memory_tracker{
+ rasterizer} {
// Ensure the first slot is used for the null buffer
void(slot_buffers.insert(runtime, NullBufferParams{}));
common_ranges.clear();
+ inline_buffer_id = NULL_BUFFER_ID;
if (!runtime.CanReportMemoryUsage()) {
minimum_memory = DEFAULT_EXPECTED_MEMORY;
@@ -543,23 +85,48 @@ void BufferCache<P>::TickFrame() {
}
++frame_tick;
delayed_destruction_ring.Tick();
+
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+ for (auto& buffer : async_buffers_death_ring) {
+ runtime.FreeDeferredStagingBuffer(buffer);
+ }
+ async_buffers_death_ring.clear();
+ }
}
template <class P>
void BufferCache<P>::WriteMemory(VAddr cpu_addr, u64 size) {
- ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) {
- buffer.MarkRegionAsCpuModified(cpu_addr, size);
- });
+ if (memory_tracker.IsRegionGpuModified(cpu_addr, size)) {
+ const IntervalType subtract_interval{cpu_addr, cpu_addr + size};
+ ClearDownload(subtract_interval);
+ common_ranges.subtract(subtract_interval);
+ }
+ memory_tracker.MarkRegionAsCpuModified(cpu_addr, size);
}
template <class P>
void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) {
- ForEachBufferInRange(cpu_addr, size, [&](BufferId buffer_id, Buffer& buffer) {
- if (!buffer.HasCachedWrites()) {
- cached_write_buffer_ids.push_back(buffer_id);
- }
- buffer.CachedCpuWrite(cpu_addr, size);
- });
+ memory_tracker.CachedCpuWrite(cpu_addr, size);
+}
+
+template <class P>
+std::optional<VideoCore::RasterizerDownloadArea> BufferCache<P>::GetFlushArea(VAddr cpu_addr,
+ u64 size) {
+ std::optional<VideoCore::RasterizerDownloadArea> area{};
+ area.emplace();
+ VAddr cpu_addr_start_aligned = Common::AlignDown(cpu_addr, Core::Memory::YUZU_PAGESIZE);
+ VAddr cpu_addr_end_aligned = Common::AlignUp(cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
+ area->start_address = cpu_addr_start_aligned;
+ area->end_address = cpu_addr_end_aligned;
+ if (memory_tracker.IsRegionPreflushable(cpu_addr, size)) {
+ area->preemtive = true;
+ return area;
+ };
+ area->preemtive =
+ !IsRegionGpuModified(cpu_addr_start_aligned, cpu_addr_end_aligned - cpu_addr_start_aligned);
+ memory_tracker.MarkRegionAsPreflushable(cpu_addr_start_aligned,
+ cpu_addr_end_aligned - cpu_addr_start_aligned);
+ return area;
}
template <class P>
@@ -571,6 +138,7 @@ void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) {
template <class P>
void BufferCache<P>::ClearDownload(IntervalType subtract_interval) {
+ RemoveEachInOverlapCounter(async_downloads, subtract_interval, -1024);
uncommitted_ranges.subtract(subtract_interval);
for (auto& interval_set : committed_ranges) {
interval_set.subtract(subtract_interval);
@@ -616,10 +184,10 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
const VAddr diff = base_address - *cpu_src_address;
const VAddr new_base_address = *cpu_dest_address + diff;
const IntervalType add_interval{new_base_address, new_base_address + size};
- uncommitted_ranges.add(add_interval);
tmp_intervals.push_back(add_interval);
+ uncommitted_ranges.add(add_interval);
};
- ForEachWrittenRange(*cpu_src_address, amount, mirror);
+ ForEachInRangeSet(common_ranges, *cpu_src_address, amount, mirror);
// This subtraction in this order is important for overlapping copies.
common_ranges.subtract(subtract_interval);
const bool has_new_downloads = tmp_intervals.size() != 0;
@@ -628,9 +196,9 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am
}
runtime.CopyBuffer(dest_buffer, src_buffer, copies);
if (has_new_downloads) {
- dest_buffer.MarkRegionAsGpuModified(*cpu_dest_address, amount);
+ memory_tracker.MarkRegionAsGpuModified(*cpu_dest_address, amount);
}
- std::vector<u8> tmp_buffer(amount);
+ tmp_buffer.resize(amount);
cpu_memory.ReadBlockUnsafe(*cpu_src_address, tmp_buffer.data(), amount);
cpu_memory.WriteBlockUnsafe(*cpu_dest_address, tmp_buffer.data(), amount);
return true;
@@ -866,10 +434,7 @@ void BufferCache<P>::BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_add
template <class P>
void BufferCache<P>::FlushCachedWrites() {
- for (const BufferId buffer_id : cached_write_buffer_ids) {
- slot_buffers[buffer_id].FlushCachedWrites();
- }
- cached_write_buffer_ids.clear();
+ memory_tracker.FlushCachedWrites();
}
template <class P>
@@ -879,10 +444,6 @@ bool BufferCache<P>::HasUncommittedFlushes() const noexcept {
template <class P>
void BufferCache<P>::AccumulateFlushes() {
- if (Settings::values.gpu_accuracy.GetValue() != Settings::GPUAccuracy::High) {
- uncommitted_ranges.clear();
- return;
- }
if (uncommitted_ranges.empty()) {
return;
}
@@ -891,7 +452,11 @@ void BufferCache<P>::AccumulateFlushes() {
template <class P>
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
- return false;
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+ return (!async_buffers.empty() && async_buffers.front().has_value());
+ } else {
+ return false;
+ }
}
template <class P>
@@ -899,11 +464,13 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
AccumulateFlushes();
if (committed_ranges.empty()) {
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+
+ async_buffers.emplace_back(std::optional<Async_Buffer>{});
+ }
return;
}
MICROPROFILE_SCOPE(GPU_DownloadMemory);
- const bool is_accuracy_normal =
- Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::Normal;
auto it = committed_ranges.begin();
while (it != committed_ranges.end()) {
@@ -926,11 +493,12 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
const std::size_t size = interval.upper() - interval.lower();
const VAddr cpu_addr = interval.lower();
ForEachBufferInRange(cpu_addr, size, [&](BufferId buffer_id, Buffer& buffer) {
- buffer.ForEachDownloadRangeAndClear(
- cpu_addr, size, [&](u64 range_offset, u64 range_size) {
- if (is_accuracy_normal) {
- return;
- }
+ const VAddr buffer_start = buffer.CpuAddr();
+ const VAddr buffer_end = buffer_start + buffer.SizeBytes();
+ const VAddr new_start = std::max(buffer_start, cpu_addr);
+ const VAddr new_end = std::min(buffer_end, cpu_addr + size);
+ memory_tracker.ForEachDownloadRange(
+ new_start, new_end - new_start, false, [&](u64 cpu_addr_out, u64 range_size) {
const VAddr buffer_addr = buffer.CpuAddr();
const auto add_download = [&](VAddr start, VAddr end) {
const u64 new_offset = start - buffer_addr;
@@ -944,92 +512,143 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
buffer_id,
});
// Align up to avoid cache conflicts
- constexpr u64 align = 8ULL;
+ constexpr u64 align = 64ULL;
constexpr u64 mask = ~(align - 1ULL);
total_size_bytes += (new_size + align - 1) & mask;
largest_copy = std::max(largest_copy, new_size);
};
- const VAddr start_address = buffer_addr + range_offset;
- const VAddr end_address = start_address + range_size;
- ForEachWrittenRange(start_address, range_size, add_download);
- const IntervalType subtract_interval{start_address, end_address};
- common_ranges.subtract(subtract_interval);
+ ForEachInRangeSet(common_ranges, cpu_addr_out, range_size, add_download);
});
});
}
}
committed_ranges.clear();
if (downloads.empty()) {
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+
+ async_buffers.emplace_back(std::optional<Async_Buffer>{});
+ }
return;
}
- if constexpr (USE_MEMORY_MAPS) {
- auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+ auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
+ boost::container::small_vector<BufferCopy, 4> normalized_copies;
+ IntervalSet new_async_range{};
runtime.PreCopyBarrier();
for (auto& [copy, buffer_id] : downloads) {
- // Have in mind the staging buffer offset for the copy
copy.dst_offset += download_staging.offset;
const std::array copies{copy};
- runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies, false);
+ BufferCopy second_copy{copy};
+ Buffer& buffer = slot_buffers[buffer_id];
+ second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
+ VAddr orig_cpu_addr = static_cast<VAddr>(second_copy.src_offset);
+ const IntervalType base_interval{orig_cpu_addr, orig_cpu_addr + copy.size};
+ async_downloads += std::make_pair(base_interval, 1);
+ runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
+ normalized_copies.push_back(second_copy);
}
runtime.PostCopyBarrier();
- runtime.Finish();
- for (const auto& [copy, buffer_id] : downloads) {
- const Buffer& buffer = slot_buffers[buffer_id];
- const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset;
- // Undo the modified offset
- const u64 dst_offset = copy.dst_offset - download_staging.offset;
- const u8* read_mapped_memory = download_staging.mapped_span.data() + dst_offset;
- cpu_memory.WriteBlockUnsafe(cpu_addr, read_mapped_memory, copy.size);
- }
+ pending_downloads.emplace_back(std::move(normalized_copies));
+ async_buffers.emplace_back(download_staging);
} else {
- const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy);
- for (const auto& [copy, buffer_id] : downloads) {
- Buffer& buffer = slot_buffers[buffer_id];
- buffer.ImmediateDownload(copy.src_offset, immediate_buffer.subspan(0, copy.size));
- const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset;
- cpu_memory.WriteBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size);
+ if (!Settings::IsGPULevelHigh()) {
+ committed_ranges.clear();
+ uncommitted_ranges.clear();
+ } else {
+ if constexpr (USE_MEMORY_MAPS) {
+ auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
+ runtime.PreCopyBarrier();
+ for (auto& [copy, buffer_id] : downloads) {
+ // Have in mind the staging buffer offset for the copy
+ copy.dst_offset += download_staging.offset;
+ const std::array copies{copy};
+ runtime.CopyBuffer(download_staging.buffer, slot_buffers[buffer_id], copies,
+ false);
+ }
+ runtime.PostCopyBarrier();
+ runtime.Finish();
+ for (const auto& [copy, buffer_id] : downloads) {
+ const Buffer& buffer = slot_buffers[buffer_id];
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset;
+ // Undo the modified offset
+ const u64 dst_offset = copy.dst_offset - download_staging.offset;
+ const u8* read_mapped_memory = download_staging.mapped_span.data() + dst_offset;
+ cpu_memory.WriteBlockUnsafe(cpu_addr, read_mapped_memory, copy.size);
+ }
+ } else {
+ const std::span<u8> immediate_buffer = ImmediateBuffer(largest_copy);
+ for (const auto& [copy, buffer_id] : downloads) {
+ Buffer& buffer = slot_buffers[buffer_id];
+ buffer.ImmediateDownload(copy.src_offset,
+ immediate_buffer.subspan(0, copy.size));
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.src_offset;
+ cpu_memory.WriteBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size);
+ }
+ }
}
}
}
template <class P>
void BufferCache<P>::CommitAsyncFlushes() {
- if (Settings::values.gpu_accuracy.GetValue() == Settings::GPUAccuracy::High) {
- CommitAsyncFlushesHigh();
- } else {
- uncommitted_ranges.clear();
- committed_ranges.clear();
- }
+ CommitAsyncFlushesHigh();
}
template <class P>
-void BufferCache<P>::PopAsyncFlushes() {}
+void BufferCache<P>::PopAsyncFlushes() {
+ MICROPROFILE_SCOPE(GPU_DownloadMemory);
+ PopAsyncBuffers();
+}
template <class P>
-bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
- const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
- for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
- const BufferId image_id = page_table[page];
- if (!image_id) {
- ++page;
- continue;
- }
- Buffer& buffer = slot_buffers[image_id];
- if (buffer.IsRegionGpuModified(addr, size)) {
- return true;
+void BufferCache<P>::PopAsyncBuffers() {
+ if (async_buffers.empty()) {
+ return;
+ }
+ if (!async_buffers.front().has_value()) {
+ async_buffers.pop_front();
+ return;
+ }
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+ auto& downloads = pending_downloads.front();
+ auto& async_buffer = async_buffers.front();
+ u8* base = async_buffer->mapped_span.data();
+ const size_t base_offset = async_buffer->offset;
+ for (const auto& copy : downloads) {
+ const VAddr cpu_addr = static_cast<VAddr>(copy.src_offset);
+ const u64 dst_offset = copy.dst_offset - base_offset;
+ const u8* read_mapped_memory = base + dst_offset;
+ ForEachInOverlapCounter(
+ async_downloads, cpu_addr, copy.size, [&](VAddr start, VAddr end, int count) {
+ cpu_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - cpu_addr],
+ end - start);
+ if (count == 1) {
+ const IntervalType base_interval{start, end};
+ common_ranges.subtract(base_interval);
+ }
+ });
+ const IntervalType subtract_interval{cpu_addr, cpu_addr + copy.size};
+ RemoveEachInOverlapCounter(async_downloads, subtract_interval, -1);
}
- const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
- page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
+ async_buffers_death_ring.emplace_back(*async_buffer);
+ async_buffers.pop_front();
+ pending_downloads.pop_front();
}
- return false;
+}
+
+template <class P>
+bool BufferCache<P>::IsRegionGpuModified(VAddr addr, size_t size) {
+ bool is_dirty = false;
+ ForEachInRangeSet(common_ranges, addr, size, [&](VAddr, VAddr) { is_dirty = true; });
+ return is_dirty;
}
template <class P>
bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) {
const VAddr end_addr = addr + size;
- const u64 page_end = Common::DivCeil(end_addr, YUZU_PAGESIZE);
- for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
+ const u64 page_end = Common::DivCeil(end_addr, CACHING_PAGESIZE);
+ for (u64 page = addr >> CACHING_PAGEBITS; page < page_end;) {
const BufferId buffer_id = page_table[page];
if (!buffer_id) {
++page;
@@ -1041,28 +660,14 @@ bool BufferCache<P>::IsRegionRegistered(VAddr addr, size_t size) {
if (buf_start_addr < end_addr && addr < buf_end_addr) {
return true;
}
- page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
+ page = Common::DivCeil(end_addr, CACHING_PAGESIZE);
}
return false;
}
template <class P>
bool BufferCache<P>::IsRegionCpuModified(VAddr addr, size_t size) {
- const u64 page_end = Common::DivCeil(addr + size, YUZU_PAGESIZE);
- for (u64 page = addr >> YUZU_PAGEBITS; page < page_end;) {
- const BufferId image_id = page_table[page];
- if (!image_id) {
- ++page;
- continue;
- }
- Buffer& buffer = slot_buffers[image_id];
- if (buffer.IsRegionCpuModified(addr, size)) {
- return true;
- }
- const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
- page = Common::DivCeil(end_addr, YUZU_PAGESIZE);
- }
- return false;
+ return memory_tracker.IsRegionCpuModified(addr, size);
}
template <class P>
@@ -1072,7 +677,7 @@ void BufferCache<P>::BindHostIndexBuffer() {
const u32 offset = buffer.Offset(index_buffer.cpu_addr);
const u32 size = index_buffer.size;
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
- if (!draw_state.inline_index_draw_indexes.empty()) {
+ if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
if constexpr (USE_MEMORY_MAPS) {
auto upload_staging = runtime.UploadStagingBuffer(size);
std::array<BufferCopy, 1> copies{
@@ -1155,7 +760,7 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
TouchBuffer(buffer, binding.buffer_id);
const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID &&
size <= uniform_buffer_skip_cache_size &&
- !buffer.IsRegionGpuModified(cpu_addr, size);
+ !memory_tracker.IsRegionGpuModified(cpu_addr, size);
if (use_fast_buffer) {
if constexpr (IS_OPENGL) {
if (runtime.HasFastBufferSubData()) {
@@ -1378,27 +983,36 @@ void BufferCache<P>::UpdateIndexBuffer() {
// We have to check for the dirty flags and index count
// The index count is currently changed without updating the dirty flags
const auto& draw_state = maxwell3d->draw_manager->GetDrawState();
- const auto& index_array = draw_state.index_buffer;
+ const auto& index_buffer_ref = draw_state.index_buffer;
auto& flags = maxwell3d->dirty.flags;
if (!flags[Dirty::IndexBuffer]) {
return;
}
flags[Dirty::IndexBuffer] = false;
- last_index_count = index_array.count;
- if (!draw_state.inline_index_draw_indexes.empty()) {
+ if (!draw_state.inline_index_draw_indexes.empty()) [[unlikely]] {
auto inline_index_size = static_cast<u32>(draw_state.inline_index_draw_indexes.size());
+ u32 buffer_size = Common::AlignUp(inline_index_size, CACHING_PAGESIZE);
+ if (inline_buffer_id == NULL_BUFFER_ID) [[unlikely]] {
+ inline_buffer_id = CreateBuffer(0, buffer_size);
+ }
+ if (slot_buffers[inline_buffer_id].SizeBytes() < buffer_size) [[unlikely]] {
+ slot_buffers.erase(inline_buffer_id);
+ inline_buffer_id = CreateBuffer(0, buffer_size);
+ }
index_buffer = Binding{
.cpu_addr = 0,
.size = inline_index_size,
- .buffer_id = CreateBuffer(0, inline_index_size),
+ .buffer_id = inline_buffer_id,
};
return;
}
- const GPUVAddr gpu_addr_begin = index_array.StartAddress();
- const GPUVAddr gpu_addr_end = index_array.EndAddress();
+
+ const GPUVAddr gpu_addr_begin = index_buffer_ref.StartAddress();
+ const GPUVAddr gpu_addr_end = index_buffer_ref.EndAddress();
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
- const u32 draw_size = (index_array.count + index_array.first) * index_array.FormatSizeInBytes();
+ const u32 draw_size =
+ (index_buffer_ref.count + index_buffer_ref.first) * index_buffer_ref.FormatSizeInBytes();
const u32 size = std::min(address_size, draw_size);
if (size == 0 || !cpu_addr) {
index_buffer = NULL_BINDING;
@@ -1434,17 +1048,15 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
const GPUVAddr gpu_addr_begin = array.Address();
const GPUVAddr gpu_addr_end = limit.Address() + 1;
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
- u32 address_size = static_cast<u32>(
- std::min(gpu_addr_end - gpu_addr_begin, static_cast<u64>(std::numeric_limits<u32>::max())));
- if (array.enable == 0 || address_size == 0 || !cpu_addr) {
+ const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
+ u32 size = address_size; // TODO: Analyze stride and number of vertices
+ if (array.enable == 0 || size == 0 || !cpu_addr) {
vertex_buffers[index] = NULL_BINDING;
return;
}
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end)) {
- address_size =
- static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, address_size));
+ size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
}
- const u32 size = address_size; // TODO: Analyze stride and number of vertices
vertex_buffers[index] = Binding{
.cpu_addr = *cpu_addr,
.size = size,
@@ -1591,16 +1203,13 @@ void BufferCache<P>::UpdateComputeTextureBuffers() {
template <class P>
void BufferCache<P>::MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size) {
- Buffer& buffer = slot_buffers[buffer_id];
- buffer.MarkRegionAsGpuModified(cpu_addr, size);
+ if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) {
+ SynchronizeBuffer(slot_buffers[buffer_id], cpu_addr, size);
+ }
+ memory_tracker.MarkRegionAsGpuModified(cpu_addr, size);
const IntervalType base_interval{cpu_addr, cpu_addr + size};
common_ranges.add(base_interval);
-
- const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue();
- if (!is_async) {
- return;
- }
uncommitted_ranges.add(base_interval);
}
@@ -1609,7 +1218,7 @@ BufferId BufferCache<P>::FindBuffer(VAddr cpu_addr, u32 size) {
if (cpu_addr == 0) {
return NULL_BUFFER_ID;
}
- const u64 page = cpu_addr >> YUZU_PAGEBITS;
+ const u64 page = cpu_addr >> CACHING_PAGEBITS;
const BufferId buffer_id = page_table[page];
if (!buffer_id) {
return CreateBuffer(cpu_addr, size);
@@ -1638,9 +1247,9 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
.has_stream_leap = has_stream_leap,
};
}
- for (; cpu_addr >> YUZU_PAGEBITS < Common::DivCeil(end, YUZU_PAGESIZE);
- cpu_addr += YUZU_PAGESIZE) {
- const BufferId overlap_id = page_table[cpu_addr >> YUZU_PAGEBITS];
+ for (; cpu_addr >> CACHING_PAGEBITS < Common::DivCeil(end, CACHING_PAGESIZE);
+ cpu_addr += CACHING_PAGESIZE) {
+ const BufferId overlap_id = page_table[cpu_addr >> CACHING_PAGEBITS];
if (!overlap_id) {
continue;
}
@@ -1666,11 +1275,11 @@ typename BufferCache<P>::OverlapResult BufferCache<P>::ResolveOverlaps(VAddr cpu
// as a stream buffer. Increase the size to skip constantly recreating buffers.
has_stream_leap = true;
if (expands_right) {
- begin -= YUZU_PAGESIZE * 256;
+ begin -= CACHING_PAGESIZE * 256;
cpu_addr = begin;
}
if (expands_left) {
- end += YUZU_PAGESIZE * 256;
+ end += CACHING_PAGESIZE * 256;
}
}
}
@@ -1690,25 +1299,22 @@ void BufferCache<P>::JoinOverlap(BufferId new_buffer_id, BufferId overlap_id,
if (accumulate_stream_score) {
new_buffer.IncreaseStreamScore(overlap.StreamScore() + 1);
}
- std::vector<BufferCopy> copies;
+ boost::container::small_vector<BufferCopy, 1> copies;
const size_t dst_base_offset = overlap.CpuAddr() - new_buffer.CpuAddr();
- overlap.ForEachDownloadRange([&](u64 begin, u64 range_size) {
- copies.push_back(BufferCopy{
- .src_offset = begin,
- .dst_offset = dst_base_offset + begin,
- .size = range_size,
- });
- new_buffer.UnmarkRegionAsCpuModified(begin, range_size);
- new_buffer.MarkRegionAsGpuModified(begin, range_size);
+ copies.push_back(BufferCopy{
+ .src_offset = 0,
+ .dst_offset = dst_base_offset,
+ .size = overlap.SizeBytes(),
});
- if (!copies.empty()) {
- runtime.CopyBuffer(slot_buffers[new_buffer_id], overlap, copies);
- }
- DeleteBuffer(overlap_id);
+ runtime.CopyBuffer(new_buffer, overlap, copies);
+ DeleteBuffer(overlap_id, true);
}
template <class P>
BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
+ VAddr cpu_addr_end = Common::AlignUp(cpu_addr + wanted_size, CACHING_PAGESIZE);
+ cpu_addr = Common::AlignDown(cpu_addr, CACHING_PAGESIZE);
+ wanted_size = static_cast<u32>(cpu_addr_end - cpu_addr);
const OverlapResult overlap = ResolveOverlaps(cpu_addr, wanted_size);
const u32 size = static_cast<u32>(overlap.end - overlap.begin);
const BufferId new_buffer_id = slot_buffers.insert(runtime, rasterizer, overlap.begin, size);
@@ -1718,7 +1324,7 @@ BufferId BufferCache<P>::CreateBuffer(VAddr cpu_addr, u32 wanted_size) {
JoinOverlap(new_buffer_id, overlap_id, !overlap.has_stream_leap);
}
Register(new_buffer_id);
- TouchBuffer(slot_buffers[new_buffer_id], new_buffer_id);
+ TouchBuffer(new_buffer, new_buffer_id);
return new_buffer_id;
}
@@ -1746,8 +1352,8 @@ void BufferCache<P>::ChangeRegister(BufferId buffer_id) {
}
const VAddr cpu_addr_begin = buffer.CpuAddr();
const VAddr cpu_addr_end = cpu_addr_begin + size;
- const u64 page_begin = cpu_addr_begin / YUZU_PAGESIZE;
- const u64 page_end = Common::DivCeil(cpu_addr_end, YUZU_PAGESIZE);
+ const u64 page_begin = cpu_addr_begin / CACHING_PAGESIZE;
+ const u64 page_end = Common::DivCeil(cpu_addr_end, CACHING_PAGESIZE);
for (u64 page = page_begin; page != page_end; ++page) {
if constexpr (insert) {
page_table[page] = buffer_id;
@@ -1766,9 +1372,6 @@ void BufferCache<P>::TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept {
template <class P>
bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size) {
- if (buffer.CpuAddr() == 0) {
- return true;
- }
return SynchronizeBufferImpl(buffer, cpu_addr, size);
}
@@ -1777,10 +1380,11 @@ bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 s
boost::container::small_vector<BufferCopy, 4> copies;
u64 total_size_bytes = 0;
u64 largest_copy = 0;
- buffer.ForEachUploadRange(cpu_addr, size, [&](u64 range_offset, u64 range_size) {
+ VAddr buffer_start = buffer.CpuAddr();
+ memory_tracker.ForEachUploadRange(cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) {
copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
- .dst_offset = range_offset,
+ .dst_offset = cpu_addr_out - buffer_start,
.size = range_size,
});
total_size_bytes += range_size;
@@ -1795,6 +1399,51 @@ bool BufferCache<P>::SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 s
}
template <class P>
+bool BufferCache<P>::SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size) {
+ boost::container::small_vector<BufferCopy, 4> copies;
+ u64 total_size_bytes = 0;
+ u64 largest_copy = 0;
+ IntervalSet found_sets{};
+ auto make_copies = [&] {
+ for (auto& interval : found_sets) {
+ const std::size_t sub_size = interval.upper() - interval.lower();
+ const VAddr cpu_addr_ = interval.lower();
+ copies.push_back(BufferCopy{
+ .src_offset = total_size_bytes,
+ .dst_offset = cpu_addr_ - buffer.CpuAddr(),
+ .size = sub_size,
+ });
+ total_size_bytes += sub_size;
+ largest_copy = std::max<u64>(largest_copy, sub_size);
+ }
+ const std::span<BufferCopy> copies_span(copies.data(), copies.size());
+ UploadMemory(buffer, total_size_bytes, largest_copy, copies_span);
+ };
+ memory_tracker.ForEachUploadRange(cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) {
+ const VAddr base_adr = cpu_addr_out;
+ const VAddr end_adr = base_adr + range_size;
+ const IntervalType add_interval{base_adr, end_adr};
+ found_sets.add(add_interval);
+ });
+ if (found_sets.empty()) {
+ return true;
+ }
+ const IntervalType search_interval{cpu_addr, cpu_addr + size};
+ auto it = common_ranges.lower_bound(search_interval);
+ auto it_end = common_ranges.upper_bound(search_interval);
+ if (it == common_ranges.end()) {
+ make_copies();
+ return false;
+ }
+ while (it != it_end) {
+ found_sets.subtract(*it);
+ it++;
+ }
+ make_copies();
+ return false;
+}
+
+template <class P>
void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
std::span<BufferCopy> copies) {
if constexpr (USE_MEMORY_MAPS) {
@@ -1805,39 +1454,45 @@ void BufferCache<P>::UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 larg
}
template <class P>
-void BufferCache<P>::ImmediateUploadMemory(Buffer& buffer, u64 largest_copy,
- std::span<const BufferCopy> copies) {
- std::span<u8> immediate_buffer;
- for (const BufferCopy& copy : copies) {
- std::span<const u8> upload_span;
- const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset;
- if (IsRangeGranular(cpu_addr, copy.size)) {
- upload_span = std::span(cpu_memory.GetPointer(cpu_addr), copy.size);
- } else {
- if (immediate_buffer.empty()) {
- immediate_buffer = ImmediateBuffer(largest_copy);
+void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
+ [[maybe_unused]] u64 largest_copy,
+ [[maybe_unused]] std::span<const BufferCopy> copies) {
+ if constexpr (!USE_MEMORY_MAPS) {
+ std::span<u8> immediate_buffer;
+ for (const BufferCopy& copy : copies) {
+ std::span<const u8> upload_span;
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset;
+ if (IsRangeGranular(cpu_addr, copy.size)) {
+ upload_span = std::span(cpu_memory.GetPointer(cpu_addr), copy.size);
+ } else {
+ if (immediate_buffer.empty()) {
+ immediate_buffer = ImmediateBuffer(largest_copy);
+ }
+ cpu_memory.ReadBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size);
+ upload_span = immediate_buffer.subspan(0, copy.size);
}
- cpu_memory.ReadBlockUnsafe(cpu_addr, immediate_buffer.data(), copy.size);
- upload_span = immediate_buffer.subspan(0, copy.size);
+ buffer.ImmediateUpload(copy.dst_offset, upload_span);
}
- buffer.ImmediateUpload(copy.dst_offset, upload_span);
}
}
template <class P>
-void BufferCache<P>::MappedUploadMemory(Buffer& buffer, u64 total_size_bytes,
- std::span<BufferCopy> copies) {
- auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes);
- const std::span<u8> staging_pointer = upload_staging.mapped_span;
- for (BufferCopy& copy : copies) {
- u8* const src_pointer = staging_pointer.data() + copy.src_offset;
- const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset;
- cpu_memory.ReadBlockUnsafe(cpu_addr, src_pointer, copy.size);
+void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
+ [[maybe_unused]] u64 total_size_bytes,
+ [[maybe_unused]] std::span<BufferCopy> copies) {
+ if constexpr (USE_MEMORY_MAPS) {
+ auto upload_staging = runtime.UploadStagingBuffer(total_size_bytes);
+ const std::span<u8> staging_pointer = upload_staging.mapped_span;
+ for (BufferCopy& copy : copies) {
+ u8* const src_pointer = staging_pointer.data() + copy.src_offset;
+ const VAddr cpu_addr = buffer.CpuAddr() + copy.dst_offset;
+ cpu_memory.ReadBlockUnsafe(cpu_addr, src_pointer, copy.size);
- // Apply the staging offset
- copy.src_offset += upload_staging.offset;
+ // Apply the staging offset
+ copy.src_offset += upload_staging.offset;
+ }
+ runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
}
- runtime.CopyBuffer(buffer, upload_staging.buffer, copies);
}
template <class P>
@@ -1847,7 +1502,9 @@ bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
if (!is_dirty) {
return false;
}
- if (!IsRegionGpuModified(dest_address, copy_size)) {
+ VAddr aligned_start = Common::AlignDown(dest_address, YUZU_PAGESIZE);
+ VAddr aligned_end = Common::AlignUp(dest_address + copy_size, YUZU_PAGESIZE);
+ if (!IsRegionGpuModified(aligned_start, aligned_end - aligned_start)) {
return false;
}
@@ -1886,30 +1543,31 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
boost::container::small_vector<BufferCopy, 1> copies;
u64 total_size_bytes = 0;
u64 largest_copy = 0;
- buffer.ForEachDownloadRangeAndClear(cpu_addr, size, [&](u64 range_offset, u64 range_size) {
- const VAddr buffer_addr = buffer.CpuAddr();
- const auto add_download = [&](VAddr start, VAddr end) {
- const u64 new_offset = start - buffer_addr;
- const u64 new_size = end - start;
- copies.push_back(BufferCopy{
- .src_offset = new_offset,
- .dst_offset = total_size_bytes,
- .size = new_size,
- });
- // Align up to avoid cache conflicts
- constexpr u64 align = 256ULL;
- constexpr u64 mask = ~(align - 1ULL);
- total_size_bytes += (new_size + align - 1) & mask;
- largest_copy = std::max(largest_copy, new_size);
- };
-
- const VAddr start_address = buffer_addr + range_offset;
- const VAddr end_address = start_address + range_size;
- ForEachWrittenRange(start_address, range_size, add_download);
- const IntervalType subtract_interval{start_address, end_address};
- ClearDownload(subtract_interval);
- common_ranges.subtract(subtract_interval);
- });
+ memory_tracker.ForEachDownloadRangeAndClear(
+ cpu_addr, size, [&](u64 cpu_addr_out, u64 range_size) {
+ const VAddr buffer_addr = buffer.CpuAddr();
+ const auto add_download = [&](VAddr start, VAddr end) {
+ const u64 new_offset = start - buffer_addr;
+ const u64 new_size = end - start;
+ copies.push_back(BufferCopy{
+ .src_offset = new_offset,
+ .dst_offset = total_size_bytes,
+ .size = new_size,
+ });
+ // Align up to avoid cache conflicts
+ constexpr u64 align = 64ULL;
+ constexpr u64 mask = ~(align - 1ULL);
+ total_size_bytes += (new_size + align - 1) & mask;
+ largest_copy = std::max(largest_copy, new_size);
+ };
+
+ const VAddr start_address = cpu_addr_out;
+ const VAddr end_address = start_address + range_size;
+ ForEachInRangeSet(common_ranges, start_address, range_size, add_download);
+ const IntervalType subtract_interval{start_address, end_address};
+ ClearDownload(subtract_interval);
+ common_ranges.subtract(subtract_interval);
+ });
if (total_size_bytes == 0) {
return;
}
@@ -1943,7 +1601,7 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, VAddr cpu_addr, u64 si
}
template <class P>
-void BufferCache<P>::DeleteBuffer(BufferId buffer_id) {
+void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
const auto scalar_replace = [buffer_id](Binding& binding) {
if (binding.buffer_id == buffer_id) {
binding.buffer_id = BufferId{};
@@ -1959,11 +1617,12 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id) {
replace(transform_feedback_buffers);
replace(compute_uniform_buffers);
replace(compute_storage_buffers);
- std::erase(cached_write_buffer_ids, buffer_id);
// Mark the whole buffer as CPU written to stop tracking CPU writes
- Buffer& buffer = slot_buffers[buffer_id];
- buffer.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes());
+ if (!do_not_mark) {
+ Buffer& buffer = slot_buffers[buffer_id];
+ memory_tracker.MarkRegionAsCpuModified(buffer.CpuAddr(), buffer.SizeBytes());
+ }
Unregister(buffer_id);
delayed_destruction_ring.Push(std::move(slot_buffers[buffer_id]));
@@ -1996,14 +1655,15 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s
const bool is_nvn_cbuf = cbuf_index == 0;
// The NVN driver buffer (index 0) is known to pack the SSBO address followed by its size.
if (is_nvn_cbuf) {
- return gpu_memory->Read<u32>(ssbo_addr + 8);
+ const u32 ssbo_size = gpu_memory->Read<u32>(ssbo_addr + 8);
+ if (ssbo_size != 0) {
+ return ssbo_size;
+ }
}
// Other titles (notably Doom Eternal) may use STG/LDG on buffer addresses in custom defined
// cbufs, which do not store the sizes adjacent to the addresses, so use the fully
// mapped buffer size for now.
const u32 memory_layout_size = static_cast<u32>(gpu_memory->GetMemoryLayoutSize(gpu_addr));
- LOG_INFO(HW_GPU, "Binding storage buffer for cbuf index {}, MemoryLayoutSize 0x{:X}",
- cbuf_index, memory_layout_size);
return memory_layout_size;
}();
const std::optional<VAddr> cpu_addr = gpu_memory->GpuToCpuAddress(gpu_addr);
@@ -2011,7 +1671,7 @@ typename BufferCache<P>::Binding BufferCache<P>::StorageBufferBinding(GPUVAddr s
LOG_WARNING(HW_GPU, "Failed to find storage buffer for cbuf index {}", cbuf_index);
return NULL_BINDING;
}
- const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, Core::Memory::YUZU_PAGESIZE);
+ const VAddr cpu_end = Common::AlignUp(*cpu_addr + size, YUZU_PAGESIZE);
const Binding binding{
.cpu_addr = *cpu_addr,
.size = is_written ? size : static_cast<u32>(cpu_end - *cpu_addr),
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
new file mode 100644
index 000000000..ac00d4d9d
--- /dev/null
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -0,0 +1,576 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <algorithm>
+#include <array>
+#include <functional>
+#include <memory>
+#include <mutex>
+#include <numeric>
+#include <span>
+#include <unordered_map>
+#include <vector>
+
+#include <boost/container/small_vector.hpp>
+#define BOOST_NO_MT
+#include <boost/pool/detail/mutex.hpp>
+#undef BOOST_NO_MT
+#include <boost/icl/interval.hpp>
+#include <boost/icl/interval_base_set.hpp>
+#include <boost/icl/interval_set.hpp>
+#include <boost/icl/split_interval_map.hpp>
+#include <boost/pool/pool.hpp>
+#include <boost/pool/pool_alloc.hpp>
+#include <boost/pool/poolfwd.hpp>
+
+#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "common/literals.h"
+#include "common/lru_cache.h"
+#include "common/microprofile.h"
+#include "common/scope_exit.h"
+#include "common/settings.h"
+#include "core/memory.h"
+#include "video_core/buffer_cache/buffer_base.h"
+#include "video_core/control/channel_state_cache.h"
+#include "video_core/delayed_destruction_ring.h"
+#include "video_core/dirty_flags.h"
+#include "video_core/engines/draw_manager.h"
+#include "video_core/engines/kepler_compute.h"
+#include "video_core/engines/maxwell_3d.h"
+#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/surface.h"
+#include "video_core/texture_cache/slot_vector.h"
+#include "video_core/texture_cache/types.h"
+
+namespace boost {
+template <typename T>
+class fast_pool_allocator<T, default_user_allocator_new_delete, details::pool::null_mutex, 4096, 0>;
+}
+
+namespace VideoCommon {
+
+MICROPROFILE_DECLARE(GPU_PrepareBuffers);
+MICROPROFILE_DECLARE(GPU_BindUploadBuffers);
+MICROPROFILE_DECLARE(GPU_DownloadMemory);
+
+using BufferId = SlotId;
+
+using VideoCore::Surface::PixelFormat;
+using namespace Common::Literals;
+
+constexpr u32 NUM_VERTEX_BUFFERS = 32;
+constexpr u32 NUM_TRANSFORM_FEEDBACK_BUFFERS = 4;
+constexpr u32 NUM_GRAPHICS_UNIFORM_BUFFERS = 18;
+constexpr u32 NUM_COMPUTE_UNIFORM_BUFFERS = 8;
+constexpr u32 NUM_STORAGE_BUFFERS = 16;
+constexpr u32 NUM_TEXTURE_BUFFERS = 16;
+constexpr u32 NUM_STAGES = 5;
+
+using UniformBufferSizes = std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>;
+using ComputeUniformBufferSizes = std::array<u32, NUM_COMPUTE_UNIFORM_BUFFERS>;
+
+enum class ObtainBufferSynchronize : u32 {
+ NoSynchronize = 0,
+ FullSynchronize = 1,
+ SynchronizeNoDirty = 2,
+};
+
+enum class ObtainBufferOperation : u32 {
+ DoNothing = 0,
+ MarkAsWritten = 1,
+ DiscardWrite = 2,
+ MarkQuery = 3,
+};
+
+template <typename P>
+class BufferCache : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
+ // Page size for caching purposes.
+ // This is unrelated to the CPU page size and it can be changed as it seems optimal.
+ static constexpr u32 CACHING_PAGEBITS = 16;
+ static constexpr u64 CACHING_PAGESIZE = u64{1} << CACHING_PAGEBITS;
+
+ static constexpr bool IS_OPENGL = P::IS_OPENGL;
+ static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS =
+ P::HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS;
+ static constexpr bool HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT =
+ P::HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT;
+ static constexpr bool NEEDS_BIND_UNIFORM_INDEX = P::NEEDS_BIND_UNIFORM_INDEX;
+ static constexpr bool NEEDS_BIND_STORAGE_INDEX = P::NEEDS_BIND_STORAGE_INDEX;
+ static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
+ static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
+ static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS;
+
+ static constexpr BufferId NULL_BUFFER_ID{0};
+
+ static constexpr s64 DEFAULT_EXPECTED_MEMORY = 512_MiB;
+ static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB;
+ static constexpr s64 TARGET_THRESHOLD = 4_GiB;
+
+ // Debug Flags.
+
+ static constexpr bool DISABLE_DOWNLOADS = true;
+
+ using Maxwell = Tegra::Engines::Maxwell3D::Regs;
+
+ using Runtime = typename P::Runtime;
+ using Buffer = typename P::Buffer;
+ using Async_Buffer = typename P::Async_Buffer;
+ using MemoryTracker = typename P::MemoryTracker;
+
+ using IntervalCompare = std::less<VAddr>;
+ using IntervalInstance = boost::icl::interval_type_default<VAddr, std::less>;
+ using IntervalAllocator = boost::fast_pool_allocator<VAddr>;
+ using IntervalSet = boost::icl::interval_set<VAddr>;
+ using IntervalType = typename IntervalSet::interval_type;
+
+ template <typename Type>
+ struct counter_add_functor : public boost::icl::identity_based_inplace_combine<Type> {
+ // types
+ typedef counter_add_functor<Type> type;
+ typedef boost::icl::identity_based_inplace_combine<Type> base_type;
+
+ // public member functions
+ void operator()(Type& current, const Type& added) const {
+ current += added;
+ if (current < base_type::identity_element()) {
+ current = base_type::identity_element();
+ }
+ }
+
+ // public static functions
+ static void version(Type&){};
+ };
+
+ using OverlapCombine = counter_add_functor<int>;
+ using OverlapSection = boost::icl::inter_section<int>;
+ using OverlapCounter = boost::icl::split_interval_map<VAddr, int>;
+
+ struct Empty {};
+
+ struct OverlapResult {
+ std::vector<BufferId> ids;
+ VAddr begin;
+ VAddr end;
+ bool has_stream_leap = false;
+ };
+
+ struct Binding {
+ VAddr cpu_addr{};
+ u32 size{};
+ BufferId buffer_id;
+ };
+
+ struct TextureBufferBinding : Binding {
+ PixelFormat format;
+ };
+
+ static constexpr Binding NULL_BINDING{
+ .cpu_addr = 0,
+ .size = 0,
+ .buffer_id = NULL_BUFFER_ID,
+ };
+
+public:
+ static constexpr u32 DEFAULT_SKIP_CACHE_SIZE = static_cast<u32>(4_KiB);
+
+ explicit BufferCache(VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_, Runtime& runtime_);
+
+ void TickFrame();
+
+ void WriteMemory(VAddr cpu_addr, u64 size);
+
+ void CachedWriteMemory(VAddr cpu_addr, u64 size);
+
+ void DownloadMemory(VAddr cpu_addr, u64 size);
+
+ std::optional<VideoCore::RasterizerDownloadArea> GetFlushArea(VAddr cpu_addr, u64 size);
+
+ bool InlineMemory(VAddr dest_address, size_t copy_size, std::span<const u8> inlined_buffer);
+
+ void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size);
+
+ void DisableGraphicsUniformBuffer(size_t stage, u32 index);
+
+ void UpdateGraphicsBuffers(bool is_indexed);
+
+ void UpdateComputeBuffers();
+
+ void BindHostGeometryBuffers(bool is_indexed);
+
+ void BindHostStageBuffers(size_t stage);
+
+ void BindHostComputeBuffers();
+
+ void SetUniformBuffersState(const std::array<u32, NUM_STAGES>& mask,
+ const UniformBufferSizes* sizes);
+
+ void SetComputeUniformBufferState(u32 mask, const ComputeUniformBufferSizes* sizes);
+
+ void UnbindGraphicsStorageBuffers(size_t stage);
+
+ void BindGraphicsStorageBuffer(size_t stage, size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
+ bool is_written);
+
+ void UnbindGraphicsTextureBuffers(size_t stage);
+
+ void BindGraphicsTextureBuffer(size_t stage, size_t tbo_index, GPUVAddr gpu_addr, u32 size,
+ PixelFormat format, bool is_written, bool is_image);
+
+ void UnbindComputeStorageBuffers();
+
+ void BindComputeStorageBuffer(size_t ssbo_index, u32 cbuf_index, u32 cbuf_offset,
+ bool is_written);
+
+ void UnbindComputeTextureBuffers();
+
+ void BindComputeTextureBuffer(size_t tbo_index, GPUVAddr gpu_addr, u32 size, PixelFormat format,
+ bool is_written, bool is_image);
+
+ [[nodiscard]] std::pair<Buffer*, u32> ObtainBuffer(GPUVAddr gpu_addr, u32 size,
+ ObtainBufferSynchronize sync_info,
+ ObtainBufferOperation post_op);
+ void FlushCachedWrites();
+
+ /// Return true when there are uncommitted buffers to be downloaded
+ [[nodiscard]] bool HasUncommittedFlushes() const noexcept;
+
+ void AccumulateFlushes();
+
+ /// Return true when the caller should wait for async downloads
+ [[nodiscard]] bool ShouldWaitAsyncFlushes() const noexcept;
+
+ /// Commit asynchronous downloads
+ void CommitAsyncFlushes();
+ void CommitAsyncFlushesHigh();
+
+ /// Pop asynchronous downloads
+ void PopAsyncFlushes();
+ void PopAsyncBuffers();
+
+ bool DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 amount);
+
+ bool DMAClear(GPUVAddr src_address, u64 amount, u32 value);
+
+ /// Return true when a CPU region is modified from the GPU
+ [[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
+
+ /// Return true when a region is registered on the cache
+ [[nodiscard]] bool IsRegionRegistered(VAddr addr, size_t size);
+
+ /// Return true when a CPU region is modified from the CPU
+ [[nodiscard]] bool IsRegionCpuModified(VAddr addr, size_t size);
+
+ void SetDrawIndirect(
+ const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect_) {
+ current_draw_indirect = current_draw_indirect_;
+ }
+
+ [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectCount();
+
+ [[nodiscard]] std::pair<Buffer*, u32> GetDrawIndirectBuffer();
+
+ std::recursive_mutex mutex;
+ Runtime& runtime;
+
+private:
+ template <typename Func>
+ static void ForEachEnabledBit(u32 enabled_mask, Func&& func) {
+ for (u32 index = 0; enabled_mask != 0; ++index, enabled_mask >>= 1) {
+ const int disabled_bits = std::countr_zero(enabled_mask);
+ index += disabled_bits;
+ enabled_mask >>= disabled_bits;
+ func(index);
+ }
+ }
+
+ template <typename Func>
+ void ForEachBufferInRange(VAddr cpu_addr, u64 size, Func&& func) {
+ const u64 page_end = Common::DivCeil(cpu_addr + size, CACHING_PAGESIZE);
+ for (u64 page = cpu_addr >> CACHING_PAGEBITS; page < page_end;) {
+ const BufferId buffer_id = page_table[page];
+ if (!buffer_id) {
+ ++page;
+ continue;
+ }
+ Buffer& buffer = slot_buffers[buffer_id];
+ func(buffer_id, buffer);
+
+ const VAddr end_addr = buffer.CpuAddr() + buffer.SizeBytes();
+ page = Common::DivCeil(end_addr, CACHING_PAGESIZE);
+ }
+ }
+
+ template <typename Func>
+ void ForEachInRangeSet(IntervalSet& current_range, VAddr cpu_addr, u64 size, Func&& func) {
+ const VAddr start_address = cpu_addr;
+ const VAddr end_address = start_address + size;
+ const IntervalType search_interval{start_address, end_address};
+ auto it = current_range.lower_bound(search_interval);
+ if (it == current_range.end()) {
+ return;
+ }
+ auto end_it = current_range.upper_bound(search_interval);
+ for (; it != end_it; it++) {
+ VAddr inter_addr_end = it->upper();
+ VAddr inter_addr = it->lower();
+ if (inter_addr_end > end_address) {
+ inter_addr_end = end_address;
+ }
+ if (inter_addr < start_address) {
+ inter_addr = start_address;
+ }
+ func(inter_addr, inter_addr_end);
+ }
+ }
+
+ template <typename Func>
+ void ForEachInOverlapCounter(OverlapCounter& current_range, VAddr cpu_addr, u64 size,
+ Func&& func) {
+ const VAddr start_address = cpu_addr;
+ const VAddr end_address = start_address + size;
+ const IntervalType search_interval{start_address, end_address};
+ auto it = current_range.lower_bound(search_interval);
+ if (it == current_range.end()) {
+ return;
+ }
+ auto end_it = current_range.upper_bound(search_interval);
+ for (; it != end_it; it++) {
+ auto& inter = it->first;
+ VAddr inter_addr_end = inter.upper();
+ VAddr inter_addr = inter.lower();
+ if (inter_addr_end > end_address) {
+ inter_addr_end = end_address;
+ }
+ if (inter_addr < start_address) {
+ inter_addr = start_address;
+ }
+ func(inter_addr, inter_addr_end, it->second);
+ }
+ }
+
+ void RemoveEachInOverlapCounter(OverlapCounter& current_range,
+ const IntervalType search_interval, int subtract_value) {
+ bool any_removals = false;
+ current_range.add(std::make_pair(search_interval, subtract_value));
+ do {
+ any_removals = false;
+ auto it = current_range.lower_bound(search_interval);
+ if (it == current_range.end()) {
+ return;
+ }
+ auto end_it = current_range.upper_bound(search_interval);
+ for (; it != end_it; it++) {
+ if (it->second <= 0) {
+ any_removals = true;
+ current_range.erase(it);
+ break;
+ }
+ }
+ } while (any_removals);
+ }
+
+ static bool IsRangeGranular(VAddr cpu_addr, size_t size) {
+ return (cpu_addr & ~Core::Memory::YUZU_PAGEMASK) ==
+ ((cpu_addr + size) & ~Core::Memory::YUZU_PAGEMASK);
+ }
+
+ void RunGarbageCollector();
+
+ void BindHostIndexBuffer();
+
+ void BindHostVertexBuffers();
+
+ void BindHostDrawIndirectBuffers();
+
+ void BindHostGraphicsUniformBuffers(size_t stage);
+
+ void BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 binding_index, bool needs_bind);
+
+ void BindHostGraphicsStorageBuffers(size_t stage);
+
+ void BindHostGraphicsTextureBuffers(size_t stage);
+
+ void BindHostTransformFeedbackBuffers();
+
+ void BindHostComputeUniformBuffers();
+
+ void BindHostComputeStorageBuffers();
+
+ void BindHostComputeTextureBuffers();
+
+ void DoUpdateGraphicsBuffers(bool is_indexed);
+
+ void DoUpdateComputeBuffers();
+
+ void UpdateIndexBuffer();
+
+ void UpdateVertexBuffers();
+
+ void UpdateVertexBuffer(u32 index);
+
+ void UpdateDrawIndirect();
+
+ void UpdateUniformBuffers(size_t stage);
+
+ void UpdateStorageBuffers(size_t stage);
+
+ void UpdateTextureBuffers(size_t stage);
+
+ void UpdateTransformFeedbackBuffers();
+
+ void UpdateTransformFeedbackBuffer(u32 index);
+
+ void UpdateComputeUniformBuffers();
+
+ void UpdateComputeStorageBuffers();
+
+ void UpdateComputeTextureBuffers();
+
+ void MarkWrittenBuffer(BufferId buffer_id, VAddr cpu_addr, u32 size);
+
+ [[nodiscard]] BufferId FindBuffer(VAddr cpu_addr, u32 size);
+
+ [[nodiscard]] OverlapResult ResolveOverlaps(VAddr cpu_addr, u32 wanted_size);
+
+ void JoinOverlap(BufferId new_buffer_id, BufferId overlap_id, bool accumulate_stream_score);
+
+ [[nodiscard]] BufferId CreateBuffer(VAddr cpu_addr, u32 wanted_size);
+
+ void Register(BufferId buffer_id);
+
+ void Unregister(BufferId buffer_id);
+
+ template <bool insert>
+ void ChangeRegister(BufferId buffer_id);
+
+ void TouchBuffer(Buffer& buffer, BufferId buffer_id) noexcept;
+
+ bool SynchronizeBuffer(Buffer& buffer, VAddr cpu_addr, u32 size);
+
+ bool SynchronizeBufferImpl(Buffer& buffer, VAddr cpu_addr, u32 size);
+
+ bool SynchronizeBufferNoModified(Buffer& buffer, VAddr cpu_addr, u32 size);
+
+ void UploadMemory(Buffer& buffer, u64 total_size_bytes, u64 largest_copy,
+ std::span<BufferCopy> copies);
+
+ void ImmediateUploadMemory(Buffer& buffer, u64 largest_copy,
+ std::span<const BufferCopy> copies);
+
+ void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
+
+ void DownloadBufferMemory(Buffer& buffer_id);
+
+ void DownloadBufferMemory(Buffer& buffer_id, VAddr cpu_addr, u64 size);
+
+ void DeleteBuffer(BufferId buffer_id, bool do_not_mark = false);
+
+ void NotifyBufferDeletion();
+
+ [[nodiscard]] Binding StorageBufferBinding(GPUVAddr ssbo_addr, u32 cbuf_index,
+ bool is_written) const;
+
+ [[nodiscard]] TextureBufferBinding GetTextureBufferBinding(GPUVAddr gpu_addr, u32 size,
+ PixelFormat format);
+
+ [[nodiscard]] std::span<const u8> ImmediateBufferWithData(VAddr cpu_addr, size_t size);
+
+ [[nodiscard]] std::span<u8> ImmediateBuffer(size_t wanted_capacity);
+
+ [[nodiscard]] bool HasFastUniformBufferBound(size_t stage, u32 binding_index) const noexcept;
+
+ void ClearDownload(IntervalType subtract_interval);
+
+ VideoCore::RasterizerInterface& rasterizer;
+ Core::Memory::Memory& cpu_memory;
+
+ SlotVector<Buffer> slot_buffers;
+ DelayedDestructionRing<Buffer, 8> delayed_destruction_ring;
+
+ const Tegra::Engines::DrawManager::IndirectParams* current_draw_indirect{};
+
+ u32 last_index_count = 0;
+
+ Binding index_buffer;
+ std::array<Binding, NUM_VERTEX_BUFFERS> vertex_buffers;
+ std::array<std::array<Binding, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES> uniform_buffers;
+ std::array<std::array<Binding, NUM_STORAGE_BUFFERS>, NUM_STAGES> storage_buffers;
+ std::array<std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS>, NUM_STAGES> texture_buffers;
+ std::array<Binding, NUM_TRANSFORM_FEEDBACK_BUFFERS> transform_feedback_buffers;
+ Binding count_buffer_binding;
+ Binding indirect_buffer_binding;
+
+ std::array<Binding, NUM_COMPUTE_UNIFORM_BUFFERS> compute_uniform_buffers;
+ std::array<Binding, NUM_STORAGE_BUFFERS> compute_storage_buffers;
+ std::array<TextureBufferBinding, NUM_TEXTURE_BUFFERS> compute_texture_buffers;
+
+ std::array<u32, NUM_STAGES> enabled_uniform_buffer_masks{};
+ u32 enabled_compute_uniform_buffer_mask = 0;
+
+ const UniformBufferSizes* uniform_buffer_sizes{};
+ const ComputeUniformBufferSizes* compute_uniform_buffer_sizes{};
+
+ std::array<u32, NUM_STAGES> enabled_storage_buffers{};
+ std::array<u32, NUM_STAGES> written_storage_buffers{};
+ u32 enabled_compute_storage_buffers = 0;
+ u32 written_compute_storage_buffers = 0;
+
+ std::array<u32, NUM_STAGES> enabled_texture_buffers{};
+ std::array<u32, NUM_STAGES> written_texture_buffers{};
+ std::array<u32, NUM_STAGES> image_texture_buffers{};
+ u32 enabled_compute_texture_buffers = 0;
+ u32 written_compute_texture_buffers = 0;
+ u32 image_compute_texture_buffers = 0;
+
+ std::array<u32, 16> uniform_cache_hits{};
+ std::array<u32, 16> uniform_cache_shots{};
+
+ u32 uniform_buffer_skip_cache_size = DEFAULT_SKIP_CACHE_SIZE;
+
+ bool has_deleted_buffers = false;
+
+ std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS, std::array<u32, NUM_STAGES>, Empty>
+ dirty_uniform_buffers{};
+ std::conditional_t<IS_OPENGL, std::array<u32, NUM_STAGES>, Empty> fast_bound_uniform_buffers{};
+ std::conditional_t<HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS,
+ std::array<std::array<u32, NUM_GRAPHICS_UNIFORM_BUFFERS>, NUM_STAGES>, Empty>
+ uniform_buffer_binding_sizes{};
+
+ MemoryTracker memory_tracker;
+ IntervalSet uncommitted_ranges;
+ IntervalSet common_ranges;
+ IntervalSet cached_ranges;
+ std::deque<IntervalSet> committed_ranges;
+
+ // Async Buffers
+ OverlapCounter async_downloads;
+ std::deque<std::optional<Async_Buffer>> async_buffers;
+ std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
+ std::optional<Async_Buffer> current_buffer;
+
+ std::deque<Async_Buffer> async_buffers_death_ring;
+
+ size_t immediate_buffer_capacity = 0;
+ Common::ScratchBuffer<u8> immediate_buffer_alloc;
+
+ struct LRUItemParams {
+ using ObjectType = BufferId;
+ using TickType = u64;
+ };
+ Common::LeastRecentlyUsedCache<LRUItemParams> lru_cache;
+ u64 frame_tick = 0;
+ u64 total_used_memory = 0;
+ u64 minimum_memory = 0;
+ u64 critical_memory = 0;
+ BufferId inline_buffer_id;
+
+ std::array<BufferId, ((1ULL << 39) >> CACHING_PAGEBITS)> page_table;
+ std::vector<u8> tmp_buffer;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/memory_tracker_base.h b/src/video_core/buffer_cache/memory_tracker_base.h
new file mode 100644
index 000000000..6036b21c9
--- /dev/null
+++ b/src/video_core/buffer_cache/memory_tracker_base.h
@@ -0,0 +1,299 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <algorithm>
+#include <bit>
+#include <deque>
+#include <limits>
+#include <type_traits>
+#include <unordered_set>
+#include <utility>
+
+#include "common/alignment.h"
+#include "common/common_types.h"
+#include "video_core/buffer_cache/word_manager.h"
+
+namespace VideoCommon {
+
+template <class RasterizerInterface>
+class MemoryTrackerBase {
+ static constexpr size_t MAX_CPU_PAGE_BITS = 39;
+ static constexpr size_t HIGHER_PAGE_BITS = 22;
+ static constexpr size_t HIGHER_PAGE_SIZE = 1ULL << HIGHER_PAGE_BITS;
+ static constexpr size_t HIGHER_PAGE_MASK = HIGHER_PAGE_SIZE - 1ULL;
+ static constexpr size_t NUM_HIGH_PAGES = 1ULL << (MAX_CPU_PAGE_BITS - HIGHER_PAGE_BITS);
+ static constexpr size_t MANAGER_POOL_SIZE = 32;
+ static constexpr size_t WORDS_STACK_NEEDED = HIGHER_PAGE_SIZE / BYTES_PER_WORD;
+ using Manager = WordManager<RasterizerInterface, WORDS_STACK_NEEDED>;
+
+public:
+ MemoryTrackerBase(RasterizerInterface& rasterizer_) : rasterizer{&rasterizer_} {}
+ ~MemoryTrackerBase() = default;
+
+ /// Returns the inclusive CPU modified range in a begin end pair
+ [[nodiscard]] std::pair<u64, u64> ModifiedCpuRegion(VAddr query_cpu_addr,
+ u64 query_size) noexcept {
+ return IteratePairs<true>(
+ query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
+ return manager->template ModifiedRegion<Type::CPU>(offset, size);
+ });
+ }
+
+ /// Returns the inclusive GPU modified range in a begin end pair
+ [[nodiscard]] std::pair<u64, u64> ModifiedGpuRegion(VAddr query_cpu_addr,
+ u64 query_size) noexcept {
+ return IteratePairs<false>(
+ query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
+ return manager->template ModifiedRegion<Type::GPU>(offset, size);
+ });
+ }
+
+ /// Returns true if a region has been modified from the CPU
+ [[nodiscard]] bool IsRegionCpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
+ return IteratePages<true>(
+ query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
+ return manager->template IsRegionModified<Type::CPU>(offset, size);
+ });
+ }
+
+ /// Returns true if a region has been modified from the GPU
+ [[nodiscard]] bool IsRegionGpuModified(VAddr query_cpu_addr, u64 query_size) noexcept {
+ return IteratePages<false>(
+ query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
+ return manager->template IsRegionModified<Type::GPU>(offset, size);
+ });
+ }
+
+ /// Returns true if a region has been marked as Preflushable
+ [[nodiscard]] bool IsRegionPreflushable(VAddr query_cpu_addr, u64 query_size) noexcept {
+ return IteratePages<false>(
+ query_cpu_addr, query_size, [](Manager* manager, u64 offset, size_t size) {
+ return manager->template IsRegionModified<Type::Preflushable>(offset, size);
+ });
+ }
+
+ /// Mark region as CPU modified, notifying the rasterizer about this change
+ void MarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
+ IteratePages<true>(dirty_cpu_addr, query_size,
+ [](Manager* manager, u64 offset, size_t size) {
+ manager->template ChangeRegionState<Type::CPU, true>(
+ manager->GetCpuAddr() + offset, size);
+ });
+ }
+
+ /// Unmark region as CPU modified, notifying the rasterizer about this change
+ void UnmarkRegionAsCpuModified(VAddr dirty_cpu_addr, u64 query_size) {
+ IteratePages<true>(dirty_cpu_addr, query_size,
+ [](Manager* manager, u64 offset, size_t size) {
+ manager->template ChangeRegionState<Type::CPU, false>(
+ manager->GetCpuAddr() + offset, size);
+ });
+ }
+
+ /// Mark region as modified from the host GPU
+ void MarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
+ IteratePages<true>(dirty_cpu_addr, query_size,
+ [](Manager* manager, u64 offset, size_t size) {
+ manager->template ChangeRegionState<Type::GPU, true>(
+ manager->GetCpuAddr() + offset, size);
+ });
+ }
+
+ /// Mark region as modified from the host GPU
+ void MarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
+ IteratePages<true>(dirty_cpu_addr, query_size,
+ [](Manager* manager, u64 offset, size_t size) {
+ manager->template ChangeRegionState<Type::Preflushable, true>(
+ manager->GetCpuAddr() + offset, size);
+ });
+ }
+
+ /// Unmark region as modified from the host GPU
+ void UnmarkRegionAsGpuModified(VAddr dirty_cpu_addr, u64 query_size) noexcept {
+ IteratePages<true>(dirty_cpu_addr, query_size,
+ [](Manager* manager, u64 offset, size_t size) {
+ manager->template ChangeRegionState<Type::GPU, false>(
+ manager->GetCpuAddr() + offset, size);
+ });
+ }
+
+ /// Unmark region as modified from the host GPU
+ void UnmarkRegionAsPreflushable(VAddr dirty_cpu_addr, u64 query_size) noexcept {
+ IteratePages<true>(dirty_cpu_addr, query_size,
+ [](Manager* manager, u64 offset, size_t size) {
+ manager->template ChangeRegionState<Type::Preflushable, false>(
+ manager->GetCpuAddr() + offset, size);
+ });
+ }
+
+ /// Mark region as modified from the CPU
+ /// but don't mark it as modified until FlusHCachedWrites is called.
+ void CachedCpuWrite(VAddr dirty_cpu_addr, u64 query_size) {
+ IteratePages<true>(
+ dirty_cpu_addr, query_size, [this](Manager* manager, u64 offset, size_t size) {
+ const VAddr cpu_address = manager->GetCpuAddr() + offset;
+ manager->template ChangeRegionState<Type::CachedCPU, true>(cpu_address, size);
+ cached_pages.insert(static_cast<u32>(cpu_address >> HIGHER_PAGE_BITS));
+ });
+ }
+
+ /// Flushes cached CPU writes, and notify the rasterizer about the deltas
+ void FlushCachedWrites(VAddr query_cpu_addr, u64 query_size) noexcept {
+ IteratePages<false>(query_cpu_addr, query_size,
+ [](Manager* manager, [[maybe_unused]] u64 offset,
+ [[maybe_unused]] size_t size) { manager->FlushCachedWrites(); });
+ }
+
+ void FlushCachedWrites() noexcept {
+ for (auto id : cached_pages) {
+ top_tier[id]->FlushCachedWrites();
+ }
+ cached_pages.clear();
+ }
+
+ /// Call 'func' for each CPU modified range and unmark those pages as CPU modified
+ template <typename Func>
+ void ForEachUploadRange(VAddr query_cpu_range, u64 query_size, Func&& func) {
+ IteratePages<true>(query_cpu_range, query_size,
+ [&func](Manager* manager, u64 offset, size_t size) {
+ manager->template ForEachModifiedRange<Type::CPU, true>(
+ manager->GetCpuAddr() + offset, size, func);
+ });
+ }
+
+ /// Call 'func' for each GPU modified range and unmark those pages as GPU modified
+ template <typename Func>
+ void ForEachDownloadRange(VAddr query_cpu_range, u64 query_size, bool clear, Func&& func) {
+ IteratePages<false>(query_cpu_range, query_size,
+ [&func, clear](Manager* manager, u64 offset, size_t size) {
+ if (clear) {
+ manager->template ForEachModifiedRange<Type::GPU, true>(
+ manager->GetCpuAddr() + offset, size, func);
+ } else {
+ manager->template ForEachModifiedRange<Type::GPU, false>(
+ manager->GetCpuAddr() + offset, size, func);
+ }
+ });
+ }
+
+ template <typename Func>
+ void ForEachDownloadRangeAndClear(VAddr query_cpu_range, u64 query_size, Func&& func) {
+ IteratePages<false>(query_cpu_range, query_size,
+ [&func](Manager* manager, u64 offset, size_t size) {
+ manager->template ForEachModifiedRange<Type::GPU, true>(
+ manager->GetCpuAddr() + offset, size, func);
+ });
+ }
+
+private:
+ template <bool create_region_on_fail, typename Func>
+ bool IteratePages(VAddr cpu_address, size_t size, Func&& func) {
+ using FuncReturn = typename std::invoke_result<Func, Manager*, u64, size_t>::type;
+ static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
+ std::size_t remaining_size{size};
+ std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS};
+ u64 page_offset{cpu_address & HIGHER_PAGE_MASK};
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min<std::size_t>(HIGHER_PAGE_SIZE - page_offset, remaining_size)};
+ auto* manager{top_tier[page_index]};
+ if (manager) {
+ if constexpr (BOOL_BREAK) {
+ if (func(manager, page_offset, copy_amount)) {
+ return true;
+ }
+ } else {
+ func(manager, page_offset, copy_amount);
+ }
+ } else if constexpr (create_region_on_fail) {
+ CreateRegion(page_index);
+ manager = top_tier[page_index];
+ if constexpr (BOOL_BREAK) {
+ if (func(manager, page_offset, copy_amount)) {
+ return true;
+ }
+ } else {
+ func(manager, page_offset, copy_amount);
+ }
+ }
+ page_index++;
+ page_offset = 0;
+ remaining_size -= copy_amount;
+ }
+ return false;
+ }
+
+ template <bool create_region_on_fail, typename Func>
+ std::pair<u64, u64> IteratePairs(VAddr cpu_address, size_t size, Func&& func) {
+ std::size_t remaining_size{size};
+ std::size_t page_index{cpu_address >> HIGHER_PAGE_BITS};
+ u64 page_offset{cpu_address & HIGHER_PAGE_MASK};
+ u64 begin = std::numeric_limits<u64>::max();
+ u64 end = 0;
+ while (remaining_size > 0) {
+ const std::size_t copy_amount{
+ std::min<std::size_t>(HIGHER_PAGE_SIZE - page_offset, remaining_size)};
+ auto* manager{top_tier[page_index]};
+ const auto execute = [&] {
+ auto [new_begin, new_end] = func(manager, page_offset, copy_amount);
+ if (new_begin != 0 || new_end != 0) {
+ const u64 base_address = page_index << HIGHER_PAGE_BITS;
+ begin = std::min(new_begin + base_address, begin);
+ end = std::max(new_end + base_address, end);
+ }
+ };
+ if (manager) {
+ execute();
+ } else if constexpr (create_region_on_fail) {
+ CreateRegion(page_index);
+ manager = top_tier[page_index];
+ execute();
+ }
+ page_index++;
+ page_offset = 0;
+ remaining_size -= copy_amount;
+ }
+ if (begin < end) {
+ return std::make_pair(begin, end);
+ } else {
+ return std::make_pair(0ULL, 0ULL);
+ }
+ }
+
+ void CreateRegion(std::size_t page_index) {
+ const VAddr base_cpu_addr = page_index << HIGHER_PAGE_BITS;
+ top_tier[page_index] = GetNewManager(base_cpu_addr);
+ }
+
+ Manager* GetNewManager(VAddr base_cpu_addess) {
+ const auto on_return = [&] {
+ auto* new_manager = free_managers.front();
+ new_manager->SetCpuAddress(base_cpu_addess);
+ free_managers.pop_front();
+ return new_manager;
+ };
+ if (!free_managers.empty()) {
+ return on_return();
+ }
+ manager_pool.emplace_back();
+ auto& last_pool = manager_pool.back();
+ for (size_t i = 0; i < MANAGER_POOL_SIZE; i++) {
+ new (&last_pool[i]) Manager(0, *rasterizer, HIGHER_PAGE_SIZE);
+ free_managers.push_back(&last_pool[i]);
+ }
+ return on_return();
+ }
+
+ std::deque<std::array<Manager, MANAGER_POOL_SIZE>> manager_pool;
+ std::deque<Manager*> free_managers;
+
+ std::array<Manager*, NUM_HIGH_PAGES> top_tier{};
+
+ std::unordered_set<u32> cached_pages;
+
+ RasterizerInterface* rasterizer = nullptr;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/buffer_cache/word_manager.h b/src/video_core/buffer_cache/word_manager.h
new file mode 100644
index 000000000..a336bde41
--- /dev/null
+++ b/src/video_core/buffer_cache/word_manager.h
@@ -0,0 +1,485 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <algorithm>
+#include <bit>
+#include <limits>
+#include <span>
+#include <utility>
+
+#include "common/alignment.h"
+#include "common/common_funcs.h"
+#include "common/common_types.h"
+#include "common/div_ceil.h"
+#include "core/memory.h"
+
+namespace VideoCommon {
+
+constexpr u64 PAGES_PER_WORD = 64;
+constexpr u64 BYTES_PER_PAGE = Core::Memory::YUZU_PAGESIZE;
+constexpr u64 BYTES_PER_WORD = PAGES_PER_WORD * BYTES_PER_PAGE;
+
+enum class Type {
+ CPU,
+ GPU,
+ CachedCPU,
+ Untracked,
+ Preflushable,
+};
+
+/// Vector tracking modified pages tightly packed with small vector optimization
+template <size_t stack_words = 1>
+struct WordsArray {
+ /// Returns the pointer to the words state
+ [[nodiscard]] const u64* Pointer(bool is_short) const noexcept {
+ return is_short ? stack.data() : heap;
+ }
+
+ /// Returns the pointer to the words state
+ [[nodiscard]] u64* Pointer(bool is_short) noexcept {
+ return is_short ? stack.data() : heap;
+ }
+
+ std::array<u64, stack_words> stack{}; ///< Small buffers storage
+ u64* heap; ///< Not-small buffers pointer to the storage
+};
+
+template <size_t stack_words = 1>
+struct Words {
+ explicit Words() = default;
+ explicit Words(u64 size_bytes_) : size_bytes{size_bytes_} {
+ num_words = Common::DivCeil(size_bytes, BYTES_PER_WORD);
+ if (IsShort()) {
+ cpu.stack.fill(~u64{0});
+ gpu.stack.fill(0);
+ cached_cpu.stack.fill(0);
+ untracked.stack.fill(~u64{0});
+ preflushable.stack.fill(0);
+ } else {
+ // Share allocation between CPU and GPU pages and set their default values
+ u64* const alloc = new u64[num_words * 5];
+ cpu.heap = alloc;
+ gpu.heap = alloc + num_words;
+ cached_cpu.heap = alloc + num_words * 2;
+ untracked.heap = alloc + num_words * 3;
+ preflushable.heap = alloc + num_words * 4;
+ std::fill_n(cpu.heap, num_words, ~u64{0});
+ std::fill_n(gpu.heap, num_words, 0);
+ std::fill_n(cached_cpu.heap, num_words, 0);
+ std::fill_n(untracked.heap, num_words, ~u64{0});
+ std::fill_n(preflushable.heap, num_words, 0);
+ }
+ // Clean up tailing bits
+ const u64 last_word_size = size_bytes % BYTES_PER_WORD;
+ const u64 last_local_page = Common::DivCeil(last_word_size, BYTES_PER_PAGE);
+ const u64 shift = (PAGES_PER_WORD - last_local_page) % PAGES_PER_WORD;
+ const u64 last_word = (~u64{0} << shift) >> shift;
+ cpu.Pointer(IsShort())[NumWords() - 1] = last_word;
+ untracked.Pointer(IsShort())[NumWords() - 1] = last_word;
+ }
+
+ ~Words() {
+ Release();
+ }
+
+ Words& operator=(Words&& rhs) noexcept {
+ Release();
+ size_bytes = rhs.size_bytes;
+ num_words = rhs.num_words;
+ cpu = rhs.cpu;
+ gpu = rhs.gpu;
+ cached_cpu = rhs.cached_cpu;
+ untracked = rhs.untracked;
+ preflushable = rhs.preflushable;
+ rhs.cpu.heap = nullptr;
+ return *this;
+ }
+
+ Words(Words&& rhs) noexcept
+ : size_bytes{rhs.size_bytes}, num_words{rhs.num_words}, cpu{rhs.cpu}, gpu{rhs.gpu},
+ cached_cpu{rhs.cached_cpu}, untracked{rhs.untracked}, preflushable{rhs.preflushable} {
+ rhs.cpu.heap = nullptr;
+ }
+
+ Words& operator=(const Words&) = delete;
+ Words(const Words&) = delete;
+
+ /// Returns true when the buffer fits in the small vector optimization
+ [[nodiscard]] bool IsShort() const noexcept {
+ return num_words <= stack_words;
+ }
+
+ /// Returns the number of words of the buffer
+ [[nodiscard]] size_t NumWords() const noexcept {
+ return num_words;
+ }
+
+ /// Release buffer resources
+ void Release() {
+ if (!IsShort()) {
+ // CPU written words is the base for the heap allocation
+ delete[] cpu.heap;
+ }
+ }
+
+ template <Type type>
+ std::span<u64> Span() noexcept {
+ if constexpr (type == Type::CPU) {
+ return std::span<u64>(cpu.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::GPU) {
+ return std::span<u64>(gpu.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::CachedCPU) {
+ return std::span<u64>(cached_cpu.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::Untracked) {
+ return std::span<u64>(untracked.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::Preflushable) {
+ return std::span<u64>(preflushable.Pointer(IsShort()), num_words);
+ }
+ }
+
+ template <Type type>
+ std::span<const u64> Span() const noexcept {
+ if constexpr (type == Type::CPU) {
+ return std::span<const u64>(cpu.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::GPU) {
+ return std::span<const u64>(gpu.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::CachedCPU) {
+ return std::span<const u64>(cached_cpu.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::Untracked) {
+ return std::span<const u64>(untracked.Pointer(IsShort()), num_words);
+ } else if constexpr (type == Type::Preflushable) {
+ return std::span<const u64>(preflushable.Pointer(IsShort()), num_words);
+ }
+ }
+
+ u64 size_bytes = 0;
+ size_t num_words = 0;
+ WordsArray<stack_words> cpu;
+ WordsArray<stack_words> gpu;
+ WordsArray<stack_words> cached_cpu;
+ WordsArray<stack_words> untracked;
+ WordsArray<stack_words> preflushable;
+};
+
+template <class RasterizerInterface, size_t stack_words = 1>
+class WordManager {
+public:
+ explicit WordManager(VAddr cpu_addr_, RasterizerInterface& rasterizer_, u64 size_bytes)
+ : cpu_addr{cpu_addr_}, rasterizer{&rasterizer_}, words{size_bytes} {}
+
+ explicit WordManager() = default;
+
+ void SetCpuAddress(VAddr new_cpu_addr) {
+ cpu_addr = new_cpu_addr;
+ }
+
+ VAddr GetCpuAddr() const {
+ return cpu_addr;
+ }
+
+ static u64 ExtractBits(u64 word, size_t page_start, size_t page_end) {
+ constexpr size_t number_bits = sizeof(u64) * 8;
+ const size_t limit_page_end = number_bits - std::min(page_end, number_bits);
+ u64 bits = (word >> page_start) << page_start;
+ bits = (bits << limit_page_end) >> limit_page_end;
+ return bits;
+ }
+
+ static std::pair<size_t, size_t> GetWordPage(VAddr address) {
+ const size_t converted_address = static_cast<size_t>(address);
+ const size_t word_number = converted_address / BYTES_PER_WORD;
+ const size_t amount_pages = converted_address % BYTES_PER_WORD;
+ return std::make_pair(word_number, amount_pages / BYTES_PER_PAGE);
+ }
+
+ template <typename Func>
+ void IterateWords(size_t offset, size_t size, Func&& func) const {
+ using FuncReturn = std::invoke_result_t<Func, std::size_t, u64>;
+ static constexpr bool BOOL_BREAK = std::is_same_v<FuncReturn, bool>;
+ const size_t start = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset), 0LL));
+ const size_t end = static_cast<size_t>(std::max<s64>(static_cast<s64>(offset + size), 0LL));
+ if (start >= SizeBytes() || end <= start) {
+ return;
+ }
+ auto [start_word, start_page] = GetWordPage(start);
+ auto [end_word, end_page] = GetWordPage(end + BYTES_PER_PAGE - 1ULL);
+ const size_t num_words = NumWords();
+ start_word = std::min(start_word, num_words);
+ end_word = std::min(end_word, num_words);
+ const size_t diff = end_word - start_word;
+ end_word += (end_page + PAGES_PER_WORD - 1ULL) / PAGES_PER_WORD;
+ end_word = std::min(end_word, num_words);
+ end_page += diff * PAGES_PER_WORD;
+ constexpr u64 base_mask{~0ULL};
+ for (size_t word_index = start_word; word_index < end_word; word_index++) {
+ const u64 mask = ExtractBits(base_mask, start_page, end_page);
+ start_page = 0;
+ end_page -= PAGES_PER_WORD;
+ if constexpr (BOOL_BREAK) {
+ if (func(word_index, mask)) {
+ return;
+ }
+ } else {
+ func(word_index, mask);
+ }
+ }
+ }
+
+ template <typename Func>
+ void IteratePages(u64 mask, Func&& func) const {
+ size_t offset = 0;
+ while (mask != 0) {
+ const size_t empty_bits = std::countr_zero(mask);
+ offset += empty_bits;
+ mask = mask >> empty_bits;
+
+ const size_t continuous_bits = std::countr_one(mask);
+ func(offset, continuous_bits);
+ mask = continuous_bits < PAGES_PER_WORD ? (mask >> continuous_bits) : 0;
+ offset += continuous_bits;
+ }
+ }
+
+ /**
+ * Change the state of a range of pages
+ *
+ * @param dirty_addr Base address to mark or unmark as modified
+ * @param size Size in bytes to mark or unmark as modified
+ */
+ template <Type type, bool enable>
+ void ChangeRegionState(u64 dirty_addr, u64 size) noexcept(type == Type::GPU) {
+ std::span<u64> state_words = words.template Span<type>();
+ [[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
+ [[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
+ IterateWords(dirty_addr - cpu_addr, size, [&](size_t index, u64 mask) {
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ NotifyRasterizer<!enable>(index, untracked_words[index], mask);
+ }
+ if constexpr (enable) {
+ state_words[index] |= mask;
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ untracked_words[index] |= mask;
+ }
+ if constexpr (type == Type::CPU) {
+ cached_words[index] &= ~mask;
+ }
+ } else {
+ if constexpr (type == Type::CPU) {
+ const u64 word = state_words[index] & mask;
+ cached_words[index] &= ~word;
+ }
+ state_words[index] &= ~mask;
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ untracked_words[index] &= ~mask;
+ }
+ }
+ });
+ }
+
+ /**
+ * Loop over each page in the given range, turn off those bits and notify the rasterizer if
+ * needed. Call the given function on each turned off range.
+ *
+ * @param query_cpu_range Base CPU address to loop over
+ * @param size Size in bytes of the CPU range to loop over
+ * @param func Function to call for each turned off region
+ */
+ template <Type type, bool clear, typename Func>
+ void ForEachModifiedRange(VAddr query_cpu_range, s64 size, Func&& func) {
+ static_assert(type != Type::Untracked);
+
+ std::span<u64> state_words = words.template Span<type>();
+ [[maybe_unused]] std::span<u64> untracked_words = words.template Span<Type::Untracked>();
+ [[maybe_unused]] std::span<u64> cached_words = words.template Span<Type::CachedCPU>();
+ const size_t offset = query_cpu_range - cpu_addr;
+ bool pending = false;
+ size_t pending_offset{};
+ size_t pending_pointer{};
+ const auto release = [&]() {
+ func(cpu_addr + pending_offset * BYTES_PER_PAGE,
+ (pending_pointer - pending_offset) * BYTES_PER_PAGE);
+ };
+ IterateWords(offset, size, [&](size_t index, u64 mask) {
+ if constexpr (type == Type::GPU) {
+ mask &= ~untracked_words[index];
+ }
+ const u64 word = state_words[index] & mask;
+ if constexpr (clear) {
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ NotifyRasterizer<true>(index, untracked_words[index], mask);
+ }
+ state_words[index] &= ~mask;
+ if constexpr (type == Type::CPU || type == Type::CachedCPU) {
+ untracked_words[index] &= ~mask;
+ }
+ if constexpr (type == Type::CPU) {
+ cached_words[index] &= ~word;
+ }
+ }
+ const size_t base_offset = index * PAGES_PER_WORD;
+ IteratePages(word, [&](size_t pages_offset, size_t pages_size) {
+ const auto reset = [&]() {
+ pending_offset = base_offset + pages_offset;
+ pending_pointer = base_offset + pages_offset + pages_size;
+ };
+ if (!pending) {
+ reset();
+ pending = true;
+ return;
+ }
+ if (pending_pointer == base_offset + pages_offset) {
+ pending_pointer += pages_size;
+ return;
+ }
+ release();
+ reset();
+ });
+ });
+ if (pending) {
+ release();
+ }
+ }
+
+ /**
+ * Returns true when a region has been modified
+ *
+ * @param offset Offset in bytes from the start of the buffer
+ * @param size Size in bytes of the region to query for modifications
+ */
+ template <Type type>
+ [[nodiscard]] bool IsRegionModified(u64 offset, u64 size) const noexcept {
+ static_assert(type != Type::Untracked);
+
+ const std::span<const u64> state_words = words.template Span<type>();
+ [[maybe_unused]] const std::span<const u64> untracked_words =
+ words.template Span<Type::Untracked>();
+ bool result = false;
+ IterateWords(offset, size, [&](size_t index, u64 mask) {
+ if constexpr (type == Type::GPU) {
+ mask &= ~untracked_words[index];
+ }
+ const u64 word = state_words[index] & mask;
+ if (word != 0) {
+ result = true;
+ return true;
+ }
+ return false;
+ });
+ return result;
+ }
+
+ /**
+ * Returns a begin end pair with the inclusive modified region
+ *
+ * @param offset Offset in bytes from the start of the buffer
+ * @param size Size in bytes of the region to query for modifications
+ */
+ template <Type type>
+ [[nodiscard]] std::pair<u64, u64> ModifiedRegion(u64 offset, u64 size) const noexcept {
+ static_assert(type != Type::Untracked);
+ const std::span<const u64> state_words = words.template Span<type>();
+ [[maybe_unused]] const std::span<const u64> untracked_words =
+ words.template Span<Type::Untracked>();
+ u64 begin = std::numeric_limits<u64>::max();
+ u64 end = 0;
+ IterateWords(offset, size, [&](size_t index, u64 mask) {
+ if constexpr (type == Type::GPU) {
+ mask &= ~untracked_words[index];
+ }
+ const u64 word = state_words[index] & mask;
+ if (word == 0) {
+ return;
+ }
+ const u64 local_page_begin = std::countr_zero(word);
+ const u64 local_page_end = PAGES_PER_WORD - std::countl_zero(word);
+ const u64 page_index = index * PAGES_PER_WORD;
+ begin = std::min(begin, page_index + local_page_begin);
+ end = page_index + local_page_end;
+ });
+ static constexpr std::pair<u64, u64> EMPTY{0, 0};
+ return begin < end ? std::make_pair(begin * BYTES_PER_PAGE, end * BYTES_PER_PAGE) : EMPTY;
+ }
+
+ /// Returns the number of words of the manager
+ [[nodiscard]] size_t NumWords() const noexcept {
+ return words.NumWords();
+ }
+
+ /// Returns the size in bytes of the manager
+ [[nodiscard]] u64 SizeBytes() const noexcept {
+ return words.size_bytes;
+ }
+
+ /// Returns true when the buffer fits in the small vector optimization
+ [[nodiscard]] bool IsShort() const noexcept {
+ return words.IsShort();
+ }
+
+ void FlushCachedWrites() noexcept {
+ const u64 num_words = NumWords();
+ u64* const cached_words = Array<Type::CachedCPU>();
+ u64* const untracked_words = Array<Type::Untracked>();
+ u64* const cpu_words = Array<Type::CPU>();
+ for (u64 word_index = 0; word_index < num_words; ++word_index) {
+ const u64 cached_bits = cached_words[word_index];
+ NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits);
+ untracked_words[word_index] |= cached_bits;
+ cpu_words[word_index] |= cached_bits;
+ cached_words[word_index] = 0;
+ }
+ }
+
+private:
+ template <Type type>
+ u64* Array() noexcept {
+ if constexpr (type == Type::CPU) {
+ return words.cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::GPU) {
+ return words.gpu.Pointer(IsShort());
+ } else if constexpr (type == Type::CachedCPU) {
+ return words.cached_cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::Untracked) {
+ return words.untracked.Pointer(IsShort());
+ }
+ }
+
+ template <Type type>
+ const u64* Array() const noexcept {
+ if constexpr (type == Type::CPU) {
+ return words.cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::GPU) {
+ return words.gpu.Pointer(IsShort());
+ } else if constexpr (type == Type::CachedCPU) {
+ return words.cached_cpu.Pointer(IsShort());
+ } else if constexpr (type == Type::Untracked) {
+ return words.untracked.Pointer(IsShort());
+ }
+ }
+
+ /**
+ * Notify rasterizer about changes in the CPU tracking state of a word in the buffer
+ *
+ * @param word_index Index to the word to notify to the rasterizer
+ * @param current_bits Current state of the word
+ * @param new_bits New state of the word
+ *
+ * @tparam add_to_rasterizer True when the rasterizer should start tracking the new pages
+ */
+ template <bool add_to_rasterizer>
+ void NotifyRasterizer(u64 word_index, u64 current_bits, u64 new_bits) const {
+ u64 changed_bits = (add_to_rasterizer ? current_bits : ~current_bits) & new_bits;
+ VAddr addr = cpu_addr + word_index * BYTES_PER_WORD;
+ IteratePages(changed_bits, [&](size_t offset, size_t size) {
+ rasterizer->UpdatePagesCachedCount(addr + offset * BYTES_PER_PAGE,
+ size * BYTES_PER_PAGE, add_to_rasterizer ? 1 : -1);
+ });
+ }
+
+ VAddr cpu_addr = 0;
+ RasterizerInterface* rasterizer = nullptr;
+ Words<stack_words> words;
+};
+
+} // namespace VideoCommon
diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp
index 4e75f33ca..ab4f4d407 100644
--- a/src/video_core/compatible_formats.cpp
+++ b/src/video_core/compatible_formats.cpp
@@ -126,15 +126,14 @@ constexpr std::array VIEW_CLASS_ASTC_8x8_RGBA{
PixelFormat::ASTC_2D_8X8_SRGB,
};
-// Missing formats:
-// PixelFormat::ASTC_2D_10X5_UNORM
-// PixelFormat::ASTC_2D_10X5_SRGB
-
-// Missing formats:
-// PixelFormat::ASTC_2D_10X6_SRGB
+constexpr std::array VIEW_CLASS_ASTC_10x5_RGBA{
+ PixelFormat::ASTC_2D_10X5_UNORM,
+ PixelFormat::ASTC_2D_10X5_SRGB,
+};
constexpr std::array VIEW_CLASS_ASTC_10x6_RGBA{
PixelFormat::ASTC_2D_10X6_UNORM,
+ PixelFormat::ASTC_2D_10X6_SRGB,
};
constexpr std::array VIEW_CLASS_ASTC_10x8_RGBA{
@@ -147,9 +146,10 @@ constexpr std::array VIEW_CLASS_ASTC_10x10_RGBA{
PixelFormat::ASTC_2D_10X10_SRGB,
};
-// Missing formats
-// ASTC_2D_12X10_UNORM,
-// ASTC_2D_12X10_SRGB,
+constexpr std::array VIEW_CLASS_ASTC_12x10_RGBA{
+ PixelFormat::ASTC_2D_12X10_UNORM,
+ PixelFormat::ASTC_2D_12X10_SRGB,
+};
constexpr std::array VIEW_CLASS_ASTC_12x12_RGBA{
PixelFormat::ASTC_2D_12X12_UNORM,
@@ -229,9 +229,11 @@ constexpr Table MakeViewTable() {
EnableRange(view, VIEW_CLASS_ASTC_6x6_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_8x5_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_8x8_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_10x5_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x6_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x8_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_10x10_RGBA);
+ EnableRange(view, VIEW_CLASS_ASTC_12x10_RGBA);
EnableRange(view, VIEW_CLASS_ASTC_12x12_RGBA);
return view;
}
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 0932fadc2..2f986097f 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -223,6 +223,9 @@ void Maxwell3D::ProcessMacro(u32 method, const u32* base_start, u32 amount, bool
}
void Maxwell3D::RefreshParametersImpl() {
+ if (!Settings::IsGPULevelHigh()) {
+ return;
+ }
size_t current_index = 0;
for (auto& segment : macro_segments) {
if (segment.first == 0) {
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index e68850dc5..ebe5536de 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -223,7 +223,7 @@ void MaxwellDMA::CopyBlockLinearToPitch() {
write_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(src_operand.address, read_buffer.data(), src_size);
- memory_manager.ReadBlockUnsafe(dst_operand.address, write_buffer.data(), dst_size);
+ memory_manager.ReadBlock(dst_operand.address, write_buffer.data(), dst_size);
UnswizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
src_params.origin.y, x_elements, regs.line_count, block_height, block_depth,
@@ -288,11 +288,7 @@ void MaxwellDMA::CopyPitchToBlockLinear() {
write_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(regs.offset_in, read_buffer.data(), src_size);
- if (Settings::IsGPULevelExtreme()) {
- memory_manager.ReadBlock(regs.offset_out, write_buffer.data(), dst_size);
- } else {
- memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
- }
+ memory_manager.ReadBlockUnsafe(regs.offset_out, write_buffer.data(), dst_size);
// If the input is linear and the output is tiled, swizzle the input and copy it over.
SwizzleSubrect(write_buffer, read_buffer, bytes_per_pixel, width, height, depth, x_offset,
diff --git a/src/video_core/engines/sw_blitter/blitter.cpp b/src/video_core/engines/sw_blitter/blitter.cpp
index 3c9f38559..ff88cd03d 100644
--- a/src/video_core/engines/sw_blitter/blitter.cpp
+++ b/src/video_core/engines/sw_blitter/blitter.cpp
@@ -5,6 +5,7 @@
#include <cmath>
#include <vector>
+#include "common/scratch_buffer.h"
#include "video_core/engines/sw_blitter/blitter.h"
#include "video_core/engines/sw_blitter/converter.h"
#include "video_core/memory_manager.h"
@@ -112,11 +113,11 @@ void Bilinear(std::span<const f32> input, std::span<f32> output, size_t src_widt
} // namespace
struct SoftwareBlitEngine::BlitEngineImpl {
- std::vector<u8> tmp_buffer;
- std::vector<u8> src_buffer;
- std::vector<u8> dst_buffer;
- std::vector<f32> intermediate_src;
- std::vector<f32> intermediate_dst;
+ Common::ScratchBuffer<u8> tmp_buffer;
+ Common::ScratchBuffer<u8> src_buffer;
+ Common::ScratchBuffer<u8> dst_buffer;
+ Common::ScratchBuffer<f32> intermediate_src;
+ Common::ScratchBuffer<f32> intermediate_dst;
ConverterFactory converter_factory;
};
@@ -158,14 +159,14 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
const auto src_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format));
const auto dst_bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(dst.format));
const size_t src_size = get_surface_size(src, src_bytes_per_pixel);
- impl->tmp_buffer.resize(src_size);
+ impl->tmp_buffer.resize_destructive(src_size);
memory_manager.ReadBlock(src.Address(), impl->tmp_buffer.data(), src_size);
const size_t src_copy_size = src_extent_x * src_extent_y * src_bytes_per_pixel;
const size_t dst_copy_size = dst_extent_x * dst_extent_y * dst_bytes_per_pixel;
- impl->src_buffer.resize(src_copy_size);
+ impl->src_buffer.resize_destructive(src_copy_size);
const bool no_passthrough =
src.format != dst.format || src_extent_x != dst_extent_x || src_extent_y != dst_extent_y;
@@ -177,8 +178,10 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
const auto convertion_phase_ir = [&]() {
auto* input_converter = impl->converter_factory.GetFormatConverter(src.format);
- impl->intermediate_src.resize((src_copy_size / src_bytes_per_pixel) * ir_components);
- impl->intermediate_dst.resize((dst_copy_size / dst_bytes_per_pixel) * ir_components);
+ impl->intermediate_src.resize_destructive((src_copy_size / src_bytes_per_pixel) *
+ ir_components);
+ impl->intermediate_dst.resize_destructive((dst_copy_size / dst_bytes_per_pixel) *
+ ir_components);
input_converter->ConvertTo(impl->src_buffer, impl->intermediate_src);
if (config.filter != Fermi2D::Filter::Bilinear) {
@@ -195,7 +198,7 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
// Do actual Blit
- impl->dst_buffer.resize(dst_copy_size);
+ impl->dst_buffer.resize_destructive(dst_copy_size);
if (src.linear == Fermi2D::MemoryLayout::BlockLinear) {
UnswizzleSubrect(impl->src_buffer, impl->tmp_buffer, src_bytes_per_pixel, src.width,
src.height, src.depth, config.src_x0, config.src_y0, src_extent_x,
@@ -218,7 +221,7 @@ bool SoftwareBlitEngine::Blit(Fermi2D::Surface& src, Fermi2D::Surface& dst,
}
const size_t dst_size = get_surface_size(dst, dst_bytes_per_pixel);
- impl->tmp_buffer.resize(dst_size);
+ impl->tmp_buffer.resize_destructive(dst_size);
memory_manager.ReadBlock(dst.Address(), impl->tmp_buffer.data(), dst_size);
if (dst.linear == Fermi2D::MemoryLayout::BlockLinear) {
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index c390ac91b..35d699bbf 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -4,13 +4,20 @@
#pragma once
#include <algorithm>
+#include <condition_variable>
#include <cstring>
#include <deque>
#include <functional>
#include <memory>
+#include <mutex>
+#include <thread>
#include <queue>
#include "common/common_types.h"
+#include "common/microprofile.h"
+#include "common/scope_exit.h"
+#include "common/settings.h"
+#include "common/thread.h"
#include "video_core/delayed_destruction_ring.h"
#include "video_core/gpu.h"
#include "video_core/host1x/host1x.h"
@@ -23,15 +30,26 @@ class FenceBase {
public:
explicit FenceBase(bool is_stubbed_) : is_stubbed{is_stubbed_} {}
+ bool IsStubbed() const {
+ return is_stubbed;
+ }
+
protected:
bool is_stubbed;
};
-template <typename TFence, typename TTextureCache, typename TTBufferCache, typename TQueryCache>
+template <typename Traits>
class FenceManager {
+ using TFence = typename Traits::FenceType;
+ using TTextureCache = typename Traits::TextureCacheType;
+ using TBufferCache = typename Traits::BufferCacheType;
+ using TQueryCache = typename Traits::QueryCacheType;
+ static constexpr bool can_async_check = Traits::HAS_ASYNC_CHECK;
+
public:
/// Notify the fence manager about a new frame
void TickFrame() {
+ std::unique_lock lock(ring_guard);
delayed_destruction_ring.Tick();
}
@@ -41,22 +59,43 @@ public:
buffer_cache.AccumulateFlushes();
}
+ void SignalReference() {
+ std::function<void()> do_nothing([] {});
+ SignalFence(std::move(do_nothing));
+ }
+
void SyncOperation(std::function<void()>&& func) {
uncommitted_operations.emplace_back(std::move(func));
}
void SignalFence(std::function<void()>&& func) {
- TryReleasePendingFences();
+ rasterizer.InvalidateGPUCache();
+ bool delay_fence = Settings::IsGPULevelHigh();
+ if constexpr (!can_async_check) {
+ TryReleasePendingFences<false>();
+ }
const bool should_flush = ShouldFlush();
CommitAsyncFlushes();
- uncommitted_operations.emplace_back(std::move(func));
- CommitOperations();
TFence new_fence = CreateFence(!should_flush);
- fences.push(new_fence);
+ if constexpr (can_async_check) {
+ guard.lock();
+ }
+ if (delay_fence) {
+ uncommitted_operations.emplace_back(std::move(func));
+ }
+ pending_operations.emplace_back(std::move(uncommitted_operations));
QueueFence(new_fence);
+ if (!delay_fence) {
+ func();
+ }
+ fences.push(std::move(new_fence));
if (should_flush) {
rasterizer.FlushCommands();
}
+ if constexpr (can_async_check) {
+ guard.unlock();
+ cv.notify_all();
+ }
}
void SignalSyncPoint(u32 value) {
@@ -66,29 +105,30 @@ public:
}
void WaitPendingFences() {
- while (!fences.empty()) {
- TFence& current_fence = fences.front();
- if (ShouldWait()) {
- WaitFence(current_fence);
- }
- PopAsyncFlushes();
- auto operations = std::move(pending_operations.front());
- pending_operations.pop_front();
- for (auto& operation : operations) {
- operation();
- }
- PopFence();
+ if constexpr (!can_async_check) {
+ TryReleasePendingFences<true>();
}
}
protected:
explicit FenceManager(VideoCore::RasterizerInterface& rasterizer_, Tegra::GPU& gpu_,
- TTextureCache& texture_cache_, TTBufferCache& buffer_cache_,
+ TTextureCache& texture_cache_, TBufferCache& buffer_cache_,
TQueryCache& query_cache_)
: rasterizer{rasterizer_}, gpu{gpu_}, syncpoint_manager{gpu.Host1x().GetSyncpointManager()},
- texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {}
+ texture_cache{texture_cache_}, buffer_cache{buffer_cache_}, query_cache{query_cache_} {
+ if constexpr (can_async_check) {
+ fence_thread =
+ std::jthread([this](std::stop_token token) { ReleaseThreadFunc(token); });
+ }
+ }
- virtual ~FenceManager() = default;
+ virtual ~FenceManager() {
+ if constexpr (can_async_check) {
+ fence_thread.request_stop();
+ cv.notify_all();
+ fence_thread.join();
+ }
+ }
/// Creates a Fence Interface, does not create a backend fence if 'is_stubbed' is
/// true
@@ -104,15 +144,20 @@ protected:
Tegra::GPU& gpu;
Tegra::Host1x::SyncpointManager& syncpoint_manager;
TTextureCache& texture_cache;
- TTBufferCache& buffer_cache;
+ TBufferCache& buffer_cache;
TQueryCache& query_cache;
private:
+ template <bool force_wait>
void TryReleasePendingFences() {
while (!fences.empty()) {
TFence& current_fence = fences.front();
if (ShouldWait() && !IsFenceSignaled(current_fence)) {
- return;
+ if constexpr (force_wait) {
+ WaitFence(current_fence);
+ } else {
+ return;
+ }
}
PopAsyncFlushes();
auto operations = std::move(pending_operations.front());
@@ -120,7 +165,49 @@ private:
for (auto& operation : operations) {
operation();
}
- PopFence();
+ {
+ std::unique_lock lock(ring_guard);
+ delayed_destruction_ring.Push(std::move(current_fence));
+ }
+ fences.pop();
+ }
+ }
+
+ void ReleaseThreadFunc(std::stop_token stop_token) {
+ std::string name = "GPUFencingThread";
+ MicroProfileOnThreadCreate(name.c_str());
+
+ // Cleanup
+ SCOPE_EXIT({ MicroProfileOnThreadExit(); });
+
+ Common::SetCurrentThreadName(name.c_str());
+ Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
+
+ TFence current_fence;
+ std::deque<std::function<void()>> current_operations;
+ while (!stop_token.stop_requested()) {
+ {
+ std::unique_lock lock(guard);
+ cv.wait(lock, [&] { return stop_token.stop_requested() || !fences.empty(); });
+ if (stop_token.stop_requested()) [[unlikely]] {
+ return;
+ }
+ current_fence = std::move(fences.front());
+ current_operations = std::move(pending_operations.front());
+ fences.pop();
+ pending_operations.pop_front();
+ }
+ if (!current_fence->IsStubbed()) {
+ WaitFence(current_fence);
+ }
+ PopAsyncFlushes();
+ for (auto& operation : current_operations) {
+ operation();
+ }
+ {
+ std::unique_lock lock(ring_guard);
+ delayed_destruction_ring.Push(std::move(current_fence));
+ }
}
}
@@ -154,19 +241,16 @@ private:
query_cache.CommitAsyncFlushes();
}
- void PopFence() {
- delayed_destruction_ring.Push(std::move(fences.front()));
- fences.pop();
- }
-
- void CommitOperations() {
- pending_operations.emplace_back(std::move(uncommitted_operations));
- }
-
std::queue<TFence> fences;
std::deque<std::function<void()>> uncommitted_operations;
std::deque<std::deque<std::function<void()>>> pending_operations;
+ std::mutex guard;
+ std::mutex ring_guard;
+ std::condition_variable cv;
+
+ std::jthread fence_thread;
+
DelayedDestructionRing<TFence, 6> delayed_destruction_ring;
};
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 2e7f9c5ed..295a416a8 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -283,6 +283,21 @@ struct GPU::Impl {
gpu_thread.FlushRegion(addr, size);
}
+ VideoCore::RasterizerDownloadArea OnCPURead(VAddr addr, u64 size) {
+ auto raster_area = rasterizer->GetFlushArea(addr, size);
+ if (raster_area.preemtive) {
+ return raster_area;
+ }
+ raster_area.preemtive = true;
+ const u64 fence = RequestSyncOperation([this, &raster_area]() {
+ rasterizer->FlushRegion(raster_area.start_address,
+ raster_area.end_address - raster_area.start_address);
+ });
+ gpu_thread.TickGPU();
+ WaitForSyncOperation(fence);
+ return raster_area;
+ }
+
/// Notify rasterizer that any caches of the specified region should be invalidated
void InvalidateRegion(VAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
@@ -538,6 +553,10 @@ void GPU::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
impl->SwapBuffers(framebuffer);
}
+VideoCore::RasterizerDownloadArea GPU::OnCPURead(VAddr addr, u64 size) {
+ return impl->OnCPURead(addr, size);
+}
+
void GPU::FlushRegion(VAddr addr, u64 size) {
impl->FlushRegion(addr, size);
}
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 8a871593a..e49c40cf2 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -10,6 +10,7 @@
#include "core/hle/service/nvdrv/nvdata.h"
#include "video_core/cdma_pusher.h"
#include "video_core/framebuffer_config.h"
+#include "video_core/rasterizer_download_area.h"
namespace Core {
class System;
@@ -241,6 +242,9 @@ public:
void SwapBuffers(const Tegra::FramebufferConfig* framebuffer);
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
+ [[nodiscard]] VideoCore::RasterizerDownloadArea OnCPURead(VAddr addr, u64 size);
+
+ /// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
void FlushRegion(VAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated
diff --git a/src/video_core/host1x/codecs/h264.cpp b/src/video_core/host1x/codecs/h264.cpp
index e87bd65fa..6ce179167 100644
--- a/src/video_core/host1x/codecs/h264.cpp
+++ b/src/video_core/host1x/codecs/h264.cpp
@@ -111,7 +111,7 @@ const std::vector<u8>& H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegist
writer.WriteUe(0);
writer.WriteBit(context.h264_parameter_set.entropy_coding_mode_flag != 0);
- writer.WriteBit(false);
+ writer.WriteBit(context.h264_parameter_set.pic_order_present_flag != 0);
writer.WriteUe(0);
writer.WriteUe(context.h264_parameter_set.num_refidx_l0_default_active);
writer.WriteUe(context.h264_parameter_set.num_refidx_l1_default_active);
@@ -129,7 +129,7 @@ const std::vector<u8>& H264::ComposeFrame(const Host1x::NvdecCommon::NvdecRegist
writer.WriteBit(context.h264_parameter_set.redundant_pic_cnt_present_flag != 0);
writer.WriteBit(context.h264_parameter_set.transform_8x8_mode_flag != 0);
- writer.WriteBit(true);
+ writer.WriteBit(true); // pic_scaling_matrix_present_flag
for (s32 index = 0; index < 6; index++) {
writer.WriteBit(true);
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 01fb5b546..7b2cde7a7 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -82,6 +82,7 @@ void MemoryManager::SetEntry(size_t position, MemoryManager::EntryType entry) {
}
PTEKind MemoryManager::GetPageKind(GPUVAddr gpu_addr) const {
+ std::unique_lock<std::mutex> lock(guard);
return kind_map.GetValueAt(gpu_addr);
}
@@ -160,7 +161,10 @@ GPUVAddr MemoryManager::BigPageTableOp(GPUVAddr gpu_addr, [[maybe_unused]] VAddr
}
remaining_size -= big_page_size;
}
- kind_map.Map(gpu_addr, gpu_addr + size, kind);
+ {
+ std::unique_lock<std::mutex> lock(guard);
+ kind_map.Map(gpu_addr, gpu_addr + size, kind);
+ }
return gpu_addr;
}
@@ -553,6 +557,7 @@ size_t MemoryManager::MaxContinuousRange(GPUVAddr gpu_addr, size_t size) const {
}
size_t MemoryManager::GetMemoryLayoutSize(GPUVAddr gpu_addr, size_t max_size) const {
+ std::unique_lock<std::mutex> lock(guard);
return kind_map.GetContinuousSizeFrom(gpu_addr);
}
@@ -745,10 +750,10 @@ void MemoryManager::FlushCaching() {
return;
}
accumulator->Callback([this](GPUVAddr addr, size_t size) {
- GetSubmappedRangeImpl<false>(addr, size, page_stash);
+ GetSubmappedRangeImpl<false>(addr, size, page_stash2);
});
- rasterizer->InnerInvalidation(page_stash);
- page_stash.clear();
+ rasterizer->InnerInvalidation(page_stash2);
+ page_stash2.clear();
accumulator->Clear();
}
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index fbbe856c4..794535122 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -5,6 +5,7 @@
#include <atomic>
#include <map>
+#include <mutex>
#include <optional>
#include <vector>
@@ -215,6 +216,9 @@ private:
std::vector<u64> big_page_continuous;
std::vector<std::pair<VAddr, std::size_t>> page_stash{};
+ std::vector<std::pair<VAddr, std::size_t>> page_stash2{};
+
+ mutable std::mutex guard;
static constexpr size_t continuous_bits = 64;
diff --git a/src/video_core/query_cache.h b/src/video_core/query_cache.h
index 8906ba6d8..1528cc1dd 100644
--- a/src/video_core/query_cache.h
+++ b/src/video_core/query_cache.h
@@ -6,6 +6,7 @@
#include <algorithm>
#include <array>
#include <cstring>
+#include <functional>
#include <iterator>
#include <list>
#include <memory>
@@ -17,13 +18,19 @@
#include "common/assert.h"
#include "common/settings.h"
+#include "core/memory.h"
#include "video_core/control/channel_state_cache.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/texture_cache/slot_vector.h"
namespace VideoCommon {
+using AsyncJobId = SlotId;
+
+static constexpr AsyncJobId NULL_ASYNC_JOB_ID{0};
+
template <class QueryCache, class HostCounter>
class CounterStreamBase {
public:
@@ -93,9 +100,13 @@ private:
template <class QueryCache, class CachedQuery, class CounterStream, class HostCounter>
class QueryCacheBase : public VideoCommon::ChannelSetupCaches<VideoCommon::ChannelInfo> {
public:
- explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_)
- : rasterizer{rasterizer_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
- VideoCore::QueryType::SamplesPassed}}} {}
+ explicit QueryCacheBase(VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_)
+ : rasterizer{rasterizer_},
+ cpu_memory{cpu_memory_}, streams{{CounterStream{static_cast<QueryCache&>(*this),
+ VideoCore::QueryType::SamplesPassed}}} {
+ (void)slot_async_jobs.insert(); // Null value
+ }
void InvalidateRegion(VAddr addr, std::size_t size) {
std::unique_lock lock{mutex};
@@ -126,10 +137,15 @@ public:
query = Register(type, *cpu_addr, host_ptr, timestamp.has_value());
}
- query->BindCounter(Stream(type).Current(), timestamp);
- if (Settings::values.use_asynchronous_gpu_emulation.GetValue()) {
- AsyncFlushQuery(*cpu_addr);
+ auto result = query->BindCounter(Stream(type).Current(), timestamp);
+ if (result) {
+ auto async_job_id = query->GetAsyncJob();
+ auto& async_job = slot_async_jobs[async_job_id];
+ async_job.collected = true;
+ async_job.value = *result;
+ query->SetAsyncJob(NULL_ASYNC_JOB_ID);
}
+ AsyncFlushQuery(query, timestamp, lock);
}
/// Updates counters from GPU state. Expected to be called once per draw, clear or dispatch.
@@ -173,15 +189,18 @@ public:
}
void CommitAsyncFlushes() {
+ std::unique_lock lock{mutex};
committed_flushes.push_back(uncommitted_flushes);
uncommitted_flushes.reset();
}
bool HasUncommittedFlushes() const {
+ std::unique_lock lock{mutex};
return uncommitted_flushes != nullptr;
}
bool ShouldWaitAsyncFlushes() const {
+ std::unique_lock lock{mutex};
if (committed_flushes.empty()) {
return false;
}
@@ -189,6 +208,7 @@ public:
}
void PopAsyncFlushes() {
+ std::unique_lock lock{mutex};
if (committed_flushes.empty()) {
return;
}
@@ -197,15 +217,25 @@ public:
committed_flushes.pop_front();
return;
}
- for (VAddr query_address : *flush_list) {
- FlushAndRemoveRegion(query_address, 4);
+ for (AsyncJobId async_job_id : *flush_list) {
+ AsyncJob& async_job = slot_async_jobs[async_job_id];
+ if (!async_job.collected) {
+ FlushAndRemoveRegion(async_job.query_location, 2, true);
+ }
}
committed_flushes.pop_front();
}
private:
+ struct AsyncJob {
+ bool collected = false;
+ u64 value = 0;
+ VAddr query_location = 0;
+ std::optional<u64> timestamp{};
+ };
+
/// Flushes a memory range to guest memory and removes it from the cache.
- void FlushAndRemoveRegion(VAddr addr, std::size_t size) {
+ void FlushAndRemoveRegion(VAddr addr, std::size_t size, bool async = false) {
const u64 addr_begin = addr;
const u64 addr_end = addr_begin + size;
const auto in_range = [addr_begin, addr_end](const CachedQuery& query) {
@@ -225,8 +255,16 @@ private:
if (!in_range(query)) {
continue;
}
- rasterizer.UpdatePagesCachedCount(query.GetCpuAddr(), query.SizeInBytes(), -1);
- query.Flush();
+ AsyncJobId async_job_id = query.GetAsyncJob();
+ auto flush_result = query.Flush(async);
+ if (async_job_id == NULL_ASYNC_JOB_ID) {
+ ASSERT_MSG(false, "This should not be reachable at all");
+ continue;
+ }
+ AsyncJob& async_job = slot_async_jobs[async_job_id];
+ async_job.collected = true;
+ async_job.value = flush_result;
+ query.SetAsyncJob(NULL_ASYNC_JOB_ID);
}
std::erase_if(contents, in_range);
}
@@ -234,7 +272,6 @@ private:
/// Registers the passed parameters as cached and returns a pointer to the stored cached query.
CachedQuery* Register(VideoCore::QueryType type, VAddr cpu_addr, u8* host_ptr, bool timestamp) {
- rasterizer.UpdatePagesCachedCount(cpu_addr, CachedQuery::SizeInBytes(timestamp), 1);
const u64 page = static_cast<u64>(cpu_addr) >> YUZU_PAGEBITS;
return &cached_queries[page].emplace_back(static_cast<QueryCache&>(*this), type, cpu_addr,
host_ptr);
@@ -253,26 +290,60 @@ private:
return found != std::end(contents) ? &*found : nullptr;
}
- void AsyncFlushQuery(VAddr addr) {
- if (!uncommitted_flushes) {
- uncommitted_flushes = std::make_shared<std::vector<VAddr>>();
+ void AsyncFlushQuery(CachedQuery* query, std::optional<u64> timestamp,
+ std::unique_lock<std::recursive_mutex>& lock) {
+ const AsyncJobId new_async_job_id = slot_async_jobs.insert();
+ {
+ AsyncJob& async_job = slot_async_jobs[new_async_job_id];
+ query->SetAsyncJob(new_async_job_id);
+ async_job.query_location = query->GetCpuAddr();
+ async_job.collected = false;
+
+ if (!uncommitted_flushes) {
+ uncommitted_flushes = std::make_shared<std::vector<AsyncJobId>>();
+ }
+ uncommitted_flushes->push_back(new_async_job_id);
}
- uncommitted_flushes->push_back(addr);
+ lock.unlock();
+ std::function<void()> operation([this, new_async_job_id, timestamp] {
+ std::unique_lock local_lock{mutex};
+ AsyncJob& async_job = slot_async_jobs[new_async_job_id];
+ u64 value = async_job.value;
+ VAddr address = async_job.query_location;
+ slot_async_jobs.erase(new_async_job_id);
+ local_lock.unlock();
+ if (timestamp) {
+ u64 timestamp_value = *timestamp;
+ cpu_memory.WriteBlockUnsafe(address + sizeof(u64), &timestamp_value, sizeof(u64));
+ cpu_memory.WriteBlockUnsafe(address, &value, sizeof(u64));
+ rasterizer.InvalidateRegion(address, sizeof(u64) * 2,
+ VideoCommon::CacheType::NoQueryCache);
+ } else {
+ u32 small_value = static_cast<u32>(value);
+ cpu_memory.WriteBlockUnsafe(address, &small_value, sizeof(u32));
+ rasterizer.InvalidateRegion(address, sizeof(u32),
+ VideoCommon::CacheType::NoQueryCache);
+ }
+ });
+ rasterizer.SyncOperation(std::move(operation));
}
static constexpr std::uintptr_t YUZU_PAGESIZE = 4096;
static constexpr unsigned YUZU_PAGEBITS = 12;
+ SlotVector<AsyncJob> slot_async_jobs;
+
VideoCore::RasterizerInterface& rasterizer;
+ Core::Memory::Memory& cpu_memory;
- std::recursive_mutex mutex;
+ mutable std::recursive_mutex mutex;
std::unordered_map<u64, std::vector<CachedQuery>> cached_queries;
std::array<CounterStream, VideoCore::NumQueryTypes> streams;
- std::shared_ptr<std::vector<VAddr>> uncommitted_flushes{};
- std::list<std::shared_ptr<std::vector<VAddr>>> committed_flushes;
+ std::shared_ptr<std::vector<AsyncJobId>> uncommitted_flushes{};
+ std::list<std::shared_ptr<std::vector<AsyncJobId>>> committed_flushes;
};
template <class QueryCache, class HostCounter>
@@ -291,12 +362,12 @@ public:
virtual ~HostCounterBase() = default;
/// Returns the current value of the query.
- u64 Query() {
+ u64 Query(bool async = false) {
if (result) {
return *result;
}
- u64 value = BlockingQuery() + base_result;
+ u64 value = BlockingQuery(async) + base_result;
if (dependency) {
value += dependency->Query();
dependency = nullptr;
@@ -317,7 +388,7 @@ public:
protected:
/// Returns the value of query from the backend API blocking as needed.
- virtual u64 BlockingQuery() const = 0;
+ virtual u64 BlockingQuery(bool async = false) const = 0;
private:
std::shared_ptr<HostCounter> dependency; ///< Counter to add to this value.
@@ -340,26 +411,33 @@ public:
CachedQueryBase& operator=(const CachedQueryBase&) = delete;
/// Flushes the query to guest memory.
- virtual void Flush() {
+ virtual u64 Flush(bool async = false) {
// When counter is nullptr it means that it's just been reset. We are supposed to write a
// zero in these cases.
- const u64 value = counter ? counter->Query() : 0;
+ const u64 value = counter ? counter->Query(async) : 0;
+ if (async) {
+ return value;
+ }
std::memcpy(host_ptr, &value, sizeof(u64));
if (timestamp) {
std::memcpy(host_ptr + TIMESTAMP_OFFSET, &*timestamp, sizeof(u64));
}
+ return value;
}
/// Binds a counter to this query.
- void BindCounter(std::shared_ptr<HostCounter> counter_, std::optional<u64> timestamp_) {
+ std::optional<u64> BindCounter(std::shared_ptr<HostCounter> counter_,
+ std::optional<u64> timestamp_) {
+ std::optional<u64> result{};
if (counter) {
// If there's an old counter set it means the query is being rewritten by the game.
// To avoid losing the data forever, flush here.
- Flush();
+ result = std::make_optional(Flush());
}
counter = std::move(counter_);
timestamp = timestamp_;
+ return result;
}
VAddr GetCpuAddr() const noexcept {
@@ -374,6 +452,14 @@ public:
return with_timestamp ? LARGE_QUERY_SIZE : SMALL_QUERY_SIZE;
}
+ void SetAsyncJob(AsyncJobId assigned_async_job_) {
+ assigned_async_job = assigned_async_job_;
+ }
+
+ AsyncJobId GetAsyncJob() const {
+ return assigned_async_job;
+ }
+
protected:
/// Returns true when querying the counter may potentially block.
bool WaitPending() const noexcept {
@@ -389,6 +475,7 @@ private:
u8* host_ptr; ///< Writable host pointer.
std::shared_ptr<HostCounter> counter; ///< Host counter to query, owns the dependency tree.
std::optional<u64> timestamp; ///< Timestamp to flush to guest memory.
+ AsyncJobId assigned_async_job;
};
} // namespace VideoCommon
diff --git a/src/video_core/rasterizer_download_area.h b/src/video_core/rasterizer_download_area.h
new file mode 100644
index 000000000..2d7425c79
--- /dev/null
+++ b/src/video_core/rasterizer_download_area.h
@@ -0,0 +1,16 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include "common/common_types.h"
+
+namespace VideoCore {
+
+struct RasterizerDownloadArea {
+ VAddr start_address;
+ VAddr end_address;
+ bool preemtive;
+};
+
+} // namespace VideoCore \ No newline at end of file
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 33e2610bc..7566a8c4e 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -12,6 +12,7 @@
#include "video_core/cache_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
+#include "video_core/rasterizer_download_area.h"
namespace Tegra {
class MemoryManager;
@@ -95,6 +96,8 @@ public:
virtual bool MustFlushRegion(VAddr addr, u64 size,
VideoCommon::CacheType which = VideoCommon::CacheType::All) = 0;
+ virtual RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) = 0;
+
/// Notify rasterizer that any caches of the specified region should be invalidated
virtual void InvalidateRegion(VAddr addr, u64 size,
VideoCommon::CacheType which = VideoCommon::CacheType::All) = 0;
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp
index 2b5c7defa..bf2ce4c49 100644
--- a/src/video_core/renderer_null/null_rasterizer.cpp
+++ b/src/video_core/renderer_null/null_rasterizer.cpp
@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include "common/alignment.h"
+#include "core/memory.h"
#include "video_core/host1x/host1x.h"
#include "video_core/memory_manager.h"
#include "video_core/renderer_null/null_rasterizer.h"
@@ -46,6 +48,14 @@ bool RasterizerNull::MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheTyp
}
void RasterizerNull::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {}
void RasterizerNull::OnCPUWrite(VAddr addr, u64 size) {}
+VideoCore::RasterizerDownloadArea RasterizerNull::GetFlushArea(VAddr addr, u64 size) {
+ VideoCore::RasterizerDownloadArea new_area{
+ .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE),
+ .end_address = Common::AlignUp(addr + size, Core::Memory::YUZU_PAGESIZE),
+ .preemtive = true,
+ };
+ return new_area;
+}
void RasterizerNull::InvalidateGPUCache() {}
void RasterizerNull::UnmapMemory(VAddr addr, u64 size) {}
void RasterizerNull::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {}
diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h
index 0c59e6a1f..a8d35d2c1 100644
--- a/src/video_core/renderer_null/null_rasterizer.h
+++ b/src/video_core/renderer_null/null_rasterizer.h
@@ -54,6 +54,7 @@ public:
void InvalidateRegion(VAddr addr, u64 size,
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void OnCPUWrite(VAddr addr, u64 size) override;
+ VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override;
void InvalidateGPUCache() override;
void UnmapMemory(VAddr addr, u64 size) override;
void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index a8c3f8b67..18d3c3ac0 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -8,6 +8,7 @@
#include "common/common_types.h"
#include "video_core/buffer_cache/buffer_cache.h"
+#include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_opengl/gl_device.h"
#include "video_core/renderer_opengl/gl_resource_manager.h"
@@ -200,6 +201,8 @@ private:
struct BufferCacheParams {
using Runtime = OpenGL::BufferCacheRuntime;
using Buffer = OpenGL::Buffer;
+ using Async_Buffer = u32;
+ using MemoryTracker = VideoCommon::MemoryTrackerBase<VideoCore::RasterizerInterface>;
static constexpr bool IS_OPENGL = true;
static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = true;
@@ -208,6 +211,7 @@ struct BufferCacheParams {
static constexpr bool NEEDS_BIND_STORAGE_INDEX = true;
static constexpr bool USE_MEMORY_MAPS = false;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = true;
+ static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = false;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache_base.cpp b/src/video_core/renderer_opengl/gl_buffer_cache_base.cpp
new file mode 100644
index 000000000..f15ae8e25
--- /dev/null
+++ b/src/video_core/renderer_opengl/gl_buffer_cache_base.cpp
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "video_core/buffer_cache/buffer_cache.h"
+#include "video_core/renderer_opengl/gl_buffer_cache.h"
+
+namespace VideoCommon {
+template class VideoCommon::BufferCache<OpenGL::BufferCacheParams>;
+}
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 22ed16ebf..400c21981 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -108,7 +108,8 @@ bool IsASTCSupported() {
[[nodiscard]] bool IsDebugToolAttached(std::span<const std::string_view> extensions) {
const bool nsight = std::getenv("NVTX_INJECTION64_PATH") || std::getenv("NSIGHT_LAUNCHED");
- return nsight || HasExtension(extensions, "GL_EXT_debug_tool");
+ return nsight || HasExtension(extensions, "GL_EXT_debug_tool") ||
+ Settings::values.renderer_debug.GetValue();
}
} // Anonymous namespace
diff --git a/src/video_core/renderer_opengl/gl_fence_manager.h b/src/video_core/renderer_opengl/gl_fence_manager.h
index f1446e732..e21b19dcc 100644
--- a/src/video_core/renderer_opengl/gl_fence_manager.h
+++ b/src/video_core/renderer_opengl/gl_fence_manager.h
@@ -30,7 +30,17 @@ private:
};
using Fence = std::shared_ptr<GLInnerFence>;
-using GenericFenceManager = VideoCommon::FenceManager<Fence, TextureCache, BufferCache, QueryCache>;
+
+struct FenceManagerParams {
+ using FenceType = Fence;
+ using BufferCacheType = BufferCache;
+ using TextureCacheType = TextureCache;
+ using QueryCacheType = QueryCache;
+
+ static constexpr bool HAS_ASYNC_CHECK = false;
+};
+
+using GenericFenceManager = VideoCommon::FenceManager<FenceManagerParams>;
class FenceManagerOpenGL final : public GenericFenceManager {
public:
diff --git a/src/video_core/renderer_opengl/gl_query_cache.cpp b/src/video_core/renderer_opengl/gl_query_cache.cpp
index 5070db441..99d7347f5 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_query_cache.cpp
@@ -26,8 +26,8 @@ constexpr GLenum GetTarget(VideoCore::QueryType type) {
} // Anonymous namespace
-QueryCache::QueryCache(RasterizerOpenGL& rasterizer_)
- : QueryCacheBase(rasterizer_), gl_rasterizer{rasterizer_} {}
+QueryCache::QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_)
+ : QueryCacheBase(rasterizer_, cpu_memory_), gl_rasterizer{rasterizer_} {}
QueryCache::~QueryCache() = default;
@@ -74,7 +74,7 @@ void HostCounter::EndQuery() {
glEndQuery(GetTarget(type));
}
-u64 HostCounter::BlockingQuery() const {
+u64 HostCounter::BlockingQuery([[maybe_unused]] bool async) const {
GLint64 value;
glGetQueryObjecti64v(query.handle, GL_QUERY_RESULT, &value);
return static_cast<u64>(value);
@@ -96,7 +96,7 @@ CachedQuery& CachedQuery::operator=(CachedQuery&& rhs) noexcept {
return *this;
}
-void CachedQuery::Flush() {
+u64 CachedQuery::Flush([[maybe_unused]] bool async) {
// Waiting for a query while another query of the same target is enabled locks Nvidia's driver.
// To avoid this disable and re-enable keeping the dependency stream.
// But we only have to do this if we have pending waits to be done.
@@ -106,11 +106,13 @@ void CachedQuery::Flush() {
stream.Update(false);
}
- VideoCommon::CachedQueryBase<HostCounter>::Flush();
+ auto result = VideoCommon::CachedQueryBase<HostCounter>::Flush();
if (slice_counter) {
stream.Update(true);
}
+
+ return result;
}
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_query_cache.h b/src/video_core/renderer_opengl/gl_query_cache.h
index 14ce59990..872513f22 100644
--- a/src/video_core/renderer_opengl/gl_query_cache.h
+++ b/src/video_core/renderer_opengl/gl_query_cache.h
@@ -28,7 +28,7 @@ using CounterStream = VideoCommon::CounterStreamBase<QueryCache, HostCounter>;
class QueryCache final
: public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit QueryCache(RasterizerOpenGL& rasterizer_);
+ explicit QueryCache(RasterizerOpenGL& rasterizer_, Core::Memory::Memory& cpu_memory_);
~QueryCache();
OGLQuery AllocateQuery(VideoCore::QueryType type);
@@ -51,7 +51,7 @@ public:
void EndQuery();
private:
- u64 BlockingQuery() const override;
+ u64 BlockingQuery(bool async = false) const override;
QueryCache& cache;
const VideoCore::QueryType type;
@@ -70,7 +70,7 @@ public:
CachedQuery(const CachedQuery&) = delete;
CachedQuery& operator=(const CachedQuery&) = delete;
- void Flush() override;
+ u64 Flush(bool async = false) override;
private:
QueryCache* cache;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 90e35e307..f5baa0f3c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -63,7 +63,7 @@ RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& emu_window_, Tegra
buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
shader_cache(*this, emu_window_, device, texture_cache, buffer_cache, program_manager,
state_tracker, gpu.ShaderNotify()),
- query_cache(*this), accelerate_dma(buffer_cache, texture_cache),
+ query_cache(*this, cpu_memory_), accelerate_dma(buffer_cache, texture_cache),
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache),
blit_image(program_manager_) {}
@@ -433,6 +433,29 @@ bool RasterizerOpenGL::MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheT
return false;
}
+VideoCore::RasterizerDownloadArea RasterizerOpenGL::GetFlushArea(VAddr addr, u64 size) {
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ auto area = texture_cache.GetFlushArea(addr, size);
+ if (area) {
+ return *area;
+ }
+ }
+ {
+ std::scoped_lock lock{buffer_cache.mutex};
+ auto area = buffer_cache.GetFlushArea(addr, size);
+ if (area) {
+ return *area;
+ }
+ }
+ VideoCore::RasterizerDownloadArea new_area{
+ .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE),
+ .end_address = Common::AlignUp(addr + size, Core::Memory::YUZU_PAGESIZE),
+ .preemtive = true,
+ };
+ return new_area;
+}
+
void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
if (addr == 0 || size == 0) {
@@ -1281,14 +1304,13 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
const Tegra::DMA::BufferOperand& buffer_operand,
const Tegra::DMA::ImageOperand& image_operand) {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
- const auto image_id = texture_cache.DmaImageId(image_operand);
+ const auto image_id = texture_cache.DmaImageId(image_operand, IS_IMAGE_UPLOAD);
if (image_id == VideoCommon::NULL_IMAGE_ID) {
return false;
}
const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height);
static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
- const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing
- : VideoCommon::ObtainBufferOperation::MarkAsWritten;
+ const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
const auto [buffer, offset] =
buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op);
@@ -1299,7 +1321,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
if constexpr (IS_IMAGE_UPLOAD) {
image->UploadMemory(buffer->Handle(), offset, copy_span);
} else {
- image->DownloadMemory(buffer->Handle(), offset, copy_span);
+ texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span,
+ buffer_operand.address, buffer_size);
}
return true;
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index ad6978bd0..410d8ffc5 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -95,6 +95,7 @@ public:
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
bool MustFlushRegion(VAddr addr, u64 size,
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
+ VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size,
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void OnCPUWrite(VAddr addr, u64 size) override;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp
index 0b9c4a904..31118886f 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp
@@ -231,7 +231,7 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
[[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime,
const VideoCommon::ImageInfo& info) {
- if (IsPixelFormatASTC(info.format) && !runtime.HasNativeASTC()) {
+ if (IsPixelFormatASTC(info.format) && info.size.depth == 1 && !runtime.HasNativeASTC()) {
return Settings::values.accelerate_astc.GetValue() &&
!Settings::values.async_astc.GetValue();
}
@@ -803,30 +803,40 @@ void Image::UploadMemory(const ImageBufferMap& map,
void Image::DownloadMemory(GLuint buffer_handle, size_t buffer_offset,
std::span<const VideoCommon::BufferImageCopy> copies) {
+ std::array buffer_handles{buffer_handle};
+ std::array buffer_offsets{buffer_offset};
+ DownloadMemory(buffer_handles, buffer_offsets, copies);
+}
+
+void Image::DownloadMemory(std::span<GLuint> buffer_handles, std::span<size_t> buffer_offsets,
+ std::span<const VideoCommon::BufferImageCopy> copies) {
const bool is_rescaled = True(flags & ImageFlagBits::Rescaled);
if (is_rescaled) {
ScaleDown();
}
glMemoryBarrier(GL_PIXEL_BUFFER_BARRIER_BIT); // TODO: Move this to its own API
- glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_handle);
- glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ for (size_t i = 0; i < buffer_handles.size(); i++) {
+ auto& buffer_handle = buffer_handles[i];
+ glBindBuffer(GL_PIXEL_PACK_BUFFER, buffer_handle);
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
- u32 current_row_length = std::numeric_limits<u32>::max();
- u32 current_image_height = std::numeric_limits<u32>::max();
+ u32 current_row_length = std::numeric_limits<u32>::max();
+ u32 current_image_height = std::numeric_limits<u32>::max();
- for (const VideoCommon::BufferImageCopy& copy : copies) {
- if (copy.image_subresource.base_level >= gl_num_levels) {
- continue;
- }
- if (current_row_length != copy.buffer_row_length) {
- current_row_length = copy.buffer_row_length;
- glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length);
- }
- if (current_image_height != copy.buffer_image_height) {
- current_image_height = copy.buffer_image_height;
- glPixelStorei(GL_PACK_IMAGE_HEIGHT, current_image_height);
+ for (const VideoCommon::BufferImageCopy& copy : copies) {
+ if (copy.image_subresource.base_level >= gl_num_levels) {
+ continue;
+ }
+ if (current_row_length != copy.buffer_row_length) {
+ current_row_length = copy.buffer_row_length;
+ glPixelStorei(GL_PACK_ROW_LENGTH, current_row_length);
+ }
+ if (current_image_height != copy.buffer_image_height) {
+ current_image_height = copy.buffer_image_height;
+ glPixelStorei(GL_PACK_IMAGE_HEIGHT, current_image_height);
+ }
+ CopyImageToBuffer(copy, buffer_offsets[i]);
}
- CopyImageToBuffer(copy, buffer_offset);
}
if (is_rescaled) {
ScaleUp(true);
@@ -851,9 +861,12 @@ GLuint Image::StorageHandle() noexcept {
case PixelFormat::ASTC_2D_8X5_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB:
case PixelFormat::ASTC_2D_5X5_SRGB:
+ case PixelFormat::ASTC_2D_10X5_SRGB:
+ case PixelFormat::ASTC_2D_10X6_SRGB:
case PixelFormat::ASTC_2D_10X8_SRGB:
case PixelFormat::ASTC_2D_6X6_SRGB:
case PixelFormat::ASTC_2D_10X10_SRGB:
+ case PixelFormat::ASTC_2D_12X10_SRGB:
case PixelFormat::ASTC_2D_12X12_SRGB:
case PixelFormat::ASTC_2D_8X6_SRGB:
case PixelFormat::ASTC_2D_6X5_SRGB:
@@ -1113,7 +1126,8 @@ bool Image::ScaleDown(bool ignore) {
ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info,
ImageId image_id_, Image& image, const SlotVector<Image>&)
- : VideoCommon::ImageViewBase{info, image.info, image_id_}, views{runtime.null_image_views} {
+ : VideoCommon::ImageViewBase{info, image.info, image_id_, image.gpu_addr},
+ views{runtime.null_image_views} {
const Device& device = runtime.device;
if (True(image.flags & ImageFlagBits::Converted)) {
internal_format = IsPixelFormatSRGB(info.format) ? GL_SRGB8_ALPHA8 : GL_RGBA8;
@@ -1204,12 +1218,12 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_)
- : VideoCommon::ImageViewBase{info, view_info}, gpu_addr{gpu_addr_},
+ : VideoCommon::ImageViewBase{info, view_info, gpu_addr_},
buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
const VideoCommon::ImageViewInfo& view_info)
- : VideoCommon::ImageViewBase{info, view_info} {}
+ : VideoCommon::ImageViewBase{info, view_info, 0} {}
ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::NullImageViewParams& params)
: VideoCommon::ImageViewBase{params}, views{runtime.null_image_views} {}
@@ -1269,7 +1283,7 @@ GLuint ImageView::MakeView(Shader::TextureType view_type, GLenum view_format) {
ApplySwizzle(view.handle, format, casted_swizzle);
}
if (set_object_label) {
- const std::string name = VideoCommon::Name(*this);
+ const std::string name = VideoCommon::Name(*this, gpu_addr);
glObjectLabel(GL_TEXTURE, view.handle, static_cast<GLsizei>(name.size()), name.data());
}
return view.handle;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 911e4607a..1190999a8 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -215,6 +215,9 @@ public:
void DownloadMemory(GLuint buffer_handle, size_t buffer_offset,
std::span<const VideoCommon::BufferImageCopy> copies);
+ void DownloadMemory(std::span<GLuint> buffer_handle, std::span<size_t> buffer_offset,
+ std::span<const VideoCommon::BufferImageCopy> copies);
+
void DownloadMemory(ImageBufferMap& map, std::span<const VideoCommon::BufferImageCopy> copies);
GLuint StorageHandle() noexcept;
@@ -311,7 +314,6 @@ private:
std::unique_ptr<StorageViews> storage_views;
GLenum internal_format = GL_NONE;
GLuint default_handle = 0;
- GPUVAddr gpu_addr = 0;
u32 buffer_size = 0;
GLuint original_texture = 0;
int num_samples = 0;
@@ -376,6 +378,7 @@ struct TextureCacheParams {
using Sampler = OpenGL::Sampler;
using Framebuffer = OpenGL::Framebuffer;
using AsyncBuffer = u32;
+ using BufferType = GLuint;
};
using TextureCache = VideoCommon::TextureCache<TextureCacheParams>;
diff --git a/src/video_core/renderer_opengl/maxwell_to_gl.h b/src/video_core/renderer_opengl/maxwell_to_gl.h
index ef1190e1f..c7dc7e0a1 100644
--- a/src/video_core/renderer_opengl/maxwell_to_gl.h
+++ b/src/video_core/renderer_opengl/maxwell_to_gl.h
@@ -100,10 +100,13 @@ constexpr std::array<FormatTuple, VideoCore::Surface::MaxPixelFormat> FORMAT_TAB
{GL_COMPRESSED_RGBA_ASTC_6x6_KHR}, // ASTC_2D_6X6_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_6x6_KHR}, // ASTC_2D_6X6_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x6_KHR}, // ASTC_2D_10X6_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x6_KHR}, // ASTC_2D_10X6_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x5_KHR}, // ASTC_2D_10X5_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x5_KHR}, // ASTC_2D_10X5_SRGB
{GL_COMPRESSED_RGBA_ASTC_10x10_KHR}, // ASTC_2D_10X10_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_10x10_KHR}, // ASTC_2D_10X10_SRGB
+ {GL_COMPRESSED_RGBA_ASTC_12x10_KHR}, // ASTC_2D_12X10_UNORM
+ {GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x10_KHR}, // ASTC_2D_12X10_SRGB
{GL_COMPRESSED_RGBA_ASTC_12x12_KHR}, // ASTC_2D_12X12_UNORM
{GL_COMPRESSED_SRGB8_ALPHA8_ASTC_12x12_KHR}, // ASTC_2D_12X12_SRGB
{GL_COMPRESSED_RGBA_ASTC_8x6_KHR}, // ASTC_2D_8X6_UNORM
diff --git a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
index 5dce51be8..8853cf0f7 100644
--- a/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
+++ b/src/video_core/renderer_vulkan/maxwell_to_vk.cpp
@@ -197,10 +197,13 @@ struct FormatTuple {
{VK_FORMAT_ASTC_6x6_UNORM_BLOCK}, // ASTC_2D_6X6_UNORM
{VK_FORMAT_ASTC_6x6_SRGB_BLOCK}, // ASTC_2D_6X6_SRGB
{VK_FORMAT_ASTC_10x6_UNORM_BLOCK}, // ASTC_2D_10X6_UNORM
+ {VK_FORMAT_ASTC_10x6_SRGB_BLOCK}, // ASTC_2D_10X6_SRGB
{VK_FORMAT_ASTC_10x5_UNORM_BLOCK}, // ASTC_2D_10X5_UNORM
{VK_FORMAT_ASTC_10x5_SRGB_BLOCK}, // ASTC_2D_10X5_SRGB
{VK_FORMAT_ASTC_10x10_UNORM_BLOCK}, // ASTC_2D_10X10_UNORM
{VK_FORMAT_ASTC_10x10_SRGB_BLOCK}, // ASTC_2D_10X10_SRGB
+ {VK_FORMAT_ASTC_12x10_UNORM_BLOCK}, // ASTC_2D_12X10_UNORM
+ {VK_FORMAT_ASTC_12x10_SRGB_BLOCK}, // ASTC_2D_12X10_SRGB
{VK_FORMAT_ASTC_12x12_UNORM_BLOCK}, // ASTC_2D_12X12_UNORM
{VK_FORMAT_ASTC_12x12_SRGB_BLOCK}, // ASTC_2D_12X12_SRGB
{VK_FORMAT_ASTC_8x6_UNORM_BLOCK}, // ASTC_2D_8X6_UNORM
diff --git a/src/video_core/renderer_vulkan/pipeline_helper.h b/src/video_core/renderer_vulkan/pipeline_helper.h
index 28b893e25..983e1c2e1 100644
--- a/src/video_core/renderer_vulkan/pipeline_helper.h
+++ b/src/video_core/renderer_vulkan/pipeline_helper.h
@@ -176,7 +176,7 @@ public:
};
inline void PushImageDescriptors(TextureCache& texture_cache,
- UpdateDescriptorQueue& update_descriptor_queue,
+ GuestDescriptorQueue& guest_descriptor_queue,
const Shader::Info& info, RescalingPushConstant& rescaling,
const VkSampler*& samplers,
const VideoCommon::ImageViewInOut*& views) {
@@ -190,7 +190,7 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
const VkSampler sampler{*(samplers++)};
ImageView& image_view{texture_cache.GetImageView(image_view_id)};
const VkImageView vk_image_view{image_view.Handle(desc.type)};
- update_descriptor_queue.AddSampledImage(vk_image_view, sampler);
+ guest_descriptor_queue.AddSampledImage(vk_image_view, sampler);
rescaling.PushTexture(texture_cache.IsRescaling(image_view));
}
}
@@ -201,7 +201,7 @@ inline void PushImageDescriptors(TextureCache& texture_cache,
texture_cache.MarkModification(image_view.image_id);
}
const VkImageView vk_image_view{image_view.StorageView(desc.type, desc.format)};
- update_descriptor_queue.AddImage(vk_image_view);
+ guest_descriptor_queue.AddImage(vk_image_view);
rescaling.PushImage(texture_cache.IsRescaling(image_view));
}
}
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.cpp b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
index 2a8d9e377..8e31eba34 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.cpp
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.cpp
@@ -88,13 +88,14 @@ RendererVulkan::RendererVulkan(Core::TelemetrySession& telemetry_session_,
instance(CreateInstance(library, dld, VK_API_VERSION_1_1, render_window.GetWindowInfo().type,
Settings::values.renderer_debug.GetValue())),
debug_callback(Settings::values.renderer_debug ? CreateDebugCallback(instance) : nullptr),
- surface(CreateSurface(instance, render_window)),
+ surface(CreateSurface(instance, render_window.GetWindowInfo())),
device(CreateDevice(instance, dld, *surface)), memory_allocator(device, false),
state_tracker(), scheduler(device, state_tracker),
swapchain(*surface, device, scheduler, render_window.GetFramebufferLayout().width,
render_window.GetFramebufferLayout().height, false),
- blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, scheduler,
- screen_info),
+ present_manager(render_window, device, memory_allocator, scheduler, swapchain),
+ blit_screen(cpu_memory, render_window, device, memory_allocator, swapchain, present_manager,
+ scheduler, screen_info),
rasterizer(render_window, gpu, cpu_memory, screen_info, device, memory_allocator,
state_tracker, scheduler) {
if (Settings::values.renderer_force_max_clock.GetValue() && device.ShouldBoostClocks()) {
@@ -121,46 +122,19 @@ void RendererVulkan::SwapBuffers(const Tegra::FramebufferConfig* framebuffer) {
return;
}
// Update screen info if the framebuffer size has changed.
- if (screen_info.width != framebuffer->width || screen_info.height != framebuffer->height) {
- screen_info.width = framebuffer->width;
- screen_info.height = framebuffer->height;
- }
+ screen_info.width = framebuffer->width;
+ screen_info.height = framebuffer->height;
+
const VAddr framebuffer_addr = framebuffer->address + framebuffer->offset;
const bool use_accelerated =
rasterizer.AccelerateDisplay(*framebuffer, framebuffer_addr, framebuffer->stride);
const bool is_srgb = use_accelerated && screen_info.is_srgb;
RenderScreenshot(*framebuffer, use_accelerated);
- bool has_been_recreated = false;
- const auto recreate_swapchain = [&](u32 width, u32 height) {
- if (!has_been_recreated) {
- has_been_recreated = true;
- scheduler.Finish();
- }
- swapchain.Create(width, height, is_srgb);
- };
-
- 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(layout.width, layout.height);
- }
- } while (is_outdated);
- if (has_been_recreated) {
- blit_screen.Recreate();
- }
- const VkSemaphore render_semaphore = blit_screen.DrawToSwapchain(*framebuffer, use_accelerated);
- const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore();
- scheduler.Flush(render_semaphore, present_semaphore);
- scheduler.WaitWorker();
- swapchain.Present(render_semaphore);
+ Frame* frame = present_manager.GetRenderFrame();
+ blit_screen.DrawToSwapchain(frame, *framebuffer, use_accelerated, is_srgb);
+ scheduler.Flush(*frame->render_ready);
+ present_manager.Present(frame);
gpu.RendererFrameEndNotify();
rasterizer.TickFrame();
@@ -246,8 +220,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
});
const VkExtent2D render_area{.width = layout.width, .height = layout.height};
const vk::Framebuffer screenshot_fb = blit_screen.CreateFramebuffer(*dst_view, render_area);
- // Since we're not rendering to the screen, ignore the render semaphore.
- void(blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated));
+ blit_screen.Draw(framebuffer, *screenshot_fb, layout, render_area, use_accelerated);
const auto buffer_size = static_cast<VkDeviceSize>(layout.width * layout.height * 4);
const VkBufferCreateInfo dst_buffer_info{
@@ -270,7 +243,7 @@ void Vulkan::RendererVulkan::RenderScreenshot(const Tegra::FramebufferConfig& fr
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
- .oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
diff --git a/src/video_core/renderer_vulkan/renderer_vulkan.h b/src/video_core/renderer_vulkan/renderer_vulkan.h
index 009e75e0d..f44367cb2 100644
--- a/src/video_core/renderer_vulkan/renderer_vulkan.h
+++ b/src/video_core/renderer_vulkan/renderer_vulkan.h
@@ -9,6 +9,7 @@
#include "common/dynamic_library.h"
#include "video_core/renderer_base.h"
#include "video_core/renderer_vulkan/vk_blit_screen.h"
+#include "video_core/renderer_vulkan/vk_present_manager.h"
#include "video_core/renderer_vulkan/vk_rasterizer.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_state_tracker.h"
@@ -76,6 +77,7 @@ private:
StateTracker state_tracker;
Scheduler scheduler;
Swapchain swapchain;
+ PresentManager present_manager;
BlitScreen blit_screen;
RasterizerVulkan rasterizer;
std::optional<TurboMode> turbo_mode;
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 2f0cc27e8..1e0fdd3d9 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -122,10 +122,12 @@ struct BlitScreen::BufferData {
BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWindow& render_window_,
const Device& device_, MemoryAllocator& memory_allocator_,
- Swapchain& swapchain_, Scheduler& scheduler_, const ScreenInfo& screen_info_)
+ Swapchain& swapchain_, PresentManager& present_manager_,
+ Scheduler& scheduler_, const ScreenInfo& screen_info_)
: cpu_memory{cpu_memory_}, render_window{render_window_}, device{device_},
- memory_allocator{memory_allocator_}, swapchain{swapchain_}, scheduler{scheduler_},
- image_count{swapchain.GetImageCount()}, screen_info{screen_info_} {
+ memory_allocator{memory_allocator_}, swapchain{swapchain_}, present_manager{present_manager_},
+ scheduler{scheduler_}, image_count{swapchain.GetImageCount()}, screen_info{screen_info_},
+ current_srgb{swapchain.IsSrgb()}, image_view_format{swapchain.GetImageViewFormat()} {
resource_ticks.resize(image_count);
CreateStaticResources();
@@ -135,25 +137,20 @@ BlitScreen::BlitScreen(Core::Memory::Memory& cpu_memory_, Core::Frontend::EmuWin
BlitScreen::~BlitScreen() = default;
void BlitScreen::Recreate() {
+ present_manager.WaitPresent();
+ scheduler.Finish();
+ device.GetLogical().WaitIdle();
CreateDynamicResources();
}
-VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
- const VkFramebuffer& host_framebuffer,
- const Layout::FramebufferLayout layout, VkExtent2D render_area,
- bool use_accelerated) {
+void BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
+ const VkFramebuffer& host_framebuffer, const Layout::FramebufferLayout layout,
+ VkExtent2D render_area, bool use_accelerated) {
RefreshResources(framebuffer);
// Finish any pending renderpass
scheduler.RequestOutsideRenderPassOperationContext();
- if (const auto swapchain_images = swapchain.GetImageCount(); swapchain_images != image_count) {
- image_count = swapchain_images;
- Recreate();
- }
-
- const std::size_t image_index = swapchain.GetImageIndex();
-
scheduler.Wait(resource_ticks[image_index]);
resource_ticks[image_index] = scheduler.CurrentTick();
@@ -169,7 +166,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
std::memcpy(mapped_span.data(), &data, sizeof(data));
if (!use_accelerated) {
- const u64 image_offset = GetRawImageOffset(framebuffer, image_index);
+ const u64 image_offset = GetRawImageOffset(framebuffer);
const VAddr framebuffer_addr = framebuffer.address + framebuffer.offset;
const u8* const host_ptr = cpu_memory.GetPointer(framebuffer_addr);
@@ -204,8 +201,8 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
.depth = 1,
},
};
- scheduler.Record([this, copy, image_index](vk::CommandBuffer cmdbuf) {
- const VkImage image = *raw_images[image_index];
+ scheduler.Record([this, copy, index = image_index](vk::CommandBuffer cmdbuf) {
+ const VkImage image = *raw_images[index];
const VkImageMemoryBarrier base_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
@@ -245,14 +242,15 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
const auto anti_alias_pass = Settings::values.anti_aliasing.GetValue();
if (use_accelerated && anti_alias_pass == Settings::AntiAliasing::Fxaa) {
- UpdateAADescriptorSet(image_index, source_image_view, false);
+ UpdateAADescriptorSet(source_image_view, false);
const u32 up_scale = Settings::values.resolution_info.up_scale;
const u32 down_shift = Settings::values.resolution_info.down_shift;
VkExtent2D size{
.width = (up_scale * framebuffer.width) >> down_shift,
.height = (up_scale * framebuffer.height) >> down_shift,
};
- scheduler.Record([this, image_index, size, anti_alias_pass](vk::CommandBuffer cmdbuf) {
+ scheduler.Record([this, index = image_index, size,
+ anti_alias_pass](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier base_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
@@ -326,7 +324,7 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *aa_pipeline_layout, 0,
- aa_descriptor_sets[image_index], {});
+ aa_descriptor_sets[index], {});
cmdbuf.Draw(4, 1, 0, 0);
cmdbuf.EndRenderPass();
@@ -369,81 +367,99 @@ VkSemaphore BlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
};
VkImageView fsr_image_view =
fsr->Draw(scheduler, image_index, source_image_view, fsr_input_size, crop_rect);
- UpdateDescriptorSet(image_index, fsr_image_view, true);
+ UpdateDescriptorSet(fsr_image_view, true);
} else {
const bool is_nn =
Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::NearestNeighbor;
- UpdateDescriptorSet(image_index, source_image_view, is_nn);
+ UpdateDescriptorSet(source_image_view, is_nn);
}
- scheduler.Record(
- [this, host_framebuffer, image_index, size = render_area](vk::CommandBuffer cmdbuf) {
- const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f;
- const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f;
- const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f;
- const VkClearValue clear_color{
- .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}},
- };
- const VkRenderPassBeginInfo renderpass_bi{
- .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
- .pNext = nullptr,
- .renderPass = *renderpass,
- .framebuffer = host_framebuffer,
- .renderArea =
- {
- .offset = {0, 0},
- .extent = size,
- },
- .clearValueCount = 1,
- .pClearValues = &clear_color,
- };
- const VkViewport viewport{
- .x = 0.0f,
- .y = 0.0f,
- .width = static_cast<float>(size.width),
- .height = static_cast<float>(size.height),
- .minDepth = 0.0f,
- .maxDepth = 1.0f,
- };
- const VkRect2D scissor{
- .offset = {0, 0},
- .extent = size,
- };
- cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
- auto graphics_pipeline = [this]() {
- switch (Settings::values.scaling_filter.GetValue()) {
- case Settings::ScalingFilter::NearestNeighbor:
- case Settings::ScalingFilter::Bilinear:
- return *bilinear_pipeline;
- case Settings::ScalingFilter::Bicubic:
- return *bicubic_pipeline;
- case Settings::ScalingFilter::Gaussian:
- return *gaussian_pipeline;
- case Settings::ScalingFilter::ScaleForce:
- return *scaleforce_pipeline;
- default:
- return *bilinear_pipeline;
- }
- }();
- cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
- cmdbuf.SetViewport(0, viewport);
- cmdbuf.SetScissor(0, scissor);
-
- cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
- cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0,
- descriptor_sets[image_index], {});
- cmdbuf.Draw(4, 1, 0, 0);
- cmdbuf.EndRenderPass();
- });
- return *semaphores[image_index];
+ scheduler.Record([this, host_framebuffer, index = image_index,
+ size = render_area](vk::CommandBuffer cmdbuf) {
+ const f32 bg_red = Settings::values.bg_red.GetValue() / 255.0f;
+ const f32 bg_green = Settings::values.bg_green.GetValue() / 255.0f;
+ const f32 bg_blue = Settings::values.bg_blue.GetValue() / 255.0f;
+ const VkClearValue clear_color{
+ .color = {.float32 = {bg_red, bg_green, bg_blue, 1.0f}},
+ };
+ const VkRenderPassBeginInfo renderpass_bi{
+ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
+ .pNext = nullptr,
+ .renderPass = *renderpass,
+ .framebuffer = host_framebuffer,
+ .renderArea =
+ {
+ .offset = {0, 0},
+ .extent = size,
+ },
+ .clearValueCount = 1,
+ .pClearValues = &clear_color,
+ };
+ const VkViewport viewport{
+ .x = 0.0f,
+ .y = 0.0f,
+ .width = static_cast<float>(size.width),
+ .height = static_cast<float>(size.height),
+ .minDepth = 0.0f,
+ .maxDepth = 1.0f,
+ };
+ const VkRect2D scissor{
+ .offset = {0, 0},
+ .extent = size,
+ };
+ cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
+ auto graphics_pipeline = [this]() {
+ switch (Settings::values.scaling_filter.GetValue()) {
+ case Settings::ScalingFilter::NearestNeighbor:
+ case Settings::ScalingFilter::Bilinear:
+ return *bilinear_pipeline;
+ case Settings::ScalingFilter::Bicubic:
+ return *bicubic_pipeline;
+ case Settings::ScalingFilter::Gaussian:
+ return *gaussian_pipeline;
+ case Settings::ScalingFilter::ScaleForce:
+ return *scaleforce_pipeline;
+ default:
+ return *bilinear_pipeline;
+ }
+ }();
+ cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
+ cmdbuf.SetViewport(0, viewport);
+ cmdbuf.SetScissor(0, scissor);
+
+ cmdbuf.BindVertexBuffer(0, *buffer, offsetof(BufferData, vertices));
+ cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline_layout, 0,
+ descriptor_sets[index], {});
+ cmdbuf.Draw(4, 1, 0, 0);
+ cmdbuf.EndRenderPass();
+ });
}
-VkSemaphore BlitScreen::DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
- bool use_accelerated) {
- const std::size_t image_index = swapchain.GetImageIndex();
- const VkExtent2D render_area = swapchain.GetSize();
+void BlitScreen::DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
+ bool use_accelerated, bool is_srgb) {
+ // Recreate dynamic resources if the the image count or colorspace changed
+ if (const std::size_t swapchain_images = swapchain.GetImageCount();
+ swapchain_images != image_count || current_srgb != is_srgb) {
+ current_srgb = is_srgb;
+ image_view_format = current_srgb ? VK_FORMAT_B8G8R8A8_SRGB : VK_FORMAT_B8G8R8A8_UNORM;
+ image_count = swapchain_images;
+ Recreate();
+ }
+
+ // Recreate the presentation frame if the dimensions of the window changed
const Layout::FramebufferLayout layout = render_window.GetFramebufferLayout();
- return Draw(framebuffer, *framebuffers[image_index], layout, render_area, use_accelerated);
+ if (layout.width != frame->width || layout.height != frame->height ||
+ is_srgb != frame->is_srgb) {
+ Recreate();
+ present_manager.RecreateFrame(frame, layout.width, layout.height, is_srgb,
+ image_view_format, *renderpass);
+ }
+
+ const VkExtent2D render_area{frame->width, frame->height};
+ Draw(framebuffer, *frame->framebuffer, layout, render_area, use_accelerated);
+ if (++image_index >= image_count) {
+ image_index = 0;
+ }
}
vk::Framebuffer BlitScreen::CreateFramebuffer(const VkImageView& image_view, VkExtent2D extent) {
@@ -471,13 +487,11 @@ void BlitScreen::CreateStaticResources() {
}
void BlitScreen::CreateDynamicResources() {
- CreateSemaphores();
CreateDescriptorPool();
CreateDescriptorSetLayout();
CreateDescriptorSets();
CreatePipelineLayout();
CreateRenderPass();
- CreateFramebuffers();
CreateGraphicsPipeline();
fsr.reset();
smaa.reset();
@@ -525,11 +539,6 @@ void BlitScreen::CreateShaders() {
}
}
-void BlitScreen::CreateSemaphores() {
- semaphores.resize(image_count);
- std::ranges::generate(semaphores, [this] { return device.GetLogical().CreateSemaphore(); });
-}
-
void BlitScreen::CreateDescriptorPool() {
const std::array<VkDescriptorPoolSize, 2> pool_sizes{{
{
@@ -571,10 +580,10 @@ void BlitScreen::CreateDescriptorPool() {
}
void BlitScreen::CreateRenderPass() {
- renderpass = CreateRenderPassImpl(swapchain.GetImageViewFormat());
+ renderpass = CreateRenderPassImpl(image_view_format);
}
-vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present) {
+vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format) {
const VkAttachmentDescription color_attachment{
.flags = 0,
.format = format,
@@ -584,7 +593,7 @@ vk::RenderPass BlitScreen::CreateRenderPassImpl(VkFormat format, bool is_present
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
- .finalLayout = is_present ? VK_IMAGE_LAYOUT_PRESENT_SRC_KHR : VK_IMAGE_LAYOUT_GENERAL,
+ .finalLayout = VK_IMAGE_LAYOUT_GENERAL,
};
const VkAttachmentReference color_attachment_ref{
@@ -1052,16 +1061,6 @@ void BlitScreen::CreateSampler() {
nn_sampler = device.GetLogical().CreateSampler(ci_nn);
}
-void BlitScreen::CreateFramebuffers() {
- const VkExtent2D size{swapchain.GetSize()};
- framebuffers.resize(image_count);
-
- for (std::size_t i = 0; i < image_count; ++i) {
- const VkImageView image_view{swapchain.GetImageViewIndex(i)};
- framebuffers[i] = CreateFramebuffer(image_view, size, renderpass);
- }
-}
-
void BlitScreen::ReleaseRawImages() {
for (const u64 tick : resource_ticks) {
scheduler.Wait(tick);
@@ -1175,7 +1174,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
return;
}
- aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer), false);
+ aa_renderpass = CreateRenderPassImpl(GetFormat(framebuffer));
aa_framebuffer = CreateFramebuffer(*aa_image_view, size, aa_renderpass);
const std::array<VkPipelineShaderStageCreateInfo, 2> fxaa_shader_stages{{
@@ -1319,8 +1318,7 @@ void BlitScreen::CreateRawImages(const Tegra::FramebufferConfig& framebuffer) {
aa_pipeline = device.GetLogical().CreateGraphicsPipeline(fxaa_pipeline_ci);
}
-void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view,
- bool nn) const {
+void BlitScreen::UpdateAADescriptorSet(VkImageView image_view, bool nn) const {
const VkDescriptorImageInfo image_info{
.sampler = nn ? *nn_sampler : *sampler,
.imageView = image_view,
@@ -1356,8 +1354,7 @@ void BlitScreen::UpdateAADescriptorSet(std::size_t image_index, VkImageView imag
device.GetLogical().UpdateDescriptorSets(std::array{sampler_write, sampler_write_2}, {});
}
-void BlitScreen::UpdateDescriptorSet(std::size_t image_index, VkImageView image_view,
- bool nn) const {
+void BlitScreen::UpdateDescriptorSet(VkImageView image_view, bool nn) const {
const VkDescriptorBufferInfo buffer_info{
.buffer = *buffer,
.offset = offsetof(BufferData, uniform),
@@ -1480,8 +1477,7 @@ u64 BlitScreen::CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer)
return sizeof(BufferData) + GetSizeInBytes(framebuffer) * image_count;
}
-u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
- std::size_t image_index) const {
+u64 BlitScreen::GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const {
constexpr auto first_image_offset = static_cast<u64>(sizeof(BufferData));
return first_image_offset + GetSizeInBytes(framebuffer) * image_index;
}
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index ebe10b08b..68ec20253 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -5,6 +5,7 @@
#include <memory>
+#include "core/frontend/framebuffer_layout.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -42,6 +43,9 @@ class RasterizerVulkan;
class Scheduler;
class SMAA;
class Swapchain;
+class PresentManager;
+
+struct Frame;
struct ScreenInfo {
VkImage image{};
@@ -55,18 +59,17 @@ class BlitScreen {
public:
explicit BlitScreen(Core::Memory::Memory& cpu_memory, Core::Frontend::EmuWindow& render_window,
const Device& device, MemoryAllocator& memory_manager, Swapchain& swapchain,
- Scheduler& scheduler, const ScreenInfo& screen_info);
+ PresentManager& present_manager, Scheduler& scheduler,
+ const ScreenInfo& screen_info);
~BlitScreen();
void Recreate();
- [[nodiscard]] VkSemaphore Draw(const Tegra::FramebufferConfig& framebuffer,
- const VkFramebuffer& host_framebuffer,
- const Layout::FramebufferLayout layout, VkExtent2D render_area,
- bool use_accelerated);
+ void Draw(const Tegra::FramebufferConfig& framebuffer, const VkFramebuffer& host_framebuffer,
+ const Layout::FramebufferLayout layout, VkExtent2D render_area, bool use_accelerated);
- [[nodiscard]] VkSemaphore DrawToSwapchain(const Tegra::FramebufferConfig& framebuffer,
- bool use_accelerated);
+ void DrawToSwapchain(Frame* frame, const Tegra::FramebufferConfig& framebuffer,
+ bool use_accelerated, bool is_srgb);
[[nodiscard]] vk::Framebuffer CreateFramebuffer(const VkImageView& image_view,
VkExtent2D extent);
@@ -79,10 +82,9 @@ private:
void CreateStaticResources();
void CreateShaders();
- void CreateSemaphores();
void CreateDescriptorPool();
void CreateRenderPass();
- vk::RenderPass CreateRenderPassImpl(VkFormat, bool is_present = true);
+ vk::RenderPass CreateRenderPassImpl(VkFormat format);
void CreateDescriptorSetLayout();
void CreateDescriptorSets();
void CreatePipelineLayout();
@@ -90,15 +92,14 @@ private:
void CreateSampler();
void CreateDynamicResources();
- void CreateFramebuffers();
void RefreshResources(const Tegra::FramebufferConfig& framebuffer);
void ReleaseRawImages();
void CreateStagingBuffer(const Tegra::FramebufferConfig& framebuffer);
void CreateRawImages(const Tegra::FramebufferConfig& framebuffer);
- void UpdateDescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const;
- void UpdateAADescriptorSet(std::size_t image_index, VkImageView image_view, bool nn) const;
+ void UpdateDescriptorSet(VkImageView image_view, bool nn) const;
+ void UpdateAADescriptorSet(VkImageView image_view, bool nn) const;
void SetUniformData(BufferData& data, const Layout::FramebufferLayout layout) const;
void SetVertexData(BufferData& data, const Tegra::FramebufferConfig& framebuffer,
const Layout::FramebufferLayout layout) const;
@@ -107,16 +108,17 @@ private:
void CreateFSR();
u64 CalculateBufferSize(const Tegra::FramebufferConfig& framebuffer) const;
- u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer,
- std::size_t image_index) const;
+ u64 GetRawImageOffset(const Tegra::FramebufferConfig& framebuffer) const;
Core::Memory::Memory& cpu_memory;
Core::Frontend::EmuWindow& render_window;
const Device& device;
MemoryAllocator& memory_allocator;
Swapchain& swapchain;
+ PresentManager& present_manager;
Scheduler& scheduler;
std::size_t image_count;
+ std::size_t image_index{};
const ScreenInfo& screen_info;
vk::ShaderModule vertex_shader;
@@ -135,7 +137,6 @@ private:
vk::Pipeline gaussian_pipeline;
vk::Pipeline scaleforce_pipeline;
vk::RenderPass renderpass;
- std::vector<vk::Framebuffer> framebuffers;
vk::DescriptorSets descriptor_sets;
vk::Sampler nn_sampler;
vk::Sampler sampler;
@@ -145,7 +146,6 @@ private:
std::vector<u64> resource_ticks;
- std::vector<vk::Semaphore> semaphores;
std::vector<vk::Image> raw_images;
std::vector<vk::ImageView> raw_image_views;
std::vector<MemoryCommit> raw_buffer_commits;
@@ -164,6 +164,8 @@ private:
u32 raw_width = 0;
u32 raw_height = 0;
Service::android::PixelFormat pixel_format{};
+ bool current_srgb;
+ VkFormat image_view_format;
std::unique_ptr<FSR> fsr;
std::unique_ptr<SMAA> smaa;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 9cbcb3c8f..9627eb129 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -298,12 +298,14 @@ private:
BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_allocator_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_,
+ GuestDescriptorQueue& guest_descriptor_queue_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue,
DescriptorPool& descriptor_pool)
: device{device_}, memory_allocator{memory_allocator_}, scheduler{scheduler_},
- staging_pool{staging_pool_}, update_descriptor_queue{update_descriptor_queue_},
- uint8_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue),
- quad_index_pass(device, scheduler, descriptor_pool, staging_pool, update_descriptor_queue) {
+ staging_pool{staging_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
+ uint8_pass(device, scheduler, descriptor_pool, staging_pool, compute_pass_descriptor_queue),
+ quad_index_pass(device, scheduler, descriptor_pool, staging_pool,
+ compute_pass_descriptor_queue) {
quad_array_index_buffer = std::make_shared<QuadArrayIndexBuffer>(device_, memory_allocator_,
scheduler_, staging_pool_);
quad_strip_index_buffer = std::make_shared<QuadStripIndexBuffer>(device_, memory_allocator_,
@@ -314,8 +316,12 @@ StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Upload);
}
-StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size) {
- return staging_pool.Request(size, MemoryUsage::Download);
+StagingBufferRef BufferCacheRuntime::DownloadStagingBuffer(size_t size, bool deferred) {
+ return staging_pool.Request(size, MemoryUsage::Download, deferred);
+}
+
+void BufferCacheRuntime::FreeDeferredStagingBuffer(StagingBufferRef& ref) {
+ staging_pool.FreeDeferred(ref);
}
u64 BufferCacheRuntime::GetDeviceLocalMemory() const {
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index 183b33632..5e9602905 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -3,7 +3,8 @@
#pragma once
-#include "video_core/buffer_cache/buffer_cache.h"
+#include "video_core/buffer_cache/buffer_cache_base.h"
+#include "video_core/buffer_cache/memory_tracker_base.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/renderer_vulkan/vk_staging_buffer_pool.h"
@@ -62,7 +63,8 @@ class BufferCacheRuntime {
public:
explicit BufferCacheRuntime(const Device& device_, MemoryAllocator& memory_manager_,
Scheduler& scheduler_, StagingBufferPool& staging_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_,
+ GuestDescriptorQueue& guest_descriptor_queue,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue,
DescriptorPool& descriptor_pool);
void Finish();
@@ -75,7 +77,9 @@ public:
[[nodiscard]] StagingBufferRef UploadStagingBuffer(size_t size);
- [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size);
+ [[nodiscard]] StagingBufferRef DownloadStagingBuffer(size_t size, bool deferred = false);
+
+ void FreeDeferredStagingBuffer(StagingBufferRef& ref);
void PreCopyBarrier();
@@ -113,12 +117,12 @@ public:
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
VideoCore::Surface::PixelFormat format) {
- update_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
+ guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format));
}
private:
void BindBuffer(VkBuffer buffer, u32 offset, u32 size) {
- update_descriptor_queue.AddBuffer(buffer, offset, size);
+ guest_descriptor_queue.AddBuffer(buffer, offset, size);
}
void ReserveNullBuffer();
@@ -127,7 +131,7 @@ private:
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
StagingBufferPool& staging_pool;
- UpdateDescriptorQueue& update_descriptor_queue;
+ GuestDescriptorQueue& guest_descriptor_queue;
std::shared_ptr<QuadArrayIndexBuffer> quad_array_index_buffer;
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
@@ -142,6 +146,8 @@ private:
struct BufferCacheParams {
using Runtime = Vulkan::BufferCacheRuntime;
using Buffer = Vulkan::Buffer;
+ using Async_Buffer = Vulkan::StagingBufferRef;
+ using MemoryTracker = VideoCommon::MemoryTrackerBase<VideoCore::RasterizerInterface>;
static constexpr bool IS_OPENGL = false;
static constexpr bool HAS_PERSISTENT_UNIFORM_BUFFER_BINDINGS = false;
@@ -150,6 +156,7 @@ struct BufferCacheParams {
static constexpr bool NEEDS_BIND_STORAGE_INDEX = false;
static constexpr bool USE_MEMORY_MAPS = true;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
+ static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache_base.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache_base.cpp
new file mode 100644
index 000000000..f9e271507
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache_base.cpp
@@ -0,0 +1,9 @@
+// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "video_core/buffer_cache/buffer_cache.h"
+#include "video_core/renderer_vulkan/vk_buffer_cache.h"
+
+namespace VideoCommon {
+template class VideoCommon::BufferCache<Vulkan::BufferCacheParams>;
+}
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.cpp b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
index 1a316b6eb..3bc8553e1 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.cpp
@@ -200,12 +200,12 @@ ComputePass::~ComputePass() = default;
Uint8Pass::Uint8Pass(const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool,
StagingBufferPool& staging_buffer_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_)
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, descriptor_pool, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO, {},
VULKAN_UINT8_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
- update_descriptor_queue{update_descriptor_queue_} {}
+ compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
Uint8Pass::~Uint8Pass() = default;
@@ -214,10 +214,10 @@ std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer
const u32 staging_size = static_cast<u32>(num_vertices * sizeof(u16));
const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
- update_descriptor_queue.Acquire();
- update_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
- update_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
- const void* const descriptor_data{update_descriptor_queue.UpdateData()};
+ compute_pass_descriptor_queue.Acquire();
+ compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, num_vertices);
+ compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
+ const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, descriptor_data, num_vertices](vk::CommandBuffer cmdbuf) {
@@ -242,12 +242,12 @@ std::pair<VkBuffer, VkDeviceSize> Uint8Pass::Assemble(u32 num_vertices, VkBuffer
QuadIndexedPass::QuadIndexedPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_)
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, descriptor_pool_, INPUT_OUTPUT_DESCRIPTOR_SET_BINDINGS,
INPUT_OUTPUT_DESCRIPTOR_UPDATE_TEMPLATE, INPUT_OUTPUT_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(u32) * 3>, VULKAN_QUAD_INDEXED_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
- update_descriptor_queue{update_descriptor_queue_} {}
+ compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
QuadIndexedPass::~QuadIndexedPass() = default;
@@ -272,10 +272,10 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
const std::size_t staging_size = num_tri_vertices * sizeof(u32);
const auto staging = staging_buffer_pool.Request(staging_size, MemoryUsage::DeviceLocal);
- update_descriptor_queue.Acquire();
- update_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
- update_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
- const void* const descriptor_data{update_descriptor_queue.UpdateData()};
+ compute_pass_descriptor_queue.Acquire();
+ compute_pass_descriptor_queue.AddBuffer(src_buffer, src_offset, input_size);
+ compute_pass_descriptor_queue.AddBuffer(staging.buffer, staging.offset, staging_size);
+ const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([this, descriptor_data, num_tri_vertices, base_vertex, index_shift,
@@ -304,13 +304,14 @@ std::pair<VkBuffer, VkDeviceSize> QuadIndexedPass::Assemble(
ASTCDecoderPass::ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_,
MemoryAllocator& memory_allocator_)
: ComputePass(device_, descriptor_pool_, ASTC_DESCRIPTOR_SET_BINDINGS,
ASTC_PASS_DESCRIPTOR_UPDATE_TEMPLATE_ENTRY, ASTC_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(AstcPushConstants)>, ASTC_DECODER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
- update_descriptor_queue{update_descriptor_queue_}, memory_allocator{memory_allocator_} {}
+ compute_pass_descriptor_queue{compute_pass_descriptor_queue_}, memory_allocator{
+ memory_allocator_} {}
ASTCDecoderPass::~ASTCDecoderPass() = default;
@@ -358,11 +359,11 @@ void ASTCDecoderPass::Assemble(Image& image, const StagingBufferRef& map,
const u32 num_dispatches_y = Common::DivCeil(swizzle.num_tiles.height, 8U);
const u32 num_dispatches_z = image.info.resources.layers;
- update_descriptor_queue.Acquire();
- update_descriptor_queue.AddBuffer(map.buffer, input_offset,
- image.guest_size_bytes - swizzle.buffer_offset);
- update_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
- const void* const descriptor_data{update_descriptor_queue.UpdateData()};
+ compute_pass_descriptor_queue.Acquire();
+ compute_pass_descriptor_queue.AddBuffer(map.buffer, input_offset,
+ image.guest_size_bytes - swizzle.buffer_offset);
+ compute_pass_descriptor_queue.AddImage(image.StorageImageView(swizzle.level));
+ const void* const descriptor_data{compute_pass_descriptor_queue.UpdateData()};
// To unswizzle the ASTC data
const auto params = MakeBlockLinearSwizzle2DParams(swizzle, image.info);
diff --git a/src/video_core/renderer_vulkan/vk_compute_pass.h b/src/video_core/renderer_vulkan/vk_compute_pass.h
index c4c8fa081..dd3927376 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pass.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pass.h
@@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
+#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/vulkan_common/vulkan_memory_allocator.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -21,7 +22,6 @@ namespace Vulkan {
class Device;
class StagingBufferPool;
class Scheduler;
-class UpdateDescriptorQueue;
class Image;
struct StagingBufferRef;
@@ -50,7 +50,7 @@ class Uint8Pass final : public ComputePass {
public:
explicit Uint8Pass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_, StagingBufferPool& staging_buffer_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_);
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~Uint8Pass();
/// Assemble uint8 indices into an uint16 index buffer
@@ -61,7 +61,7 @@ public:
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
- UpdateDescriptorQueue& update_descriptor_queue;
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class QuadIndexedPass final : public ComputePass {
@@ -69,7 +69,7 @@ public:
explicit QuadIndexedPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_);
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~QuadIndexedPass();
std::pair<VkBuffer, VkDeviceSize> Assemble(
@@ -79,7 +79,7 @@ public:
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
- UpdateDescriptorQueue& update_descriptor_queue;
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class ASTCDecoderPass final : public ComputePass {
@@ -87,7 +87,7 @@ public:
explicit ASTCDecoderPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_,
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue_,
MemoryAllocator& memory_allocator_);
~ASTCDecoderPass();
@@ -97,7 +97,7 @@ public:
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
- UpdateDescriptorQueue& update_descriptor_queue;
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue;
MemoryAllocator& memory_allocator;
};
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
index 2a0f0dbf0..733e70d9d 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.cpp
@@ -26,13 +26,13 @@ using Tegra::Texture::TexturePair;
ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipeline_cache_,
DescriptorPool& descriptor_pool,
- UpdateDescriptorQueue& update_descriptor_queue_,
+ GuestDescriptorQueue& guest_descriptor_queue_,
Common::ThreadWorker* thread_worker,
PipelineStatistics* pipeline_statistics,
VideoCore::ShaderNotify* shader_notify, const Shader::Info& info_,
vk::ShaderModule spv_module_)
- : device{device_}, pipeline_cache(pipeline_cache_),
- update_descriptor_queue{update_descriptor_queue_}, info{info_},
+ : device{device_},
+ pipeline_cache(pipeline_cache_), guest_descriptor_queue{guest_descriptor_queue_}, info{info_},
spv_module(std::move(spv_module_)) {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
@@ -99,7 +99,7 @@ ComputePipeline::ComputePipeline(const Device& device_, vk::PipelineCache& pipel
void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
Tegra::MemoryManager& gpu_memory, Scheduler& scheduler,
BufferCache& buffer_cache, TextureCache& texture_cache) {
- update_descriptor_queue.Acquire();
+ guest_descriptor_queue.Acquire();
buffer_cache.SetComputeUniformBufferState(info.constant_buffer_mask, &uniform_buffer_sizes);
buffer_cache.UnbindComputeStorageBuffers();
@@ -194,7 +194,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
RescalingPushConstant rescaling;
const VkSampler* samplers_it{samplers.data()};
const VideoCommon::ImageViewInOut* views_it{views.data()};
- PushImageDescriptors(texture_cache, update_descriptor_queue, info, rescaling, samplers_it,
+ PushImageDescriptors(texture_cache, guest_descriptor_queue, info, rescaling, samplers_it,
views_it);
if (!is_built.load(std::memory_order::relaxed)) {
@@ -204,7 +204,7 @@ void ComputePipeline::Configure(Tegra::Engines::KeplerCompute& kepler_compute,
build_condvar.wait(lock, [this] { return is_built.load(std::memory_order::relaxed); });
});
}
- const void* const descriptor_data{update_descriptor_queue.UpdateData()};
+ const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
const bool is_rescaling = !info.texture_descriptors.empty() || !info.image_descriptors.empty();
scheduler.Record([this, descriptor_data, is_rescaling,
rescaling_data = rescaling.Data()](vk::CommandBuffer cmdbuf) {
diff --git a/src/video_core/renderer_vulkan/vk_compute_pipeline.h b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
index 78d77027f..d1a1e2c46 100644
--- a/src/video_core/renderer_vulkan/vk_compute_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_compute_pipeline.h
@@ -30,7 +30,7 @@ class ComputePipeline {
public:
explicit ComputePipeline(const Device& device, vk::PipelineCache& pipeline_cache,
DescriptorPool& descriptor_pool,
- UpdateDescriptorQueue& update_descriptor_queue,
+ GuestDescriptorQueue& guest_descriptor_queue,
Common::ThreadWorker* thread_worker,
PipelineStatistics* pipeline_statistics,
VideoCore::ShaderNotify* shader_notify, const Shader::Info& info,
@@ -48,7 +48,7 @@ public:
private:
const Device& device;
vk::PipelineCache& pipeline_cache;
- UpdateDescriptorQueue& update_descriptor_queue;
+ GuestDescriptorQueue& guest_descriptor_queue;
Shader::Info info;
VideoCommon::ComputeUniformBufferSizes uniform_buffer_sizes{};
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.cpp b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
index 0214b103a..fad9e3832 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.cpp
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.cpp
@@ -5,6 +5,7 @@
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
#include "video_core/renderer_vulkan/vk_fence_manager.h"
+#include "video_core/renderer_vulkan/vk_query_cache.h"
#include "video_core/renderer_vulkan/vk_scheduler.h"
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "video_core/vulkan_common/vulkan_device.h"
diff --git a/src/video_core/renderer_vulkan/vk_fence_manager.h b/src/video_core/renderer_vulkan/vk_fence_manager.h
index 7fe2afcd9..145359d4e 100644
--- a/src/video_core/renderer_vulkan/vk_fence_manager.h
+++ b/src/video_core/renderer_vulkan/vk_fence_manager.h
@@ -40,7 +40,16 @@ private:
};
using Fence = std::shared_ptr<InnerFence>;
-using GenericFenceManager = VideoCommon::FenceManager<Fence, TextureCache, BufferCache, QueryCache>;
+struct FenceManagerParams {
+ using FenceType = Fence;
+ using BufferCacheType = BufferCache;
+ using TextureCacheType = TextureCache;
+ using QueryCacheType = QueryCache;
+
+ static constexpr bool HAS_ASYNC_CHECK = true;
+};
+
+using GenericFenceManager = VideoCommon::FenceManager<FenceManagerParams>;
class FenceManager final : public GenericFenceManager {
public:
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
index baedc4424..f1bcd5cd6 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.cpp
@@ -236,13 +236,13 @@ GraphicsPipeline::GraphicsPipeline(
Scheduler& scheduler_, BufferCache& buffer_cache_, TextureCache& texture_cache_,
vk::PipelineCache& pipeline_cache_, VideoCore::ShaderNotify* shader_notify,
const Device& device_, DescriptorPool& descriptor_pool,
- UpdateDescriptorQueue& update_descriptor_queue_, Common::ThreadWorker* worker_thread,
+ GuestDescriptorQueue& guest_descriptor_queue_, Common::ThreadWorker* worker_thread,
PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache,
const GraphicsPipelineCacheKey& key_, std::array<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos)
: key{key_}, device{device_}, texture_cache{texture_cache_}, buffer_cache{buffer_cache_},
pipeline_cache(pipeline_cache_), scheduler{scheduler_},
- update_descriptor_queue{update_descriptor_queue_}, spv_modules{std::move(stages)} {
+ guest_descriptor_queue{guest_descriptor_queue_}, spv_modules{std::move(stages)} {
if (shader_notify) {
shader_notify->MarkShaderBuilding();
}
@@ -449,7 +449,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
buffer_cache.UpdateGraphicsBuffers(is_indexed);
buffer_cache.BindHostGeometryBuffers(is_indexed);
- update_descriptor_queue.Acquire();
+ guest_descriptor_queue.Acquire();
RescalingPushConstant rescaling;
RenderAreaPushConstant render_area;
@@ -457,7 +457,7 @@ void GraphicsPipeline::ConfigureImpl(bool is_indexed) {
const VideoCommon::ImageViewInOut* views_it{views.data()};
const auto prepare_stage{[&](size_t stage) LAMBDA_FORCEINLINE {
buffer_cache.BindHostStageBuffers(stage);
- PushImageDescriptors(texture_cache, update_descriptor_queue, stage_infos[stage], rescaling,
+ PushImageDescriptors(texture_cache, guest_descriptor_queue, stage_infos[stage], rescaling,
samplers_it, views_it);
const auto& info{stage_infos[0]};
if (info.uses_render_area) {
@@ -499,7 +499,7 @@ void GraphicsPipeline::ConfigureDraw(const RescalingPushConstant& rescaling,
const bool is_rescaling{texture_cache.IsRescaling()};
const bool update_rescaling{scheduler.UpdateRescaling(is_rescaling)};
const bool bind_pipeline{scheduler.UpdateGraphicsPipeline(this)};
- const void* const descriptor_data{update_descriptor_queue.UpdateData()};
+ const void* const descriptor_data{guest_descriptor_queue.UpdateData()};
scheduler.Record([this, descriptor_data, bind_pipeline, rescaling_data = rescaling.Data(),
is_rescaling, update_rescaling,
uses_render_area = render_area.uses_render_area,
diff --git a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
index 67c657d0e..99e56e9ad 100644
--- a/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
+++ b/src/video_core/renderer_vulkan/vk_graphics_pipeline.h
@@ -64,7 +64,6 @@ class RenderPassCache;
class RescalingPushConstant;
class RenderAreaPushConstant;
class Scheduler;
-class UpdateDescriptorQueue;
class GraphicsPipeline {
static constexpr size_t NUM_STAGES = Tegra::Engines::Maxwell3D::Regs::MaxShaderStage;
@@ -74,7 +73,7 @@ public:
Scheduler& scheduler, BufferCache& buffer_cache, TextureCache& texture_cache,
vk::PipelineCache& pipeline_cache, VideoCore::ShaderNotify* shader_notify,
const Device& device, DescriptorPool& descriptor_pool,
- UpdateDescriptorQueue& update_descriptor_queue, Common::ThreadWorker* worker_thread,
+ GuestDescriptorQueue& guest_descriptor_queue, Common::ThreadWorker* worker_thread,
PipelineStatistics* pipeline_statistics, RenderPassCache& render_pass_cache,
const GraphicsPipelineCacheKey& key, std::array<vk::ShaderModule, NUM_STAGES> stages,
const std::array<const Shader::Info*, NUM_STAGES>& infos);
@@ -133,7 +132,7 @@ private:
BufferCache& buffer_cache;
vk::PipelineCache& pipeline_cache;
Scheduler& scheduler;
- UpdateDescriptorQueue& update_descriptor_queue;
+ GuestDescriptorQueue& guest_descriptor_queue;
void (*configure_func)(GraphicsPipeline*, bool){};
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
index 985cc3203..66dfe5733 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
@@ -277,11 +277,11 @@ bool GraphicsPipelineCacheKey::operator==(const GraphicsPipelineCacheKey& rhs) c
PipelineCache::PipelineCache(RasterizerVulkan& rasterizer_, const Device& device_,
Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
- UpdateDescriptorQueue& update_descriptor_queue_,
+ GuestDescriptorQueue& guest_descriptor_queue_,
RenderPassCache& render_pass_cache_, BufferCache& buffer_cache_,
TextureCache& texture_cache_, VideoCore::ShaderNotify& shader_notify_)
: VideoCommon::ShaderCache{rasterizer_}, device{device_}, scheduler{scheduler_},
- descriptor_pool{descriptor_pool_}, update_descriptor_queue{update_descriptor_queue_},
+ descriptor_pool{descriptor_pool_}, guest_descriptor_queue{guest_descriptor_queue_},
render_pass_cache{render_pass_cache_}, buffer_cache{buffer_cache_},
texture_cache{texture_cache_}, shader_notify{shader_notify_},
use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()},
@@ -643,7 +643,7 @@ std::unique_ptr<GraphicsPipeline> PipelineCache::CreateGraphicsPipeline(
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
return std::make_unique<GraphicsPipeline>(
scheduler, buffer_cache, texture_cache, vulkan_pipeline_cache, &shader_notify, device,
- descriptor_pool, update_descriptor_queue, thread_worker, statistics, render_pass_cache, key,
+ descriptor_pool, guest_descriptor_queue, thread_worker, statistics, render_pass_cache, key,
std::move(modules), infos);
} catch (const Shader::Exception& exception) {
@@ -696,6 +696,14 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
ShaderPools& pools, const ComputePipelineCacheKey& key, Shader::Environment& env,
PipelineStatistics* statistics, bool build_in_parallel) try {
+ // TODO: Remove this when Intel fixes their shader compiler.
+ // https://github.com/IGCIT/Intel-GPU-Community-Issue-Tracker-IGCIT/issues/159
+ if (device.GetDriverID() == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS &&
+ !Settings::values.enable_compute_pipelines.GetValue()) {
+ LOG_ERROR(Render_Vulkan, "Skipping 0x{:016x}", key.Hash());
+ return nullptr;
+ }
+
LOG_INFO(Render_Vulkan, "0x{:016x}", key.Hash());
Shader::Maxwell::Flow::CFG cfg{env, pools.flow_block, env.StartAddress()};
@@ -715,7 +723,7 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
}
Common::ThreadWorker* const thread_worker{build_in_parallel ? &workers : nullptr};
return std::make_unique<ComputePipeline>(device, vulkan_pipeline_cache, descriptor_pool,
- update_descriptor_queue, thread_worker, statistics,
+ guest_descriptor_queue, thread_worker, statistics,
&shader_notify, program.info, std::move(spv_module));
} catch (const Shader::Exception& exception) {
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.h b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
index 5171912d7..15aa7e224 100644
--- a/src/video_core/renderer_vulkan/vk_pipeline_cache.h
+++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.h
@@ -82,7 +82,6 @@ class PipelineStatistics;
class RasterizerVulkan;
class RenderPassCache;
class Scheduler;
-class UpdateDescriptorQueue;
using VideoCommon::ShaderInfo;
@@ -102,7 +101,7 @@ class PipelineCache : public VideoCommon::ShaderCache {
public:
explicit PipelineCache(RasterizerVulkan& rasterizer, const Device& device, Scheduler& scheduler,
DescriptorPool& descriptor_pool,
- UpdateDescriptorQueue& update_descriptor_queue,
+ GuestDescriptorQueue& guest_descriptor_queue,
RenderPassCache& render_pass_cache, BufferCache& buffer_cache,
TextureCache& texture_cache, VideoCore::ShaderNotify& shader_notify_);
~PipelineCache();
@@ -144,7 +143,7 @@ private:
const Device& device;
Scheduler& scheduler;
DescriptorPool& descriptor_pool;
- UpdateDescriptorQueue& update_descriptor_queue;
+ GuestDescriptorQueue& guest_descriptor_queue;
RenderPassCache& render_pass_cache;
BufferCache& buffer_cache;
TextureCache& texture_cache;
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp
new file mode 100644
index 000000000..c49583013
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp
@@ -0,0 +1,457 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include "common/microprofile.h"
+#include "common/settings.h"
+#include "common/thread.h"
+#include "video_core/renderer_vulkan/vk_present_manager.h"
+#include "video_core/renderer_vulkan/vk_scheduler.h"
+#include "video_core/renderer_vulkan/vk_swapchain.h"
+#include "video_core/vulkan_common/vulkan_device.h"
+
+namespace Vulkan {
+
+MICROPROFILE_DEFINE(Vulkan_WaitPresent, "Vulkan", "Wait For Present", MP_RGB(128, 128, 128));
+MICROPROFILE_DEFINE(Vulkan_CopyToSwapchain, "Vulkan", "Copy to swapchain", MP_RGB(192, 255, 192));
+
+namespace {
+
+bool CanBlitToSwapchain(const vk::PhysicalDevice& physical_device, VkFormat format) {
+ const VkFormatProperties props{physical_device.GetFormatProperties(format)};
+ return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_BLIT_DST_BIT);
+}
+
+[[nodiscard]] VkImageSubresourceLayers MakeImageSubresourceLayers() {
+ return VkImageSubresourceLayers{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .mipLevel = 0,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ };
+}
+
+[[nodiscard]] VkImageBlit MakeImageBlit(s32 frame_width, s32 frame_height, s32 swapchain_width,
+ s32 swapchain_height) {
+ return VkImageBlit{
+ .srcSubresource = MakeImageSubresourceLayers(),
+ .srcOffsets =
+ {
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ {
+ .x = frame_width,
+ .y = frame_height,
+ .z = 1,
+ },
+ },
+ .dstSubresource = MakeImageSubresourceLayers(),
+ .dstOffsets =
+ {
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ {
+ .x = swapchain_width,
+ .y = swapchain_height,
+ .z = 1,
+ },
+ },
+ };
+}
+
+[[nodiscard]] VkImageCopy MakeImageCopy(u32 frame_width, u32 frame_height, u32 swapchain_width,
+ u32 swapchain_height) {
+ return VkImageCopy{
+ .srcSubresource = MakeImageSubresourceLayers(),
+ .srcOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .dstSubresource = MakeImageSubresourceLayers(),
+ .dstOffset =
+ {
+ .x = 0,
+ .y = 0,
+ .z = 0,
+ },
+ .extent =
+ {
+ .width = std::min(frame_width, swapchain_width),
+ .height = std::min(frame_height, swapchain_height),
+ .depth = 1,
+ },
+ };
+}
+
+} // Anonymous namespace
+
+PresentManager::PresentManager(Core::Frontend::EmuWindow& render_window_, const Device& device_,
+ MemoryAllocator& memory_allocator_, Scheduler& scheduler_,
+ Swapchain& swapchain_)
+ : render_window{render_window_}, device{device_},
+ memory_allocator{memory_allocator_}, scheduler{scheduler_}, swapchain{swapchain_},
+ blit_supported{CanBlitToSwapchain(device.GetPhysical(), swapchain.GetImageViewFormat())},
+ use_present_thread{Settings::values.async_presentation.GetValue()},
+ image_count{swapchain.GetImageCount()} {
+
+ auto& dld = device.GetLogical();
+ cmdpool = dld.CreateCommandPool({
+ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
+ .pNext = nullptr,
+ .flags =
+ VK_COMMAND_POOL_CREATE_TRANSIENT_BIT | VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
+ .queueFamilyIndex = device.GetGraphicsFamily(),
+ });
+ auto cmdbuffers = cmdpool.Allocate(image_count);
+
+ frames.resize(image_count);
+ for (u32 i = 0; i < frames.size(); i++) {
+ Frame& frame = frames[i];
+ frame.cmdbuf = vk::CommandBuffer{cmdbuffers[i], device.GetDispatchLoader()};
+ frame.render_ready = dld.CreateSemaphore({
+ .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ });
+ frame.present_done = dld.CreateFence({
+ .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = VK_FENCE_CREATE_SIGNALED_BIT,
+ });
+ free_queue.push(&frame);
+ }
+
+ if (use_present_thread) {
+ present_thread = std::jthread([this](std::stop_token token) { PresentThread(token); });
+ }
+}
+
+PresentManager::~PresentManager() = default;
+
+Frame* PresentManager::GetRenderFrame() {
+ MICROPROFILE_SCOPE(Vulkan_WaitPresent);
+
+ // Wait for free presentation frames
+ std::unique_lock lock{free_mutex};
+ free_cv.wait(lock, [this] { return !free_queue.empty(); });
+
+ // Take the frame from the queue
+ Frame* frame = free_queue.front();
+ free_queue.pop();
+
+ // Wait for the presentation to be finished so all frame resources are free
+ frame->present_done.Wait();
+ frame->present_done.Reset();
+
+ return frame;
+}
+
+void PresentManager::Present(Frame* frame) {
+ if (!use_present_thread) {
+ scheduler.WaitWorker();
+ CopyToSwapchain(frame);
+ free_queue.push(frame);
+ return;
+ }
+
+ scheduler.Record([this, frame](vk::CommandBuffer) {
+ std::unique_lock lock{queue_mutex};
+ present_queue.push(frame);
+ frame_cv.notify_one();
+ });
+}
+
+void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb,
+ VkFormat image_view_format, VkRenderPass rd) {
+ auto& dld = device.GetLogical();
+
+ frame->width = width;
+ frame->height = height;
+ frame->is_srgb = is_srgb;
+
+ frame->image = dld.CreateImage({
+ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT,
+ .imageType = VK_IMAGE_TYPE_2D,
+ .format = swapchain.GetImageFormat(),
+ .extent =
+ {
+ .width = width,
+ .height = height,
+ .depth = 1,
+ },
+ .mipLevels = 1,
+ .arrayLayers = 1,
+ .samples = VK_SAMPLE_COUNT_1_BIT,
+ .tiling = VK_IMAGE_TILING_OPTIMAL,
+ .usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .sharingMode = VK_SHARING_MODE_EXCLUSIVE,
+ .queueFamilyIndexCount = 0,
+ .pQueueFamilyIndices = nullptr,
+ .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ });
+
+ frame->image_commit = memory_allocator.Commit(frame->image, MemoryUsage::DeviceLocal);
+
+ frame->image_view = dld.CreateImageView({
+ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .image = *frame->image,
+ .viewType = VK_IMAGE_VIEW_TYPE_2D,
+ .format = image_view_format,
+ .components =
+ {
+ .r = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .g = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .b = VK_COMPONENT_SWIZZLE_IDENTITY,
+ .a = VK_COMPONENT_SWIZZLE_IDENTITY,
+ },
+ .subresourceRange =
+ {
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = 1,
+ },
+ });
+
+ const VkImageView image_view{*frame->image_view};
+ frame->framebuffer = dld.CreateFramebuffer({
+ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
+ .pNext = nullptr,
+ .flags = 0,
+ .renderPass = rd,
+ .attachmentCount = 1,
+ .pAttachments = &image_view,
+ .width = width,
+ .height = height,
+ .layers = 1,
+ });
+}
+
+void PresentManager::WaitPresent() {
+ if (!use_present_thread) {
+ return;
+ }
+
+ // Wait for the present queue to be empty
+ {
+ std::unique_lock queue_lock{queue_mutex};
+ frame_cv.wait(queue_lock, [this] { return present_queue.empty(); });
+ }
+
+ // The above condition will be satisfied when the last frame is taken from the queue.
+ // To ensure that frame has been presented as well take hold of the swapchain
+ // mutex.
+ std::scoped_lock swapchain_lock{swapchain_mutex};
+}
+
+void PresentManager::PresentThread(std::stop_token token) {
+ Common::SetCurrentThreadName("VulkanPresent");
+ while (!token.stop_requested()) {
+ std::unique_lock lock{queue_mutex};
+
+ // Wait for presentation frames
+ Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); });
+ if (token.stop_requested()) {
+ return;
+ }
+
+ // Take the frame and notify anyone waiting
+ Frame* frame = present_queue.front();
+ present_queue.pop();
+ frame_cv.notify_one();
+
+ // By exchanging the lock ownership we take the swapchain lock
+ // before the queue lock goes out of scope. This way the swapchain
+ // lock in WaitPresent is guaranteed to occur after here.
+ std::exchange(lock, std::unique_lock{swapchain_mutex});
+
+ CopyToSwapchain(frame);
+
+ // Free the frame for reuse
+ std::scoped_lock fl{free_mutex};
+ free_queue.push(frame);
+ free_cv.notify_one();
+ }
+}
+
+void PresentManager::CopyToSwapchain(Frame* frame) {
+ MICROPROFILE_SCOPE(Vulkan_CopyToSwapchain);
+
+ const auto recreate_swapchain = [&] {
+ swapchain.Create(frame->width, frame->height, frame->is_srgb);
+ image_count = swapchain.GetImageCount();
+ };
+
+ // If the size or colorspace of the incoming frames has changed, recreate the swapchain
+ // to account for that.
+ const bool srgb_changed = swapchain.NeedsRecreation(frame->is_srgb);
+ const bool size_changed =
+ swapchain.GetWidth() != frame->width || swapchain.GetHeight() != frame->height;
+ if (srgb_changed || size_changed) {
+ recreate_swapchain();
+ }
+
+ while (swapchain.AcquireNextImage()) {
+ recreate_swapchain();
+ }
+
+ const vk::CommandBuffer cmdbuf{frame->cmdbuf};
+ cmdbuf.Begin({
+ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
+ .pNext = nullptr,
+ .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
+ .pInheritanceInfo = nullptr,
+ });
+
+ const VkImage image{swapchain.CurrentImage()};
+ const VkExtent2D extent = swapchain.GetExtent();
+ const std::array pre_barriers{
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = 0,
+ .dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ },
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = *frame->image,
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ },
+ };
+ const std::array post_barriers{
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = image,
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ },
+ VkImageMemoryBarrier{
+ .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+ .newLayout = VK_IMAGE_LAYOUT_GENERAL,
+ .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
+ .image = *frame->image,
+ .subresourceRange{
+ .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
+ .baseMipLevel = 0,
+ .levelCount = 1,
+ .baseArrayLayer = 0,
+ .layerCount = VK_REMAINING_ARRAY_LAYERS,
+ },
+ },
+ };
+
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, {},
+ {}, {}, pre_barriers);
+
+ if (blit_supported) {
+ cmdbuf.BlitImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ MakeImageBlit(frame->width, frame->height, extent.width, extent.height),
+ VK_FILTER_LINEAR);
+ } else {
+ cmdbuf.CopyImage(*frame->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image,
+ VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+ MakeImageCopy(frame->width, frame->height, extent.width, extent.height));
+ }
+
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, {},
+ {}, {}, post_barriers);
+
+ cmdbuf.End();
+
+ const VkSemaphore present_semaphore = swapchain.CurrentPresentSemaphore();
+ const VkSemaphore render_semaphore = swapchain.CurrentRenderSemaphore();
+ const std::array wait_semaphores = {present_semaphore, *frame->render_ready};
+
+ static constexpr std::array<VkPipelineStageFlags, 2> wait_stage_masks{
+ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
+ VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
+ };
+
+ const VkSubmitInfo submit_info{
+ .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
+ .pNext = nullptr,
+ .waitSemaphoreCount = 2U,
+ .pWaitSemaphores = wait_semaphores.data(),
+ .pWaitDstStageMask = wait_stage_masks.data(),
+ .commandBufferCount = 1,
+ .pCommandBuffers = cmdbuf.address(),
+ .signalSemaphoreCount = 1U,
+ .pSignalSemaphores = &render_semaphore,
+ };
+
+ // Submit the image copy/blit to the swapchain
+ {
+ std::scoped_lock lock{scheduler.submit_mutex};
+ switch (const VkResult result =
+ device.GetGraphicsQueue().Submit(submit_info, *frame->present_done)) {
+ case VK_SUCCESS:
+ break;
+ case VK_ERROR_DEVICE_LOST:
+ device.ReportLoss();
+ [[fallthrough]];
+ default:
+ vk::Check(result);
+ break;
+ }
+ }
+
+ // Present
+ swapchain.Present(render_semaphore);
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_present_manager.h b/src/video_core/renderer_vulkan/vk_present_manager.h
new file mode 100644
index 000000000..420a775e2
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_present_manager.h
@@ -0,0 +1,83 @@
+// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <condition_variable>
+#include <mutex>
+#include <queue>
+
+#include "common/common_types.h"
+#include "common/polyfill_thread.h"
+#include "video_core/vulkan_common/vulkan_memory_allocator.h"
+#include "video_core/vulkan_common/vulkan_wrapper.h"
+
+namespace Core::Frontend {
+class EmuWindow;
+} // namespace Core::Frontend
+
+namespace Vulkan {
+
+class Device;
+class Scheduler;
+class Swapchain;
+
+struct Frame {
+ u32 width;
+ u32 height;
+ bool is_srgb;
+ vk::Image image;
+ vk::ImageView image_view;
+ vk::Framebuffer framebuffer;
+ MemoryCommit image_commit;
+ vk::CommandBuffer cmdbuf;
+ vk::Semaphore render_ready;
+ vk::Fence present_done;
+};
+
+class PresentManager {
+public:
+ PresentManager(Core::Frontend::EmuWindow& render_window, const Device& device,
+ MemoryAllocator& memory_allocator, Scheduler& scheduler, Swapchain& swapchain);
+ ~PresentManager();
+
+ /// Returns the last used presentation frame
+ Frame* GetRenderFrame();
+
+ /// Pushes a frame for presentation
+ void Present(Frame* frame);
+
+ /// Recreates the present frame to match the provided parameters
+ void RecreateFrame(Frame* frame, u32 width, u32 height, bool is_srgb,
+ VkFormat image_view_format, VkRenderPass rd);
+
+ /// Waits for the present thread to finish presenting all queued frames.
+ void WaitPresent();
+
+private:
+ void PresentThread(std::stop_token token);
+
+ void CopyToSwapchain(Frame* frame);
+
+private:
+ Core::Frontend::EmuWindow& render_window;
+ const Device& device;
+ MemoryAllocator& memory_allocator;
+ Scheduler& scheduler;
+ Swapchain& swapchain;
+ vk::CommandPool cmdpool;
+ std::vector<Frame> frames;
+ std::queue<Frame*> present_queue;
+ std::queue<Frame*> free_queue;
+ std::condition_variable_any frame_cv;
+ std::condition_variable free_cv;
+ std::mutex swapchain_mutex;
+ std::mutex queue_mutex;
+ std::mutex free_mutex;
+ std::jthread present_thread;
+ bool blit_supported;
+ bool use_present_thread;
+ std::size_t image_count;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.cpp b/src/video_core/renderer_vulkan/vk_query_cache.cpp
index 929c8ece6..d67490449 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_query_cache.cpp
@@ -66,9 +66,10 @@ void QueryPool::Reserve(std::pair<VkQueryPool, u32> query) {
}
}
-QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_, const Device& device_,
+QueryCache::QueryCache(VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_, const Device& device_,
Scheduler& scheduler_)
- : QueryCacheBase{rasterizer_}, device{device_}, scheduler{scheduler_},
+ : QueryCacheBase{rasterizer_, cpu_memory_}, device{device_}, scheduler{scheduler_},
query_pools{
QueryPool{device_, scheduler_, QueryType::SamplesPassed},
} {}
@@ -98,8 +99,10 @@ HostCounter::HostCounter(QueryCache& cache_, std::shared_ptr<HostCounter> depend
query{cache_.AllocateQuery(type_)}, tick{cache_.GetScheduler().CurrentTick()} {
const vk::Device* logical = &cache.GetDevice().GetLogical();
cache.GetScheduler().Record([logical, query = query](vk::CommandBuffer cmdbuf) {
+ const bool use_precise = Settings::IsGPULevelHigh();
logical->ResetQueryPool(query.first, query.second, 1);
- cmdbuf.BeginQuery(query.first, query.second, VK_QUERY_CONTROL_PRECISE_BIT);
+ cmdbuf.BeginQuery(query.first, query.second,
+ use_precise ? VK_QUERY_CONTROL_PRECISE_BIT : 0);
});
}
@@ -112,8 +115,10 @@ void HostCounter::EndQuery() {
[query = query](vk::CommandBuffer cmdbuf) { cmdbuf.EndQuery(query.first, query.second); });
}
-u64 HostCounter::BlockingQuery() const {
- cache.GetScheduler().Wait(tick);
+u64 HostCounter::BlockingQuery(bool async) const {
+ if (!async) {
+ cache.GetScheduler().Wait(tick);
+ }
u64 data;
const VkResult query_result = cache.GetDevice().GetLogical().GetQueryResults(
query.first, query.second, 1, sizeof(data), &data, sizeof(data),
diff --git a/src/video_core/renderer_vulkan/vk_query_cache.h b/src/video_core/renderer_vulkan/vk_query_cache.h
index 26762ee09..c1b9552eb 100644
--- a/src/video_core/renderer_vulkan/vk_query_cache.h
+++ b/src/video_core/renderer_vulkan/vk_query_cache.h
@@ -52,7 +52,8 @@ private:
class QueryCache final
: public VideoCommon::QueryCacheBase<QueryCache, CachedQuery, CounterStream, HostCounter> {
public:
- explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_, const Device& device_,
+ explicit QueryCache(VideoCore::RasterizerInterface& rasterizer_,
+ Core::Memory::Memory& cpu_memory_, const Device& device_,
Scheduler& scheduler_);
~QueryCache();
@@ -83,7 +84,7 @@ public:
void EndQuery();
private:
- u64 BlockingQuery() const override;
+ u64 BlockingQuery(bool async = false) const override;
QueryCache& cache;
const VideoCore::QueryType type;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index 673ab478e..64bd2f6a5 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -160,19 +160,19 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
: RasterizerAccelerated{cpu_memory_}, gpu{gpu_}, screen_info{screen_info_}, device{device_},
memory_allocator{memory_allocator_}, state_tracker{state_tracker_}, scheduler{scheduler_},
staging_pool(device, memory_allocator, scheduler), descriptor_pool(device, scheduler),
- update_descriptor_queue(device, scheduler),
- blit_image(device, scheduler, state_tracker, descriptor_pool),
- render_pass_cache(device), texture_cache_runtime{device, scheduler,
- memory_allocator, staging_pool,
- blit_image, render_pass_cache,
- descriptor_pool, update_descriptor_queue},
+ guest_descriptor_queue(device, scheduler), compute_pass_descriptor_queue(device, scheduler),
+ blit_image(device, scheduler, state_tracker, descriptor_pool), render_pass_cache(device),
+ texture_cache_runtime{
+ device, scheduler, memory_allocator, staging_pool,
+ blit_image, render_pass_cache, descriptor_pool, compute_pass_descriptor_queue},
texture_cache(texture_cache_runtime, *this),
buffer_cache_runtime(device, memory_allocator, scheduler, staging_pool,
- update_descriptor_queue, descriptor_pool),
+ guest_descriptor_queue, compute_pass_descriptor_queue, descriptor_pool),
buffer_cache(*this, cpu_memory_, buffer_cache_runtime),
- pipeline_cache(*this, device, scheduler, descriptor_pool, update_descriptor_queue,
+ pipeline_cache(*this, device, scheduler, descriptor_pool, guest_descriptor_queue,
render_pass_cache, buffer_cache, texture_cache, gpu.ShaderNotify()),
- query_cache{*this, device, scheduler}, accelerate_dma(buffer_cache, texture_cache, scheduler),
+ query_cache{*this, cpu_memory_, device, scheduler},
+ accelerate_dma(buffer_cache, texture_cache, scheduler),
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
wfi_event(device.GetLogical().CreateEvent()) {
scheduler.SetQueryCache(query_cache);
@@ -501,6 +501,22 @@ bool RasterizerVulkan::MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheT
return false;
}
+VideoCore::RasterizerDownloadArea RasterizerVulkan::GetFlushArea(VAddr addr, u64 size) {
+ {
+ std::scoped_lock lock{texture_cache.mutex};
+ auto area = texture_cache.GetFlushArea(addr, size);
+ if (area) {
+ return *area;
+ }
+ }
+ VideoCore::RasterizerDownloadArea new_area{
+ .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE),
+ .end_address = Common::AlignUp(addr + size, Core::Memory::YUZU_PAGESIZE),
+ .preemtive = true,
+ };
+ return new_area;
+}
+
void RasterizerVulkan::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType which) {
if (addr == 0 || size == 0) {
return;
@@ -597,7 +613,7 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) {
}
void RasterizerVulkan::SignalReference() {
- fence_manager.SignalOrdering();
+ fence_manager.SignalReference();
}
void RasterizerVulkan::ReleaseFences() {
@@ -630,7 +646,7 @@ void RasterizerVulkan::WaitForIdle() {
cmdbuf.SetEvent(event, flags);
cmdbuf.WaitEvents(event, flags, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, {}, {}, {});
});
- SignalReference();
+ fence_manager.SignalOrdering();
}
void RasterizerVulkan::FragmentBarrier() {
@@ -652,7 +668,8 @@ void RasterizerVulkan::FlushCommands() {
void RasterizerVulkan::TickFrame() {
draw_counter = 0;
- update_descriptor_queue.TickFrame();
+ guest_descriptor_queue.TickFrame();
+ compute_pass_descriptor_queue.TickFrame();
fence_manager.TickFrame();
staging_pool.TickFrame();
{
@@ -675,7 +692,8 @@ bool RasterizerVulkan::AccelerateConditionalRendering() {
const GPUVAddr condition_address{maxwell3d->regs.render_enable.Address()};
Maxwell::ReportSemaphore::Compare cmp;
if (gpu_memory->IsMemoryDirty(condition_address, sizeof(cmp),
- VideoCommon::CacheType::BufferCache)) {
+ VideoCommon::CacheType::BufferCache |
+ VideoCommon::CacheType::QueryCache)) {
return true;
}
return false;
@@ -775,14 +793,13 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
const Tegra::DMA::BufferOperand& buffer_operand,
const Tegra::DMA::ImageOperand& image_operand) {
std::scoped_lock lock{buffer_cache.mutex, texture_cache.mutex};
- const auto image_id = texture_cache.DmaImageId(image_operand);
+ const auto image_id = texture_cache.DmaImageId(image_operand, IS_IMAGE_UPLOAD);
if (image_id == VideoCommon::NULL_IMAGE_ID) {
return false;
}
const u32 buffer_size = static_cast<u32>(buffer_operand.pitch * buffer_operand.height);
static constexpr auto sync_info = VideoCommon::ObtainBufferSynchronize::FullSynchronize;
- const auto post_op = IS_IMAGE_UPLOAD ? VideoCommon::ObtainBufferOperation::DoNothing
- : VideoCommon::ObtainBufferOperation::MarkAsWritten;
+ const auto post_op = VideoCommon::ObtainBufferOperation::DoNothing;
const auto [buffer, offset] =
buffer_cache.ObtainBuffer(buffer_operand.address, buffer_size, sync_info, post_op);
@@ -793,7 +810,8 @@ bool AccelerateDMA::DmaBufferImageCopy(const Tegra::DMA::ImageCopy& copy_info,
if constexpr (IS_IMAGE_UPLOAD) {
image->UploadMemory(buffer->Handle(), offset, copy_span);
} else {
- image->DownloadMemory(buffer->Handle(), offset, copy_span);
+ texture_cache.DownloadImageIntoBuffer(image, buffer->Handle(), offset, copy_span,
+ buffer_operand.address, buffer_size);
}
return true;
}
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index 1659fbc13..b39710b3c 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -92,6 +92,7 @@ public:
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
bool MustFlushRegion(VAddr addr, u64 size,
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
+ VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override;
void InvalidateRegion(VAddr addr, u64 size,
VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
void InnerInvalidation(std::span<const std::pair<VAddr, std::size_t>> sequences) override;
@@ -183,7 +184,8 @@ private:
StagingBufferPool staging_pool;
DescriptorPool descriptor_pool;
- UpdateDescriptorQueue update_descriptor_queue;
+ GuestDescriptorQueue guest_descriptor_queue;
+ ComputePassDescriptorQueue compute_pass_descriptor_queue;
BlitImageHelper blit_image;
RenderPassCache render_pass_cache;
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp
index 057e16967..80455ec08 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.cpp
+++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp
@@ -46,10 +46,11 @@ Scheduler::Scheduler(const Device& device_, StateTracker& state_tracker_)
Scheduler::~Scheduler() = default;
-void Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
+u64 Scheduler::Flush(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
// When flushing, we only send data to the worker thread; no waiting is necessary.
- SubmitExecution(signal_semaphore, wait_semaphore);
+ const u64 signal_value = SubmitExecution(signal_semaphore, wait_semaphore);
AllocateNewContext();
+ return signal_value;
}
void Scheduler::Finish(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
@@ -205,7 +206,7 @@ void Scheduler::AllocateWorkerCommandBuffer() {
});
}
-void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
+u64 Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore) {
EndPendingOperations();
InvalidateState();
@@ -217,6 +218,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s
on_submit();
}
+ std::scoped_lock lock{submit_mutex};
switch (const VkResult result = master_semaphore->SubmitQueue(
cmdbuf, signal_semaphore, wait_semaphore, signal_value)) {
case VK_SUCCESS:
@@ -231,6 +233,7 @@ void Scheduler::SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_s
});
chunk->MarkSubmit();
DispatchWork();
+ return signal_value;
}
void Scheduler::AllocateNewContext() {
diff --git a/src/video_core/renderer_vulkan/vk_scheduler.h b/src/video_core/renderer_vulkan/vk_scheduler.h
index 8d75ce987..475c682eb 100644
--- a/src/video_core/renderer_vulkan/vk_scheduler.h
+++ b/src/video_core/renderer_vulkan/vk_scheduler.h
@@ -34,7 +34,7 @@ public:
~Scheduler();
/// Sends the current execution context to the GPU.
- void Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
+ u64 Flush(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
/// Sends the current execution context to the GPU and waits for it to complete.
void Finish(VkSemaphore signal_semaphore = nullptr, VkSemaphore wait_semaphore = nullptr);
@@ -106,6 +106,8 @@ public:
return *master_semaphore;
}
+ std::mutex submit_mutex;
+
private:
class Command {
public:
@@ -201,7 +203,7 @@ private:
void AllocateWorkerCommandBuffer();
- void SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore);
+ u64 SubmitExecution(VkSemaphore signal_semaphore, VkSemaphore wait_semaphore);
void AllocateNewContext();
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.cpp b/src/video_core/renderer_vulkan/vk_swapchain.cpp
index b1465e35c..1e80ce463 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.cpp
+++ b/src/video_core/renderer_vulkan/vk_swapchain.cpp
@@ -14,6 +14,7 @@
#include "video_core/renderer_vulkan/vk_swapchain.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
+#include "vulkan/vulkan_core.h"
namespace Vulkan {
@@ -33,23 +34,47 @@ VkSurfaceFormatKHR ChooseSwapSurfaceFormat(vk::Span<VkSurfaceFormatKHR> formats)
return found != formats.end() ? *found : formats[0];
}
-VkPresentModeKHR ChooseSwapPresentMode(vk::Span<VkPresentModeKHR> modes) {
- // Mailbox (triple buffering) doesn't lock the application like fifo (vsync),
- // prefer it if vsync option is not selected
- const auto found_mailbox = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_MAILBOX_KHR);
- if (Settings::values.fullscreen_mode.GetValue() == Settings::FullscreenMode::Borderless &&
- found_mailbox != modes.end() && !Settings::values.use_vsync.GetValue()) {
- return VK_PRESENT_MODE_MAILBOX_KHR;
- }
- if (!Settings::values.use_speed_limit.GetValue()) {
- // FIFO present mode locks the framerate to the monitor's refresh rate,
- // Find an alternative to surpass this limitation if FPS is unlocked.
- const auto found_imm = std::find(modes.begin(), modes.end(), VK_PRESENT_MODE_IMMEDIATE_KHR);
- if (found_imm != modes.end()) {
- return VK_PRESENT_MODE_IMMEDIATE_KHR;
+static constexpr VkPresentModeKHR ChooseSwapPresentMode(bool has_imm, bool has_mailbox,
+ bool has_fifo_relaxed) {
+ // Mailbox doesn't lock the application like FIFO (vsync)
+ // FIFO present mode locks the framerate to the monitor's refresh rate
+ Settings::VSyncMode setting = [has_imm, has_mailbox]() {
+ // Choose Mailbox or Immediate if unlocked and those modes are supported
+ const auto mode = Settings::values.vsync_mode.GetValue();
+ if (Settings::values.use_speed_limit.GetValue()) {
+ return mode;
+ }
+ switch (mode) {
+ case Settings::VSyncMode::FIFO:
+ case Settings::VSyncMode::FIFORelaxed:
+ if (has_mailbox) {
+ return Settings::VSyncMode::Mailbox;
+ } else if (has_imm) {
+ return Settings::VSyncMode::Immediate;
+ }
+ [[fallthrough]];
+ default:
+ return mode;
}
+ }();
+ if ((setting == Settings::VSyncMode::Mailbox && !has_mailbox) ||
+ (setting == Settings::VSyncMode::Immediate && !has_imm) ||
+ (setting == Settings::VSyncMode::FIFORelaxed && !has_fifo_relaxed)) {
+ setting = Settings::VSyncMode::FIFO;
+ }
+
+ switch (setting) {
+ case Settings::VSyncMode::Immediate:
+ return VK_PRESENT_MODE_IMMEDIATE_KHR;
+ case Settings::VSyncMode::Mailbox:
+ return VK_PRESENT_MODE_MAILBOX_KHR;
+ case Settings::VSyncMode::FIFO:
+ return VK_PRESENT_MODE_FIFO_KHR;
+ case Settings::VSyncMode::FIFORelaxed:
+ return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
+ default:
+ return VK_PRESENT_MODE_FIFO_KHR;
}
- return VK_PRESENT_MODE_FIFO_KHR;
}
VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 width, u32 height) {
@@ -99,18 +124,16 @@ void Swapchain::Create(u32 width_, u32 height_, bool srgb) {
return;
}
- device.GetLogical().WaitIdle();
Destroy();
CreateSwapchain(capabilities, srgb);
CreateSemaphores();
- CreateImageViews();
resource_ticks.clear();
resource_ticks.resize(image_count);
}
-void Swapchain::AcquireNextImage() {
+bool Swapchain::AcquireNextImage() {
const VkResult result = device.GetLogical().AcquireNextImageKHR(
*swapchain, std::numeric_limits<u64>::max(), *present_semaphores[frame_index],
VK_NULL_HANDLE, &image_index);
@@ -127,8 +150,11 @@ void Swapchain::AcquireNextImage() {
LOG_ERROR(Render_Vulkan, "vkAcquireNextImageKHR returned {}", vk::ToString(result));
break;
}
+
scheduler.Wait(resource_ticks[image_index]);
resource_ticks[image_index] = scheduler.CurrentTick();
+
+ return is_suboptimal || is_outdated;
}
void Swapchain::Present(VkSemaphore render_semaphore) {
@@ -143,6 +169,7 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
.pImageIndices = &image_index,
.pResults = nullptr,
};
+ std::scoped_lock lock{scheduler.submit_mutex};
switch (const VkResult result = present_queue.Present(present_info)) {
case VK_SUCCESS:
break;
@@ -165,11 +192,17 @@ void Swapchain::Present(VkSemaphore render_semaphore) {
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)};
+ const auto present_modes = physical_device.GetSurfacePresentModesKHR(surface);
+ has_mailbox = std::find(present_modes.begin(), present_modes.end(),
+ VK_PRESENT_MODE_MAILBOX_KHR) != present_modes.end();
+ has_imm = std::find(present_modes.begin(), present_modes.end(),
+ VK_PRESENT_MODE_IMMEDIATE_KHR) != present_modes.end();
+ has_fifo_relaxed = std::find(present_modes.begin(), present_modes.end(),
+ VK_PRESENT_MODE_FIFO_RELAXED_KHR) != present_modes.end();
const VkCompositeAlphaFlagBitsKHR alpha_flags{ChooseAlphaFlags(capabilities)};
- const VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)};
- present_mode = ChooseSwapPresentMode(present_modes);
+ surface_format = ChooseSwapSurfaceFormat(formats);
+ present_mode = ChooseSwapPresentMode(has_imm, has_mailbox, has_fifo_relaxed);
u32 requested_image_count{capabilities.minImageCount + 1};
// Ensure Triple buffering if possible.
@@ -193,7 +226,7 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
.imageColorSpace = surface_format.colorSpace,
.imageExtent = {},
.imageArrayLayers = 1,
- .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
+ .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@@ -230,7 +263,6 @@ void Swapchain::CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bo
extent = swapchain_ci.imageExtent;
current_srgb = srgb;
- current_fps_unlocked = !Settings::values.use_speed_limit.GetValue();
images = swapchain.GetImages();
image_count = static_cast<u32>(images.size());
@@ -241,56 +273,20 @@ void Swapchain::CreateSemaphores() {
present_semaphores.resize(image_count);
std::ranges::generate(present_semaphores,
[this] { return device.GetLogical().CreateSemaphore(); });
-}
-
-void Swapchain::CreateImageViews() {
- VkImageViewCreateInfo ci{
- .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
- .pNext = nullptr,
- .flags = 0,
- .image = {},
- .viewType = VK_IMAGE_VIEW_TYPE_2D,
- .format = image_view_format,
- .components =
- {
- .r = VK_COMPONENT_SWIZZLE_IDENTITY,
- .g = VK_COMPONENT_SWIZZLE_IDENTITY,
- .b = VK_COMPONENT_SWIZZLE_IDENTITY,
- .a = VK_COMPONENT_SWIZZLE_IDENTITY,
- },
- .subresourceRange =
- {
- .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
- .baseMipLevel = 0,
- .levelCount = 1,
- .baseArrayLayer = 0,
- .layerCount = 1,
- },
- };
-
- image_views.resize(image_count);
- for (std::size_t i = 0; i < image_count; i++) {
- ci.image = images[i];
- image_views[i] = device.GetLogical().CreateImageView(ci);
- }
+ render_semaphores.resize(image_count);
+ std::ranges::generate(render_semaphores,
+ [this] { return device.GetLogical().CreateSemaphore(); });
}
void Swapchain::Destroy() {
frame_index = 0;
present_semaphores.clear();
- framebuffers.clear();
- image_views.clear();
swapchain.reset();
}
-bool Swapchain::HasFpsUnlockChanged() const {
- return current_fps_unlocked != !Settings::values.use_speed_limit.GetValue();
-}
-
bool Swapchain::NeedsPresentModeUpdate() const {
- // Mailbox present mode is the ideal for all scenarios. If it is not available,
- // A different present mode is needed to support unlocked FPS above the monitor's refresh rate.
- return present_mode != VK_PRESENT_MODE_MAILBOX_KHR && HasFpsUnlockChanged();
+ const auto requested_mode = ChooseSwapPresentMode(has_imm, has_mailbox, has_fifo_relaxed);
+ return present_mode != requested_mode;
}
} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_swapchain.h b/src/video_core/renderer_vulkan/vk_swapchain.h
index caf1ff32b..bf1ea7254 100644
--- a/src/video_core/renderer_vulkan/vk_swapchain.h
+++ b/src/video_core/renderer_vulkan/vk_swapchain.h
@@ -27,7 +27,7 @@ public:
void Create(u32 width, u32 height, bool srgb);
/// Acquires the next image in the swapchain, waits as needed.
- void AcquireNextImage();
+ bool AcquireNextImage();
/// Presents the rendered image to the swapchain.
void Present(VkSemaphore render_semaphore);
@@ -52,6 +52,11 @@ public:
return is_suboptimal;
}
+ /// Returns true when the swapchain format is in the srgb color space
+ bool IsSrgb() const {
+ return current_srgb;
+ }
+
VkExtent2D GetSize() const {
return extent;
}
@@ -64,22 +69,34 @@ public:
return image_index;
}
+ std::size_t GetFrameIndex() const {
+ return frame_index;
+ }
+
VkImage GetImageIndex(std::size_t index) const {
return images[index];
}
- VkImageView GetImageViewIndex(std::size_t index) const {
- return *image_views[index];
+ VkImage CurrentImage() const {
+ return images[image_index];
}
VkFormat GetImageViewFormat() const {
return image_view_format;
}
+ VkFormat GetImageFormat() const {
+ return surface_format.format;
+ }
+
VkSemaphore CurrentPresentSemaphore() const {
return *present_semaphores[frame_index];
}
+ VkSemaphore CurrentRenderSemaphore() const {
+ return *render_semaphores[frame_index];
+ }
+
u32 GetWidth() const {
return width;
}
@@ -88,6 +105,10 @@ public:
return height;
}
+ VkExtent2D GetExtent() const {
+ return extent;
+ }
+
private:
void CreateSwapchain(const VkSurfaceCapabilitiesKHR& capabilities, bool srgb);
void CreateSemaphores();
@@ -95,8 +116,6 @@ private:
void Destroy();
- bool HasFpsUnlockChanged() const;
-
bool NeedsPresentModeUpdate() const;
const VkSurfaceKHR surface;
@@ -107,10 +126,9 @@ private:
std::size_t image_count{};
std::vector<VkImage> images;
- std::vector<vk::ImageView> image_views;
- std::vector<vk::Framebuffer> framebuffers;
std::vector<u64> resource_ticks;
std::vector<vk::Semaphore> present_semaphores;
+ std::vector<vk::Semaphore> render_semaphores;
u32 width;
u32 height;
@@ -121,9 +139,12 @@ private:
VkFormat image_view_format{};
VkExtent2D extent{};
VkPresentModeKHR present_mode{};
+ VkSurfaceFormatKHR surface_format{};
+ bool has_imm{false};
+ bool has_mailbox{false};
+ bool has_fifo_relaxed{false};
bool current_srgb{};
- bool current_fps_unlocked{};
bool is_outdated{};
bool is_suboptimal{};
};
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index ae15f6976..4d0481f2a 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -1,10 +1,11 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-License-Identifier: GPL-3.0-or-later
#include <algorithm>
#include <array>
#include <span>
#include <vector>
+#include <boost/container/small_vector.hpp>
#include "common/bit_cast.h"
#include "common/bit_util.h"
@@ -797,13 +798,13 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
BlitImageHelper& blit_image_helper_,
RenderPassCache& render_pass_cache_,
DescriptorPool& descriptor_pool,
- UpdateDescriptorQueue& update_descriptor_queue)
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue)
: device{device_}, scheduler{scheduler_}, memory_allocator{memory_allocator_},
staging_buffer_pool{staging_buffer_pool_}, blit_image_helper{blit_image_helper_},
render_pass_cache{render_pass_cache_}, resolution{Settings::values.resolution_info} {
if (Settings::values.accelerate_astc) {
astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
- update_descriptor_queue, memory_allocator);
+ compute_pass_descriptor_queue, memory_allocator);
}
}
@@ -1267,7 +1268,7 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
if (Settings::values.async_astc.GetValue()) {
flags |= VideoCommon::ImageFlagBits::AsynchronousDecode;
- } else if (Settings::values.accelerate_astc.GetValue()) {
+ } else if (Settings::values.accelerate_astc.GetValue() && info.size.depth == 1) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
}
flags |= VideoCommon::ImageFlagBits::Converted;
@@ -1343,14 +1344,31 @@ void Image::UploadMemory(const StagingBufferRef& map, std::span<const BufferImag
void Image::DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
std::span<const VideoCommon::BufferImageCopy> copies) {
+ std::array buffer_handles{
+ buffer,
+ };
+ std::array buffer_offsets{
+ offset,
+ };
+ DownloadMemory(buffer_handles, buffer_offsets, copies);
+}
+
+void Image::DownloadMemory(std::span<VkBuffer> buffers_span, std::span<VkDeviceSize> offsets_span,
+ std::span<const VideoCommon::BufferImageCopy> copies) {
const bool is_rescaled = True(flags & ImageFlagBits::Rescaled);
if (is_rescaled) {
ScaleDown();
}
- std::vector vk_copies = TransformBufferImageCopies(copies, offset, aspect_mask);
+ boost::container::small_vector<VkBuffer, 1> buffers_vector{};
+ boost::container::small_vector<std::vector<VkBufferImageCopy>, 1> vk_copies;
+ for (size_t index = 0; index < buffers_span.size(); index++) {
+ buffers_vector.emplace_back(buffers_span[index]);
+ vk_copies.emplace_back(
+ TransformBufferImageCopies(copies, offsets_span[index], aspect_mask));
+ }
scheduler->RequestOutsideRenderPassOperationContext();
- scheduler->Record([buffer, image = *original_image, aspect_mask = aspect_mask,
- vk_copies](vk::CommandBuffer cmdbuf) {
+ scheduler->Record([buffers = std::move(buffers_vector), image = *original_image,
+ aspect_mask = aspect_mask, vk_copies](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier read_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
@@ -1369,6 +1387,20 @@ void Image::DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
+ cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
+ 0, read_barrier);
+
+ for (size_t index = 0; index < buffers.size(); index++) {
+ cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffers[index],
+ vk_copies[index]);
+ }
+
+ const VkMemoryBarrier memory_write_barrier{
+ .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
+ .pNext = nullptr,
+ .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
+ .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
+ };
const VkImageMemoryBarrier image_write_barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
@@ -1387,15 +1419,6 @@ void Image::DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
- const VkMemoryBarrier memory_write_barrier{
- .sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
- .pNext = nullptr,
- .srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT,
- .dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT,
- };
- cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT,
- 0, read_barrier);
- cmdbuf.CopyImageToBuffer(image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, buffer, vk_copies);
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
0, memory_write_barrier, nullptr, image_write_barrier);
});
@@ -1405,7 +1428,13 @@ void Image::DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
}
void Image::DownloadMemory(const StagingBufferRef& map, std::span<const BufferImageCopy> copies) {
- DownloadMemory(map.buffer, map.offset, copies);
+ std::array buffers{
+ map.buffer,
+ };
+ std::array offsets{
+ map.offset,
+ };
+ DownloadMemory(buffers, offsets, copies);
}
bool Image::IsRescaled() const noexcept {
@@ -1555,8 +1584,9 @@ bool Image::NeedsScaleHelper() const {
ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewInfo& info,
ImageId image_id_, Image& image)
- : VideoCommon::ImageViewBase{info, image.info, image_id_}, device{&runtime.device},
- image_handle{image.Handle()}, samples(ConvertSampleCount(image.info.num_samples)) {
+ : VideoCommon::ImageViewBase{info, image.info, image_id_, image.gpu_addr},
+ device{&runtime.device}, image_handle{image.Handle()},
+ samples(ConvertSampleCount(image.info.num_samples)) {
using Shader::TextureType;
const VkImageAspectFlags aspect_mask = ImageViewAspectMask(info);
@@ -1602,7 +1632,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
}
vk::ImageView handle = device->GetLogical().CreateImageView(ci);
if (device->HasDebuggingToolAttached()) {
- handle.SetObjectNameEXT(VideoCommon::Name(*this).c_str());
+ handle.SetObjectNameEXT(VideoCommon::Name(*this, gpu_addr).c_str());
}
image_views[static_cast<size_t>(tex_type)] = std::move(handle);
};
@@ -1643,7 +1673,7 @@ ImageView::ImageView(TextureCacheRuntime& runtime, const VideoCommon::ImageViewI
ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::ImageInfo& info,
const VideoCommon::ImageViewInfo& view_info, GPUVAddr gpu_addr_)
- : VideoCommon::ImageViewBase{info, view_info}, gpu_addr{gpu_addr_},
+ : VideoCommon::ImageViewBase{info, view_info, gpu_addr_},
buffer_size{VideoCommon::CalculateGuestSizeInBytes(info)} {}
ImageView::ImageView(TextureCacheRuntime&, const VideoCommon::NullImageViewParams& params)
@@ -1834,6 +1864,7 @@ void Framebuffer::CreateFramebuffer(TextureCacheRuntime& runtime,
num_layers = std::max(num_layers, color_buffer->range.extent.layers);
images[num_images] = color_buffer->ImageHandle();
image_ranges[num_images] = MakeSubresourceRange(color_buffer);
+ rt_map[index] = num_images;
samples = color_buffer->Samples();
++num_images;
}
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index d5ee23f8d..4166b3d20 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -1,5 +1,5 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
-// SPDX-License-Identifier: GPL-2.0-or-later
+// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -34,7 +34,6 @@ class ImageView;
class Framebuffer;
class RenderPassCache;
class StagingBufferPool;
-class UpdateDescriptorQueue;
class Scheduler;
class TextureCacheRuntime {
@@ -45,7 +44,7 @@ public:
BlitImageHelper& blit_image_helper_,
RenderPassCache& render_pass_cache_,
DescriptorPool& descriptor_pool,
- UpdateDescriptorQueue& update_descriptor_queue);
+ ComputePassDescriptorQueue& compute_pass_descriptor_queue);
void Finish();
@@ -141,6 +140,9 @@ public:
void DownloadMemory(VkBuffer buffer, VkDeviceSize offset,
std::span<const VideoCommon::BufferImageCopy> copies);
+ void DownloadMemory(std::span<VkBuffer> buffers, std::span<VkDeviceSize> offsets,
+ std::span<const VideoCommon::BufferImageCopy> copies);
+
void DownloadMemory(const StagingBufferRef& map,
std::span<const VideoCommon::BufferImageCopy> copies);
@@ -262,7 +264,6 @@ private:
VkImage image_handle = VK_NULL_HANDLE;
VkImageView render_target = VK_NULL_HANDLE;
VkSampleCountFlagBits samples = VK_SAMPLE_COUNT_1_BIT;
- GPUVAddr gpu_addr = 0;
u32 buffer_size = 0;
};
@@ -333,7 +334,7 @@ public:
}
[[nodiscard]] bool HasAspectColorBit(size_t index) const noexcept {
- return (image_ranges.at(index).aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
+ return (image_ranges.at(rt_map[index]).aspectMask & VK_IMAGE_ASPECT_COLOR_BIT) != 0;
}
[[nodiscard]] bool HasAspectDepthBit() const noexcept {
@@ -353,6 +354,7 @@ private:
u32 num_images = 0;
std::array<VkImage, 9> images{};
std::array<VkImageSubresourceRange, 9> image_ranges{};
+ std::array<size_t, NUM_RT> rt_map{};
bool has_depth{};
bool has_stencil{};
};
@@ -371,6 +373,7 @@ struct TextureCacheParams {
using Sampler = Vulkan::Sampler;
using Framebuffer = Vulkan::Framebuffer;
using AsyncBuffer = Vulkan::StagingBufferRef;
+ using BufferType = VkBuffer;
};
using TextureCache = VideoCommon::TextureCache<TextureCacheParams>;
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
index 009dab0b6..0630ebda5 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.cpp
@@ -14,13 +14,18 @@ namespace Vulkan {
UpdateDescriptorQueue::UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_)
: device{device_}, scheduler{scheduler_} {
+ payload_start = payload.data();
payload_cursor = payload.data();
}
UpdateDescriptorQueue::~UpdateDescriptorQueue() = default;
void UpdateDescriptorQueue::TickFrame() {
- payload_cursor = payload.data();
+ if (++frame_index >= FRAMES_IN_FLIGHT) {
+ frame_index = 0;
+ }
+ payload_start = payload.data() + frame_index * FRAME_PAYLOAD_SIZE;
+ payload_cursor = payload_start;
}
void UpdateDescriptorQueue::Acquire() {
@@ -28,10 +33,10 @@ void UpdateDescriptorQueue::Acquire() {
// This is the maximum number of entries a single draw call might use.
static constexpr size_t MIN_ENTRIES = 0x400;
- if (std::distance(payload.data(), payload_cursor) + MIN_ENTRIES >= payload.max_size()) {
+ if (std::distance(payload_start, payload_cursor) + MIN_ENTRIES >= FRAME_PAYLOAD_SIZE) {
LOG_WARNING(Render_Vulkan, "Payload overflow, waiting for worker thread");
scheduler.WaitWorker();
- payload_cursor = payload.data();
+ payload_cursor = payload_start;
}
upload_start = payload_cursor;
}
diff --git a/src/video_core/renderer_vulkan/vk_update_descriptor.h b/src/video_core/renderer_vulkan/vk_update_descriptor.h
index 625bcc809..310fb551a 100644
--- a/src/video_core/renderer_vulkan/vk_update_descriptor.h
+++ b/src/video_core/renderer_vulkan/vk_update_descriptor.h
@@ -29,6 +29,12 @@ struct DescriptorUpdateEntry {
};
class UpdateDescriptorQueue final {
+ // This should be plenty for the vast majority of cases. Most desktop platforms only
+ // provide up to 3 swapchain images.
+ static constexpr size_t FRAMES_IN_FLIGHT = 5;
+ static constexpr size_t FRAME_PAYLOAD_SIZE = 0x20000;
+ static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT;
+
public:
explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_);
~UpdateDescriptorQueue();
@@ -73,9 +79,15 @@ private:
const Device& device;
Scheduler& scheduler;
+ size_t frame_index{0};
DescriptorUpdateEntry* payload_cursor = nullptr;
+ DescriptorUpdateEntry* payload_start = nullptr;
const DescriptorUpdateEntry* upload_start = nullptr;
- std::array<DescriptorUpdateEntry, 0x10000> payload;
+ std::array<DescriptorUpdateEntry, PAYLOAD_SIZE> payload;
};
+// TODO: should these be separate classes instead?
+using GuestDescriptorQueue = UpdateDescriptorQueue;
+using ComputePassDescriptorQueue = UpdateDescriptorQueue;
+
} // namespace Vulkan
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index d9482371b..c5213875b 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -228,14 +228,14 @@ const ShaderInfo* ShaderCache::MakeShaderInfo(GenericEnvironment& env, VAddr cpu
auto info = std::make_unique<ShaderInfo>();
if (const std::optional<u64> cached_hash{env.Analyze()}) {
info->unique_hash = *cached_hash;
- info->size_bytes = env.CachedSize();
+ info->size_bytes = env.CachedSizeBytes();
} else {
// Slow path, not really hit on commercial games
// Build a control flow graph to get the real shader size
Shader::ObjectPool<Shader::Maxwell::Flow::Block> flow_block;
Shader::Maxwell::Flow::CFG cfg{env, flow_block, env.StartAddress()};
info->unique_hash = env.CalculateHash();
- info->size_bytes = env.ReadSize();
+ info->size_bytes = env.ReadSizeBytes();
}
const size_t size_bytes{info->size_bytes};
const ShaderInfo* const result{info.get()};
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp
index 574760f80..c7cb56243 100644
--- a/src/video_core/shader_environment.cpp
+++ b/src/video_core/shader_environment.cpp
@@ -170,15 +170,19 @@ std::optional<u64> GenericEnvironment::Analyze() {
void GenericEnvironment::SetCachedSize(size_t size_bytes) {
cached_lowest = start_address;
cached_highest = start_address + static_cast<u32>(size_bytes);
- code.resize(CachedSize());
+ code.resize(CachedSizeWords());
gpu_memory->ReadBlock(program_base + cached_lowest, code.data(), code.size() * sizeof(u64));
}
-size_t GenericEnvironment::CachedSize() const noexcept {
- return cached_highest - cached_lowest + INST_SIZE;
+size_t GenericEnvironment::CachedSizeWords() const noexcept {
+ return CachedSizeBytes() / INST_SIZE;
}
-size_t GenericEnvironment::ReadSize() const noexcept {
+size_t GenericEnvironment::CachedSizeBytes() const noexcept {
+ return static_cast<size_t>(cached_highest) - cached_lowest + INST_SIZE;
+}
+
+size_t GenericEnvironment::ReadSizeBytes() const noexcept {
return read_highest - read_lowest + INST_SIZE;
}
@@ -187,7 +191,7 @@ bool GenericEnvironment::CanBeSerialized() const noexcept {
}
u64 GenericEnvironment::CalculateHash() const {
- const size_t size{ReadSize()};
+ const size_t size{ReadSizeBytes()};
const auto data{std::make_unique<char[]>(size)};
gpu_memory->ReadBlock(program_base + read_lowest, data.get(), size);
return Common::CityHash64(data.get(), size);
@@ -198,7 +202,7 @@ void GenericEnvironment::Dump(u64 hash) {
}
void GenericEnvironment::Serialize(std::ofstream& file) const {
- const u64 code_size{static_cast<u64>(CachedSize())};
+ const u64 code_size{static_cast<u64>(CachedSizeBytes())};
const u64 num_texture_types{static_cast<u64>(texture_types.size())};
const u64 num_texture_pixel_formats{static_cast<u64>(texture_pixel_formats.size())};
const u64 num_cbuf_values{static_cast<u64>(cbuf_values.size())};
diff --git a/src/video_core/shader_environment.h b/src/video_core/shader_environment.h
index d75987a52..a0f61cbda 100644
--- a/src/video_core/shader_environment.h
+++ b/src/video_core/shader_environment.h
@@ -48,9 +48,11 @@ public:
void SetCachedSize(size_t size_bytes);
- [[nodiscard]] size_t CachedSize() const noexcept;
+ [[nodiscard]] size_t CachedSizeWords() const noexcept;
- [[nodiscard]] size_t ReadSize() const noexcept;
+ [[nodiscard]] size_t CachedSizeBytes() const noexcept;
+
+ [[nodiscard]] size_t ReadSizeBytes() const noexcept;
[[nodiscard]] bool CanBeSerialized() const noexcept;
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 1a76d4178..cb51529e4 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -250,10 +250,13 @@ bool IsPixelFormatASTC(PixelFormat format) {
case PixelFormat::ASTC_2D_6X6_UNORM:
case PixelFormat::ASTC_2D_6X6_SRGB:
case PixelFormat::ASTC_2D_10X6_UNORM:
+ case PixelFormat::ASTC_2D_10X6_SRGB:
case PixelFormat::ASTC_2D_10X5_UNORM:
case PixelFormat::ASTC_2D_10X5_SRGB:
case PixelFormat::ASTC_2D_10X10_UNORM:
case PixelFormat::ASTC_2D_10X10_SRGB:
+ case PixelFormat::ASTC_2D_12X10_UNORM:
+ case PixelFormat::ASTC_2D_12X10_SRGB:
case PixelFormat::ASTC_2D_12X12_UNORM:
case PixelFormat::ASTC_2D_12X12_SRGB:
case PixelFormat::ASTC_2D_8X6_UNORM:
@@ -279,11 +282,13 @@ bool IsPixelFormatSRGB(PixelFormat format) {
case PixelFormat::ASTC_2D_8X5_SRGB:
case PixelFormat::ASTC_2D_5X4_SRGB:
case PixelFormat::ASTC_2D_5X5_SRGB:
+ case PixelFormat::ASTC_2D_10X6_SRGB:
case PixelFormat::ASTC_2D_10X8_SRGB:
case PixelFormat::ASTC_2D_6X6_SRGB:
case PixelFormat::ASTC_2D_10X5_SRGB:
case PixelFormat::ASTC_2D_10X10_SRGB:
case PixelFormat::ASTC_2D_12X12_SRGB:
+ case PixelFormat::ASTC_2D_12X10_SRGB:
case PixelFormat::ASTC_2D_8X6_SRGB:
case PixelFormat::ASTC_2D_6X5_SRGB:
return true;
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 44b79af20..0225d3287 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -95,10 +95,13 @@ enum class PixelFormat {
ASTC_2D_6X6_UNORM,
ASTC_2D_6X6_SRGB,
ASTC_2D_10X6_UNORM,
+ ASTC_2D_10X6_SRGB,
ASTC_2D_10X5_UNORM,
ASTC_2D_10X5_SRGB,
ASTC_2D_10X10_UNORM,
ASTC_2D_10X10_SRGB,
+ ASTC_2D_12X10_UNORM,
+ ASTC_2D_12X10_SRGB,
ASTC_2D_12X12_UNORM,
ASTC_2D_12X12_SRGB,
ASTC_2D_8X6_UNORM,
@@ -232,10 +235,13 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_WIDTH_TABLE = {{
6, // ASTC_2D_6X6_UNORM
6, // ASTC_2D_6X6_SRGB
10, // ASTC_2D_10X6_UNORM
+ 10, // ASTC_2D_10X6_SRGB
10, // ASTC_2D_10X5_UNORM
10, // ASTC_2D_10X5_SRGB
10, // ASTC_2D_10X10_UNORM
10, // ASTC_2D_10X10_SRGB
+ 12, // ASTC_2D_12X10_UNORM
+ 12, // ASTC_2D_12X10_SRGB
12, // ASTC_2D_12X12_UNORM
12, // ASTC_2D_12X12_SRGB
8, // ASTC_2D_8X6_UNORM
@@ -338,10 +344,13 @@ constexpr std::array<u8, MaxPixelFormat> BLOCK_HEIGHT_TABLE = {{
6, // ASTC_2D_6X6_UNORM
6, // ASTC_2D_6X6_SRGB
6, // ASTC_2D_10X6_UNORM
+ 6, // ASTC_2D_10X6_SRGB
5, // ASTC_2D_10X5_UNORM
5, // ASTC_2D_10X5_SRGB
10, // ASTC_2D_10X10_UNORM
10, // ASTC_2D_10X10_SRGB
+ 10, // ASTC_2D_12X10_UNORM
+ 10, // ASTC_2D_12X10_SRGB
12, // ASTC_2D_12X12_UNORM
12, // ASTC_2D_12X12_SRGB
6, // ASTC_2D_8X6_UNORM
@@ -444,10 +453,13 @@ constexpr std::array<u8, MaxPixelFormat> BITS_PER_BLOCK_TABLE = {{
128, // ASTC_2D_6X6_UNORM
128, // ASTC_2D_6X6_SRGB
128, // ASTC_2D_10X6_UNORM
+ 128, // ASTC_2D_10X6_SRGB
128, // ASTC_2D_10X5_UNORM
128, // ASTC_2D_10X5_SRGB
128, // ASTC_2D_10X10_UNORM
128, // ASTC_2D_10X10_SRGB
+ 128, // ASTC_2D_12X10_UNORM
+ 128, // ASTC_2D_12X10_SRGB
128, // ASTC_2D_12X12_UNORM
128, // ASTC_2D_12X12_SRGB
128, // ASTC_2D_8X6_UNORM
diff --git a/src/video_core/texture_cache/format_lookup_table.cpp b/src/video_core/texture_cache/format_lookup_table.cpp
index 5fc2b2fec..11ced6c38 100644
--- a/src/video_core/texture_cache/format_lookup_table.cpp
+++ b/src/video_core/texture_cache/format_lookup_table.cpp
@@ -210,6 +210,8 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::ASTC_2D_6X6_SRGB;
case Hash(TextureFormat::ASTC_2D_10X6, UNORM, LINEAR):
return PixelFormat::ASTC_2D_10X6_UNORM;
+ case Hash(TextureFormat::ASTC_2D_10X6, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_10X6_SRGB;
case Hash(TextureFormat::ASTC_2D_10X5, UNORM, LINEAR):
return PixelFormat::ASTC_2D_10X5_UNORM;
case Hash(TextureFormat::ASTC_2D_10X5, UNORM, SRGB):
@@ -218,6 +220,10 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
return PixelFormat::ASTC_2D_10X10_UNORM;
case Hash(TextureFormat::ASTC_2D_10X10, UNORM, SRGB):
return PixelFormat::ASTC_2D_10X10_SRGB;
+ case Hash(TextureFormat::ASTC_2D_12X10, UNORM, LINEAR):
+ return PixelFormat::ASTC_2D_12X10_UNORM;
+ case Hash(TextureFormat::ASTC_2D_12X10, UNORM, SRGB):
+ return PixelFormat::ASTC_2D_12X10_SRGB;
case Hash(TextureFormat::ASTC_2D_12X12, UNORM, LINEAR):
return PixelFormat::ASTC_2D_12X12_UNORM;
case Hash(TextureFormat::ASTC_2D_12X12, UNORM, SRGB):
diff --git a/src/video_core/texture_cache/formatter.cpp b/src/video_core/texture_cache/formatter.cpp
index 30f72361d..6279d8e9e 100644
--- a/src/video_core/texture_cache/formatter.cpp
+++ b/src/video_core/texture_cache/formatter.cpp
@@ -46,7 +46,7 @@ std::string Name(const ImageBase& image) {
return "Invalid";
}
-std::string Name(const ImageViewBase& image_view) {
+std::string Name(const ImageViewBase& image_view, GPUVAddr addr) {
const u32 width = image_view.size.width;
const u32 height = image_view.size.height;
const u32 depth = image_view.size.depth;
@@ -56,23 +56,25 @@ std::string Name(const ImageViewBase& image_view) {
const std::string level = num_levels > 1 ? fmt::format(":{}", num_levels) : "";
switch (image_view.type) {
case ImageViewType::e1D:
- return fmt::format("ImageView 1D {}{}", width, level);
+ return fmt::format("ImageView 1D 0x{:X} {}{}", addr, width, level);
case ImageViewType::e2D:
- return fmt::format("ImageView 2D {}x{}{}", width, height, level);
+ return fmt::format("ImageView 2D 0x{:X} {}x{}{}", addr, width, height, level);
case ImageViewType::Cube:
- return fmt::format("ImageView Cube {}x{}{}", width, height, level);
+ return fmt::format("ImageView Cube 0x{:X} {}x{}{}", addr, width, height, level);
case ImageViewType::e3D:
- return fmt::format("ImageView 3D {}x{}x{}{}", width, height, depth, level);
+ return fmt::format("ImageView 3D 0x{:X} {}x{}x{}{}", addr, width, height, depth, level);
case ImageViewType::e1DArray:
- return fmt::format("ImageView 1DArray {}{}|{}", width, level, num_layers);
+ return fmt::format("ImageView 1DArray 0x{:X} {}{}|{}", addr, width, level, num_layers);
case ImageViewType::e2DArray:
- return fmt::format("ImageView 2DArray {}x{}{}|{}", width, height, level, num_layers);
+ return fmt::format("ImageView 2DArray 0x{:X} {}x{}{}|{}", addr, width, height, level,
+ num_layers);
case ImageViewType::CubeArray:
- return fmt::format("ImageView CubeArray {}x{}{}|{}", width, height, level, num_layers);
+ return fmt::format("ImageView CubeArray 0x{:X} {}x{}{}|{}", addr, width, height, level,
+ num_layers);
case ImageViewType::Rect:
- return fmt::format("ImageView Rect {}x{}{}", width, height, level);
+ return fmt::format("ImageView Rect 0x{:X} {}x{}{}", addr, width, height, level);
case ImageViewType::Buffer:
- return fmt::format("BufferView {}", width);
+ return fmt::format("BufferView 0x{:X} {}", addr, width);
}
return "Invalid";
}
diff --git a/src/video_core/texture_cache/formatter.h b/src/video_core/texture_cache/formatter.h
index f1f0a057b..9ee57a076 100644
--- a/src/video_core/texture_cache/formatter.h
+++ b/src/video_core/texture_cache/formatter.h
@@ -179,6 +179,8 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "ASTC_2D_6X6_SRGB";
case PixelFormat::ASTC_2D_10X6_UNORM:
return "ASTC_2D_10X6_UNORM";
+ case PixelFormat::ASTC_2D_10X6_SRGB:
+ return "ASTC_2D_10X6_SRGB";
case PixelFormat::ASTC_2D_10X5_UNORM:
return "ASTC_2D_10X5_UNORM";
case PixelFormat::ASTC_2D_10X5_SRGB:
@@ -187,6 +189,10 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
return "ASTC_2D_10X10_UNORM";
case PixelFormat::ASTC_2D_10X10_SRGB:
return "ASTC_2D_10X10_SRGB";
+ case PixelFormat::ASTC_2D_12X10_UNORM:
+ return "ASTC_2D_12X10_UNORM";
+ case PixelFormat::ASTC_2D_12X10_SRGB:
+ return "ASTC_2D_12X10_SRGB";
case PixelFormat::ASTC_2D_12X12_UNORM:
return "ASTC_2D_12X12_UNORM";
case PixelFormat::ASTC_2D_12X12_SRGB:
@@ -268,7 +274,7 @@ struct RenderTargets;
[[nodiscard]] std::string Name(const ImageBase& image);
-[[nodiscard]] std::string Name(const ImageViewBase& image_view);
+[[nodiscard]] std::string Name(const ImageViewBase& image_view, GPUVAddr addr);
[[nodiscard]] std::string Name(const RenderTargets& render_targets);
diff --git a/src/video_core/texture_cache/image_info.cpp b/src/video_core/texture_cache/image_info.cpp
index 11f3f78a1..e8ddde691 100644
--- a/src/video_core/texture_cache/image_info.cpp
+++ b/src/video_core/texture_cache/image_info.cpp
@@ -4,6 +4,7 @@
#include <fmt/format.h>
#include "common/assert.h"
+#include "common/settings.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/format_lookup_table.h"
#include "video_core/texture_cache/image_info.h"
@@ -22,6 +23,8 @@ using VideoCore::Surface::PixelFormat;
using VideoCore::Surface::SurfaceType;
ImageInfo::ImageInfo(const TICEntry& config) noexcept {
+ forced_flushed = config.IsPitchLinear() && !Settings::values.use_reactive_flushing.GetValue();
+ dma_downloaded = forced_flushed;
format = PixelFormatFromTextureInfo(config.format, config.r_type, config.g_type, config.b_type,
config.a_type, config.srgb_conversion);
num_samples = NumSamples(config.msaa_mode);
@@ -117,6 +120,9 @@ ImageInfo::ImageInfo(const TICEntry& config) noexcept {
ImageInfo::ImageInfo(const Maxwell3D::Regs::RenderTargetConfig& ct,
Tegra::Texture::MsaaMode msaa_mode) noexcept {
+ forced_flushed =
+ ct.tile_mode.is_pitch_linear && !Settings::values.use_reactive_flushing.GetValue();
+ dma_downloaded = forced_flushed;
format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(ct.format);
rescaleable = false;
if (ct.tile_mode.is_pitch_linear) {
@@ -155,6 +161,9 @@ ImageInfo::ImageInfo(const Maxwell3D::Regs::RenderTargetConfig& ct,
ImageInfo::ImageInfo(const Maxwell3D::Regs::Zeta& zt, const Maxwell3D::Regs::ZetaSize& zt_size,
Tegra::Texture::MsaaMode msaa_mode) noexcept {
+ forced_flushed =
+ zt.tile_mode.is_pitch_linear && !Settings::values.use_reactive_flushing.GetValue();
+ dma_downloaded = forced_flushed;
format = VideoCore::Surface::PixelFormatFromDepthFormat(zt.format);
size.width = zt_size.width;
size.height = zt_size.height;
@@ -195,6 +204,9 @@ ImageInfo::ImageInfo(const Maxwell3D::Regs::Zeta& zt, const Maxwell3D::Regs::Zet
ImageInfo::ImageInfo(const Fermi2D::Surface& config) noexcept {
UNIMPLEMENTED_IF_MSG(config.layer != 0, "Surface layer is not zero");
+ forced_flushed = config.linear == Fermi2D::MemoryLayout::Pitch &&
+ !Settings::values.use_reactive_flushing.GetValue();
+ dma_downloaded = forced_flushed;
format = VideoCore::Surface::PixelFormatFromRenderTargetFormat(config.format);
rescaleable = false;
if (config.linear == Fermi2D::MemoryLayout::Pitch) {
diff --git a/src/video_core/texture_cache/image_info.h b/src/video_core/texture_cache/image_info.h
index 4b7dfa315..8a4cb0cbd 100644
--- a/src/video_core/texture_cache/image_info.h
+++ b/src/video_core/texture_cache/image_info.h
@@ -39,6 +39,8 @@ struct ImageInfo {
u32 tile_width_spacing = 0;
bool rescaleable = false;
bool downscaleable = false;
+ bool forced_flushed = false;
+ bool dma_downloaded = false;
};
} // namespace VideoCommon
diff --git a/src/video_core/texture_cache/image_view_base.cpp b/src/video_core/texture_cache/image_view_base.cpp
index 04fb84bfa..d134b6738 100644
--- a/src/video_core/texture_cache/image_view_base.cpp
+++ b/src/video_core/texture_cache/image_view_base.cpp
@@ -4,7 +4,6 @@
#include <algorithm>
#include "common/assert.h"
-#include "common/settings.h"
#include "video_core/compatible_formats.h"
#include "video_core/surface.h"
#include "video_core/texture_cache/formatter.h"
@@ -16,8 +15,8 @@
namespace VideoCommon {
ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info,
- ImageId image_id_)
- : image_id{image_id_}, format{info.format}, type{info.type}, range{info.range},
+ ImageId image_id_, GPUVAddr addr)
+ : image_id{image_id_}, gpu_addr{addr}, format{info.format}, type{info.type}, range{info.range},
size{
.width = std::max(image_info.size.width >> range.base.level, 1u),
.height = std::max(image_info.size.height >> range.base.level, 1u),
@@ -26,8 +25,7 @@ ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_i
ASSERT_MSG(VideoCore::Surface::IsViewCompatible(image_info.format, info.format, false, true),
"Image view format {} is incompatible with image format {}", info.format,
image_info.format);
- const bool is_async = Settings::values.use_asynchronous_gpu_emulation.GetValue();
- if (image_info.type == ImageType::Linear && is_async) {
+ if (image_info.forced_flushed) {
flags |= ImageViewFlagBits::PreemtiveDownload;
}
if (image_info.type == ImageType::e3D && info.type != ImageViewType::e3D) {
@@ -35,8 +33,8 @@ ImageViewBase::ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_i
}
}
-ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info)
- : image_id{NULL_IMAGE_ID}, format{info.format}, type{ImageViewType::Buffer},
+ImageViewBase::ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info, GPUVAddr addr)
+ : image_id{NULL_IMAGE_ID}, gpu_addr{addr}, format{info.format}, type{ImageViewType::Buffer},
size{
.width = info.size.width,
.height = 1,
diff --git a/src/video_core/texture_cache/image_view_base.h b/src/video_core/texture_cache/image_view_base.h
index 69c9776e7..a25ae1d4a 100644
--- a/src/video_core/texture_cache/image_view_base.h
+++ b/src/video_core/texture_cache/image_view_base.h
@@ -24,9 +24,9 @@ enum class ImageViewFlagBits : u16 {
DECLARE_ENUM_FLAG_OPERATORS(ImageViewFlagBits)
struct ImageViewBase {
- explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info,
- ImageId image_id);
- explicit ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info);
+ explicit ImageViewBase(const ImageViewInfo& info, const ImageInfo& image_info, ImageId image_id,
+ GPUVAddr addr);
+ explicit ImageViewBase(const ImageInfo& info, const ImageViewInfo& view_info, GPUVAddr addr);
explicit ImageViewBase(const NullImageViewParams&);
[[nodiscard]] bool IsBuffer() const noexcept {
@@ -34,6 +34,7 @@ struct ImageViewBase {
}
ImageId image_id{};
+ GPUVAddr gpu_addr = 0;
PixelFormat format{};
ImageViewType type{};
SubresourceRange range;
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index ed5c768d8..b24086fce 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -1,9 +1,10 @@
-// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <unordered_set>
+#include <boost/container/small_vector.hpp>
#include "common/alignment.h"
#include "common/settings.h"
@@ -17,15 +18,10 @@
namespace VideoCommon {
-using Tegra::Texture::SwizzleSource;
-using Tegra::Texture::TextureType;
using Tegra::Texture::TICEntry;
using Tegra::Texture::TSCEntry;
using VideoCore::Surface::GetFormatType;
-using VideoCore::Surface::IsCopyCompatible;
using VideoCore::Surface::PixelFormat;
-using VideoCore::Surface::PixelFormatFromDepthFormat;
-using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
using VideoCore::Surface::SurfaceType;
using namespace Common::Literals;
@@ -143,6 +139,13 @@ void TextureCache<P>::TickFrame() {
runtime.TickFrame();
critical_gc = 0;
++frame_tick;
+
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+ for (auto& buffer : async_buffers_death_ring) {
+ runtime.FreeDeferredStagingBuffer(buffer);
+ }
+ async_buffers_death_ring.clear();
+ }
}
template <class P>
@@ -488,6 +491,32 @@ void TextureCache<P>::DownloadMemory(VAddr cpu_addr, size_t size) {
}
template <class P>
+std::optional<VideoCore::RasterizerDownloadArea> TextureCache<P>::GetFlushArea(VAddr cpu_addr,
+ u64 size) {
+ std::optional<VideoCore::RasterizerDownloadArea> area{};
+ ForEachImageInRegion(cpu_addr, size, [&](ImageId, ImageBase& image) {
+ if (False(image.flags & ImageFlagBits::GpuModified)) {
+ return;
+ }
+ if (!area) {
+ area.emplace();
+ area->start_address = cpu_addr;
+ area->end_address = cpu_addr + size;
+ area->preemtive = true;
+ }
+ area->start_address = std::min(area->start_address, image.cpu_addr);
+ area->end_address = std::max(area->end_address, image.cpu_addr_end);
+ for (auto image_view_id : image.image_view_ids) {
+ auto& image_view = slot_image_views[image_view_id];
+ image_view.flags |= ImageViewFlagBits::PreemtiveDownload;
+ }
+ area->preemtive &= image.info.forced_flushed;
+ image.info.forced_flushed = true;
+ });
+ return area;
+}
+
+template <class P>
void TextureCache<P>::UnmapMemory(VAddr cpu_addr, size_t size) {
std::vector<ImageId> deleted_images;
ForEachImageInRegion(cpu_addr, size, [&](ImageId id, Image&) { deleted_images.push_back(id); });
@@ -661,25 +690,41 @@ template <class P>
void TextureCache<P>::CommitAsyncFlushes() {
// This is intentionally passing the value by copy
if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
- const std::span<const ImageId> download_ids = uncommitted_downloads;
+ auto& download_ids = uncommitted_downloads;
if (download_ids.empty()) {
committed_downloads.emplace_back(std::move(uncommitted_downloads));
uncommitted_downloads.clear();
- async_buffers.emplace_back(std::optional<AsyncBuffer>{});
+ async_buffers.emplace_back(std::move(uncommitted_async_buffers));
+ uncommitted_async_buffers.clear();
return;
}
size_t total_size_bytes = 0;
- for (const ImageId image_id : download_ids) {
- total_size_bytes += slot_images[image_id].unswizzled_size_bytes;
+ size_t last_async_buffer_id = uncommitted_async_buffers.size();
+ bool any_none_dma = false;
+ for (PendingDownload& download_info : download_ids) {
+ if (download_info.is_swizzle) {
+ total_size_bytes +=
+ Common::AlignUp(slot_images[download_info.object_id].unswizzled_size_bytes, 64);
+ any_none_dma = true;
+ download_info.async_buffer_id = last_async_buffer_id;
+ }
}
- auto download_map = runtime.DownloadStagingBuffer(total_size_bytes, true);
- for (const ImageId image_id : download_ids) {
- Image& image = slot_images[image_id];
- const auto copies = FullDownloadCopies(image.info);
- image.DownloadMemory(download_map, copies);
- download_map.offset += Common::AlignUp(image.unswizzled_size_bytes, 64);
+
+ if (any_none_dma) {
+ auto download_map = runtime.DownloadStagingBuffer(total_size_bytes, true);
+ for (const PendingDownload& download_info : download_ids) {
+ if (download_info.is_swizzle) {
+ Image& image = slot_images[download_info.object_id];
+ const auto copies = FullDownloadCopies(image.info);
+ image.DownloadMemory(download_map, copies);
+ download_map.offset += Common::AlignUp(image.unswizzled_size_bytes, 64);
+ }
+ }
+ uncommitted_async_buffers.emplace_back(download_map);
}
- async_buffers.emplace_back(download_map);
+
+ async_buffers.emplace_back(std::move(uncommitted_async_buffers));
+ uncommitted_async_buffers.clear();
}
committed_downloads.emplace_back(std::move(uncommitted_downloads));
uncommitted_downloads.clear();
@@ -691,39 +736,57 @@ void TextureCache<P>::PopAsyncFlushes() {
return;
}
if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
- const std::span<const ImageId> download_ids = committed_downloads.front();
+ const auto& download_ids = committed_downloads.front();
if (download_ids.empty()) {
committed_downloads.pop_front();
async_buffers.pop_front();
return;
}
- auto download_map = *async_buffers.front();
- std::span<u8> download_span = download_map.mapped_span;
+ auto download_map = std::move(async_buffers.front());
for (size_t i = download_ids.size(); i > 0; i--) {
- const ImageBase& image = slot_images[download_ids[i - 1]];
- const auto copies = FullDownloadCopies(image.info);
- download_map.offset -= Common::AlignUp(image.unswizzled_size_bytes, 64);
- std::span<u8> download_span_alt = download_span.subspan(download_map.offset);
- SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span_alt,
- swizzle_data_buffer);
+ auto& download_info = download_ids[i - 1];
+ auto& download_buffer = download_map[download_info.async_buffer_id];
+ if (download_info.is_swizzle) {
+ const ImageBase& image = slot_images[download_info.object_id];
+ const auto copies = FullDownloadCopies(image.info);
+ download_buffer.offset -= Common::AlignUp(image.unswizzled_size_bytes, 64);
+ std::span<u8> download_span =
+ download_buffer.mapped_span.subspan(download_buffer.offset);
+ SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span,
+ swizzle_data_buffer);
+ } else {
+ const BufferDownload& buffer_info = slot_buffer_downloads[download_info.object_id];
+ std::span<u8> download_span =
+ download_buffer.mapped_span.subspan(download_buffer.offset);
+ gpu_memory->WriteBlockUnsafe(buffer_info.address, download_span.data(),
+ buffer_info.size);
+ slot_buffer_downloads.erase(download_info.object_id);
+ }
+ }
+ for (auto& download_buffer : download_map) {
+ async_buffers_death_ring.emplace_back(download_buffer);
}
- runtime.FreeDeferredStagingBuffer(download_map);
committed_downloads.pop_front();
async_buffers.pop_front();
} else {
- const std::span<const ImageId> download_ids = committed_downloads.front();
+ const auto& download_ids = committed_downloads.front();
if (download_ids.empty()) {
committed_downloads.pop_front();
return;
}
size_t total_size_bytes = 0;
- for (const ImageId image_id : download_ids) {
- total_size_bytes += slot_images[image_id].unswizzled_size_bytes;
+ for (const PendingDownload& download_info : download_ids) {
+ if (download_info.is_swizzle) {
+ total_size_bytes += slot_images[download_info.object_id].unswizzled_size_bytes;
+ }
}
auto download_map = runtime.DownloadStagingBuffer(total_size_bytes);
const size_t original_offset = download_map.offset;
- for (const ImageId image_id : download_ids) {
- Image& image = slot_images[image_id];
+ for (const PendingDownload& download_info : download_ids) {
+ if (!download_info.is_swizzle) {
+ continue;
+ }
+ Image& image = slot_images[download_info.object_id];
const auto copies = FullDownloadCopies(image.info);
image.DownloadMemory(download_map, copies);
download_map.offset += image.unswizzled_size_bytes;
@@ -732,8 +795,11 @@ void TextureCache<P>::PopAsyncFlushes() {
runtime.Finish();
download_map.offset = original_offset;
std::span<u8> download_span = download_map.mapped_span;
- for (const ImageId image_id : download_ids) {
- const ImageBase& image = slot_images[image_id];
+ for (const PendingDownload& download_info : download_ids) {
+ if (!download_info.is_swizzle) {
+ continue;
+ }
+ const ImageBase& image = slot_images[download_info.object_id];
const auto copies = FullDownloadCopies(image.info);
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, download_span,
swizzle_data_buffer);
@@ -745,17 +811,22 @@ void TextureCache<P>::PopAsyncFlushes() {
}
template <class P>
-ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand) {
+ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload) {
const ImageInfo dst_info(operand);
const ImageId dst_id = FindDMAImage(dst_info, operand.address);
if (!dst_id) {
return NULL_IMAGE_ID;
}
- const auto& image = slot_images[dst_id];
+ auto& image = slot_images[dst_id];
if (False(image.flags & ImageFlagBits::GpuModified)) {
// No need to waste time on an image that's synced with guest
return NULL_IMAGE_ID;
}
+ if (!is_upload && !image.info.dma_downloaded) {
+ // Force a full sync.
+ image.info.dma_downloaded = true;
+ return NULL_IMAGE_ID;
+ }
const auto base = image.TryFindBase(operand.address);
if (!base) {
return NULL_IMAGE_ID;
@@ -834,6 +905,33 @@ std::pair<typename TextureCache<P>::Image*, BufferImageCopy> TextureCache<P>::Dm
}
template <class P>
+void TextureCache<P>::DownloadImageIntoBuffer(typename TextureCache<P>::Image* image,
+ typename TextureCache<P>::BufferType buffer,
+ size_t buffer_offset,
+ std::span<const VideoCommon::BufferImageCopy> copies,
+ GPUVAddr address, size_t size) {
+ if constexpr (IMPLEMENTS_ASYNC_DOWNLOADS) {
+ const BufferDownload new_buffer_download{address, size};
+ auto slot = slot_buffer_downloads.insert(new_buffer_download);
+ const PendingDownload new_download{false, uncommitted_async_buffers.size(), slot};
+ uncommitted_downloads.emplace_back(new_download);
+ auto download_map = runtime.DownloadStagingBuffer(size, true);
+ uncommitted_async_buffers.emplace_back(download_map);
+ std::array buffers{
+ buffer,
+ download_map.buffer,
+ };
+ std::array<u64, 2> buffer_offsets{
+ buffer_offset,
+ download_map.offset,
+ };
+ image->DownloadMemory(buffers, buffer_offsets, copies);
+ } else {
+ image->DownloadMemory(buffer, buffer_offset, copies);
+ }
+}
+
+template <class P>
void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
if (False(image.flags & ImageFlagBits::CpuModified)) {
// Only upload modified images
@@ -1225,7 +1323,6 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
all_siblings.push_back(overlap_id);
} else {
bad_overlap_ids.push_back(overlap_id);
- overlap.flags |= ImageFlagBits::BadOverlap;
}
};
ForEachImageInRegion(cpu_addr, size_bytes, region_check);
@@ -1294,6 +1391,12 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
ScaleDown(new_image);
}
+ std::ranges::sort(overlap_ids, [this](const ImageId lhs, const ImageId rhs) {
+ const ImageBase& lhs_image = slot_images[lhs];
+ const ImageBase& rhs_image = slot_images[rhs];
+ return lhs_image.modification_tick < rhs_image.modification_tick;
+ });
+
for (const ImageId overlap_id : overlap_ids) {
Image& overlap = slot_images[overlap_id];
if (True(overlap.flags & ImageFlagBits::GpuModified)) {
@@ -1330,7 +1433,12 @@ ImageId TextureCache<P>::JoinImages(const ImageInfo& info, GPUVAddr gpu_addr, VA
ImageBase& aliased = slot_images[aliased_id];
aliased.overlapping_images.push_back(new_image_id);
new_image.overlapping_images.push_back(aliased_id);
- new_image.flags |= ImageFlagBits::BadOverlap;
+ if (aliased.info.resources.levels == 1 && aliased.overlapping_images.size() > 1) {
+ aliased.flags |= ImageFlagBits::BadOverlap;
+ }
+ if (new_image.info.resources.levels == 1 && new_image.overlapping_images.size() > 1) {
+ new_image.flags |= ImageFlagBits::BadOverlap;
+ }
}
RegisterImage(new_image_id);
return new_image_id;
@@ -1361,7 +1469,7 @@ std::optional<typename TextureCache<P>::BlitImages> TextureCache<P>::GetBlitImag
if (!copy.must_accelerate) {
do {
if (!src_id && !dst_id) {
- return std::nullopt;
+ break;
}
if (src_id && True(slot_images[src_id].flags & ImageFlagBits::GpuModified)) {
break;
@@ -2209,7 +2317,8 @@ void TextureCache<P>::BindRenderTarget(ImageViewId* old_id, ImageViewId new_id)
if (new_id) {
const ImageViewBase& old_view = slot_image_views[new_id];
if (True(old_view.flags & ImageViewFlagBits::PreemtiveDownload)) {
- uncommitted_downloads.push_back(old_view.image_id);
+ const PendingDownload new_download{true, 0, old_view.image_id};
+ uncommitted_downloads.emplace_back(new_download);
}
}
*old_id = new_id;
diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h
index 5a5b4179c..0720494e5 100644
--- a/src/video_core/texture_cache/texture_cache_base.h
+++ b/src/video_core/texture_cache/texture_cache_base.h
@@ -1,4 +1,4 @@
-// SPDX-FileCopyrightText: 2021 yuzu Emulator Project
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
@@ -40,14 +40,9 @@ struct ChannelState;
namespace VideoCommon {
-using Tegra::Texture::SwizzleSource;
using Tegra::Texture::TICEntry;
using Tegra::Texture::TSCEntry;
-using VideoCore::Surface::GetFormatType;
-using VideoCore::Surface::IsCopyCompatible;
using VideoCore::Surface::PixelFormat;
-using VideoCore::Surface::PixelFormatFromDepthFormat;
-using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
using namespace Common::Literals;
struct ImageViewInOut {
@@ -119,6 +114,7 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
using Sampler = typename P::Sampler;
using Framebuffer = typename P::Framebuffer;
using AsyncBuffer = typename P::AsyncBuffer;
+ using BufferType = typename P::BufferType;
struct BlitImages {
ImageId dst_id;
@@ -183,6 +179,8 @@ public:
/// Download contents of host images to guest memory in a region
void DownloadMemory(VAddr cpu_addr, size_t size);
+ std::optional<VideoCore::RasterizerDownloadArea> GetFlushArea(VAddr cpu_addr, u64 size);
+
/// Remove images in a region
void UnmapMemory(VAddr cpu_addr, size_t size);
@@ -209,12 +207,16 @@ public:
/// Pop asynchronous downloads
void PopAsyncFlushes();
- [[nodiscard]] ImageId DmaImageId(const Tegra::DMA::ImageOperand& operand);
+ [[nodiscard]] ImageId DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload);
[[nodiscard]] std::pair<Image*, BufferImageCopy> DmaBufferImageCopy(
const Tegra::DMA::ImageCopy& copy_info, const Tegra::DMA::BufferOperand& buffer_operand,
const Tegra::DMA::ImageOperand& image_operand, ImageId image_id, bool modifies_image);
+ void DownloadImageIntoBuffer(Image* image, BufferType buffer, size_t buffer_offset,
+ std::span<const VideoCommon::BufferImageCopy> copies,
+ GPUVAddr address = 0, size_t size = 0);
+
/// Return true when a CPU region is modified from the GPU
[[nodiscard]] bool IsRegionGpuModified(VAddr addr, size_t size);
@@ -424,17 +426,32 @@ private:
u64 critical_memory;
size_t critical_gc;
+ struct BufferDownload {
+ GPUVAddr address;
+ size_t size;
+ };
+
+ struct PendingDownload {
+ bool is_swizzle;
+ size_t async_buffer_id;
+ SlotId object_id;
+ };
+
SlotVector<Image> slot_images;
SlotVector<ImageMapView> slot_map_views;
SlotVector<ImageView> slot_image_views;
SlotVector<ImageAlloc> slot_image_allocs;
SlotVector<Sampler> slot_samplers;
SlotVector<Framebuffer> slot_framebuffers;
+ SlotVector<BufferDownload> slot_buffer_downloads;
// TODO: This data structure is not optimal and it should be reworked
- std::vector<ImageId> uncommitted_downloads;
- std::deque<std::vector<ImageId>> committed_downloads;
- std::deque<std::optional<AsyncBuffer>> async_buffers;
+
+ std::vector<PendingDownload> uncommitted_downloads;
+ std::deque<std::vector<PendingDownload>> committed_downloads;
+ std::vector<AsyncBuffer> uncommitted_async_buffers;
+ std::deque<std::vector<AsyncBuffer>> async_buffers;
+ std::deque<AsyncBuffer> async_buffers_death_ring;
struct LRUItemParams {
using ObjectType = ImageId;
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index de37db684..f1071aa23 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -896,11 +896,11 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8
ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width));
ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height));
if (IsPixelFormatASTC(info.format)) {
- ASSERT(copy.image_extent.depth == 1);
- Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset),
- copy.image_extent.width, copy.image_extent.height,
- copy.image_subresource.num_layers, tile_size.width,
- tile_size.height, output.subspan(output_offset));
+ Tegra::Texture::ASTC::Decompress(
+ input.subspan(copy.buffer_offset), copy.image_extent.width,
+ copy.image_extent.height,
+ copy.image_subresource.num_layers * copy.image_extent.depth, tile_size.width,
+ tile_size.height, output.subspan(output_offset));
} else {
DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent,
output.subspan(output_offset));
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 6f288b3f8..161f050b8 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -617,7 +617,9 @@ bool Device::ShouldBoostClocks() const {
const bool is_steam_deck = vendor_id == 0x1002 && device_id == 0x163F;
- return validated_driver && !is_steam_deck;
+ const bool is_debugging = this->HasDebuggingToolAttached();
+
+ return validated_driver && !is_steam_deck && !is_debugging;
}
bool Device::GetSuitability(bool requires_swapchain) {
@@ -1007,6 +1009,8 @@ void Device::CollectPhysicalMemoryInfo() {
device_access_memory += mem_properties.memoryHeaps[element].size;
}
if (!is_integrated) {
+ const u64 reserve_memory = std::min<u64>(device_access_memory / 8, 1_GiB);
+ device_access_memory -= reserve_memory;
return;
}
const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage);
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 7d5018151..5f1c63ff9 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -10,6 +10,7 @@
#include <vector>
#include "common/common_types.h"
+#include "common/settings.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
// Define all features which may be used by the implementation here.
@@ -510,7 +511,7 @@ public:
/// Returns true when a known debugging tool is attached.
bool HasDebuggingToolAttached() const {
- return has_renderdoc || has_nsight_graphics;
+ return has_renderdoc || has_nsight_graphics || Settings::values.renderer_debug.GetValue();
}
/// Returns true when the device does not properly support cube compatibility.
diff --git a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
index 1732866e0..e28a556f8 100644
--- a/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
+++ b/src/video_core/vulkan_common/vulkan_memory_allocator.cpp
@@ -147,7 +147,7 @@ public:
/// Returns whether this allocation is compatible with the arguments.
[[nodiscard]] bool IsCompatible(VkMemoryPropertyFlags flags, u32 type_mask) const {
- return (flags & property_flags) == property_flags && (type_mask & shifted_memory_type) != 0;
+ return (flags & property_flags) == flags && (type_mask & shifted_memory_type) != 0;
}
private:
diff --git a/src/video_core/vulkan_common/vulkan_surface.cpp b/src/video_core/vulkan_common/vulkan_surface.cpp
index fa9bafa20..c34599365 100644
--- a/src/video_core/vulkan_common/vulkan_surface.cpp
+++ b/src/video_core/vulkan_common/vulkan_surface.cpp
@@ -23,10 +23,10 @@
namespace Vulkan {
-vk::SurfaceKHR CreateSurface(const vk::Instance& instance,
- const Core::Frontend::EmuWindow& emu_window) {
+vk::SurfaceKHR CreateSurface(
+ const vk::Instance& instance,
+ [[maybe_unused]] const Core::Frontend::EmuWindow::WindowSystemInfo& window_info) {
[[maybe_unused]] const vk::InstanceDispatch& dld = instance.Dispatch();
- [[maybe_unused]] const auto& window_info = emu_window.GetWindowInfo();
VkSurfaceKHR unsafe_surface = nullptr;
#ifdef _WIN32
diff --git a/src/video_core/vulkan_common/vulkan_surface.h b/src/video_core/vulkan_common/vulkan_surface.h
index 5725143e6..5e18c06c4 100644
--- a/src/video_core/vulkan_common/vulkan_surface.h
+++ b/src/video_core/vulkan_common/vulkan_surface.h
@@ -3,15 +3,12 @@
#pragma once
+#include "core/frontend/emu_window.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
-namespace Core::Frontend {
-class EmuWindow;
-}
-
namespace Vulkan {
-[[nodiscard]] vk::SurfaceKHR CreateSurface(const vk::Instance& instance,
- const Core::Frontend::EmuWindow& emu_window);
+[[nodiscard]] vk::SurfaceKHR CreateSurface(
+ const vk::Instance& instance, const Core::Frontend::EmuWindow::WindowSystemInfo& window_info);
} // namespace Vulkan
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt
index 0f8c1e6a6..2d7b9ab65 100644
--- a/src/yuzu/CMakeLists.txt
+++ b/src/yuzu/CMakeLists.txt
@@ -189,6 +189,8 @@ add_executable(yuzu
multiplayer/state.h
multiplayer/validation.h
precompiled_headers.h
+ qt_common.cpp
+ qt_common.h
startup_checks.cpp
startup_checks.h
uisettings.cpp
diff --git a/src/yuzu/applets/qt_amiibo_settings.cpp b/src/yuzu/applets/qt_amiibo_settings.cpp
index 4559df5b1..4988fcc83 100644
--- a/src/yuzu/applets/qt_amiibo_settings.cpp
+++ b/src/yuzu/applets/qt_amiibo_settings.cpp
@@ -8,7 +8,7 @@
#include "common/assert.h"
#include "common/string_util.h"
-#include "core/hle/service/nfp/nfp_device.h"
+#include "core/hle/service/nfc/common/device.h"
#include "core/hle/service/nfp/nfp_result.h"
#include "input_common/drivers/virtual_amiibo.h"
#include "input_common/main.h"
@@ -22,7 +22,7 @@
QtAmiiboSettingsDialog::QtAmiiboSettingsDialog(QWidget* parent,
Core::Frontend::CabinetParameters parameters_,
InputCommon::InputSubsystem* input_subsystem_,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device_)
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device_)
: QDialog(parent), ui(std::make_unique<Ui::QtAmiiboSettingsDialog>()),
input_subsystem{input_subsystem_}, nfp_device{std::move(nfp_device_)},
parameters(std::move(parameters_)) {
@@ -52,11 +52,11 @@ void QtAmiiboSettingsDialog::LoadInfo() {
return;
}
- if (nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagFound &&
- nfp_device->GetCurrentState() != Service::NFP::DeviceState::TagMounted) {
+ if (nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagFound &&
+ nfp_device->GetCurrentState() != Service::NFC::DeviceState::TagMounted) {
return;
}
- nfp_device->Mount(Service::NFP::MountTarget::All);
+ nfp_device->Mount(Service::NFP::ModelType::Amiibo, Service::NFP::MountTarget::All);
LoadAmiiboInfo();
LoadAmiiboData();
@@ -261,7 +261,7 @@ void QtAmiiboSettings::Close() const {
void QtAmiiboSettings::ShowCabinetApplet(
const Core::Frontend::CabinetCallback& callback_,
const Core::Frontend::CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const {
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const {
callback = std::move(callback_);
emit MainWindowShowAmiiboSettings(parameters, nfp_device);
}
diff --git a/src/yuzu/applets/qt_amiibo_settings.h b/src/yuzu/applets/qt_amiibo_settings.h
index bc389a33f..ee66a0255 100644
--- a/src/yuzu/applets/qt_amiibo_settings.h
+++ b/src/yuzu/applets/qt_amiibo_settings.h
@@ -23,9 +23,9 @@ namespace Ui {
class QtAmiiboSettingsDialog;
}
-namespace Service::NFP {
-class NfpDevice;
-} // namespace Service::NFP
+namespace Service::NFC {
+class NfcDevice;
+} // namespace Service::NFC
class QtAmiiboSettingsDialog final : public QDialog {
Q_OBJECT
@@ -33,7 +33,7 @@ class QtAmiiboSettingsDialog final : public QDialog {
public:
explicit QtAmiiboSettingsDialog(QWidget* parent, Core::Frontend::CabinetParameters parameters_,
InputCommon::InputSubsystem* input_subsystem_,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device_);
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device_);
~QtAmiiboSettingsDialog() override;
int exec() override;
@@ -52,7 +52,7 @@ private:
std::unique_ptr<Ui::QtAmiiboSettingsDialog> ui;
InputCommon::InputSubsystem* input_subsystem;
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device;
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device;
// Parameters sent in from the backend HLE applet.
Core::Frontend::CabinetParameters parameters;
@@ -71,11 +71,11 @@ public:
void Close() const override;
void ShowCabinetApplet(const Core::Frontend::CabinetCallback& callback_,
const Core::Frontend::CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const override;
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const override;
signals:
void MainWindowShowAmiiboSettings(const Core::Frontend::CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device) const;
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) const;
void MainWindowRequestExit() const;
private:
diff --git a/src/yuzu/applets/qt_profile_select.cpp b/src/yuzu/applets/qt_profile_select.cpp
index 2448e46b6..1f3f23038 100644
--- a/src/yuzu/applets/qt_profile_select.cpp
+++ b/src/yuzu/applets/qt_profile_select.cpp
@@ -95,6 +95,7 @@ QtProfileSelectionDialog::QtProfileSelectionDialog(
scroll_area->setLayout(layout);
connect(tree_view, &QTreeView::clicked, this, &QtProfileSelectionDialog::SelectUser);
+ connect(tree_view, &QTreeView::doubleClicked, this, &QtProfileSelectionDialog::accept);
connect(controller_navigation, &ControllerNavigation::TriggerKeyboardEvent,
[this](Qt::Key key) {
if (!this->isActiveWindow()) {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 4c7bf28d8..59d226113 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -1,36 +1,48 @@
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
+#include <algorithm>
+#include <array>
+#include <cmath>
+#include <cstring>
+#include <string>
+#include <tuple>
+#include <type_traits>
#include <glad/glad.h>
-#include <QApplication>
+#include <QtCore/qglobal.h>
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0)) && YUZU_USE_QT_MULTIMEDIA
+#include <QCamera>
#include <QCameraImageCapture>
#include <QCameraInfo>
#endif
+#include <QCursor>
+#include <QEvent>
+#include <QGuiApplication>
#include <QHBoxLayout>
+#include <QKeyEvent>
+#include <QLayout>
+#include <QList>
#include <QMessageBox>
-#include <QPainter>
#include <QScreen>
-#include <QString>
-#include <QStringList>
+#include <QSize>
+#include <QStringLiteral>
+#include <QSurfaceFormat>
+#include <QTimer>
#include <QWindow>
+#include <QtCore/qobjectdefs.h>
#ifdef HAS_OPENGL
#include <QOffscreenSurface>
#include <QOpenGLContext>
#endif
-#if !defined(WIN32)
-#include <qpa/qplatformnativeinterface.h>
-#endif
-
-#include <fmt/format.h>
-
-#include "common/assert.h"
#include "common/microprofile.h"
+#include "common/polyfill_thread.h"
#include "common/scm_rev.h"
#include "common/settings.h"
+#include "common/settings_input.h"
+#include "common/thread.h"
#include "core/core.h"
#include "core/cpu_manager.h"
#include "core/frontend/framebuffer_layout.h"
@@ -40,11 +52,16 @@
#include "input_common/drivers/tas_input.h"
#include "input_common/drivers/touch_screen.h"
#include "input_common/main.h"
+#include "video_core/gpu.h"
+#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
#include "yuzu/bootmanager.h"
#include "yuzu/main.h"
+#include "yuzu/qt_common.h"
-static Core::Frontend::WindowSystemType GetWindowSystemType();
+class QObject;
+class QPaintEngine;
+class QSurface;
EmuThread::EmuThread(Core::System& system) : m_system{system} {}
@@ -154,7 +171,10 @@ public:
// disable vsync for any shared contexts
auto format = share_context->format();
- format.setSwapInterval(main_surface ? Settings::values.use_vsync.GetValue() : 0);
+ const int swap_interval =
+ Settings::values.vsync_mode.GetValue() == Settings::VSyncMode::Immediate ? 0 : 1;
+
+ format.setSwapInterval(main_surface ? swap_interval : 0);
context = std::make_unique<QOpenGLContext>();
context->setShareContext(share_context);
@@ -221,7 +241,7 @@ public:
explicit RenderWidget(GRenderWindow* parent) : QWidget(parent), render_window(parent) {
setAttribute(Qt::WA_NativeWindow);
setAttribute(Qt::WA_PaintOnScreen);
- if (GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) {
+ if (QtCommon::GetWindowSystemType() == Core::Frontend::WindowSystemType::Wayland) {
setAttribute(Qt::WA_DontCreateNativeAncestors);
}
}
@@ -259,46 +279,6 @@ struct NullRenderWidget : public RenderWidget {
explicit NullRenderWidget(GRenderWindow* parent) : RenderWidget(parent) {}
};
-static Core::Frontend::WindowSystemType GetWindowSystemType() {
- // Determine WSI type based on Qt platform.
- QString platform_name = QGuiApplication::platformName();
- if (platform_name == QStringLiteral("windows"))
- return Core::Frontend::WindowSystemType::Windows;
- else if (platform_name == QStringLiteral("xcb"))
- 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 {}!", platform_name.toStdString());
- return Core::Frontend::WindowSystemType::Windows;
-}
-
-static Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
- Core::Frontend::EmuWindow::WindowSystemInfo wsi;
- wsi.type = GetWindowSystemType();
-
- // Our Win32 Qt external doesn't have the private API.
-#if defined(WIN32) || defined(__APPLE__)
- wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
-#else
- QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
- wsi.display_connection = pni->nativeResourceForWindow("display", window);
- if (wsi.type == Core::Frontend::WindowSystemType::Wayland)
- wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
- else
- wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
-#endif
- wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
-
- return wsi;
-}
-
GRenderWindow::GRenderWindow(GMainWindow* parent, EmuThread* emu_thread_,
std::shared_ptr<InputCommon::InputSubsystem> input_subsystem_,
Core::System& system_)
@@ -904,7 +884,7 @@ bool GRenderWindow::InitRenderTarget() {
}
// Update the Window System information with the new render target
- window_info = GetWindowSystemInfo(child_widget->windowHandle());
+ window_info = QtCommon::GetWindowSystemInfo(child_widget->windowHandle());
child_widget->resize(Layout::ScreenUndocked::Width, Layout::ScreenUndocked::Height);
layout()->addWidget(child_widget);
diff --git a/src/yuzu/bootmanager.h b/src/yuzu/bootmanager.h
index bb4eca07f..b7b9d4141 100644
--- a/src/yuzu/bootmanager.h
+++ b/src/yuzu/bootmanager.h
@@ -5,27 +5,45 @@
#include <atomic>
#include <condition_variable>
+#include <cstddef>
#include <memory>
#include <mutex>
+#include <utility>
+#include <vector>
+#include <QByteArray>
#include <QImage>
+#include <QObject>
+#include <QPoint>
+#include <QString>
#include <QStringList>
#include <QThread>
-#include <QTouchEvent>
#include <QWidget>
+#include <qglobal.h>
+#include <qnamespace.h>
+#include <qobjectdefs.h>
+#include "common/common_types.h"
+#include "common/logging/log.h"
#include "common/polyfill_thread.h"
#include "common/thread.h"
#include "core/frontend/emu_window.h"
-class GRenderWindow;
class GMainWindow;
class QCamera;
class QCameraImageCapture;
+class QCloseEvent;
+class QFocusEvent;
class QKeyEvent;
+class QMouseEvent;
+class QObject;
+class QResizeEvent;
+class QShowEvent;
+class QTimer;
+class QTouchEvent;
+class QWheelEvent;
namespace Core {
-enum class SystemResultStatus : u32;
class System;
} // namespace Core
@@ -40,7 +58,6 @@ enum class TasState;
namespace VideoCore {
enum class LoadCallbackStage;
-class RendererBase;
} // namespace VideoCore
class EmuThread final : public QThread {
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index bb731276e..70737c54e 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -6,6 +6,7 @@
#include <QSettings>
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
+#include "common/settings.h"
#include "core/core.h"
#include "core/hle/service/acc/profile_manager.h"
#include "core/hle/service/hid/controllers/npad.h"
@@ -442,6 +443,7 @@ void Config::ReadControlValues() {
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
ReadBasicSetting(Settings::values.enable_joycon_driver);
ReadBasicSetting(Settings::values.enable_procon_driver);
+ ReadBasicSetting(Settings::values.random_amiibo_id);
ReadBasicSetting(Settings::values.tas_enable);
ReadBasicSetting(Settings::values.tas_loop);
@@ -497,7 +499,7 @@ void Config::ReadCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
ReadGlobalSetting(Settings::values.use_multi_core);
- ReadGlobalSetting(Settings::values.use_extended_memory_layout);
+ ReadGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);
qt_config->endGroup();
}
@@ -692,6 +694,7 @@ void Config::ReadRendererValues() {
qt_config->beginGroup(QStringLiteral("Renderer"));
ReadGlobalSetting(Settings::values.renderer_backend);
+ ReadGlobalSetting(Settings::values.async_presentation);
ReadGlobalSetting(Settings::values.renderer_force_max_clock);
ReadGlobalSetting(Settings::values.vulkan_device);
ReadGlobalSetting(Settings::values.fullscreen_mode);
@@ -708,17 +711,21 @@ void Config::ReadRendererValues() {
ReadGlobalSetting(Settings::values.nvdec_emulation);
ReadGlobalSetting(Settings::values.accelerate_astc);
ReadGlobalSetting(Settings::values.async_astc);
- ReadGlobalSetting(Settings::values.use_vsync);
+ ReadGlobalSetting(Settings::values.use_reactive_flushing);
ReadGlobalSetting(Settings::values.shader_backend);
ReadGlobalSetting(Settings::values.use_asynchronous_shaders);
ReadGlobalSetting(Settings::values.use_fast_gpu_time);
- ReadGlobalSetting(Settings::values.use_pessimistic_flushes);
ReadGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
+ ReadGlobalSetting(Settings::values.enable_compute_pipelines);
ReadGlobalSetting(Settings::values.bg_red);
ReadGlobalSetting(Settings::values.bg_green);
ReadGlobalSetting(Settings::values.bg_blue);
if (global) {
+ Settings::values.vsync_mode.SetValue(static_cast<Settings::VSyncMode>(
+ ReadSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()),
+ static_cast<u32>(Settings::values.vsync_mode.GetDefault()))
+ .value<u32>()));
ReadBasicSetting(Settings::values.renderer_debug);
ReadBasicSetting(Settings::values.renderer_shader_feedback);
ReadBasicSetting(Settings::values.enable_nsight_aftermath);
@@ -1145,6 +1152,7 @@ void Config::SaveControlValues() {
WriteBasicSetting(Settings::values.enable_raw_input);
WriteBasicSetting(Settings::values.enable_joycon_driver);
WriteBasicSetting(Settings::values.enable_procon_driver);
+ WriteBasicSetting(Settings::values.random_amiibo_id);
WriteBasicSetting(Settings::values.keyboard_enabled);
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);
@@ -1161,7 +1169,7 @@ void Config::SaveCoreValues() {
qt_config->beginGroup(QStringLiteral("Core"));
WriteGlobalSetting(Settings::values.use_multi_core);
- WriteGlobalSetting(Settings::values.use_extended_memory_layout);
+ WriteGlobalSetting(Settings::values.use_unsafe_extended_memory_layout);
qt_config->endGroup();
}
@@ -1313,6 +1321,7 @@ void Config::SaveRendererValues() {
static_cast<u32>(Settings::values.renderer_backend.GetValue(global)),
static_cast<u32>(Settings::values.renderer_backend.GetDefault()),
Settings::values.renderer_backend.UsingGlobal());
+ WriteGlobalSetting(Settings::values.async_presentation);
WriteGlobalSetting(Settings::values.renderer_force_max_clock);
WriteGlobalSetting(Settings::values.vulkan_device);
WriteSetting(QString::fromStdString(Settings::values.fullscreen_mode.GetLabel()),
@@ -1350,20 +1359,23 @@ void Config::SaveRendererValues() {
Settings::values.nvdec_emulation.UsingGlobal());
WriteGlobalSetting(Settings::values.accelerate_astc);
WriteGlobalSetting(Settings::values.async_astc);
- WriteGlobalSetting(Settings::values.use_vsync);
+ WriteGlobalSetting(Settings::values.use_reactive_flushing);
WriteSetting(QString::fromStdString(Settings::values.shader_backend.GetLabel()),
static_cast<u32>(Settings::values.shader_backend.GetValue(global)),
static_cast<u32>(Settings::values.shader_backend.GetDefault()),
Settings::values.shader_backend.UsingGlobal());
WriteGlobalSetting(Settings::values.use_asynchronous_shaders);
WriteGlobalSetting(Settings::values.use_fast_gpu_time);
- WriteGlobalSetting(Settings::values.use_pessimistic_flushes);
WriteGlobalSetting(Settings::values.use_vulkan_driver_pipeline_cache);
+ WriteGlobalSetting(Settings::values.enable_compute_pipelines);
WriteGlobalSetting(Settings::values.bg_red);
WriteGlobalSetting(Settings::values.bg_green);
WriteGlobalSetting(Settings::values.bg_blue);
if (global) {
+ WriteSetting(QString::fromStdString(Settings::values.vsync_mode.GetLabel()),
+ static_cast<u32>(Settings::values.vsync_mode.GetValue()),
+ static_cast<u32>(Settings::values.vsync_mode.GetDefault()));
WriteBasicSetting(Settings::values.renderer_debug);
WriteBasicSetting(Settings::values.renderer_shader_feedback);
WriteBasicSetting(Settings::values.enable_nsight_aftermath);
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index 2aaefcc05..8e76a819a 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -36,8 +36,9 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_,
debug_tab_tab{std::make_unique<ConfigureDebugTab>(system_, this)},
filesystem_tab{std::make_unique<ConfigureFilesystem>(this)},
general_tab{std::make_unique<ConfigureGeneral>(system_, this)},
- graphics_tab{std::make_unique<ConfigureGraphics>(system_, this)},
graphics_advanced_tab{std::make_unique<ConfigureGraphicsAdvanced>(system_, this)},
+ graphics_tab{std::make_unique<ConfigureGraphics>(
+ system_, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this)},
hotkeys_tab{std::make_unique<ConfigureHotkeys>(system_.HIDCore(), this)},
input_tab{std::make_unique<ConfigureInput>(system_, this)},
network_tab{std::make_unique<ConfigureNetwork>(system_, this)},
diff --git a/src/yuzu/configuration/configure_dialog.h b/src/yuzu/configuration/configure_dialog.h
index 1f724834a..a086a07c4 100644
--- a/src/yuzu/configuration/configure_dialog.h
+++ b/src/yuzu/configuration/configure_dialog.h
@@ -72,8 +72,8 @@ private:
std::unique_ptr<ConfigureDebugTab> debug_tab_tab;
std::unique_ptr<ConfigureFilesystem> filesystem_tab;
std::unique_ptr<ConfigureGeneral> general_tab;
- std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
+ std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureHotkeys> hotkeys_tab;
std::unique_ptr<ConfigureInput> input_tab;
std::unique_ptr<ConfigureNetwork> network_tab;
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 207bcdc4d..26258d744 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -35,9 +35,6 @@ void ConfigureGeneral::SetConfiguration() {
ui->use_multi_core->setEnabled(runtime_lock);
ui->use_multi_core->setChecked(Settings::values.use_multi_core.GetValue());
- ui->use_extended_memory_layout->setEnabled(runtime_lock);
- ui->use_extended_memory_layout->setChecked(
- Settings::values.use_extended_memory_layout.GetValue());
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing.GetValue());
ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot.GetValue());
@@ -79,9 +76,6 @@ void ConfigureGeneral::ResetDefaults() {
void ConfigureGeneral::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core,
use_multi_core);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_extended_memory_layout,
- ui->use_extended_memory_layout,
- use_extended_memory_layout);
if (Settings::IsConfiguringGlobal()) {
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
@@ -141,9 +135,6 @@ void ConfigureGeneral::SetupPerGameUI() {
Settings::values.use_speed_limit, use_speed_limit);
ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core,
use_multi_core);
- ConfigurationShared::SetColoredTristate(ui->use_extended_memory_layout,
- Settings::values.use_extended_memory_layout,
- use_extended_memory_layout);
connect(ui->toggle_speed_limit, &QCheckBox::clicked, ui->speed_limit, [this]() {
ui->speed_limit->setEnabled(ui->toggle_speed_limit->isChecked() &&
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h
index a090c1a3f..7ff63f425 100644
--- a/src/yuzu/configuration/configure_general.h
+++ b/src/yuzu/configuration/configure_general.h
@@ -47,7 +47,6 @@ private:
ConfigurationShared::CheckState use_speed_limit;
ConfigurationShared::CheckState use_multi_core;
- ConfigurationShared::CheckState use_extended_memory_layout;
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index add110bb0..986a1625b 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -62,13 +62,6 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="use_extended_memory_layout">
- <property name="text">
- <string>Extended memory layout (8GB DRAM)</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="QCheckBox" name="toggle_check_exit">
<property name="text">
<string>Confirm exit while emulation is running</string>
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index e9388daad..f316b598c 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -2,24 +2,85 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Include this early to include Vulkan headers how we want to
+#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
+#include <algorithm>
+#include <functional>
+#include <iosfwd>
+#include <iterator>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+#include <QBoxLayout>
+#include <QCheckBox>
#include <QColorDialog>
-#include <QVulkanInstance>
+#include <QComboBox>
+#include <QIcon>
+#include <QLabel>
+#include <QPixmap>
+#include <QPushButton>
+#include <QSlider>
+#include <QStringLiteral>
+#include <QtCore/qobjectdefs.h>
+#include <qcoreevent.h>
+#include <qglobal.h>
+#include <vulkan/vulkan_core.h>
#include "common/common_types.h"
+#include "common/dynamic_library.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "core/core.h"
#include "ui_configure_graphics.h"
#include "video_core/vulkan_common/vulkan_instance.h"
#include "video_core/vulkan_common/vulkan_library.h"
+#include "video_core/vulkan_common/vulkan_surface.h"
#include "yuzu/configuration/configuration_shared.h"
#include "yuzu/configuration/configure_graphics.h"
+#include "yuzu/qt_common.h"
#include "yuzu/uisettings.h"
-ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* parent)
- : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} {
+static const std::vector<VkPresentModeKHR> default_present_modes{VK_PRESENT_MODE_IMMEDIATE_KHR,
+ VK_PRESENT_MODE_FIFO_KHR};
+
+// Converts a setting to a present mode (or vice versa)
+static constexpr VkPresentModeKHR VSyncSettingToMode(Settings::VSyncMode mode) {
+ switch (mode) {
+ case Settings::VSyncMode::Immediate:
+ return VK_PRESENT_MODE_IMMEDIATE_KHR;
+ case Settings::VSyncMode::Mailbox:
+ return VK_PRESENT_MODE_MAILBOX_KHR;
+ case Settings::VSyncMode::FIFO:
+ return VK_PRESENT_MODE_FIFO_KHR;
+ case Settings::VSyncMode::FIFORelaxed:
+ return VK_PRESENT_MODE_FIFO_RELAXED_KHR;
+ default:
+ return VK_PRESENT_MODE_FIFO_KHR;
+ }
+}
+
+static constexpr Settings::VSyncMode PresentModeToSetting(VkPresentModeKHR mode) {
+ switch (mode) {
+ case VK_PRESENT_MODE_IMMEDIATE_KHR:
+ return Settings::VSyncMode::Immediate;
+ case VK_PRESENT_MODE_MAILBOX_KHR:
+ return Settings::VSyncMode::Mailbox;
+ case VK_PRESENT_MODE_FIFO_KHR:
+ return Settings::VSyncMode::FIFO;
+ case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
+ return Settings::VSyncMode::FIFORelaxed;
+ default:
+ return Settings::VSyncMode::FIFO;
+ }
+}
+
+ConfigureGraphics::ConfigureGraphics(const Core::System& system_,
+ const std::function<void()>& expose_compute_option_,
+ QWidget* parent)
+ : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()},
+ expose_compute_option{expose_compute_option_}, system{system_} {
vulkan_device = Settings::values.vulkan_device.GetValue();
RetrieveVulkanDevices();
@@ -39,13 +100,16 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
connect(ui->api, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] {
UpdateAPILayout();
+ PopulateVSyncModeSelection();
if (!Settings::IsConfiguringGlobal()) {
ConfigurationShared::SetHighlight(
ui->api_widget, ui->api->currentIndex() != ConfigurationShared::USE_GLOBAL_INDEX);
}
});
- connect(ui->device, qOverload<int>(&QComboBox::activated), this,
- [this](int device) { UpdateDeviceSelection(device); });
+ connect(ui->device, qOverload<int>(&QComboBox::activated), this, [this](int device) {
+ UpdateDeviceSelection(device);
+ PopulateVSyncModeSelection();
+ });
connect(ui->backend, qOverload<int>(&QComboBox::activated), this,
[this](int backend) { UpdateShaderBackendSelection(backend); });
@@ -70,6 +134,43 @@ ConfigureGraphics::ConfigureGraphics(const Core::System& system_, QWidget* paren
ui->fsr_sharpening_label->setVisible(Settings::IsConfiguringGlobal());
}
+void ConfigureGraphics::PopulateVSyncModeSelection() {
+ const Settings::RendererBackend backend{GetCurrentGraphicsBackend()};
+ if (backend == Settings::RendererBackend::Null) {
+ ui->vsync_mode_combobox->setEnabled(false);
+ return;
+ }
+ ui->vsync_mode_combobox->setEnabled(true);
+
+ const int current_index = //< current selected vsync mode from combobox
+ ui->vsync_mode_combobox->currentIndex();
+ const auto current_mode = //< current selected vsync mode as a VkPresentModeKHR
+ current_index == -1 ? VSyncSettingToMode(Settings::values.vsync_mode.GetValue())
+ : vsync_mode_combobox_enum_map[current_index];
+ int index{};
+ const int device{ui->device->currentIndex()}; //< current selected Vulkan device
+ const auto& present_modes = //< relevant vector of present modes for the selected device or API
+ backend == Settings::RendererBackend::Vulkan ? device_present_modes[device]
+ : default_present_modes;
+
+ ui->vsync_mode_combobox->clear();
+ vsync_mode_combobox_enum_map.clear();
+ vsync_mode_combobox_enum_map.reserve(present_modes.size());
+ for (const auto present_mode : present_modes) {
+ const auto mode_name = TranslateVSyncMode(present_mode, backend);
+ if (mode_name.isEmpty()) {
+ continue;
+ }
+
+ ui->vsync_mode_combobox->insertItem(index, mode_name);
+ vsync_mode_combobox_enum_map.push_back(present_mode);
+ if (present_mode == current_mode) {
+ ui->vsync_mode_combobox->setCurrentIndex(index);
+ }
+ index++;
+ }
+}
+
void ConfigureGraphics::UpdateDeviceSelection(int device) {
if (device == -1) {
return;
@@ -99,6 +200,9 @@ void ConfigureGraphics::SetConfiguration() {
ui->nvdec_emulation_widget->setEnabled(runtime_lock);
ui->resolution_combobox->setEnabled(runtime_lock);
ui->accelerate_astc->setEnabled(runtime_lock);
+ ui->vsync_mode_layout->setEnabled(runtime_lock ||
+ Settings::values.renderer_backend.GetValue() ==
+ Settings::RendererBackend::Vulkan);
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
ui->use_asynchronous_gpu_emulation->setChecked(
Settings::values.use_asynchronous_gpu_emulation.GetValue());
@@ -170,7 +274,24 @@ void ConfigureGraphics::SetConfiguration() {
Settings::values.bg_green.GetValue(),
Settings::values.bg_blue.GetValue()));
UpdateAPILayout();
+ PopulateVSyncModeSelection(); //< must happen after UpdateAPILayout
SetFSRIndicatorText(ui->fsr_sharpening_slider->sliderPosition());
+
+ // VSync setting needs to be determined after populating the VSync combobox
+ if (Settings::IsConfiguringGlobal()) {
+ const auto vsync_mode_setting = Settings::values.vsync_mode.GetValue();
+ const auto vsync_mode = VSyncSettingToMode(vsync_mode_setting);
+ int index{};
+ for (const auto mode : vsync_mode_combobox_enum_map) {
+ if (mode == vsync_mode) {
+ break;
+ }
+ index++;
+ }
+ if (static_cast<unsigned long>(index) < vsync_mode_combobox_enum_map.size()) {
+ ui->vsync_mode_combobox->setCurrentIndex(index);
+ }
+ }
}
void ConfigureGraphics::SetFSRIndicatorText(int percentage) {
@@ -178,6 +299,27 @@ void ConfigureGraphics::SetFSRIndicatorText(int percentage) {
tr("%1%", "FSR sharpening percentage (e.g. 50%)").arg(100 - (percentage / 2)));
}
+const QString ConfigureGraphics::TranslateVSyncMode(VkPresentModeKHR mode,
+ Settings::RendererBackend backend) const {
+ switch (mode) {
+ case VK_PRESENT_MODE_IMMEDIATE_KHR:
+ return backend == Settings::RendererBackend::OpenGL
+ ? tr("Off")
+ : QStringLiteral("Immediate (%1)").arg(tr("VSync Off"));
+ case VK_PRESENT_MODE_MAILBOX_KHR:
+ return QStringLiteral("Mailbox (%1)").arg(tr("Recommended"));
+ case VK_PRESENT_MODE_FIFO_KHR:
+ return backend == Settings::RendererBackend::OpenGL
+ ? tr("On")
+ : QStringLiteral("FIFO (%1)").arg(tr("VSync On"));
+ case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
+ return QStringLiteral("FIFO Relaxed");
+ default:
+ return {};
+ break;
+ }
+}
+
void ConfigureGraphics::ApplyConfiguration() {
const auto resolution_setup = static_cast<Settings::ResolutionSetup>(
ui->resolution_combobox->currentIndex() -
@@ -232,6 +374,10 @@ void ConfigureGraphics::ApplyConfiguration() {
Settings::values.anti_aliasing.SetValue(anti_aliasing);
}
Settings::values.fsr_sharpening_slider.SetValue(ui->fsr_sharpening_slider->value());
+
+ const auto mode = vsync_mode_combobox_enum_map[ui->vsync_mode_combobox->currentIndex()];
+ const auto vsync_mode = PresentModeToSetting(mode);
+ Settings::values.vsync_mode.SetValue(vsync_mode);
} else {
if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
Settings::values.resolution_setup.SetGlobal(true);
@@ -345,7 +491,9 @@ void ConfigureGraphics::UpdateAPILayout() {
ui->backend_widget->setVisible(true);
break;
case Settings::RendererBackend::Vulkan:
- ui->device->setCurrentIndex(vulkan_device);
+ if (static_cast<int>(vulkan_device) < ui->device->count()) {
+ ui->device->setCurrentIndex(vulkan_device);
+ }
ui->device_widget->setVisible(true);
ui->backend_widget->setVisible(false);
break;
@@ -363,16 +511,37 @@ void ConfigureGraphics::RetrieveVulkanDevices() try {
using namespace Vulkan;
+ auto* window = this->window()->windowHandle();
+ auto wsi = QtCommon::GetWindowSystemInfo(window);
+
vk::InstanceDispatch dld;
const Common::DynamicLibrary library = OpenLibrary();
- const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_1);
+ const vk::Instance instance = CreateInstance(library, dld, VK_API_VERSION_1_1, wsi.type);
const std::vector<VkPhysicalDevice> physical_devices = instance.EnumeratePhysicalDevices();
+ vk::SurfaceKHR surface = CreateSurface(instance, wsi);
vulkan_devices.clear();
vulkan_devices.reserve(physical_devices.size());
+ device_present_modes.clear();
+ device_present_modes.reserve(physical_devices.size());
for (const VkPhysicalDevice device : physical_devices) {
- const std::string name = vk::PhysicalDevice(device, dld).GetProperties().deviceName;
+ const auto physical_device = vk::PhysicalDevice(device, dld);
+ const std::string name = physical_device.GetProperties().deviceName;
+ const std::vector<VkPresentModeKHR> present_modes =
+ physical_device.GetSurfacePresentModesKHR(*surface);
vulkan_devices.push_back(QString::fromStdString(name));
+ device_present_modes.push_back(present_modes);
+
+ VkPhysicalDeviceDriverProperties driver_properties{};
+ driver_properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
+ driver_properties.pNext = nullptr;
+ VkPhysicalDeviceProperties2 properties{};
+ properties.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2_KHR;
+ properties.pNext = &driver_properties;
+ dld.vkGetPhysicalDeviceProperties2(physical_device, &properties);
+ if (driver_properties.driverID == VK_DRIVER_ID_INTEL_PROPRIETARY_WINDOWS) {
+ expose_compute_option();
+ }
}
} catch (const Vulkan::vk::Exception& exception) {
LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what());
@@ -465,4 +634,6 @@ void ConfigureGraphics::SetupPerGameUI() {
ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
ConfigurationShared::InsertGlobalItem(
ui->nvdec_emulation, static_cast<int>(Settings::values.nvdec_emulation.GetValue(true)));
+
+ ui->vsync_mode_layout->setVisible(false);
}
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h
index d98d6624e..364b1cac2 100644
--- a/src/yuzu/configuration/configure_graphics.h
+++ b/src/yuzu/configuration/configure_graphics.h
@@ -3,11 +3,24 @@
#pragma once
+#include <functional>
#include <memory>
#include <vector>
+#include <QColor>
#include <QString>
#include <QWidget>
-#include "common/settings.h"
+#include <qobjectdefs.h>
+#include <vulkan/vulkan_core.h>
+#include "common/common_types.h"
+
+class QEvent;
+class QObject;
+
+namespace Settings {
+enum class NvdecEmulation : u32;
+enum class RendererBackend : u32;
+enum class ShaderBackend : u32;
+} // namespace Settings
namespace Core {
class System;
@@ -25,7 +38,9 @@ class ConfigureGraphics : public QWidget {
Q_OBJECT
public:
- explicit ConfigureGraphics(const Core::System& system_, QWidget* parent = nullptr);
+ explicit ConfigureGraphics(const Core::System& system_,
+ const std::function<void()>& expose_compute_option_,
+ QWidget* parent = nullptr);
~ConfigureGraphics() override;
void ApplyConfiguration();
@@ -35,6 +50,7 @@ private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
+ void PopulateVSyncModeSelection();
void UpdateBackgroundColorButton(QColor color);
void UpdateAPILayout();
void UpdateDeviceSelection(int device);
@@ -43,6 +59,10 @@ private:
void RetrieveVulkanDevices();
void SetFSRIndicatorText(int percentage);
+ /* Turns a Vulkan present mode into a textual string for a UI
+ * (and eventually for a human to read) */
+ const QString TranslateVSyncMode(VkPresentModeKHR mode,
+ Settings::RendererBackend backend) const;
void SetupPerGameUI();
@@ -58,8 +78,13 @@ private:
ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
std::vector<QString> vulkan_devices;
+ std::vector<std::vector<VkPresentModeKHR>> device_present_modes;
+ std::vector<VkPresentModeKHR>
+ vsync_mode_combobox_enum_map; //< Keeps track of which present mode corresponds to which
+ // selection in the combobox
u32 vulkan_device{};
Settings::ShaderBackend shader_backend{};
+ const std::function<void()>& expose_compute_option;
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index a45ec69ec..39f70e406 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -189,6 +189,44 @@
</widget>
</item>
<item>
+ <widget class="QWidget" name="vsync_mode_layout" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QLabel" name="vsync_mode_label">
+ <property name="text">
+ <string>VSync Mode:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="vsync_mode_combobox">
+ <property name="toolTip">
+ <string>FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
+FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
+Mailbox can have lower latency than FIFO and does not tear but may drop frames.
+Immediate (no synchronization) just presents whatever is available and can exhibit tearing.</string>
+ </property>
+ <property name="currentText">
+ <string/>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
<widget class="QWidget" name="nvdec_emulation_widget" native="true">
<layout class="QHBoxLayout" name="nvdec_emulation_layout">
<property name="leftMargin">
@@ -366,7 +404,7 @@
</item>
<item>
<property name="text">
- <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string>
+ <string>1.5X (1080p/1620p) [EXPERIMENTAL]</string>
</property>
</item>
<item>
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp
index 59fb1b334..1f3e489d0 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.cpp
+++ b/src/yuzu/configuration/configure_graphics_advanced.cpp
@@ -15,26 +15,32 @@ ConfigureGraphicsAdvanced::ConfigureGraphicsAdvanced(const Core::System& system_
SetupPerGameUI();
SetConfiguration();
+
+ ui->enable_compute_pipelines_checkbox->setVisible(false);
}
ConfigureGraphicsAdvanced::~ConfigureGraphicsAdvanced() = default;
void ConfigureGraphicsAdvanced::SetConfiguration() {
const bool runtime_lock = !system.IsPoweredOn();
- ui->use_vsync->setEnabled(runtime_lock);
+ ui->use_reactive_flushing->setEnabled(runtime_lock);
+ ui->async_present->setEnabled(runtime_lock);
ui->renderer_force_max_clock->setEnabled(runtime_lock);
ui->async_astc->setEnabled(runtime_lock);
ui->use_asynchronous_shaders->setEnabled(runtime_lock);
ui->anisotropic_filtering_combobox->setEnabled(runtime_lock);
+ ui->enable_compute_pipelines_checkbox->setEnabled(runtime_lock);
+ ui->async_present->setChecked(Settings::values.async_presentation.GetValue());
ui->renderer_force_max_clock->setChecked(Settings::values.renderer_force_max_clock.GetValue());
- ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue());
+ ui->use_reactive_flushing->setChecked(Settings::values.use_reactive_flushing.GetValue());
ui->async_astc->setChecked(Settings::values.async_astc.GetValue());
ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue());
ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue());
- ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue());
ui->use_vulkan_driver_pipeline_cache->setChecked(
Settings::values.use_vulkan_driver_pipeline_cache.GetValue());
+ ui->enable_compute_pipelines_checkbox->setChecked(
+ Settings::values.enable_compute_pipelines.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setCurrentIndex(
@@ -54,12 +60,15 @@ void ConfigureGraphicsAdvanced::SetConfiguration() {
void ConfigureGraphicsAdvanced::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.gpu_accuracy, ui->gpu_accuracy);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_presentation,
+ ui->async_present, async_present);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.renderer_force_max_clock,
ui->renderer_force_max_clock,
renderer_force_max_clock);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.max_anisotropy,
ui->anisotropic_filtering_combobox);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vsync, ui->use_vsync, use_vsync);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_reactive_flushing,
+ ui->use_reactive_flushing, use_reactive_flushing);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.async_astc, ui->async_astc,
async_astc);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_asynchronous_shaders,
@@ -67,11 +76,12 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() {
use_asynchronous_shaders);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time,
ui->use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes,
- ui->use_pessimistic_flushes, use_pessimistic_flushes);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_vulkan_driver_pipeline_cache,
ui->use_vulkan_driver_pipeline_cache,
use_vulkan_driver_pipeline_cache);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.enable_compute_pipelines,
+ ui->enable_compute_pipelines_checkbox,
+ enable_compute_pipelines);
}
void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) {
@@ -90,27 +100,31 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
// Disable if not global (only happens during game)
if (Settings::IsConfiguringGlobal()) {
ui->gpu_accuracy->setEnabled(Settings::values.gpu_accuracy.UsingGlobal());
+ ui->async_present->setEnabled(Settings::values.async_presentation.UsingGlobal());
ui->renderer_force_max_clock->setEnabled(
Settings::values.renderer_force_max_clock.UsingGlobal());
- ui->use_vsync->setEnabled(Settings::values.use_vsync.UsingGlobal());
+ ui->use_reactive_flushing->setEnabled(Settings::values.use_reactive_flushing.UsingGlobal());
ui->async_astc->setEnabled(Settings::values.async_astc.UsingGlobal());
ui->use_asynchronous_shaders->setEnabled(
Settings::values.use_asynchronous_shaders.UsingGlobal());
ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal());
- ui->use_pessimistic_flushes->setEnabled(
- Settings::values.use_pessimistic_flushes.UsingGlobal());
ui->use_vulkan_driver_pipeline_cache->setEnabled(
Settings::values.use_vulkan_driver_pipeline_cache.UsingGlobal());
ui->anisotropic_filtering_combobox->setEnabled(
Settings::values.max_anisotropy.UsingGlobal());
+ ui->enable_compute_pipelines_checkbox->setEnabled(
+ Settings::values.enable_compute_pipelines.UsingGlobal());
return;
}
+ ConfigurationShared::SetColoredTristate(ui->async_present, Settings::values.async_presentation,
+ async_present);
ConfigurationShared::SetColoredTristate(ui->renderer_force_max_clock,
Settings::values.renderer_force_max_clock,
renderer_force_max_clock);
- ConfigurationShared::SetColoredTristate(ui->use_vsync, Settings::values.use_vsync, use_vsync);
+ ConfigurationShared::SetColoredTristate(
+ ui->use_reactive_flushing, Settings::values.use_reactive_flushing, use_reactive_flushing);
ConfigurationShared::SetColoredTristate(ui->async_astc, Settings::values.async_astc,
async_astc);
ConfigurationShared::SetColoredTristate(ui->use_asynchronous_shaders,
@@ -118,12 +132,12 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
use_asynchronous_shaders);
ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time,
Settings::values.use_fast_gpu_time, use_fast_gpu_time);
- ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes,
- Settings::values.use_pessimistic_flushes,
- use_pessimistic_flushes);
ConfigurationShared::SetColoredTristate(ui->use_vulkan_driver_pipeline_cache,
Settings::values.use_vulkan_driver_pipeline_cache,
use_vulkan_driver_pipeline_cache);
+ ConfigurationShared::SetColoredTristate(ui->enable_compute_pipelines_checkbox,
+ Settings::values.enable_compute_pipelines,
+ enable_compute_pipelines);
ConfigurationShared::SetColoredComboBox(
ui->gpu_accuracy, ui->label_gpu_accuracy,
static_cast<int>(Settings::values.gpu_accuracy.GetValue(true)));
@@ -131,3 +145,7 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() {
ui->anisotropic_filtering_combobox, ui->af_label,
static_cast<int>(Settings::values.max_anisotropy.GetValue(true)));
}
+
+void ConfigureGraphicsAdvanced::ExposeComputeOption() {
+ ui->enable_compute_pipelines_checkbox->setVisible(true);
+}
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h
index bf1b04749..1c7b636b9 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.h
+++ b/src/yuzu/configuration/configure_graphics_advanced.h
@@ -28,6 +28,8 @@ public:
void ApplyConfiguration();
void SetConfiguration();
+ void ExposeComputeOption();
+
private:
void changeEvent(QEvent* event) override;
void RetranslateUI();
@@ -36,13 +38,15 @@ private:
std::unique_ptr<Ui::ConfigureGraphicsAdvanced> ui;
+ ConfigurationShared::CheckState async_present;
ConfigurationShared::CheckState renderer_force_max_clock;
ConfigurationShared::CheckState use_vsync;
ConfigurationShared::CheckState async_astc;
+ ConfigurationShared::CheckState use_reactive_flushing;
ConfigurationShared::CheckState use_asynchronous_shaders;
ConfigurationShared::CheckState use_fast_gpu_time;
- ConfigurationShared::CheckState use_pessimistic_flushes;
ConfigurationShared::CheckState use_vulkan_driver_pipeline_cache;
+ ConfigurationShared::CheckState enable_compute_pipelines;
const Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui
index a7dbdc18c..9ef7c8e8f 100644
--- a/src/yuzu/configuration/configure_graphics_advanced.ui
+++ b/src/yuzu/configuration/configure_graphics_advanced.ui
@@ -7,7 +7,7 @@
<x>0</x>
<y>0</y>
<width>404</width>
- <height>321</height>
+ <height>376</height>
</rect>
</property>
<property name="windowTitle">
@@ -70,6 +70,13 @@
</widget>
</item>
<item>
+ <widget class="QCheckBox" name="async_present">
+ <property name="text">
+ <string>Enable asynchronous presentation (Vulkan only)</string>
+ </property>
+ </widget>
+ </item>
+ <item>
<widget class="QCheckBox" name="renderer_force_max_clock">
<property name="toolTip">
<string>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string>
@@ -80,22 +87,22 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="use_vsync">
+ <widget class="QCheckBox" name="async_astc">
<property name="toolTip">
- <string>VSync prevents the screen from tearing, but some graphics cards have lower performance with VSync enabled. Keep it enabled if you don't notice a performance difference.</string>
+ <string>Enables asynchronous ASTC texture decoding, which may reduce load time stutter. This feature is experimental.</string>
</property>
<property name="text">
- <string>Use VSync</string>
+ <string>Decode ASTC textures asynchronously (Hack)</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="async_astc">
+ <widget class="QCheckBox" name="use_reactive_flushing">
<property name="toolTip">
- <string>Enables asynchronous ASTC texture decoding, which may reduce load time stutter. This feature is experimental.</string>
+ <string>Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.</string>
</property>
<property name="text">
- <string>Decode ASTC textures asynchronously (Hack)</string>
+ <string>Enable Reactive Flushing</string>
</property>
</widget>
</item>
@@ -112,7 +119,7 @@
<item>
<widget class="QCheckBox" name="use_fast_gpu_time">
<property name="toolTip">
- <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string>
+ <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string>
</property>
<property name="text">
<string>Use Fast GPU Time (Hack)</string>
@@ -120,22 +127,23 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="use_pessimistic_flushes">
+ <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache">
<property name="toolTip">
- <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string>
+ <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string>
</property>
<property name="text">
- <string>Use pessimistic buffer flushes (Hack)</string>
+ <string>Use Vulkan pipeline cache</string>
</property>
</widget>
</item>
<item>
- <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache">
+ <widget class="QCheckBox" name="enable_compute_pipelines_checkbox">
<property name="toolTip">
- <string>Enables GPU vendor-specific pipeline cache. This option can improve shader loading time significantly in cases where the Vulkan driver does not store pipeline cache files internally.</string>
+ <string>Enable compute pipelines, required by some games. This setting only exists for Intel proprietary drivers, and may crash if enabled.
+Compute pipelines are always enabled on all other drivers.</string>
</property>
<property name="text">
- <string>Use Vulkan pipeline cache</string>
+ <string>Enable Compute Pipelines (Intel Vulkan only)</string>
</property>
</widget>
</item>
diff --git a/src/yuzu/configuration/configure_input_advanced.cpp b/src/yuzu/configuration/configure_input_advanced.cpp
index 8d81322f3..f13156434 100644
--- a/src/yuzu/configuration/configure_input_advanced.cpp
+++ b/src/yuzu/configuration/configure_input_advanced.cpp
@@ -140,6 +140,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.enable_ir_sensor = ui->enable_ir_sensor->isChecked();
Settings::values.enable_joycon_driver = ui->enable_joycon_driver->isChecked();
Settings::values.enable_procon_driver = ui->enable_procon_driver->isChecked();
+ Settings::values.random_amiibo_id = ui->random_amiibo_id->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
@@ -176,6 +177,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->enable_ir_sensor->setChecked(Settings::values.enable_ir_sensor.GetValue());
ui->enable_joycon_driver->setChecked(Settings::values.enable_joycon_driver.GetValue());
ui->enable_procon_driver->setChecked(Settings::values.enable_procon_driver.GetValue());
+ ui->random_amiibo_id->setChecked(Settings::values.random_amiibo_id.GetValue());
UpdateUIEnabled();
}
diff --git a/src/yuzu/configuration/configure_input_advanced.ui b/src/yuzu/configuration/configure_input_advanced.ui
index 0eb2b34bc..2e8b13660 100644
--- a/src/yuzu/configuration/configure_input_advanced.ui
+++ b/src/yuzu/configuration/configure_input_advanced.ui
@@ -2728,6 +2728,22 @@
</widget>
</item>
<item row="7" column="0">
+ <widget class="QCheckBox" name="random_amiibo_id">
+ <property name="toolTip">
+ <string>Allows unlimited uses of the same Amiibo in games that would otherwise limit you to one use.</string>
+ </property>
+ <property name="minimumSize">
+ <size>
+ <width>0</width>
+ <height>23</height>
+ </size>
+ </property>
+ <property name="text">
+ <string>Use random Amiibo ID</string>
+ </property>
+ </widget>
+ </item>
+ <item row="8" column="0">
<widget class="QCheckBox" name="mouse_panning">
<property name="minimumSize">
<size>
@@ -2740,7 +2756,7 @@
</property>
</widget>
</item>
- <item row="7" column="2">
+ <item row="8" column="2">
<widget class="QSpinBox" name="mouse_panning_sensitivity">
<property name="toolTip">
<string>Mouse sensitivity</string>
@@ -2762,14 +2778,14 @@
</property>
</widget>
</item>
- <item row="8" column="0">
+ <item row="9" column="0">
<widget class="QLabel" name="motion_touch">
<property name="text">
<string>Motion / Touch</string>
</property>
</widget>
</item>
- <item row="8" column="2">
+ <item row="9" column="2">
<widget class="QPushButton" name="buttonMotionTouch">
<property name="text">
<string>Configure</string>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 50b62293e..2c2e7e47b 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -8,6 +8,7 @@
#include <QInputDialog>
#include <QMenu>
#include <QMessageBox>
+#include <QMouseEvent>
#include <QTimer>
#include "common/assert.h"
#include "common/param_package.h"
@@ -206,7 +207,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
}
if (param.Has("axis")) {
const QString axis = QString::fromStdString(param.Get("axis", ""));
- return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis);
+ return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, axis);
}
if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) {
const QString axis_x = QString::fromStdString(param.Get("axis_x", ""));
@@ -229,7 +230,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) {
return QObject::tr("%1%2%3Hat %4").arg(turbo, toggle, inverted, button_name);
}
if (param.Has("axis")) {
- return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
+ return QObject::tr("%1%2%3Axis %4").arg(toggle, inverted, invert, button_name);
}
if (param.Has("motion")) {
return QObject::tr("%1%2Axis %3").arg(toggle, inverted, button_name);
@@ -410,6 +411,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
button_map[button_id]->setText(ButtonToText(param));
emulated_controller->SetButtonParam(button_id, param);
});
+ context_menu.addAction(tr("Invert button"), [&] {
+ const bool invert_value = !param.Get("inverted", false);
+ param.Set("inverted", invert_value);
+ button_map[button_id]->setText(ButtonToText(param));
+ emulated_controller->SetButtonParam(button_id, param);
+ });
context_menu.addAction(tr("Set threshold"), [&] {
const int button_threshold =
static_cast<int>(param.Get("threshold", 0.5f) * 100.0f);
@@ -472,6 +479,9 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
param.Set("threshold", new_threshold / 1000.0f);
emulated_controller->SetMotionParam(motion_id, param);
});
+ context_menu.addAction(tr("Calibrate sensor"), [&] {
+ emulated_controller->StartMotionCalibration();
+ });
}
context_menu.exec(motion_map[motion_id]->mapToGlobal(menu_location));
});
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp
index c287220fc..a188eef92 100644
--- a/src/yuzu/configuration/configure_input_player_widget.cpp
+++ b/src/yuzu/configuration/configure_input_player_widget.cpp
@@ -180,6 +180,10 @@ void PlayerControlPreview::ControllerUpdate(Core::HID::ControllerTriggerType typ
battery_values = controller->GetBatteryValues();
needs_redraw = true;
break;
+ case Core::HID::ControllerTriggerType::Motion:
+ motion_values = controller->GetMotions();
+ needs_redraw = true;
+ break;
default:
break;
}
@@ -313,6 +317,15 @@ void PlayerControlPreview::DrawLeftController(QPainter& p, const QPointF center)
DrawRawJoystick(p, center + QPointF(-140, 90), QPointF(0, 0));
}
+ {
+ // Draw motion cubes
+ using namespace Settings::NativeMotion;
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ Draw3dCube(p, center + QPointF(-140, 90),
+ motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f);
+ }
+
using namespace Settings::NativeButton;
// D-pad constants
@@ -435,6 +448,15 @@ void PlayerControlPreview::DrawRightController(QPainter& p, const QPointF center
DrawRawJoystick(p, QPointF(0, 0), center + QPointF(140, 90));
}
+ {
+ // Draw motion cubes
+ using namespace Settings::NativeMotion;
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ Draw3dCube(p, center + QPointF(140, 90),
+ motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f);
+ }
+
using namespace Settings::NativeButton;
// Face buttons constants
@@ -555,6 +577,17 @@ void PlayerControlPreview::DrawDualController(QPainter& p, const QPointF center)
DrawRawJoystick(p, center + QPointF(-180, 90), center + QPointF(180, 90));
}
+ {
+ // Draw motion cubes
+ using namespace Settings::NativeMotion;
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ Draw3dCube(p, center + QPointF(-180, 90),
+ motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f);
+ Draw3dCube(p, center + QPointF(180, 90),
+ motion_values[Settings::NativeMotion::MotionRight].euler, 20.0f);
+ }
+
using namespace Settings::NativeButton;
// Face buttons constants
@@ -647,6 +680,15 @@ void PlayerControlPreview::DrawHandheldController(QPainter& p, const QPointF cen
DrawRawJoystick(p, center + QPointF(-50, 0), center + QPointF(50, 0));
}
+ {
+ // Draw motion cubes
+ using namespace Settings::NativeMotion;
+ p.setPen(colors.outline);
+ p.setBrush(colors.transparent);
+ Draw3dCube(p, center + QPointF(0, -115),
+ motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f);
+ }
+
using namespace Settings::NativeButton;
// Face buttons constants
@@ -750,6 +792,15 @@ void PlayerControlPreview::DrawProController(QPainter& p, const QPointF center)
DrawRawJoystick(p, center + QPointF(-50, 105), center + QPointF(50, 105));
}
+ {
+ // Draw motion cubes
+ using namespace Settings::NativeMotion;
+ p.setPen(colors.button);
+ p.setBrush(colors.transparent);
+ Draw3dCube(p, center + QPointF(0, -100),
+ motion_values[Settings::NativeMotion::MotionLeft].euler, 15.0f);
+ }
+
using namespace Settings::NativeButton;
// Face buttons constants
@@ -2871,6 +2922,46 @@ void PlayerControlPreview::DrawArrow(QPainter& p, const QPointF center, const Di
DrawPolygon(p, arrow_symbol);
}
+// Draw motion functions
+void PlayerControlPreview::Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler,
+ float size) {
+ std::array<Common::Vec3f, 8> cube{
+ Common::Vec3f{-0.7f, -1, -0.5f},
+ {-0.7f, 1, -0.5f},
+ {0.7f, 1, -0.5f},
+ {0.7f, -1, -0.5f},
+ {-0.7f, -1, 0.5f},
+ {-0.7f, 1, 0.5f},
+ {0.7f, 1, 0.5f},
+ {0.7f, -1, 0.5f},
+ };
+
+ for (Common::Vec3f& point : cube) {
+ point.RotateFromOrigin(euler.x, euler.y, euler.z);
+ point *= size;
+ }
+
+ const std::array<QPointF, 4> front_face{
+ center + QPointF{cube[0].x, cube[0].y},
+ center + QPointF{cube[1].x, cube[1].y},
+ center + QPointF{cube[2].x, cube[2].y},
+ center + QPointF{cube[3].x, cube[3].y},
+ };
+ const std::array<QPointF, 4> back_face{
+ center + QPointF{cube[4].x, cube[4].y},
+ center + QPointF{cube[5].x, cube[5].y},
+ center + QPointF{cube[6].x, cube[6].y},
+ center + QPointF{cube[7].x, cube[7].y},
+ };
+
+ DrawPolygon(p, front_face);
+ DrawPolygon(p, back_face);
+ p.drawLine(center + QPointF{cube[0].x, cube[0].y}, center + QPointF{cube[4].x, cube[4].y});
+ p.drawLine(center + QPointF{cube[1].x, cube[1].y}, center + QPointF{cube[5].x, cube[5].y});
+ p.drawLine(center + QPointF{cube[2].x, cube[2].y}, center + QPointF{cube[6].x, cube[6].y});
+ p.drawLine(center + QPointF{cube[3].x, cube[3].y}, center + QPointF{cube[7].x, cube[7].y});
+}
+
template <size_t N>
void PlayerControlPreview::DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon) {
p.drawPolygon(polygon.data(), static_cast<int>(polygon.size()));
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h
index 267d134de..a16943c3c 100644
--- a/src/yuzu/configuration/configure_input_player_widget.h
+++ b/src/yuzu/configuration/configure_input_player_widget.h
@@ -9,6 +9,7 @@
#include "common/input.h"
#include "common/settings_input.h"
+#include "common/vector_math.h"
#include "core/hid/emulated_controller.h"
#include "core/hid/hid_types.h"
@@ -193,6 +194,9 @@ private:
void DrawSymbol(QPainter& p, QPointF center, Symbol symbol, float icon_size);
void DrawArrow(QPainter& p, QPointF center, Direction direction, float size);
+ // Draw motion functions
+ void Draw3dCube(QPainter& p, QPointF center, const Common::Vec3f& euler, float size);
+
// Draw primitive types
template <size_t N>
void DrawPolygon(QPainter& p, const std::array<QPointF, N>& polygon);
@@ -222,4 +226,5 @@ private:
Core::HID::SticksValues stick_values{};
Core::HID::TriggerValues trigger_values{};
Core::HID::BatteryValues battery_values{};
+ Core::HID::MotionState motion_values{};
};
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp
index 7e757eafd..7ac162586 100644
--- a/src/yuzu/configuration/configure_per_game.cpp
+++ b/src/yuzu/configuration/configure_per_game.cpp
@@ -48,8 +48,9 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st
audio_tab = std::make_unique<ConfigureAudio>(system_, this);
cpu_tab = std::make_unique<ConfigureCpu>(system_, this);
general_tab = std::make_unique<ConfigureGeneral>(system_, this);
- graphics_tab = std::make_unique<ConfigureGraphics>(system_, this);
graphics_advanced_tab = std::make_unique<ConfigureGraphicsAdvanced>(system_, this);
+ graphics_tab = std::make_unique<ConfigureGraphics>(
+ system_, [&]() { graphics_advanced_tab->ExposeComputeOption(); }, this);
input_tab = std::make_unique<ConfigureInputPerGame>(system_, game_config.get(), this);
system_tab = std::make_unique<ConfigureSystem>(system_, this);
diff --git a/src/yuzu/configuration/configure_per_game.h b/src/yuzu/configuration/configure_per_game.h
index 4ecc43541..85752f1fa 100644
--- a/src/yuzu/configuration/configure_per_game.h
+++ b/src/yuzu/configuration/configure_per_game.h
@@ -75,8 +75,8 @@ private:
std::unique_ptr<ConfigureAudio> audio_tab;
std::unique_ptr<ConfigureCpu> cpu_tab;
std::unique_ptr<ConfigureGeneral> general_tab;
- std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureGraphicsAdvanced> graphics_advanced_tab;
+ std::unique_ptr<ConfigureGraphics> graphics_tab;
std::unique_ptr<ConfigureInputPerGame> input_tab;
std::unique_ptr<ConfigureSystem> system_tab;
};
diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp
index 6af34f793..286ccc5cd 100644
--- a/src/yuzu/configuration/configure_system.cpp
+++ b/src/yuzu/configuration/configure_system.cpp
@@ -111,6 +111,9 @@ void ConfigureSystem::SetConfiguration() {
ui->custom_rtc_edit->setDateTime(QDateTime::fromSecsSinceEpoch(rtc_time));
ui->device_name_edit->setText(
QString::fromUtf8(Settings::values.device_name.GetValue().c_str()));
+ ui->use_unsafe_extended_memory_layout->setEnabled(enabled);
+ ui->use_unsafe_extended_memory_layout->setChecked(
+ Settings::values.use_unsafe_extended_memory_layout.GetValue());
if (Settings::IsConfiguringGlobal()) {
ui->combo_language->setCurrentIndex(Settings::values.language_index.GetValue());
@@ -160,6 +163,9 @@ void ConfigureSystem::ApplyConfiguration() {
ConfigurationShared::ApplyPerGameSetting(&Settings::values.region_index, ui->combo_region);
ConfigurationShared::ApplyPerGameSetting(&Settings::values.time_zone_index,
ui->combo_time_zone);
+ ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_unsafe_extended_memory_layout,
+ ui->use_unsafe_extended_memory_layout,
+ use_unsafe_extended_memory_layout);
if (Settings::IsConfiguringGlobal()) {
// Guard if during game and set to game-specific value
@@ -215,6 +221,10 @@ void ConfigureSystem::SetupPerGameUI() {
Settings::values.rng_seed.GetValue().has_value(),
Settings::values.rng_seed.GetValue(true).has_value(), use_rng_seed);
+ ConfigurationShared::SetColoredTristate(ui->use_unsafe_extended_memory_layout,
+ Settings::values.use_unsafe_extended_memory_layout,
+ use_unsafe_extended_memory_layout);
+
ui->custom_rtc_checkbox->setVisible(false);
ui->custom_rtc_edit->setVisible(false);
}
diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h
index ec28724a1..ce1a91601 100644
--- a/src/yuzu/configuration/configure_system.h
+++ b/src/yuzu/configuration/configure_system.h
@@ -41,6 +41,7 @@ private:
bool enabled = false;
ConfigurationShared::CheckState use_rng_seed;
+ ConfigurationShared::CheckState use_unsafe_extended_memory_layout;
Core::System& system;
};
diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui
index 9e7bc3b93..e0caecd5e 100644
--- a/src/yuzu/configuration/configure_system.ui
+++ b/src/yuzu/configuration/configure_system.ui
@@ -478,6 +478,13 @@
</property>
</widget>
</item>
+ <item row="7" column="0">
+ <widget class="QCheckBox" name="use_unsafe_extended_memory_layout">
+ <property name="text">
+ <string>Unsafe extended memory layout (8GB DRAM)</string>
+ </property>
+ </widget>
+ </item>
</layout>
</item>
</layout>
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index 519a2906f..4489f43af 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -27,6 +27,7 @@
#include "configuration/configure_input.h"
#include "configuration/configure_per_game.h"
#include "configuration/configure_tas.h"
+#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs.h"
#include "core/file_sys/vfs_real.h"
#include "core/frontend/applets/cabinet.h"
@@ -570,8 +571,8 @@ void GMainWindow::RegisterMetaTypes() {
// Cabinet Applet
qRegisterMetaType<Core::Frontend::CabinetParameters>("Core::Frontend::CabinetParameters");
- qRegisterMetaType<std::shared_ptr<Service::NFP::NfpDevice>>(
- "std::shared_ptr<Service::NFP::NfpDevice>");
+ qRegisterMetaType<std::shared_ptr<Service::NFC::NfcDevice>>(
+ "std::shared_ptr<Service::NFC::NfcDevice>");
// Controller Applet
qRegisterMetaType<Core::Frontend::ControllerParameters>("Core::Frontend::ControllerParameters");
@@ -599,7 +600,7 @@ void GMainWindow::RegisterMetaTypes() {
}
void GMainWindow::AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device) {
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device) {
cabinet_applet =
new QtAmiiboSettingsDialog(this, parameters, input_subsystem.get(), nfp_device);
SCOPE_EXIT({
@@ -4179,6 +4180,8 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
}
Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance();
+ bool all_keys_present{true};
+
if (keys.BaseDeriveNecessary()) {
Core::Crypto::PartitionDataManager pdm{vfs->OpenDirectory("", FileSys::Mode::Read)};
@@ -4203,6 +4206,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
errors += tr(" - Missing PRODINFO");
}
if (!errors.isEmpty()) {
+ all_keys_present = false;
QMessageBox::warning(
this, tr("Derivation Components Missing"),
tr("Encryption keys are missing. "
@@ -4230,11 +4234,40 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
system->GetFileSystemController().CreateFactories(*vfs);
+ if (all_keys_present && !this->CheckSystemArchiveDecryption()) {
+ LOG_WARNING(Frontend, "Mii model decryption failed");
+ QMessageBox::warning(
+ this, tr("System Archive Decryption Failed"),
+ tr("Encryption keys failed to decrypt firmware. "
+ "<br>Please follow <a href='https://yuzu-emu.org/help/quickstart/'>the yuzu "
+ "quickstart guide</a> to get all your keys, firmware and "
+ "games."));
+ }
+
if (behavior == ReinitializeKeyBehavior::Warning) {
game_list->PopulateAsync(UISettings::values.game_dirs);
}
}
+bool GMainWindow::CheckSystemArchiveDecryption() {
+ constexpr u64 MiiModelId = 0x0100000000000802;
+
+ auto bis_system = system->GetFileSystemController().GetSystemNANDContents();
+ if (!bis_system) {
+ // Not having system BIS files is not an error.
+ return true;
+ }
+
+ auto mii_nca = bis_system->GetEntry(MiiModelId, FileSys::ContentRecordType::Data);
+ if (!mii_nca) {
+ // Not having the Mii model is not an error.
+ return true;
+ }
+
+ // Return whether we are able to decrypt the RomFS of the Mii model.
+ return mii_nca->GetRomFS().get() != nullptr;
+}
+
std::optional<u64> GMainWindow::SelectRomFSDumpTarget(const FileSys::ContentProvider& installed,
u64 program_id) {
const auto dlc_entries =
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 71d78a3db..17631a2d9 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -93,9 +93,9 @@ enum class SwkbdReplyType : u32;
enum class WebExitReason : u32;
} // namespace Service::AM::Applets
-namespace Service::NFP {
-class NfpDevice;
-} // namespace Service::NFP
+namespace Service::NFC {
+class NfcDevice;
+} // namespace Service::NFC
namespace Ui {
class MainWindow;
@@ -188,7 +188,7 @@ public slots:
void OnExit();
void OnSaveConfig();
void AmiiboSettingsShowDialog(const Core::Frontend::CabinetParameters& parameters,
- std::shared_ptr<Service::NFP::NfpDevice> nfp_device);
+ std::shared_ptr<Service::NFC::NfcDevice> nfp_device);
void AmiiboSettingsRequestExit();
void ControllerSelectorReconfigureControllers(
const Core::Frontend::ControllerParameters& parameters);
@@ -393,6 +393,7 @@ private:
void LoadTranslation();
void OpenPerGameConfiguration(u64 title_id, const std::string& file_name);
bool CheckDarkMode();
+ bool CheckSystemArchiveDecryption();
QString GetTasStateDescription() const;
bool CreateShortcut(const std::string& shortcut_path, const std::string& title,
diff --git a/src/yuzu/qt_common.cpp b/src/yuzu/qt_common.cpp
new file mode 100644
index 000000000..5d0fd7674
--- /dev/null
+++ b/src/yuzu/qt_common.cpp
@@ -0,0 +1,55 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#include <QGuiApplication>
+#include <QStringLiteral>
+#include <QWindow>
+#include "common/logging/log.h"
+#include "core/frontend/emu_window.h"
+#include "yuzu/qt_common.h"
+
+#if !defined(WIN32) && !defined(__APPLE__)
+#include <qpa/qplatformnativeinterface.h>
+#endif
+
+namespace QtCommon {
+Core::Frontend::WindowSystemType GetWindowSystemType() {
+ // Determine WSI type based on Qt platform.
+ QString platform_name = QGuiApplication::platformName();
+ if (platform_name == QStringLiteral("windows"))
+ return Core::Frontend::WindowSystemType::Windows;
+ else if (platform_name == QStringLiteral("xcb"))
+ 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 {}!", platform_name.toStdString());
+ return Core::Frontend::WindowSystemType::Windows;
+} // namespace Core::Frontend::WindowSystemType
+
+Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window) {
+ Core::Frontend::EmuWindow::WindowSystemInfo wsi;
+ wsi.type = GetWindowSystemType();
+
+ // Our Win32 Qt external doesn't have the private API.
+#if defined(WIN32) || defined(__APPLE__)
+ wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
+#else
+ QPlatformNativeInterface* pni = QGuiApplication::platformNativeInterface();
+ wsi.display_connection = pni->nativeResourceForWindow("display", window);
+ if (wsi.type == Core::Frontend::WindowSystemType::Wayland)
+ wsi.render_surface = window ? pni->nativeResourceForWindow("surface", window) : nullptr;
+ else
+ wsi.render_surface = window ? reinterpret_cast<void*>(window->winId()) : nullptr;
+#endif
+ wsi.render_surface_scale = window ? static_cast<float>(window->devicePixelRatio()) : 1.0f;
+
+ return wsi;
+}
+} // namespace QtCommon
diff --git a/src/yuzu/qt_common.h b/src/yuzu/qt_common.h
new file mode 100644
index 000000000..9c63f08f3
--- /dev/null
+++ b/src/yuzu/qt_common.h
@@ -0,0 +1,15 @@
+// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
+// SPDX-License-Identifier: GPL-2.0-or-later
+
+#pragma once
+
+#include <QWindow>
+#include "core/frontend/emu_window.h"
+
+namespace QtCommon {
+
+Core::Frontend::WindowSystemType GetWindowSystemType();
+
+Core::Frontend::EmuWindow::WindowSystemInfo GetWindowSystemInfo(QWindow* window);
+
+} // namespace QtCommon
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp
index 464da3231..dc9a3d68f 100644
--- a/src/yuzu_cmd/config.cpp
+++ b/src/yuzu_cmd/config.cpp
@@ -4,18 +4,8 @@
#include <memory>
#include <optional>
#include <sstream>
-
-// Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307
-#ifdef __clang__
-#pragma clang diagnostic push
-#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
-#endif
-#include <SDL.h>
-#ifdef __clang__
-#pragma clang diagnostic pop
-#endif
-
#include <INIReader.h>
+#include <SDL.h>
#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
@@ -179,6 +169,7 @@ void Config::ReadValues() {
ReadSetting("ControlsGeneral", Settings::values.enable_raw_input);
ReadSetting("ControlsGeneral", Settings::values.enable_joycon_driver);
ReadSetting("ControlsGeneral", Settings::values.enable_procon_driver);
+ ReadSetting("ControlsGeneral", Settings::values.random_amiibo_id);
ReadSetting("ControlsGeneral", Settings::values.emulate_analog_keyboard);
ReadSetting("ControlsGeneral", Settings::values.vibration_enabled);
ReadSetting("ControlsGeneral", Settings::values.enable_accurate_vibrations);
@@ -274,7 +265,7 @@ void Config::ReadValues() {
// Core
ReadSetting("Core", Settings::values.use_multi_core);
- ReadSetting("Core", Settings::values.use_extended_memory_layout);
+ ReadSetting("Core", Settings::values.use_unsafe_extended_memory_layout);
// Cpu
ReadSetting("Cpu", Settings::values.cpu_accuracy);
@@ -300,6 +291,7 @@ void Config::ReadValues() {
// Renderer
ReadSetting("Renderer", Settings::values.renderer_backend);
+ ReadSetting("Renderer", Settings::values.async_presentation);
ReadSetting("Renderer", Settings::values.renderer_force_max_clock);
ReadSetting("Renderer", Settings::values.renderer_debug);
ReadSetting("Renderer", Settings::values.renderer_shader_feedback);
@@ -319,14 +311,14 @@ void Config::ReadValues() {
ReadSetting("Renderer", Settings::values.use_disk_shader_cache);
ReadSetting("Renderer", Settings::values.gpu_accuracy);
ReadSetting("Renderer", Settings::values.use_asynchronous_gpu_emulation);
- ReadSetting("Renderer", Settings::values.use_vsync);
+ ReadSetting("Renderer", Settings::values.vsync_mode);
ReadSetting("Renderer", Settings::values.shader_backend);
+ ReadSetting("Renderer", Settings::values.use_reactive_flushing);
ReadSetting("Renderer", Settings::values.use_asynchronous_shaders);
ReadSetting("Renderer", Settings::values.nvdec_emulation);
ReadSetting("Renderer", Settings::values.accelerate_astc);
ReadSetting("Renderer", Settings::values.async_astc);
ReadSetting("Renderer", Settings::values.use_fast_gpu_time);
- ReadSetting("Renderer", Settings::values.use_pessimistic_flushes);
ReadSetting("Renderer", Settings::values.use_vulkan_driver_pipeline_cache);
ReadSetting("Renderer", Settings::values.bg_red);
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h
index 209cfc28a..5e7c3ac04 100644
--- a/src/yuzu_cmd/default_ini.h
+++ b/src/yuzu_cmd/default_ini.h
@@ -163,9 +163,9 @@ keyboard_enabled =
# 0: Disabled, 1 (default): Enabled
use_multi_core =
-# Enable extended guest system memory layout (8GB DRAM)
+# Enable unsafe extended guest system memory layout (8GB DRAM)
# 0 (default): Disabled, 1: Enabled
-use_extended_memory_layout =
+use_unsafe_extended_memory_layout =
[Cpu]
# Adjusts various optimizations.
@@ -264,6 +264,10 @@ cpuopt_unsafe_ignore_global_monitor =
# 0: OpenGL, 1 (default): Vulkan
backend =
+# Whether to enable asynchronous presentation (Vulkan only)
+# 0 (default): Off, 1: On
+async_presentation =
+
# Enable graphics API debugging mode.
# 0 (default): Disabled, 1: Enabled
debug =
@@ -321,8 +325,14 @@ aspect_ratio =
# 0: Default, 1: 2x, 2: 4x, 3: 8x, 4: 16x
max_anisotropy =
-# Whether to enable V-Sync (caps the framerate at 60FPS) or not.
-# 0 (default): Off, 1: On
+# Whether to enable VSync or not.
+# OpenGL: Values other than 0 enable VSync
+# Vulkan: FIFO is selected if the requested mode is not supported by the driver.
+# FIFO (VSync) does not drop frames or exhibit tearing but is limited by the screen refresh rate.
+# FIFO Relaxed is similar to FIFO but allows tearing as it recovers from a slow down.
+# Mailbox can have lower latency than FIFO and does not tear but may drop frames.
+# Immediate (no synchronization) just presents whatever is available and can exhibit tearing.
+# 0: Immediate (Off), 1: Mailbox, 2 (Default): FIFO (On), 3: FIFO Relaxed
use_vsync =
# Selects the OpenGL shader backend. NV_gpu_program5 is required for GLASM. If NV_gpu_program5 is
@@ -330,6 +340,10 @@ use_vsync =
# 0: GLSL, 1 (default): GLASM, 2: SPIR-V
shader_backend =
+# Uses reactive flushing instead of predictive flushing. Allowing a more accurate syncing of memory.
+# 0: Off, 1 (default): On
+use_reactive_flushing =
+
# Whether to allow asynchronous shader building.
# 0 (default): Off, 1: On
use_asynchronous_shaders =
@@ -370,10 +384,6 @@ use_asynchronous_gpu_emulation =
# 0: Off, 1 (default): On
use_fast_gpu_time =
-# Force unmodified buffers to be flushed, which can cost performance.
-# 0: Off (default), 1: On
-use_pessimistic_flushes =
-
# Whether to use garbage collection or not for GPU caches.
# 0 (default): Off, 1: On
use_caches_gc =