diff options
Diffstat (limited to '')
178 files changed, 7676 insertions, 5101 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/settings.cpp b/src/common/settings.cpp index 84955030b..db1774c71 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,11 +224,10 @@ 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); diff --git a/src/common/settings.h b/src/common/settings.h index b77a1580a..f4eb4e3cd 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,12 +463,13 @@ 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"}; 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/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 a29c9a6f8..c7f0af71f 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp @@ -280,6 +280,10 @@ void EmulatedController::LoadVirtualGamepadParams() { virtual_stick_params[Settings::NativeAnalog::LStick].Set("axis_y", 1); virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_x", 2); virtual_stick_params[Settings::NativeAnalog::RStick].Set("axis_y", 3); + virtual_stick_params[Settings::NativeAnalog::LStick].Set("deadzone", 0.0f); + virtual_stick_params[Settings::NativeAnalog::LStick].Set("range", 1.0f); + virtual_stick_params[Settings::NativeAnalog::RStick].Set("deadzone", 0.0f); + virtual_stick_params[Settings::NativeAnalog::RStick].Set("range", 1.0f); } void EmulatedController::ReloadInput() { @@ -372,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); } @@ -547,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() { @@ -730,6 +737,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; @@ -970,16 +979,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); } @@ -1612,19 +1617,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; } @@ -1690,8 +1682,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..fa4ad7fae 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{}; }; @@ -414,8 +415,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 +528,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..b60478dbb 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" @@ -51,6 +53,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; } @@ -222,6 +238,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..482719359 100644 --- a/src/core/hid/motion_input.h +++ b/src/core/hid/motion_input.h @@ -35,6 +35,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); @@ -54,6 +55,7 @@ public: [[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; 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 a17c46121..e59de844c 100644 --- a/src/core/hle/service/am/am.cpp +++ b/src/core/hle/service/am/am.cpp @@ -1807,7 +1807,7 @@ void IApplicationFunctions::GetFriendInvitationStorageChannelEvent(HLERequestCon } void IApplicationFunctions::TryPopFromFriendInvitationStorageChannel(HLERequestContext& ctx) { - LOG_WARNING(Service_AM, "(STUBBED) called"); + LOG_DEBUG(Service_AM, "(STUBBED) called"); IPC::ResponseBuilder rb{ctx, 2}; rb.Push(AM::ResultNoDataInChannel); 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/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index 23b8be993..3e62fa4fc 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp @@ -49,12 +49,6 @@ public: }; // clang-format on RegisterHandlers(functions); - - if (impl->GetSystem() - .Initialize(device_name, in_params, handle, applet_resource_user_id) - .IsError()) { - LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!"); - } } ~IAudioOut() override { @@ -287,6 +281,14 @@ void AudOutU::OpenAudioOut(HLERequestContext& ctx) { auto audio_out = std::make_shared<IAudioOut>(system, *impl, new_session_id, device_name, in_params, handle, applet_resource_user_id); + result = audio_out->GetImpl()->GetSystem().Initialize(device_name, in_params, handle, + applet_resource_user_id); + if (result.IsError()) { + LOG_ERROR(Service_Audio, "Failed to initialize the AudioOut System!"); + IPC::ResponseBuilder rb{ctx, 2}; + rb.Push(result); + return; + } impl->sessions[new_session_id] = audio_out->GetImpl(); impl->applet_resource_user_ids[new_session_id] = applet_resource_user_id; diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp index 8abf71608..ef4aec4ea 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; 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..e5de65ce0 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{ @@ -54,9 +53,9 @@ NfpDevice::NfpDevice(Core::HID::NpadIdType npad_id_, Core::System& system_, 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 +63,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 +91,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 +106,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 +137,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 +147,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 +179,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 +197,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 +206,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 +214,129 @@ 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; + if (is_mifare) { + tag_info = { + .uuid = encrypted_tag_data.uuid.uid, + .uuid_extension = {}, + .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), + .protocol = NfcProtocol::TypeA, + .tag_type = TagType::Type4, + }; + return ResultSuccess; } - auto& settings = tag_data.settings; - - const auto& current_date = GetAmiiboDate(current_posix_time); - if (settings.write_date.raw_date != current_date.raw_date) { - settings.write_date = current_date; - UpdateSettingsCrc(); - } + // Protocol and tag type may change here + tag_info = { + .uuid = encrypted_tag_data.uuid.uid, + .uuid_extension = {}, + .uuid_length = static_cast<u8>(encrypted_tag_data.uuid.uid.size()), + .protocol = NfcProtocol::TypeA, + .tag_type = TagType::Type2, + }; - tag_data.write_counter++; + return ResultSuccess; +} - FlushWithBreak(BreakType::Normal); +Result NfcDevice::ReadMifare(std::span<const MifareReadBlockParameter> parameters, + std::span<MifareReadBlockData> read_block_data) const { + Result result = ResultSuccess; - is_data_moddified = false; + for (std::size_t i = 0; i < parameters.size(); i++) { + result = ReadMifare(parameters[i], read_block_data[i]); + if (result.IsError()) { + break; + } + } - return ResultSuccess; + 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 NfpDevice::Mount(MountTarget mount_target_) { +Result NfcDevice::SendCommandByPassThrough(const Time::Clock::TimeSpanType& timeout, + std::span<const u8> command_data, + std::span<u8> out_data) { + // Not implemented + return ResultSuccess; +} + +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 +346,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 +369,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 +384,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(current_posix_time); + 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 NfpDevice::GetCommonInfo(CommonInfo& common_info) const { +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 NfcDevice::Restore() { + 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_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 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; } const auto& settings = tag_data.settings; @@ -393,18 +510,18 @@ 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; @@ -418,22 +535,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 +567,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 +599,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 +620,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,22 +650,22 @@ 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{}; @@ -564,18 +682,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; @@ -587,7 +705,7 @@ Result NfpDevice::SetRegisterInfoPrivate(const AmiiboName& amiibo_name) { 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 +719,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 +738,7 @@ Result NfpDevice::RestoreAmiibo() { return ResultSuccess; } -Result NfpDevice::Format() { +Result NfcDevice::Format() { auto result1 = DeleteApplicationArea(); auto result2 = DeleteRegisterInfo(); @@ -635,28 +753,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 +782,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,64 +808,61 @@ 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; } @@ -756,9 +871,9 @@ Result NfpDevice::SetApplicationArea(std::span<const u8> data) { 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 +882,64 @@ 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{}; 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 +950,30 @@ 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.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 +987,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 +1006,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 +1045,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 +1054,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 +1092,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 +1111,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 +1140,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 +1156,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 +1165,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 +1189,14 @@ 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::RemoveVersionByte(u64 application_id) const { + return application_id & ~(0xfULL << NFP::application_id_version_offset); } -void NfpDevice::UpdateSettingsCrc() { +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 +1207,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 +1232,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..654eda98e --- /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 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{}; + s64 current_posix_time{}; + 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(®ister_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/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/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/dedicated_room/yuzu_room.cpp b/src/dedicated_room/yuzu_room.cpp index 359891883..d707dabe2 100644 --- a/src/dedicated_room/yuzu_room.cpp +++ b/src/dedicated_room/yuzu_room.cpp @@ -49,6 +49,7 @@ static void PrintHelp(const char* argv0) { " [options] <filename>\n" "--room-name The name of the room\n" "--room-description The room description\n" + "--bind-address The bind address for the room\n" "--port The port used for the room\n" "--max_members The maximum number of players for this room\n" "--password The password for the room\n" @@ -195,6 +196,7 @@ int main(int argc, char** argv) { std::string web_api_url; std::string ban_list_file; std::string log_file = "yuzu-room.log"; + std::string bind_address; u64 preferred_game_id = 0; u32 port = Network::DefaultRoomPort; u32 max_members = 16; @@ -203,6 +205,7 @@ int main(int argc, char** argv) { static struct option long_options[] = { {"room-name", required_argument, 0, 'n'}, {"room-description", required_argument, 0, 'd'}, + {"bind-address", required_argument, 0, 's'}, {"port", required_argument, 0, 'p'}, {"max_members", required_argument, 0, 'm'}, {"password", required_argument, 0, 'w'}, @@ -222,7 +225,8 @@ int main(int argc, char** argv) { InitializeLogging(log_file); while (optind < argc) { - int arg = getopt_long(argc, argv, "n:d:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index); + int arg = + getopt_long(argc, argv, "n:d:s:p:m:w:g:u:t:a:i:l:hv", long_options, &option_index); if (arg != -1) { switch (static_cast<char>(arg)) { case 'n': @@ -231,6 +235,9 @@ int main(int argc, char** argv) { case 'd': room_description.assign(optarg); break; + case 's': + bind_address.assign(optarg); + break; case 'p': port = strtoul(optarg, &endarg, 0); break; @@ -295,6 +302,9 @@ int main(int argc, char** argv) { PrintHelp(argv[0]); return -1; } + if (bind_address.empty()) { + LOG_INFO(Network, "Bind address is empty: defaulting to 0.0.0.0"); + } if (port > UINT16_MAX) { LOG_ERROR(Network, "Port needs to be in the range 0 - 65535!"); PrintHelp(argv[0]); @@ -358,8 +368,8 @@ int main(int argc, char** argv) { if (auto room = network.GetRoom().lock()) { AnnounceMultiplayerRoom::GameInfo preferred_game_info{.name = preferred_game, .id = preferred_game_id}; - if (!room->Create(room_name, room_description, "", port, password, max_members, username, - preferred_game_info, std::move(verify_backend), ban_list, + if (!room->Create(room_name, room_description, bind_address, port, password, max_members, + username, preferred_game_info, std::move(verify_backend), ban_list, enable_yuzu_mods)) { LOG_INFO(Network, "Failed to create room: "); return -1; 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..fee510f7b 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 @@ -473,7 +473,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/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..6624919a4 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{}; - - 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{}; +#include "video_core/buffer_cache/buffer_cache_base.h" - 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,35 +85,78 @@ 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); - }); + memory_tracker.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); + } } 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; + }; + memory_tracker.MarkRegionAsPreflushable(cpu_addr_start_aligned, + cpu_addr_end_aligned - cpu_addr_start_aligned); + area->preemtive = !IsRegionGpuModified(cpu_addr, size); + return area; } template <class P> void BufferCache<P>::DownloadMemory(VAddr cpu_addr, u64 size) { + WaitOnAsyncFlushes(cpu_addr, size); ForEachBufferInRange(cpu_addr, size, [&](BufferId, Buffer& buffer) { DownloadBufferMemory(buffer, cpu_addr, size); }); } template <class P> +void BufferCache<P>::WaitOnAsyncFlushes(VAddr cpu_addr, u64 size) { + bool must_wait = false; + ForEachInOverlapCounter(async_downloads, cpu_addr, size, + [&](VAddr, VAddr, int) { must_wait = true; }); + bool must_release = false; + ForEachInRangeSet(pending_ranges, cpu_addr, size, [&](VAddr, VAddr) { must_release = true; }); + if (must_release) { + std::function<void()> tmp([]() {}); + rasterizer.SignalFence(std::move(tmp)); + } + if (must_wait || must_release) { + rasterizer.ReleaseFences(); + } +} + +template <class P> void BufferCache<P>::ClearDownload(IntervalType subtract_interval) { + RemoveEachInOverlapCounter(async_downloads, subtract_interval, -1024); uncommitted_ranges.subtract(subtract_interval); + pending_ranges.subtract(subtract_interval); for (auto& interval_set : committed_ranges) { interval_set.subtract(subtract_interval); } @@ -591,6 +176,7 @@ bool BufferCache<P>::DMACopy(GPUVAddr src_address, GPUVAddr dest_address, u64 am } const IntervalType subtract_interval{*cpu_dest_address, *cpu_dest_address + amount}; + WaitOnAsyncFlushes(*cpu_src_address, static_cast<u32>(amount)); ClearDownload(subtract_interval); BufferId buffer_a; @@ -616,10 +202,11 @@ 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); + pending_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 +215,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 +453,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 +463,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 +471,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,12 +483,15 @@ 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; + pending_ranges.clear(); auto it = committed_ranges.begin(); while (it != committed_ranges.end()) { auto& current_intervals = *it; @@ -926,11 +513,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 +532,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 +680,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 +697,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 +780,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 +1003,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 +1068,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,17 +1223,16 @@ 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); + memory_tracker.MarkRegionAsGpuModified(cpu_addr, size); + + if (memory_tracker.IsRegionCpuModified(cpu_addr, size)) { + SynchronizeBuffer(slot_buffers[buffer_id], 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); + pending_ranges.add(base_interval); } template <class P> @@ -1609,7 +1240,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 +1269,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 +1297,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 +1321,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 +1346,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 +1374,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 +1394,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 +1402,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 +1421,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 +1476,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 +1524,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 +1565,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 +1623,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 +1639,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])); @@ -2011,7 +1692,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..0445ec47f --- /dev/null +++ b/src/video_core/buffer_cache/buffer_cache_base.h @@ -0,0 +1,579 @@ +// 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 WaitOnAsyncFlushes(VAddr cpu_addr, u64 size); + + 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; + IntervalSet pending_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/fermi_2d.cpp b/src/video_core/engines/fermi_2d.cpp index a126c359c..02e161270 100644 --- a/src/video_core/engines/fermi_2d.cpp +++ b/src/video_core/engines/fermi_2d.cpp @@ -77,6 +77,14 @@ void Fermi2D::Blit() { const auto bytes_per_pixel = BytesPerBlock(PixelFormatFromRenderTargetFormat(src.format)); const bool delegate_to_gpu = src.width > 512 && src.height > 512 && bytes_per_pixel <= 8 && src.format != regs.dst.format; + + auto srcX = args.src_x0; + auto srcY = args.src_y0; + if (args.sample_mode.origin == Origin::Corner) { + srcX -= (args.du_dx >> 33) << 32; + srcY -= (args.dv_dy >> 33) << 32; + } + Config config{ .operation = regs.operation, .filter = args.sample_mode.filter, @@ -86,10 +94,10 @@ void Fermi2D::Blit() { .dst_y0 = args.dst_y0, .dst_x1 = args.dst_x0 + args.dst_width, .dst_y1 = args.dst_y0 + args.dst_height, - .src_x0 = static_cast<s32>(args.src_x0 >> 32), - .src_y0 = static_cast<s32>(args.src_y0 >> 32), - .src_x1 = static_cast<s32>((args.du_dx * args.dst_width + args.src_x0) >> 32), - .src_y1 = static_cast<s32>((args.dv_dy * args.dst_height + args.src_y0) >> 32), + .src_x0 = static_cast<s32>(srcX >> 32), + .src_y0 = static_cast<s32>(srcY >> 32), + .src_x1 = static_cast<s32>((srcX + args.du_dx * args.dst_width) >> 32), + .src_y1 = static_cast<s32>((srcY + args.dv_dy * args.dst_height) >> 32), }; const auto need_align_to_pitch = diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 614d61db4..2f986097f 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp @@ -4,6 +4,7 @@ #include <cstring> #include <optional> #include "common/assert.h" +#include "common/bit_util.h" #include "common/scope_exit.h" #include "common/settings.h" #include "core/core.h" @@ -222,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) { @@ -259,12 +263,13 @@ u32 Maxwell3D::GetMaxCurrentVertices() { size_t Maxwell3D::EstimateIndexBufferSize() { GPUVAddr start_address = regs.index_buffer.StartAddress(); GPUVAddr end_address = regs.index_buffer.EndAddress(); - static constexpr std::array<size_t, 4> max_sizes = { - std::numeric_limits<u8>::max(), std::numeric_limits<u16>::max(), - std::numeric_limits<u32>::max(), std::numeric_limits<u32>::max()}; + static constexpr std::array<size_t, 3> max_sizes = {std::numeric_limits<u8>::max(), + std::numeric_limits<u16>::max(), + std::numeric_limits<u32>::max()}; const size_t byte_size = regs.index_buffer.FormatSizeInBytes(); + const size_t log2_byte_size = Common::Log2Ceil64(byte_size); return std::min<size_t>( - memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[byte_size]) / + memory_manager.GetMemoryLayoutSize(start_address, byte_size * max_sizes[log2_byte_size]) / byte_size, static_cast<size_t>(end_address - start_address)); } 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/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/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), ×tamp_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..052456f61 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp @@ -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/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..510602e8e 100644 --- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp @@ -314,8 +314,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..879f1ed94 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" @@ -75,7 +76,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(); @@ -142,6 +145,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 +155,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_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_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 985cc3203..a318d643e 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -696,6 +696,13 @@ 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) { + 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()}; 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..628e1376f 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp @@ -172,7 +172,8 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra buffer_cache(*this, cpu_memory_, buffer_cache_runtime), pipeline_cache(*this, device, scheduler, descriptor_pool, update_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 +502,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 +614,7 @@ void RasterizerVulkan::SignalSyncPoint(u32 value) { } void RasterizerVulkan::SignalReference() { - fence_manager.SignalOrdering(); + fence_manager.SignalReference(); } void RasterizerVulkan::ReleaseFences() { @@ -630,7 +647,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() { @@ -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..9bd422850 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; 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 85fdce6e5..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) { @@ -65,6 +90,18 @@ VkExtent2D ChooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities, u32 wi return extent; } +VkCompositeAlphaFlagBitsKHR ChooseAlphaFlags(const VkSurfaceCapabilitiesKHR& capabilities) { + if (capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) { + return VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + } else if (capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR) { + return VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; + } else { + LOG_ERROR(Render_Vulkan, "Unknown composite alpha flags value {:#x}", + capabilities.supportedCompositeAlpha); + return VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; + } +} + } // Anonymous namespace Swapchain::Swapchain(VkSurfaceKHR surface_, const Device& device_, Scheduler& scheduler_, @@ -87,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); @@ -115,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) { @@ -131,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; @@ -153,10 +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 VkSurfaceFormatKHR surface_format{ChooseSwapSurfaceFormat(formats)}; - present_mode = ChooseSwapPresentMode(present_modes); + const VkCompositeAlphaFlagBitsKHR alpha_flags{ChooseAlphaFlags(capabilities)}; + 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. @@ -180,12 +226,12 @@ 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, .preTransform = capabilities.currentTransform, - .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, + .compositeAlpha = alpha_flags, .presentMode = present_mode, .clipped = VK_FALSE, .oldSwapchain = nullptr, @@ -217,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()); @@ -228,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..99dd1260a 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" @@ -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) diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h index d5ee23f8d..6f360177a 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 @@ -141,6 +141,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 +265,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; }; @@ -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..1c1a7020b 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 = 0x10000; + static constexpr size_t PAYLOAD_SIZE = FRAME_PAYLOAD_SIZE * FRAMES_IN_FLIGHT; + public: explicit UpdateDescriptorQueue(const Device& device_, Scheduler& scheduler_); ~UpdateDescriptorQueue(); @@ -73,9 +79,11 @@ 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; }; } // 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..e1198dcf8 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; @@ -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/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp index 6f288b3f8..6ffca2af2 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) { 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_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..a49d12266 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" @@ -497,7 +498,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 +693,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 +710,20 @@ 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.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); @@ -1161,7 +1166,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 +1318,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 +1356,22 @@ 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.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_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..76e5b7499 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp @@ -4,20 +4,76 @@ // Include this early to include Vulkan headers how we want to #include "video_core/vulkan_common/vulkan_wrapper.h" +#include <algorithm> +#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" +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_, QWidget* parent) : QWidget(parent), ui{std::make_unique<Ui::ConfigureGraphics>()}, system{system_} { vulkan_device = Settings::values.vulkan_device.GetValue(); @@ -39,13 +95,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 +129,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 +195,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 +269,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 +294,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 +369,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 +486,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 +506,27 @@ 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 = //< needed to view present modes for a device + 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); } } catch (const Vulkan::vk::Exception& exception) { LOG_ERROR(Frontend, "Failed to enumerate devices with error: {}", exception.what()); @@ -465,4 +619,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..901f604a5 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h @@ -5,9 +5,21 @@ #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; @@ -35,6 +47,7 @@ private: void changeEvent(QEvent* event) override; void RetranslateUI(); + void PopulateVSyncModeSelection(); void UpdateBackgroundColorButton(QColor color); void UpdateAPILayout(); void UpdateDeviceSelection(int device); @@ -43,6 +56,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,6 +75,10 @@ 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{}; 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..627ed8b17 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -21,18 +21,19 @@ 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->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()); @@ -54,12 +55,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,8 +71,6 @@ 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); @@ -90,15 +92,14 @@ 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( @@ -107,10 +108,13 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { 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,9 +122,6 @@ 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); diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index bf1b04749..ae3c10946 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -36,12 +36,13 @@ 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; const Core::System& system; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index a7dbdc18c..9d8cbea09 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,22 +70,19 @@ </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> - </property> + <widget class="QCheckBox" name="async_present"> <property name="text"> - <string>Force maximum clocks (Vulkan only)</string> + <string>Enable asynchronous presentation (Vulkan only)</string> </property> </widget> </item> <item> - <widget class="QCheckBox" name="use_vsync"> + <widget class="QCheckBox" name="renderer_force_max_clock"> <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>Runs work in the background while waiting for graphics commands to keep the GPU from lowering its clock speed.</string> </property> <property name="text"> - <string>Use VSync</string> + <string>Force maximum clocks (Vulkan only)</string> </property> </widget> </item> @@ -100,39 +97,39 @@ </widget> </item> <item> - <widget class="QCheckBox" name="use_asynchronous_shaders"> + <widget class="QCheckBox" name="use_reactive_flushing"> <property name="toolTip"> - <string>Enables asynchronous shader compilation, which may reduce shader 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>Use asynchronous shader building (Hack)</string> + <string>Enable Reactive Flushing</string> </property> </widget> </item> <item> - <widget class="QCheckBox" name="use_fast_gpu_time"> + <widget class="QCheckBox" name="use_asynchronous_shaders"> <property name="toolTip"> - <string>Enables Fast GPU Time. This option will force most games to run at their highest native resolution.</string> + <string>Enables asynchronous shader compilation, which may reduce shader stutter. This feature is experimental.</string> </property> <property name="text"> - <string>Use Fast GPU Time (Hack)</string> + <string>Use asynchronous shader building (Hack)</string> </property> </widget> </item> <item> - <widget class="QCheckBox" name="use_pessimistic_flushes"> + <widget class="QCheckBox" name="use_fast_gpu_time"> <property name="toolTip"> - <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</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 pessimistic buffer flushes (Hack)</string> + <string>Use Fast GPU Time (Hack)</string> </property> </widget> </item> <item> <widget class="QCheckBox" name="use_vulkan_driver_pipeline_cache"> <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>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 Vulkan pipeline cache</string> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 50b62293e..561a08dc5 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); diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index c287220fc..fe1ee2289 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, -5), + motion_values[Settings::NativeMotion::MotionLeft].euler, 20.0f); + Draw3dCube(p, center + QPointF(180, -5), + 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{-1, -1, -1}, + {-1, 1, -1}, + {1, 1, -1}, + {1, -1, -1}, + {-1, -1, 1}, + {-1, 1, 1}, + {1, 1, 1}, + {1, -1, 1}, + }; + + 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_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 b79409a68..d932e33a7 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({ @@ -4171,6 +4172,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)}; @@ -4195,6 +4198,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. " @@ -4222,11 +4226,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 8b5c1d747..7b23f2a59 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); @@ -392,6 +392,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..5ac9fe310 --- /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" + +#ifdef __linux__ +#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..abe7092fc 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" @@ -274,7 +264,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 +290,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 +310,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 = |