diff options
Diffstat (limited to 'src')
49 files changed, 1151 insertions, 496 deletions
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index 6e9812e6e..0e06468da 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -11,7 +11,6 @@ | |||
| 11 | 11 | ||
| 12 | namespace Core::HID { | 12 | namespace Core::HID { |
| 13 | constexpr s32 HID_JOYSTICK_MAX = 0x7fff; | 13 | constexpr s32 HID_JOYSTICK_MAX = 0x7fff; |
| 14 | constexpr s32 HID_JOYSTICK_MIN = 0x7ffe; | ||
| 15 | constexpr s32 HID_TRIGGER_MAX = 0x7fff; | 14 | constexpr s32 HID_TRIGGER_MAX = 0x7fff; |
| 16 | // Use a common UUID for TAS and Virtual Gamepad | 15 | // Use a common UUID for TAS and Virtual Gamepad |
| 17 | constexpr Common::UUID TAS_UUID = | 16 | constexpr Common::UUID TAS_UUID = |
| @@ -864,16 +863,9 @@ void EmulatedController::SetStick(const Common::Input::CallbackStatus& callback, | |||
| 864 | return; | 863 | return; |
| 865 | } | 864 | } |
| 866 | 865 | ||
| 867 | const auto FloatToShort = [](float a) { | ||
| 868 | if (a > 0) { | ||
| 869 | return static_cast<s32>(a * HID_JOYSTICK_MAX); | ||
| 870 | } | ||
| 871 | return static_cast<s32>(a * HID_JOYSTICK_MIN); | ||
| 872 | }; | ||
| 873 | |||
| 874 | const AnalogStickState stick{ | 866 | const AnalogStickState stick{ |
| 875 | .x = FloatToShort(controller.stick_values[index].x.value), | 867 | .x = static_cast<s32>(controller.stick_values[index].x.value * HID_JOYSTICK_MAX), |
| 876 | .y = FloatToShort(controller.stick_values[index].y.value), | 868 | .y = static_cast<s32>(controller.stick_values[index].y.value * HID_JOYSTICK_MAX), |
| 877 | }; | 869 | }; |
| 878 | 870 | ||
| 879 | switch (index) { | 871 | switch (index) { |
diff --git a/src/core/hle/kernel/k_code_memory.cpp b/src/core/hle/kernel/k_code_memory.cpp index d9da1e600..884eba001 100644 --- a/src/core/hle/kernel/k_code_memory.cpp +++ b/src/core/hle/kernel/k_code_memory.cpp | |||
| @@ -74,7 +74,7 @@ Result KCodeMemory::Map(VAddr address, size_t size) { | |||
| 74 | R_UNLESS(!m_is_mapped, ResultInvalidState); | 74 | R_UNLESS(!m_is_mapped, ResultInvalidState); |
| 75 | 75 | ||
| 76 | // Map the memory. | 76 | // Map the memory. |
| 77 | R_TRY(kernel.CurrentProcess()->PageTable().MapPages( | 77 | R_TRY(kernel.CurrentProcess()->PageTable().MapPageGroup( |
| 78 | address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite)); | 78 | address, *m_page_group, KMemoryState::CodeOut, KMemoryPermission::UserReadWrite)); |
| 79 | 79 | ||
| 80 | // Mark ourselves as mapped. | 80 | // Mark ourselves as mapped. |
| @@ -91,8 +91,8 @@ Result KCodeMemory::Unmap(VAddr address, size_t size) { | |||
| 91 | KScopedLightLock lk(m_lock); | 91 | KScopedLightLock lk(m_lock); |
| 92 | 92 | ||
| 93 | // Unmap the memory. | 93 | // Unmap the memory. |
| 94 | R_TRY(kernel.CurrentProcess()->PageTable().UnmapPages(address, *m_page_group, | 94 | R_TRY(kernel.CurrentProcess()->PageTable().UnmapPageGroup(address, *m_page_group, |
| 95 | KMemoryState::CodeOut)); | 95 | KMemoryState::CodeOut)); |
| 96 | 96 | ||
| 97 | // Mark ourselves as unmapped. | 97 | // Mark ourselves as unmapped. |
| 98 | m_is_mapped = false; | 98 | m_is_mapped = false; |
| @@ -125,8 +125,8 @@ Result KCodeMemory::MapToOwner(VAddr address, size_t size, Svc::MemoryPermission | |||
| 125 | } | 125 | } |
| 126 | 126 | ||
| 127 | // Map the memory. | 127 | // Map the memory. |
| 128 | R_TRY( | 128 | R_TRY(m_owner->PageTable().MapPageGroup(address, *m_page_group, KMemoryState::GeneratedCode, |
| 129 | m_owner->PageTable().MapPages(address, *m_page_group, KMemoryState::GeneratedCode, k_perm)); | 129 | k_perm)); |
| 130 | 130 | ||
| 131 | // Mark ourselves as mapped. | 131 | // Mark ourselves as mapped. |
| 132 | m_is_owner_mapped = true; | 132 | m_is_owner_mapped = true; |
| @@ -142,7 +142,7 @@ Result KCodeMemory::UnmapFromOwner(VAddr address, size_t size) { | |||
| 142 | KScopedLightLock lk(m_lock); | 142 | KScopedLightLock lk(m_lock); |
| 143 | 143 | ||
| 144 | // Unmap the memory. | 144 | // Unmap the memory. |
| 145 | R_TRY(m_owner->PageTable().UnmapPages(address, *m_page_group, KMemoryState::GeneratedCode)); | 145 | R_TRY(m_owner->PageTable().UnmapPageGroup(address, *m_page_group, KMemoryState::GeneratedCode)); |
| 146 | 146 | ||
| 147 | // Mark ourselves as unmapped. | 147 | // Mark ourselves as unmapped. |
| 148 | m_is_owner_mapped = false; | 148 | m_is_owner_mapped = false; |
diff --git a/src/core/hle/kernel/k_condition_variable.cpp b/src/core/hle/kernel/k_condition_variable.cpp index 124149697..0c6b20db3 100644 --- a/src/core/hle/kernel/k_condition_variable.cpp +++ b/src/core/hle/kernel/k_condition_variable.cpp | |||
| @@ -171,7 +171,7 @@ Result KConditionVariable::WaitForAddress(Handle handle, VAddr addr, u32 value) | |||
| 171 | R_UNLESS(owner_thread != nullptr, ResultInvalidHandle); | 171 | R_UNLESS(owner_thread != nullptr, ResultInvalidHandle); |
| 172 | 172 | ||
| 173 | // Update the lock. | 173 | // Update the lock. |
| 174 | cur_thread->SetAddressKey(addr, value); | 174 | cur_thread->SetUserAddressKey(addr, value); |
| 175 | owner_thread->AddWaiter(cur_thread); | 175 | owner_thread->AddWaiter(cur_thread); |
| 176 | 176 | ||
| 177 | // Begin waiting. | 177 | // Begin waiting. |
diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp index 43185320d..d791acbe3 100644 --- a/src/core/hle/kernel/k_light_lock.cpp +++ b/src/core/hle/kernel/k_light_lock.cpp | |||
| @@ -68,7 +68,7 @@ bool KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { | |||
| 68 | 68 | ||
| 69 | // Add the current thread as a waiter on the owner. | 69 | // Add the current thread as a waiter on the owner. |
| 70 | KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL); | 70 | KThread* owner_thread = reinterpret_cast<KThread*>(_owner & ~1ULL); |
| 71 | cur_thread->SetAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag))); | 71 | cur_thread->SetKernelAddressKey(reinterpret_cast<uintptr_t>(std::addressof(tag))); |
| 72 | owner_thread->AddWaiter(cur_thread); | 72 | owner_thread->AddWaiter(cur_thread); |
| 73 | 73 | ||
| 74 | // Begin waiting to hold the lock. | 74 | // Begin waiting to hold the lock. |
diff --git a/src/core/hle/kernel/k_memory_layout.h b/src/core/hle/kernel/k_memory_layout.h index fd6e1d3e6..17fa1a6ed 100644 --- a/src/core/hle/kernel/k_memory_layout.h +++ b/src/core/hle/kernel/k_memory_layout.h | |||
| @@ -67,9 +67,9 @@ constexpr size_t KernelPageBufferAdditionalSize = 0x33C000; | |||
| 67 | constexpr std::size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize + | 67 | constexpr std::size_t KernelResourceSize = KernelPageTableHeapSize + KernelInitialPageHeapSize + |
| 68 | KernelSlabHeapSize + KernelPageBufferHeapSize; | 68 | KernelSlabHeapSize + KernelPageBufferHeapSize; |
| 69 | 69 | ||
| 70 | constexpr bool IsKernelAddressKey(VAddr key) { | 70 | //! NB: Use KThread::GetAddressKeyIsKernel(). |
| 71 | return KernelVirtualAddressSpaceBase <= key && key <= KernelVirtualAddressSpaceLast; | 71 | //! See explanation for deviation of GetAddressKey. |
| 72 | } | 72 | bool IsKernelAddressKey(VAddr key) = delete; |
| 73 | 73 | ||
| 74 | constexpr bool IsKernelAddress(VAddr address) { | 74 | constexpr bool IsKernelAddress(VAddr address) { |
| 75 | return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd; | 75 | return KernelVirtualAddressSpaceBase <= address && address < KernelVirtualAddressSpaceEnd; |
diff --git a/src/core/hle/kernel/k_page_table.cpp b/src/core/hle/kernel/k_page_table.cpp index 9c7ac22dc..2e13d5d0d 100644 --- a/src/core/hle/kernel/k_page_table.cpp +++ b/src/core/hle/kernel/k_page_table.cpp | |||
| @@ -435,6 +435,9 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si | |||
| 435 | KPageGroup pg{m_kernel, m_block_info_manager}; | 435 | KPageGroup pg{m_kernel, m_block_info_manager}; |
| 436 | AddRegionToPages(src_address, num_pages, pg); | 436 | AddRegionToPages(src_address, num_pages, pg); |
| 437 | 437 | ||
| 438 | // We're going to perform an update, so create a helper. | ||
| 439 | KScopedPageTableUpdater updater(this); | ||
| 440 | |||
| 438 | // Reprotect the source as kernel-read/not mapped. | 441 | // Reprotect the source as kernel-read/not mapped. |
| 439 | const auto new_perm = static_cast<KMemoryPermission>(KMemoryPermission::KernelRead | | 442 | const auto new_perm = static_cast<KMemoryPermission>(KMemoryPermission::KernelRead | |
| 440 | KMemoryPermission::NotMapped); | 443 | KMemoryPermission::NotMapped); |
| @@ -447,7 +450,10 @@ Result KPageTable::MapCodeMemory(VAddr dst_address, VAddr src_address, size_t si | |||
| 447 | }); | 450 | }); |
| 448 | 451 | ||
| 449 | // Map the alias pages. | 452 | // Map the alias pages. |
| 450 | R_TRY(MapPages(dst_address, pg, new_perm)); | 453 | const KPageProperties dst_properties = {new_perm, false, false, |
| 454 | DisableMergeAttribute::DisableHead}; | ||
| 455 | R_TRY( | ||
| 456 | this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_properties, false)); | ||
| 451 | 457 | ||
| 452 | // We successfully mapped the alias pages, so we don't need to unprotect the src pages on | 458 | // We successfully mapped the alias pages, so we don't need to unprotect the src pages on |
| 453 | // failure. | 459 | // failure. |
| @@ -1881,7 +1887,8 @@ Result KPageTable::UnmapPhysicalMemory(VAddr address, size_t size) { | |||
| 1881 | R_SUCCEED(); | 1887 | R_SUCCEED(); |
| 1882 | } | 1888 | } |
| 1883 | 1889 | ||
| 1884 | Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) { | 1890 | Result KPageTable::MapMemory(KProcessAddress dst_address, KProcessAddress src_address, |
| 1891 | size_t size) { | ||
| 1885 | // Lock the table. | 1892 | // Lock the table. |
| 1886 | KScopedLightLock lk(m_general_lock); | 1893 | KScopedLightLock lk(m_general_lock); |
| 1887 | 1894 | ||
| @@ -1902,53 +1909,73 @@ Result KPageTable::MapMemory(VAddr dst_address, VAddr src_address, size_t size) | |||
| 1902 | KMemoryAttribute::None)); | 1909 | KMemoryAttribute::None)); |
| 1903 | 1910 | ||
| 1904 | // Create an update allocator for the source. | 1911 | // Create an update allocator for the source. |
| 1905 | Result src_allocator_result{ResultSuccess}; | 1912 | Result src_allocator_result; |
| 1906 | KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), | 1913 | KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), |
| 1907 | m_memory_block_slab_manager, | 1914 | m_memory_block_slab_manager, |
| 1908 | num_src_allocator_blocks); | 1915 | num_src_allocator_blocks); |
| 1909 | R_TRY(src_allocator_result); | 1916 | R_TRY(src_allocator_result); |
| 1910 | 1917 | ||
| 1911 | // Create an update allocator for the destination. | 1918 | // Create an update allocator for the destination. |
| 1912 | Result dst_allocator_result{ResultSuccess}; | 1919 | Result dst_allocator_result; |
| 1913 | KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), | 1920 | KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), |
| 1914 | m_memory_block_slab_manager, | 1921 | m_memory_block_slab_manager, |
| 1915 | num_dst_allocator_blocks); | 1922 | num_dst_allocator_blocks); |
| 1916 | R_TRY(dst_allocator_result); | 1923 | R_TRY(dst_allocator_result); |
| 1917 | 1924 | ||
| 1918 | // Map the memory. | 1925 | // Map the memory. |
| 1919 | KPageGroup page_linked_list{m_kernel, m_block_info_manager}; | ||
| 1920 | const size_t num_pages{size / PageSize}; | ||
| 1921 | const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>( | ||
| 1922 | KMemoryPermission::KernelRead | KMemoryPermission::NotMapped); | ||
| 1923 | const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked; | ||
| 1924 | |||
| 1925 | AddRegionToPages(src_address, num_pages, page_linked_list); | ||
| 1926 | { | 1926 | { |
| 1927 | // Determine the number of pages being operated on. | ||
| 1928 | const size_t num_pages = size / PageSize; | ||
| 1929 | |||
| 1930 | // Create page groups for the memory being unmapped. | ||
| 1931 | KPageGroup pg{m_kernel, m_block_info_manager}; | ||
| 1932 | |||
| 1933 | // Create the page group representing the source. | ||
| 1934 | R_TRY(this->MakePageGroup(pg, src_address, num_pages)); | ||
| 1935 | |||
| 1936 | // We're going to perform an update, so create a helper. | ||
| 1937 | KScopedPageTableUpdater updater(this); | ||
| 1938 | |||
| 1927 | // Reprotect the source as kernel-read/not mapped. | 1939 | // Reprotect the source as kernel-read/not mapped. |
| 1928 | auto block_guard = detail::ScopeExit([&] { | 1940 | const KMemoryPermission new_src_perm = static_cast<KMemoryPermission>( |
| 1929 | Operate(src_address, num_pages, KMemoryPermission::UserReadWrite, | 1941 | KMemoryPermission::KernelRead | KMemoryPermission::NotMapped); |
| 1930 | OperationType::ChangePermissions); | 1942 | const KMemoryAttribute new_src_attr = KMemoryAttribute::Locked; |
| 1931 | }); | 1943 | const KPageProperties src_properties = {new_src_perm, false, false, |
| 1932 | R_TRY(Operate(src_address, num_pages, new_src_perm, OperationType::ChangePermissions)); | 1944 | DisableMergeAttribute::DisableHeadBodyTail}; |
| 1933 | R_TRY(MapPages(dst_address, page_linked_list, KMemoryPermission::UserReadWrite)); | 1945 | R_TRY(this->Operate(src_address, num_pages, src_properties.perm, |
| 1946 | OperationType::ChangePermissions)); | ||
| 1934 | 1947 | ||
| 1935 | block_guard.Cancel(); | 1948 | // Ensure that we unprotect the source pages on failure. |
| 1936 | } | 1949 | ON_RESULT_FAILURE { |
| 1950 | const KPageProperties unprotect_properties = { | ||
| 1951 | KMemoryPermission::UserReadWrite, false, false, | ||
| 1952 | DisableMergeAttribute::EnableHeadBodyTail}; | ||
| 1953 | ASSERT(this->Operate(src_address, num_pages, unprotect_properties.perm, | ||
| 1954 | OperationType::ChangePermissions) == ResultSuccess); | ||
| 1955 | }; | ||
| 1937 | 1956 | ||
| 1938 | // Apply the memory block updates. | 1957 | // Map the alias pages. |
| 1939 | m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, | 1958 | const KPageProperties dst_map_properties = {KMemoryPermission::UserReadWrite, false, false, |
| 1940 | new_src_perm, new_src_attr, | 1959 | DisableMergeAttribute::DisableHead}; |
| 1941 | KMemoryBlockDisableMergeAttribute::Locked, | 1960 | R_TRY(this->MapPageGroupImpl(updater.GetPageList(), dst_address, pg, dst_map_properties, |
| 1942 | KMemoryBlockDisableMergeAttribute::None); | 1961 | false)); |
| 1943 | m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, | 1962 | |
| 1944 | KMemoryState::Stack, KMemoryPermission::UserReadWrite, | 1963 | // Apply the memory block updates. |
| 1945 | KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, | 1964 | m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, |
| 1946 | KMemoryBlockDisableMergeAttribute::None); | 1965 | src_state, new_src_perm, new_src_attr, |
| 1966 | KMemoryBlockDisableMergeAttribute::Locked, | ||
| 1967 | KMemoryBlockDisableMergeAttribute::None); | ||
| 1968 | m_memory_block_manager.Update( | ||
| 1969 | std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::Stack, | ||
| 1970 | KMemoryPermission::UserReadWrite, KMemoryAttribute::None, | ||
| 1971 | KMemoryBlockDisableMergeAttribute::Normal, KMemoryBlockDisableMergeAttribute::None); | ||
| 1972 | } | ||
| 1947 | 1973 | ||
| 1948 | R_SUCCEED(); | 1974 | R_SUCCEED(); |
| 1949 | } | 1975 | } |
| 1950 | 1976 | ||
| 1951 | Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size) { | 1977 | Result KPageTable::UnmapMemory(KProcessAddress dst_address, KProcessAddress src_address, |
| 1978 | size_t size) { | ||
| 1952 | // Lock the table. | 1979 | // Lock the table. |
| 1953 | KScopedLightLock lk(m_general_lock); | 1980 | KScopedLightLock lk(m_general_lock); |
| 1954 | 1981 | ||
| @@ -1970,108 +1997,208 @@ Result KPageTable::UnmapMemory(VAddr dst_address, VAddr src_address, size_t size | |||
| 1970 | KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); | 1997 | KMemoryPermission::None, KMemoryAttribute::All, KMemoryAttribute::None)); |
| 1971 | 1998 | ||
| 1972 | // Create an update allocator for the source. | 1999 | // Create an update allocator for the source. |
| 1973 | Result src_allocator_result{ResultSuccess}; | 2000 | Result src_allocator_result; |
| 1974 | KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), | 2001 | KMemoryBlockManagerUpdateAllocator src_allocator(std::addressof(src_allocator_result), |
| 1975 | m_memory_block_slab_manager, | 2002 | m_memory_block_slab_manager, |
| 1976 | num_src_allocator_blocks); | 2003 | num_src_allocator_blocks); |
| 1977 | R_TRY(src_allocator_result); | 2004 | R_TRY(src_allocator_result); |
| 1978 | 2005 | ||
| 1979 | // Create an update allocator for the destination. | 2006 | // Create an update allocator for the destination. |
| 1980 | Result dst_allocator_result{ResultSuccess}; | 2007 | Result dst_allocator_result; |
| 1981 | KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), | 2008 | KMemoryBlockManagerUpdateAllocator dst_allocator(std::addressof(dst_allocator_result), |
| 1982 | m_memory_block_slab_manager, | 2009 | m_memory_block_slab_manager, |
| 1983 | num_dst_allocator_blocks); | 2010 | num_dst_allocator_blocks); |
| 1984 | R_TRY(dst_allocator_result); | 2011 | R_TRY(dst_allocator_result); |
| 1985 | 2012 | ||
| 1986 | KPageGroup src_pages{m_kernel, m_block_info_manager}; | 2013 | // Unmap the memory. |
| 1987 | KPageGroup dst_pages{m_kernel, m_block_info_manager}; | 2014 | { |
| 1988 | const size_t num_pages{size / PageSize}; | 2015 | // Determine the number of pages being operated on. |
| 2016 | const size_t num_pages = size / PageSize; | ||
| 1989 | 2017 | ||
| 1990 | AddRegionToPages(src_address, num_pages, src_pages); | 2018 | // Create page groups for the memory being unmapped. |
| 1991 | AddRegionToPages(dst_address, num_pages, dst_pages); | 2019 | KPageGroup pg{m_kernel, m_block_info_manager}; |
| 1992 | 2020 | ||
| 1993 | R_UNLESS(dst_pages.IsEquivalentTo(src_pages), ResultInvalidMemoryRegion); | 2021 | // Create the page group representing the destination. |
| 2022 | R_TRY(this->MakePageGroup(pg, dst_address, num_pages)); | ||
| 1994 | 2023 | ||
| 1995 | { | 2024 | // Ensure the page group is the valid for the source. |
| 1996 | auto block_guard = detail::ScopeExit([&] { MapPages(dst_address, dst_pages, dst_perm); }); | 2025 | R_UNLESS(this->IsValidPageGroup(pg, src_address, num_pages), ResultInvalidMemoryRegion); |
| 1997 | 2026 | ||
| 1998 | R_TRY(Operate(dst_address, num_pages, KMemoryPermission::None, OperationType::Unmap)); | 2027 | // We're going to perform an update, so create a helper. |
| 1999 | R_TRY(Operate(src_address, num_pages, KMemoryPermission::UserReadWrite, | 2028 | KScopedPageTableUpdater updater(this); |
| 2000 | OperationType::ChangePermissions)); | ||
| 2001 | 2029 | ||
| 2002 | block_guard.Cancel(); | 2030 | // Unmap the aliased copy of the pages. |
| 2003 | } | 2031 | const KPageProperties dst_unmap_properties = {KMemoryPermission::None, false, false, |
| 2032 | DisableMergeAttribute::None}; | ||
| 2033 | R_TRY( | ||
| 2034 | this->Operate(dst_address, num_pages, dst_unmap_properties.perm, OperationType::Unmap)); | ||
| 2035 | |||
| 2036 | // Ensure that we re-map the aliased pages on failure. | ||
| 2037 | ON_RESULT_FAILURE { | ||
| 2038 | this->RemapPageGroup(updater.GetPageList(), dst_address, size, pg); | ||
| 2039 | }; | ||
| 2004 | 2040 | ||
| 2005 | // Apply the memory block updates. | 2041 | // Try to set the permissions for the source pages back to what they should be. |
| 2006 | m_memory_block_manager.Update(std::addressof(src_allocator), src_address, num_pages, src_state, | 2042 | const KPageProperties src_properties = {KMemoryPermission::UserReadWrite, false, false, |
| 2007 | KMemoryPermission::UserReadWrite, KMemoryAttribute::None, | 2043 | DisableMergeAttribute::EnableAndMergeHeadBodyTail}; |
| 2008 | KMemoryBlockDisableMergeAttribute::None, | 2044 | R_TRY(this->Operate(src_address, num_pages, src_properties.perm, |
| 2009 | KMemoryBlockDisableMergeAttribute::Locked); | 2045 | OperationType::ChangePermissions)); |
| 2010 | m_memory_block_manager.Update(std::addressof(dst_allocator), dst_address, num_pages, | 2046 | |
| 2011 | KMemoryState::None, KMemoryPermission::None, | 2047 | // Apply the memory block updates. |
| 2012 | KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::None, | 2048 | m_memory_block_manager.Update( |
| 2013 | KMemoryBlockDisableMergeAttribute::Normal); | 2049 | std::addressof(src_allocator), src_address, num_pages, src_state, |
| 2050 | KMemoryPermission::UserReadWrite, KMemoryAttribute::None, | ||
| 2051 | KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Locked); | ||
| 2052 | m_memory_block_manager.Update( | ||
| 2053 | std::addressof(dst_allocator), dst_address, num_pages, KMemoryState::None, | ||
| 2054 | KMemoryPermission::None, KMemoryAttribute::None, | ||
| 2055 | KMemoryBlockDisableMergeAttribute::None, KMemoryBlockDisableMergeAttribute::Normal); | ||
| 2056 | } | ||
| 2014 | 2057 | ||
| 2015 | R_SUCCEED(); | 2058 | R_SUCCEED(); |
| 2016 | } | 2059 | } |
| 2017 | 2060 | ||
| 2018 | Result KPageTable::MapPages(VAddr addr, const KPageGroup& page_linked_list, | 2061 | Result KPageTable::AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address, |
| 2019 | KMemoryPermission perm) { | 2062 | size_t num_pages, KMemoryPermission perm) { |
| 2020 | ASSERT(this->IsLockedByCurrentThread()); | 2063 | ASSERT(this->IsLockedByCurrentThread()); |
| 2021 | 2064 | ||
| 2022 | VAddr cur_addr{addr}; | 2065 | // Create a page group to hold the pages we allocate. |
| 2066 | KPageGroup pg{m_kernel, m_block_info_manager}; | ||
| 2023 | 2067 | ||
| 2024 | for (const auto& node : page_linked_list) { | 2068 | // Allocate the pages. |
| 2025 | if (const auto result{ | 2069 | R_TRY( |
| 2026 | Operate(cur_addr, node.GetNumPages(), perm, OperationType::Map, node.GetAddress())}; | 2070 | m_kernel.MemoryManager().AllocateAndOpen(std::addressof(pg), num_pages, m_allocate_option)); |
| 2027 | result.IsError()) { | ||
| 2028 | const size_t num_pages{(addr - cur_addr) / PageSize}; | ||
| 2029 | 2071 | ||
| 2030 | ASSERT(Operate(addr, num_pages, KMemoryPermission::None, OperationType::Unmap) | 2072 | // Ensure that the page group is closed when we're done working with it. |
| 2031 | .IsSuccess()); | 2073 | SCOPE_EXIT({ pg.Close(); }); |
| 2032 | 2074 | ||
| 2033 | R_RETURN(result); | 2075 | // Clear all pages. |
| 2076 | for (const auto& it : pg) { | ||
| 2077 | std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), m_heap_fill_value, | ||
| 2078 | it.GetSize()); | ||
| 2079 | } | ||
| 2080 | |||
| 2081 | // Map the pages. | ||
| 2082 | R_RETURN(this->Operate(address, num_pages, pg, OperationType::MapGroup)); | ||
| 2083 | } | ||
| 2084 | |||
| 2085 | Result KPageTable::MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address, | ||
| 2086 | const KPageGroup& pg, const KPageProperties properties, | ||
| 2087 | bool reuse_ll) { | ||
| 2088 | ASSERT(this->IsLockedByCurrentThread()); | ||
| 2089 | |||
| 2090 | // Note the current address, so that we can iterate. | ||
| 2091 | const KProcessAddress start_address = address; | ||
| 2092 | KProcessAddress cur_address = address; | ||
| 2093 | |||
| 2094 | // Ensure that we clean up on failure. | ||
| 2095 | ON_RESULT_FAILURE { | ||
| 2096 | ASSERT(!reuse_ll); | ||
| 2097 | if (cur_address != start_address) { | ||
| 2098 | const KPageProperties unmap_properties = {KMemoryPermission::None, false, false, | ||
| 2099 | DisableMergeAttribute::None}; | ||
| 2100 | ASSERT(this->Operate(start_address, (cur_address - start_address) / PageSize, | ||
| 2101 | unmap_properties.perm, OperationType::Unmap) == ResultSuccess); | ||
| 2034 | } | 2102 | } |
| 2103 | }; | ||
| 2035 | 2104 | ||
| 2036 | cur_addr += node.GetNumPages() * PageSize; | 2105 | // Iterate, mapping all pages in the group. |
| 2106 | for (const auto& block : pg) { | ||
| 2107 | // Map and advance. | ||
| 2108 | const KPageProperties cur_properties = | ||
| 2109 | (cur_address == start_address) | ||
| 2110 | ? properties | ||
| 2111 | : KPageProperties{properties.perm, properties.io, properties.uncached, | ||
| 2112 | DisableMergeAttribute::None}; | ||
| 2113 | this->Operate(cur_address, block.GetNumPages(), cur_properties.perm, OperationType::Map, | ||
| 2114 | block.GetAddress()); | ||
| 2115 | cur_address += block.GetSize(); | ||
| 2037 | } | 2116 | } |
| 2038 | 2117 | ||
| 2118 | // We succeeded! | ||
| 2039 | R_SUCCEED(); | 2119 | R_SUCCEED(); |
| 2040 | } | 2120 | } |
| 2041 | 2121 | ||
| 2042 | Result KPageTable::MapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state, | 2122 | void KPageTable::RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size, |
| 2043 | KMemoryPermission perm) { | 2123 | const KPageGroup& pg) { |
| 2044 | // Check that the map is in range. | 2124 | ASSERT(this->IsLockedByCurrentThread()); |
| 2045 | const size_t num_pages{page_linked_list.GetNumPages()}; | ||
| 2046 | const size_t size{num_pages * PageSize}; | ||
| 2047 | R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory); | ||
| 2048 | 2125 | ||
| 2049 | // Lock the table. | 2126 | // Note the current address, so that we can iterate. |
| 2050 | KScopedLightLock lk(m_general_lock); | 2127 | const KProcessAddress start_address = address; |
| 2128 | const KProcessAddress last_address = start_address + size - 1; | ||
| 2129 | const KProcessAddress end_address = last_address + 1; | ||
| 2051 | 2130 | ||
| 2052 | // Check the memory state. | 2131 | // Iterate over the memory. |
| 2053 | R_TRY(this->CheckMemoryState(address, size, KMemoryState::All, KMemoryState::Free, | 2132 | auto pg_it = pg.begin(); |
| 2054 | KMemoryPermission::None, KMemoryPermission::None, | 2133 | ASSERT(pg_it != pg.end()); |
| 2055 | KMemoryAttribute::None, KMemoryAttribute::None)); | ||
| 2056 | 2134 | ||
| 2057 | // Create an update allocator. | 2135 | KPhysicalAddress pg_phys_addr = pg_it->GetAddress(); |
| 2058 | Result allocator_result{ResultSuccess}; | 2136 | size_t pg_pages = pg_it->GetNumPages(); |
| 2059 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | ||
| 2060 | m_memory_block_slab_manager); | ||
| 2061 | 2137 | ||
| 2062 | // Map the pages. | 2138 | auto it = m_memory_block_manager.FindIterator(start_address); |
| 2063 | R_TRY(MapPages(address, page_linked_list, perm)); | 2139 | while (true) { |
| 2140 | // Check that the iterator is valid. | ||
| 2141 | ASSERT(it != m_memory_block_manager.end()); | ||
| 2064 | 2142 | ||
| 2065 | // Update the blocks. | 2143 | // Get the memory info. |
| 2066 | m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, | 2144 | const KMemoryInfo info = it->GetMemoryInfo(); |
| 2067 | KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, | ||
| 2068 | KMemoryBlockDisableMergeAttribute::None); | ||
| 2069 | 2145 | ||
| 2070 | R_SUCCEED(); | 2146 | // Determine the range to map. |
| 2147 | KProcessAddress map_address = std::max<VAddr>(info.GetAddress(), start_address); | ||
| 2148 | const KProcessAddress map_end_address = std::min<VAddr>(info.GetEndAddress(), end_address); | ||
| 2149 | ASSERT(map_end_address != map_address); | ||
| 2150 | |||
| 2151 | // Determine if we should disable head merge. | ||
| 2152 | const bool disable_head_merge = | ||
| 2153 | info.GetAddress() >= start_address && | ||
| 2154 | True(info.GetDisableMergeAttribute() & KMemoryBlockDisableMergeAttribute::Normal); | ||
| 2155 | const KPageProperties map_properties = { | ||
| 2156 | info.GetPermission(), false, false, | ||
| 2157 | disable_head_merge ? DisableMergeAttribute::DisableHead : DisableMergeAttribute::None}; | ||
| 2158 | |||
| 2159 | // While we have pages to map, map them. | ||
| 2160 | size_t map_pages = (map_end_address - map_address) / PageSize; | ||
| 2161 | while (map_pages > 0) { | ||
| 2162 | // Check if we're at the end of the physical block. | ||
| 2163 | if (pg_pages == 0) { | ||
| 2164 | // Ensure there are more pages to map. | ||
| 2165 | ASSERT(pg_it != pg.end()); | ||
| 2166 | |||
| 2167 | // Advance our physical block. | ||
| 2168 | ++pg_it; | ||
| 2169 | pg_phys_addr = pg_it->GetAddress(); | ||
| 2170 | pg_pages = pg_it->GetNumPages(); | ||
| 2171 | } | ||
| 2172 | |||
| 2173 | // Map whatever we can. | ||
| 2174 | const size_t cur_pages = std::min(pg_pages, map_pages); | ||
| 2175 | ASSERT(this->Operate(map_address, map_pages, map_properties.perm, OperationType::Map, | ||
| 2176 | pg_phys_addr) == ResultSuccess); | ||
| 2177 | |||
| 2178 | // Advance. | ||
| 2179 | map_address += cur_pages * PageSize; | ||
| 2180 | map_pages -= cur_pages; | ||
| 2181 | |||
| 2182 | pg_phys_addr += cur_pages * PageSize; | ||
| 2183 | pg_pages -= cur_pages; | ||
| 2184 | } | ||
| 2185 | |||
| 2186 | // Check if we're done. | ||
| 2187 | if (last_address <= info.GetLastAddress()) { | ||
| 2188 | break; | ||
| 2189 | } | ||
| 2190 | |||
| 2191 | // Advance. | ||
| 2192 | ++it; | ||
| 2193 | } | ||
| 2194 | |||
| 2195 | // Check that we re-mapped precisely the page group. | ||
| 2196 | ASSERT((++pg_it) == pg.end()); | ||
| 2071 | } | 2197 | } |
| 2072 | 2198 | ||
| 2073 | Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, | 2199 | Result KPageTable::MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, |
| 2074 | bool is_pa_valid, VAddr region_start, size_t region_num_pages, | 2200 | KPhysicalAddress phys_addr, bool is_pa_valid, |
| 2201 | KProcessAddress region_start, size_t region_num_pages, | ||
| 2075 | KMemoryState state, KMemoryPermission perm) { | 2202 | KMemoryState state, KMemoryPermission perm) { |
| 2076 | ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize); | 2203 | ASSERT(Common::IsAligned(alignment, PageSize) && alignment >= PageSize); |
| 2077 | 2204 | ||
| @@ -2084,26 +2211,30 @@ Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, | |||
| 2084 | KScopedLightLock lk(m_general_lock); | 2211 | KScopedLightLock lk(m_general_lock); |
| 2085 | 2212 | ||
| 2086 | // Find a random address to map at. | 2213 | // Find a random address to map at. |
| 2087 | VAddr addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, 0, | 2214 | KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, alignment, |
| 2088 | this->GetNumGuardPages()); | 2215 | 0, this->GetNumGuardPages()); |
| 2089 | R_UNLESS(addr != 0, ResultOutOfMemory); | 2216 | R_UNLESS(addr != 0, ResultOutOfMemory); |
| 2090 | ASSERT(Common::IsAligned(addr, alignment)); | 2217 | ASSERT(Common::IsAligned(addr, alignment)); |
| 2091 | ASSERT(this->CanContain(addr, num_pages * PageSize, state)); | 2218 | ASSERT(this->CanContain(addr, num_pages * PageSize, state)); |
| 2092 | ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free, | 2219 | ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free, |
| 2093 | KMemoryPermission::None, KMemoryPermission::None, | 2220 | KMemoryPermission::None, KMemoryPermission::None, |
| 2094 | KMemoryAttribute::None, KMemoryAttribute::None) | 2221 | KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess); |
| 2095 | .IsSuccess()); | ||
| 2096 | 2222 | ||
| 2097 | // Create an update allocator. | 2223 | // Create an update allocator. |
| 2098 | Result allocator_result{ResultSuccess}; | 2224 | Result allocator_result; |
| 2099 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | 2225 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), |
| 2100 | m_memory_block_slab_manager); | 2226 | m_memory_block_slab_manager); |
| 2227 | R_TRY(allocator_result); | ||
| 2228 | |||
| 2229 | // We're going to perform an update, so create a helper. | ||
| 2230 | KScopedPageTableUpdater updater(this); | ||
| 2101 | 2231 | ||
| 2102 | // Perform mapping operation. | 2232 | // Perform mapping operation. |
| 2103 | if (is_pa_valid) { | 2233 | if (is_pa_valid) { |
| 2104 | R_TRY(this->Operate(addr, num_pages, perm, OperationType::Map, phys_addr)); | 2234 | const KPageProperties properties = {perm, false, false, DisableMergeAttribute::DisableHead}; |
| 2235 | R_TRY(this->Operate(addr, num_pages, properties.perm, OperationType::Map, phys_addr)); | ||
| 2105 | } else { | 2236 | } else { |
| 2106 | UNIMPLEMENTED(); | 2237 | R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), addr, num_pages, perm)); |
| 2107 | } | 2238 | } |
| 2108 | 2239 | ||
| 2109 | // Update the blocks. | 2240 | // Update the blocks. |
| @@ -2116,28 +2247,45 @@ Result KPageTable::MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, | |||
| 2116 | R_SUCCEED(); | 2247 | R_SUCCEED(); |
| 2117 | } | 2248 | } |
| 2118 | 2249 | ||
| 2119 | Result KPageTable::UnmapPages(VAddr addr, const KPageGroup& page_linked_list) { | 2250 | Result KPageTable::MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, |
| 2120 | ASSERT(this->IsLockedByCurrentThread()); | 2251 | KMemoryPermission perm) { |
| 2252 | // Check that the map is in range. | ||
| 2253 | const size_t size = num_pages * PageSize; | ||
| 2254 | R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory); | ||
| 2121 | 2255 | ||
| 2122 | VAddr cur_addr{addr}; | 2256 | // Lock the table. |
| 2257 | KScopedLightLock lk(m_general_lock); | ||
| 2123 | 2258 | ||
| 2124 | for (const auto& node : page_linked_list) { | 2259 | // Check the memory state. |
| 2125 | if (const auto result{Operate(cur_addr, node.GetNumPages(), KMemoryPermission::None, | 2260 | size_t num_allocator_blocks; |
| 2126 | OperationType::Unmap)}; | 2261 | R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, |
| 2127 | result.IsError()) { | 2262 | KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, |
| 2128 | R_RETURN(result); | 2263 | KMemoryPermission::None, KMemoryAttribute::None, |
| 2129 | } | 2264 | KMemoryAttribute::None)); |
| 2130 | 2265 | ||
| 2131 | cur_addr += node.GetNumPages() * PageSize; | 2266 | // Create an update allocator. |
| 2132 | } | 2267 | Result allocator_result; |
| 2268 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | ||
| 2269 | m_memory_block_slab_manager, num_allocator_blocks); | ||
| 2270 | R_TRY(allocator_result); | ||
| 2271 | |||
| 2272 | // We're going to perform an update, so create a helper. | ||
| 2273 | KScopedPageTableUpdater updater(this); | ||
| 2274 | |||
| 2275 | // Map the pages. | ||
| 2276 | R_TRY(this->AllocateAndMapPagesImpl(updater.GetPageList(), address, num_pages, perm)); | ||
| 2277 | |||
| 2278 | // Update the blocks. | ||
| 2279 | m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, state, perm, | ||
| 2280 | KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, | ||
| 2281 | KMemoryBlockDisableMergeAttribute::None); | ||
| 2133 | 2282 | ||
| 2134 | R_SUCCEED(); | 2283 | R_SUCCEED(); |
| 2135 | } | 2284 | } |
| 2136 | 2285 | ||
| 2137 | Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemoryState state) { | 2286 | Result KPageTable::UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state) { |
| 2138 | // Check that the unmap is in range. | 2287 | // Check that the unmap is in range. |
| 2139 | const size_t num_pages{page_linked_list.GetNumPages()}; | 2288 | const size_t size = num_pages * PageSize; |
| 2140 | const size_t size{num_pages * PageSize}; | ||
| 2141 | R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); | 2289 | R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); |
| 2142 | 2290 | ||
| 2143 | // Lock the table. | 2291 | // Lock the table. |
| @@ -2151,13 +2299,18 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo | |||
| 2151 | KMemoryAttribute::None)); | 2299 | KMemoryAttribute::None)); |
| 2152 | 2300 | ||
| 2153 | // Create an update allocator. | 2301 | // Create an update allocator. |
| 2154 | Result allocator_result{ResultSuccess}; | 2302 | Result allocator_result; |
| 2155 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | 2303 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), |
| 2156 | m_memory_block_slab_manager, num_allocator_blocks); | 2304 | m_memory_block_slab_manager, num_allocator_blocks); |
| 2157 | R_TRY(allocator_result); | 2305 | R_TRY(allocator_result); |
| 2158 | 2306 | ||
| 2307 | // We're going to perform an update, so create a helper. | ||
| 2308 | KScopedPageTableUpdater updater(this); | ||
| 2309 | |||
| 2159 | // Perform the unmap. | 2310 | // Perform the unmap. |
| 2160 | R_TRY(UnmapPages(address, page_linked_list)); | 2311 | const KPageProperties unmap_properties = {KMemoryPermission::None, false, false, |
| 2312 | DisableMergeAttribute::None}; | ||
| 2313 | R_TRY(this->Operate(address, num_pages, unmap_properties.perm, OperationType::Unmap)); | ||
| 2161 | 2314 | ||
| 2162 | // Update the blocks. | 2315 | // Update the blocks. |
| 2163 | m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, | 2316 | m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, |
| @@ -2168,29 +2321,130 @@ Result KPageTable::UnmapPages(VAddr address, KPageGroup& page_linked_list, KMemo | |||
| 2168 | R_SUCCEED(); | 2321 | R_SUCCEED(); |
| 2169 | } | 2322 | } |
| 2170 | 2323 | ||
| 2171 | Result KPageTable::UnmapPages(VAddr address, size_t num_pages, KMemoryState state) { | 2324 | Result KPageTable::MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg, |
| 2172 | // Check that the unmap is in range. | 2325 | KProcessAddress region_start, size_t region_num_pages, |
| 2326 | KMemoryState state, KMemoryPermission perm) { | ||
| 2327 | ASSERT(!this->IsLockedByCurrentThread()); | ||
| 2328 | |||
| 2329 | // Ensure this is a valid map request. | ||
| 2330 | const size_t num_pages = pg.GetNumPages(); | ||
| 2331 | R_UNLESS(this->CanContain(region_start, region_num_pages * PageSize, state), | ||
| 2332 | ResultInvalidCurrentMemory); | ||
| 2333 | R_UNLESS(num_pages < region_num_pages, ResultOutOfMemory); | ||
| 2334 | |||
| 2335 | // Lock the table. | ||
| 2336 | KScopedLightLock lk(m_general_lock); | ||
| 2337 | |||
| 2338 | // Find a random address to map at. | ||
| 2339 | KProcessAddress addr = this->FindFreeArea(region_start, region_num_pages, num_pages, PageSize, | ||
| 2340 | 0, this->GetNumGuardPages()); | ||
| 2341 | R_UNLESS(addr != 0, ResultOutOfMemory); | ||
| 2342 | ASSERT(this->CanContain(addr, num_pages * PageSize, state)); | ||
| 2343 | ASSERT(this->CheckMemoryState(addr, num_pages * PageSize, KMemoryState::All, KMemoryState::Free, | ||
| 2344 | KMemoryPermission::None, KMemoryPermission::None, | ||
| 2345 | KMemoryAttribute::None, KMemoryAttribute::None) == ResultSuccess); | ||
| 2346 | |||
| 2347 | // Create an update allocator. | ||
| 2348 | Result allocator_result; | ||
| 2349 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | ||
| 2350 | m_memory_block_slab_manager); | ||
| 2351 | R_TRY(allocator_result); | ||
| 2352 | |||
| 2353 | // We're going to perform an update, so create a helper. | ||
| 2354 | KScopedPageTableUpdater updater(this); | ||
| 2355 | |||
| 2356 | // Perform mapping operation. | ||
| 2357 | const KPageProperties properties = {perm, state == KMemoryState::Io, false, | ||
| 2358 | DisableMergeAttribute::DisableHead}; | ||
| 2359 | R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); | ||
| 2360 | |||
| 2361 | // Update the blocks. | ||
| 2362 | m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, | ||
| 2363 | KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, | ||
| 2364 | KMemoryBlockDisableMergeAttribute::None); | ||
| 2365 | |||
| 2366 | // We successfully mapped the pages. | ||
| 2367 | *out_addr = addr; | ||
| 2368 | R_SUCCEED(); | ||
| 2369 | } | ||
| 2370 | |||
| 2371 | Result KPageTable::MapPageGroup(KProcessAddress addr, const KPageGroup& pg, KMemoryState state, | ||
| 2372 | KMemoryPermission perm) { | ||
| 2373 | ASSERT(!this->IsLockedByCurrentThread()); | ||
| 2374 | |||
| 2375 | // Ensure this is a valid map request. | ||
| 2376 | const size_t num_pages = pg.GetNumPages(); | ||
| 2173 | const size_t size = num_pages * PageSize; | 2377 | const size_t size = num_pages * PageSize; |
| 2174 | R_UNLESS(this->Contains(address, size), ResultInvalidCurrentMemory); | 2378 | R_UNLESS(this->CanContain(addr, size, state), ResultInvalidCurrentMemory); |
| 2175 | 2379 | ||
| 2176 | // Lock the table. | 2380 | // Lock the table. |
| 2177 | KScopedLightLock lk(m_general_lock); | 2381 | KScopedLightLock lk(m_general_lock); |
| 2178 | 2382 | ||
| 2179 | // Check the memory state. | 2383 | // Check if state allows us to map. |
| 2180 | size_t num_allocator_blocks{}; | 2384 | size_t num_allocator_blocks; |
| 2385 | R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), addr, size, | ||
| 2386 | KMemoryState::All, KMemoryState::Free, KMemoryPermission::None, | ||
| 2387 | KMemoryPermission::None, KMemoryAttribute::None, | ||
| 2388 | KMemoryAttribute::None)); | ||
| 2389 | |||
| 2390 | // Create an update allocator. | ||
| 2391 | Result allocator_result; | ||
| 2392 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | ||
| 2393 | m_memory_block_slab_manager, num_allocator_blocks); | ||
| 2394 | R_TRY(allocator_result); | ||
| 2395 | |||
| 2396 | // We're going to perform an update, so create a helper. | ||
| 2397 | KScopedPageTableUpdater updater(this); | ||
| 2398 | |||
| 2399 | // Perform mapping operation. | ||
| 2400 | const KPageProperties properties = {perm, state == KMemoryState::Io, false, | ||
| 2401 | DisableMergeAttribute::DisableHead}; | ||
| 2402 | R_TRY(this->MapPageGroupImpl(updater.GetPageList(), addr, pg, properties, false)); | ||
| 2403 | |||
| 2404 | // Update the blocks. | ||
| 2405 | m_memory_block_manager.Update(std::addressof(allocator), addr, num_pages, state, perm, | ||
| 2406 | KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, | ||
| 2407 | KMemoryBlockDisableMergeAttribute::None); | ||
| 2408 | |||
| 2409 | // We successfully mapped the pages. | ||
| 2410 | R_SUCCEED(); | ||
| 2411 | } | ||
| 2412 | |||
| 2413 | Result KPageTable::UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, | ||
| 2414 | KMemoryState state) { | ||
| 2415 | ASSERT(!this->IsLockedByCurrentThread()); | ||
| 2416 | |||
| 2417 | // Ensure this is a valid unmap request. | ||
| 2418 | const size_t num_pages = pg.GetNumPages(); | ||
| 2419 | const size_t size = num_pages * PageSize; | ||
| 2420 | R_UNLESS(this->CanContain(address, size, state), ResultInvalidCurrentMemory); | ||
| 2421 | |||
| 2422 | // Lock the table. | ||
| 2423 | KScopedLightLock lk(m_general_lock); | ||
| 2424 | |||
| 2425 | // Check if state allows us to unmap. | ||
| 2426 | size_t num_allocator_blocks; | ||
| 2181 | R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, | 2427 | R_TRY(this->CheckMemoryState(std::addressof(num_allocator_blocks), address, size, |
| 2182 | KMemoryState::All, state, KMemoryPermission::None, | 2428 | KMemoryState::All, state, KMemoryPermission::None, |
| 2183 | KMemoryPermission::None, KMemoryAttribute::All, | 2429 | KMemoryPermission::None, KMemoryAttribute::All, |
| 2184 | KMemoryAttribute::None)); | 2430 | KMemoryAttribute::None)); |
| 2185 | 2431 | ||
| 2432 | // Check that the page group is valid. | ||
| 2433 | R_UNLESS(this->IsValidPageGroup(pg, address, num_pages), ResultInvalidCurrentMemory); | ||
| 2434 | |||
| 2186 | // Create an update allocator. | 2435 | // Create an update allocator. |
| 2187 | Result allocator_result{ResultSuccess}; | 2436 | Result allocator_result; |
| 2188 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | 2437 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), |
| 2189 | m_memory_block_slab_manager, num_allocator_blocks); | 2438 | m_memory_block_slab_manager, num_allocator_blocks); |
| 2190 | R_TRY(allocator_result); | 2439 | R_TRY(allocator_result); |
| 2191 | 2440 | ||
| 2192 | // Perform the unmap. | 2441 | // We're going to perform an update, so create a helper. |
| 2193 | R_TRY(Operate(address, num_pages, KMemoryPermission::None, OperationType::Unmap)); | 2442 | KScopedPageTableUpdater updater(this); |
| 2443 | |||
| 2444 | // Perform unmapping operation. | ||
| 2445 | const KPageProperties properties = {KMemoryPermission::None, false, false, | ||
| 2446 | DisableMergeAttribute::None}; | ||
| 2447 | R_TRY(this->Operate(address, num_pages, properties.perm, OperationType::Unmap)); | ||
| 2194 | 2448 | ||
| 2195 | // Update the blocks. | 2449 | // Update the blocks. |
| 2196 | m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, | 2450 | m_memory_block_manager.Update(std::addressof(allocator), address, num_pages, KMemoryState::Free, |
| @@ -2550,54 +2804,6 @@ Result KPageTable::SetHeapSize(VAddr* out, size_t size) { | |||
| 2550 | } | 2804 | } |
| 2551 | } | 2805 | } |
| 2552 | 2806 | ||
| 2553 | ResultVal<VAddr> KPageTable::AllocateAndMapMemory(size_t needed_num_pages, size_t align, | ||
| 2554 | bool is_map_only, VAddr region_start, | ||
| 2555 | size_t region_num_pages, KMemoryState state, | ||
| 2556 | KMemoryPermission perm, PAddr map_addr) { | ||
| 2557 | KScopedLightLock lk(m_general_lock); | ||
| 2558 | |||
| 2559 | R_UNLESS(CanContain(region_start, region_num_pages * PageSize, state), | ||
| 2560 | ResultInvalidCurrentMemory); | ||
| 2561 | R_UNLESS(region_num_pages > needed_num_pages, ResultOutOfMemory); | ||
| 2562 | const VAddr addr{ | ||
| 2563 | AllocateVirtualMemory(region_start, region_num_pages, needed_num_pages, align)}; | ||
| 2564 | R_UNLESS(addr, ResultOutOfMemory); | ||
| 2565 | |||
| 2566 | // Create an update allocator. | ||
| 2567 | Result allocator_result{ResultSuccess}; | ||
| 2568 | KMemoryBlockManagerUpdateAllocator allocator(std::addressof(allocator_result), | ||
| 2569 | m_memory_block_slab_manager); | ||
| 2570 | |||
| 2571 | if (is_map_only) { | ||
| 2572 | R_TRY(Operate(addr, needed_num_pages, perm, OperationType::Map, map_addr)); | ||
| 2573 | } else { | ||
| 2574 | // Create a page group tohold the pages we allocate. | ||
| 2575 | KPageGroup pg{m_kernel, m_block_info_manager}; | ||
| 2576 | |||
| 2577 | R_TRY(m_system.Kernel().MemoryManager().AllocateAndOpen( | ||
| 2578 | &pg, needed_num_pages, | ||
| 2579 | KMemoryManager::EncodeOption(m_memory_pool, m_allocation_option))); | ||
| 2580 | |||
| 2581 | // Ensure that the page group is closed when we're done working with it. | ||
| 2582 | SCOPE_EXIT({ pg.Close(); }); | ||
| 2583 | |||
| 2584 | // Clear all pages. | ||
| 2585 | for (const auto& it : pg) { | ||
| 2586 | std::memset(m_system.DeviceMemory().GetPointer<void>(it.GetAddress()), | ||
| 2587 | m_heap_fill_value, it.GetSize()); | ||
| 2588 | } | ||
| 2589 | |||
| 2590 | R_TRY(Operate(addr, needed_num_pages, pg, OperationType::MapGroup)); | ||
| 2591 | } | ||
| 2592 | |||
| 2593 | // Update the blocks. | ||
| 2594 | m_memory_block_manager.Update(std::addressof(allocator), addr, needed_num_pages, state, perm, | ||
| 2595 | KMemoryAttribute::None, KMemoryBlockDisableMergeAttribute::Normal, | ||
| 2596 | KMemoryBlockDisableMergeAttribute::None); | ||
| 2597 | |||
| 2598 | return addr; | ||
| 2599 | } | ||
| 2600 | |||
| 2601 | Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size, | 2807 | Result KPageTable::LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size, |
| 2602 | KMemoryPermission perm, bool is_aligned, | 2808 | KMemoryPermission perm, bool is_aligned, |
| 2603 | bool check_heap) { | 2809 | bool check_heap) { |
diff --git a/src/core/hle/kernel/k_page_table.h b/src/core/hle/kernel/k_page_table.h index 0a454b05b..367dab613 100644 --- a/src/core/hle/kernel/k_page_table.h +++ b/src/core/hle/kernel/k_page_table.h | |||
| @@ -24,12 +24,36 @@ class System; | |||
| 24 | 24 | ||
| 25 | namespace Kernel { | 25 | namespace Kernel { |
| 26 | 26 | ||
| 27 | enum class DisableMergeAttribute : u8 { | ||
| 28 | None = (0U << 0), | ||
| 29 | DisableHead = (1U << 0), | ||
| 30 | DisableHeadAndBody = (1U << 1), | ||
| 31 | EnableHeadAndBody = (1U << 2), | ||
| 32 | DisableTail = (1U << 3), | ||
| 33 | EnableTail = (1U << 4), | ||
| 34 | EnableAndMergeHeadBodyTail = (1U << 5), | ||
| 35 | EnableHeadBodyTail = EnableHeadAndBody | EnableTail, | ||
| 36 | DisableHeadBodyTail = DisableHeadAndBody | DisableTail, | ||
| 37 | }; | ||
| 38 | |||
| 39 | struct KPageProperties { | ||
| 40 | KMemoryPermission perm; | ||
| 41 | bool io; | ||
| 42 | bool uncached; | ||
| 43 | DisableMergeAttribute disable_merge_attributes; | ||
| 44 | }; | ||
| 45 | static_assert(std::is_trivial_v<KPageProperties>); | ||
| 46 | static_assert(sizeof(KPageProperties) == sizeof(u32)); | ||
| 47 | |||
| 27 | class KBlockInfoManager; | 48 | class KBlockInfoManager; |
| 28 | class KMemoryBlockManager; | 49 | class KMemoryBlockManager; |
| 29 | class KResourceLimit; | 50 | class KResourceLimit; |
| 30 | class KSystemResource; | 51 | class KSystemResource; |
| 31 | 52 | ||
| 32 | class KPageTable final { | 53 | class KPageTable final { |
| 54 | protected: | ||
| 55 | struct PageLinkedList; | ||
| 56 | |||
| 33 | public: | 57 | public: |
| 34 | enum class ICacheInvalidationStrategy : u32 { InvalidateRange, InvalidateAll }; | 58 | enum class ICacheInvalidationStrategy : u32 { InvalidateRange, InvalidateAll }; |
| 35 | 59 | ||
| @@ -57,27 +81,12 @@ public: | |||
| 57 | Result UnmapPhysicalMemory(VAddr addr, size_t size); | 81 | Result UnmapPhysicalMemory(VAddr addr, size_t size); |
| 58 | Result MapMemory(VAddr dst_addr, VAddr src_addr, size_t size); | 82 | Result MapMemory(VAddr dst_addr, VAddr src_addr, size_t size); |
| 59 | Result UnmapMemory(VAddr dst_addr, VAddr src_addr, size_t size); | 83 | Result UnmapMemory(VAddr dst_addr, VAddr src_addr, size_t size); |
| 60 | Result MapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state, | ||
| 61 | KMemoryPermission perm); | ||
| 62 | Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, | ||
| 63 | KMemoryState state, KMemoryPermission perm) { | ||
| 64 | R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, | ||
| 65 | this->GetRegionAddress(state), | ||
| 66 | this->GetRegionSize(state) / PageSize, state, perm)); | ||
| 67 | } | ||
| 68 | Result UnmapPages(VAddr addr, KPageGroup& page_linked_list, KMemoryState state); | ||
| 69 | Result UnmapPages(VAddr address, size_t num_pages, KMemoryState state); | ||
| 70 | Result SetProcessMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission svc_perm); | 84 | Result SetProcessMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission svc_perm); |
| 71 | KMemoryInfo QueryInfo(VAddr addr); | 85 | KMemoryInfo QueryInfo(VAddr addr); |
| 72 | Result SetMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission perm); | 86 | Result SetMemoryPermission(VAddr addr, size_t size, Svc::MemoryPermission perm); |
| 73 | Result SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 attr); | 87 | Result SetMemoryAttribute(VAddr addr, size_t size, u32 mask, u32 attr); |
| 74 | Result SetMaxHeapSize(size_t size); | 88 | Result SetMaxHeapSize(size_t size); |
| 75 | Result SetHeapSize(VAddr* out, size_t size); | 89 | Result SetHeapSize(VAddr* out, size_t size); |
| 76 | ResultVal<VAddr> AllocateAndMapMemory(size_t needed_num_pages, size_t align, bool is_map_only, | ||
| 77 | VAddr region_start, size_t region_num_pages, | ||
| 78 | KMemoryState state, KMemoryPermission perm, | ||
| 79 | PAddr map_addr = 0); | ||
| 80 | |||
| 81 | Result LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size, | 90 | Result LockForMapDeviceAddressSpace(bool* out_is_io, VAddr address, size_t size, |
| 82 | KMemoryPermission perm, bool is_aligned, bool check_heap); | 91 | KMemoryPermission perm, bool is_aligned, bool check_heap); |
| 83 | Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap); | 92 | Result LockForUnmapDeviceAddressSpace(VAddr address, size_t size, bool check_heap); |
| @@ -113,6 +122,40 @@ public: | |||
| 113 | 122 | ||
| 114 | bool CanContain(VAddr addr, size_t size, KMemoryState state) const; | 123 | bool CanContain(VAddr addr, size_t size, KMemoryState state) const; |
| 115 | 124 | ||
| 125 | Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, | ||
| 126 | KPhysicalAddress phys_addr, KProcessAddress region_start, | ||
| 127 | size_t region_num_pages, KMemoryState state, KMemoryPermission perm) { | ||
| 128 | R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, region_start, | ||
| 129 | region_num_pages, state, perm)); | ||
| 130 | } | ||
| 131 | |||
| 132 | Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, | ||
| 133 | KPhysicalAddress phys_addr, KMemoryState state, KMemoryPermission perm) { | ||
| 134 | R_RETURN(this->MapPages(out_addr, num_pages, alignment, phys_addr, true, | ||
| 135 | this->GetRegionAddress(state), | ||
| 136 | this->GetRegionSize(state) / PageSize, state, perm)); | ||
| 137 | } | ||
| 138 | |||
| 139 | Result MapPages(KProcessAddress* out_addr, size_t num_pages, KMemoryState state, | ||
| 140 | KMemoryPermission perm) { | ||
| 141 | R_RETURN(this->MapPages(out_addr, num_pages, PageSize, 0, false, | ||
| 142 | this->GetRegionAddress(state), | ||
| 143 | this->GetRegionSize(state) / PageSize, state, perm)); | ||
| 144 | } | ||
| 145 | |||
| 146 | Result MapPages(KProcessAddress address, size_t num_pages, KMemoryState state, | ||
| 147 | KMemoryPermission perm); | ||
| 148 | Result UnmapPages(KProcessAddress address, size_t num_pages, KMemoryState state); | ||
| 149 | |||
| 150 | Result MapPageGroup(KProcessAddress* out_addr, const KPageGroup& pg, | ||
| 151 | KProcessAddress region_start, size_t region_num_pages, KMemoryState state, | ||
| 152 | KMemoryPermission perm); | ||
| 153 | Result MapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state, | ||
| 154 | KMemoryPermission perm); | ||
| 155 | Result UnmapPageGroup(KProcessAddress address, const KPageGroup& pg, KMemoryState state); | ||
| 156 | void RemapPageGroup(PageLinkedList* page_list, KProcessAddress address, size_t size, | ||
| 157 | const KPageGroup& pg); | ||
| 158 | |||
| 116 | protected: | 159 | protected: |
| 117 | struct PageLinkedList { | 160 | struct PageLinkedList { |
| 118 | private: | 161 | private: |
| @@ -166,11 +209,9 @@ private: | |||
| 166 | static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr = | 209 | static constexpr KMemoryAttribute DefaultMemoryIgnoreAttr = |
| 167 | KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared; | 210 | KMemoryAttribute::IpcLocked | KMemoryAttribute::DeviceShared; |
| 168 | 211 | ||
| 169 | Result MapPages(VAddr addr, const KPageGroup& page_linked_list, KMemoryPermission perm); | 212 | Result MapPages(KProcessAddress* out_addr, size_t num_pages, size_t alignment, |
| 170 | Result MapPages(VAddr* out_addr, size_t num_pages, size_t alignment, PAddr phys_addr, | 213 | KPhysicalAddress phys_addr, bool is_pa_valid, KProcessAddress region_start, |
| 171 | bool is_pa_valid, VAddr region_start, size_t region_num_pages, | 214 | size_t region_num_pages, KMemoryState state, KMemoryPermission perm); |
| 172 | KMemoryState state, KMemoryPermission perm); | ||
| 173 | Result UnmapPages(VAddr addr, const KPageGroup& page_linked_list); | ||
| 174 | bool IsRegionContiguous(VAddr addr, u64 size) const; | 215 | bool IsRegionContiguous(VAddr addr, u64 size) const; |
| 175 | void AddRegionToPages(VAddr start, size_t num_pages, KPageGroup& page_linked_list); | 216 | void AddRegionToPages(VAddr start, size_t num_pages, KPageGroup& page_linked_list); |
| 176 | KMemoryInfo QueryInfoImpl(VAddr addr); | 217 | KMemoryInfo QueryInfoImpl(VAddr addr); |
| @@ -265,6 +306,11 @@ private: | |||
| 265 | void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address, | 306 | void CleanupForIpcClientOnServerSetupFailure(PageLinkedList* page_list, VAddr address, |
| 266 | size_t size, KMemoryPermission prot_perm); | 307 | size_t size, KMemoryPermission prot_perm); |
| 267 | 308 | ||
| 309 | Result AllocateAndMapPagesImpl(PageLinkedList* page_list, KProcessAddress address, | ||
| 310 | size_t num_pages, KMemoryPermission perm); | ||
| 311 | Result MapPageGroupImpl(PageLinkedList* page_list, KProcessAddress address, | ||
| 312 | const KPageGroup& pg, const KPageProperties properties, bool reuse_ll); | ||
| 313 | |||
| 268 | mutable KLightLock m_general_lock; | 314 | mutable KLightLock m_general_lock; |
| 269 | mutable KLightLock m_map_physical_memory_lock; | 315 | mutable KLightLock m_map_physical_memory_lock; |
| 270 | 316 | ||
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index a1abf5d68..e201bb0cd 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp | |||
| @@ -417,9 +417,8 @@ Result KProcess::LoadFromMetadata(const FileSys::ProgramMetadata& metadata, std: | |||
| 417 | } | 417 | } |
| 418 | 418 | ||
| 419 | void KProcess::Run(s32 main_thread_priority, u64 stack_size) { | 419 | void KProcess::Run(s32 main_thread_priority, u64 stack_size) { |
| 420 | AllocateMainThreadStack(stack_size); | 420 | ASSERT(AllocateMainThreadStack(stack_size) == ResultSuccess); |
| 421 | resource_limit->Reserve(LimitableResource::ThreadCountMax, 1); | 421 | resource_limit->Reserve(LimitableResource::ThreadCountMax, 1); |
| 422 | resource_limit->Reserve(LimitableResource::PhysicalMemoryMax, main_thread_stack_size); | ||
| 423 | 422 | ||
| 424 | const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)}; | 423 | const std::size_t heap_capacity{memory_usage_capacity - (main_thread_stack_size + image_size)}; |
| 425 | ASSERT(!page_table.SetMaxHeapSize(heap_capacity).IsError()); | 424 | ASSERT(!page_table.SetMaxHeapSize(heap_capacity).IsError()); |
| @@ -675,20 +674,31 @@ void KProcess::ChangeState(State new_state) { | |||
| 675 | } | 674 | } |
| 676 | 675 | ||
| 677 | Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { | 676 | Result KProcess::AllocateMainThreadStack(std::size_t stack_size) { |
| 678 | ASSERT(stack_size); | 677 | // Ensure that we haven't already allocated stack. |
| 679 | 678 | ASSERT(main_thread_stack_size == 0); | |
| 680 | // The kernel always ensures that the given stack size is page aligned. | 679 | |
| 681 | main_thread_stack_size = Common::AlignUp(stack_size, PageSize); | 680 | // Ensure that we're allocating a valid stack. |
| 682 | 681 | stack_size = Common::AlignUp(stack_size, PageSize); | |
| 683 | const VAddr start{page_table.GetStackRegionStart()}; | 682 | // R_UNLESS(stack_size + image_size <= m_max_process_memory, ResultOutOfMemory); |
| 684 | const std::size_t size{page_table.GetStackRegionEnd() - start}; | 683 | R_UNLESS(stack_size + image_size >= image_size, ResultOutOfMemory); |
| 685 | 684 | ||
| 686 | CASCADE_RESULT(main_thread_stack_top, | 685 | // Place a tentative reservation of memory for our new stack. |
| 687 | page_table.AllocateAndMapMemory( | 686 | KScopedResourceReservation mem_reservation(this, Svc::LimitableResource::PhysicalMemoryMax, |
| 688 | main_thread_stack_size / PageSize, PageSize, false, start, size / PageSize, | 687 | stack_size); |
| 689 | KMemoryState::Stack, KMemoryPermission::UserReadWrite)); | 688 | R_UNLESS(mem_reservation.Succeeded(), ResultLimitReached); |
| 689 | |||
| 690 | // Allocate and map our stack. | ||
| 691 | if (stack_size) { | ||
| 692 | KProcessAddress stack_bottom; | ||
| 693 | R_TRY(page_table.MapPages(std::addressof(stack_bottom), stack_size / PageSize, | ||
| 694 | KMemoryState::Stack, KMemoryPermission::UserReadWrite)); | ||
| 695 | |||
| 696 | main_thread_stack_top = stack_bottom + stack_size; | ||
| 697 | main_thread_stack_size = stack_size; | ||
| 698 | } | ||
| 690 | 699 | ||
| 691 | main_thread_stack_top += main_thread_stack_size; | 700 | // We succeeded! Commit our memory reservation. |
| 701 | mem_reservation.Commit(); | ||
| 692 | 702 | ||
| 693 | R_SUCCEED(); | 703 | R_SUCCEED(); |
| 694 | } | 704 | } |
diff --git a/src/core/hle/kernel/k_shared_memory.cpp b/src/core/hle/kernel/k_shared_memory.cpp index 3cf2b5d91..df505edfe 100644 --- a/src/core/hle/kernel/k_shared_memory.cpp +++ b/src/core/hle/kernel/k_shared_memory.cpp | |||
| @@ -94,15 +94,15 @@ Result KSharedMemory::Map(KProcess& target_process, VAddr address, std::size_t m | |||
| 94 | R_UNLESS(map_perm == test_perm, ResultInvalidNewMemoryPermission); | 94 | R_UNLESS(map_perm == test_perm, ResultInvalidNewMemoryPermission); |
| 95 | } | 95 | } |
| 96 | 96 | ||
| 97 | return target_process.PageTable().MapPages(address, *page_group, KMemoryState::Shared, | 97 | return target_process.PageTable().MapPageGroup(address, *page_group, KMemoryState::Shared, |
| 98 | ConvertToKMemoryPermission(map_perm)); | 98 | ConvertToKMemoryPermission(map_perm)); |
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | Result KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) { | 101 | Result KSharedMemory::Unmap(KProcess& target_process, VAddr address, std::size_t unmap_size) { |
| 102 | // Validate the size. | 102 | // Validate the size. |
| 103 | R_UNLESS(size == unmap_size, ResultInvalidSize); | 103 | R_UNLESS(size == unmap_size, ResultInvalidSize); |
| 104 | 104 | ||
| 105 | return target_process.PageTable().UnmapPages(address, *page_group, KMemoryState::Shared); | 105 | return target_process.PageTable().UnmapPageGroup(address, *page_group, KMemoryState::Shared); |
| 106 | } | 106 | } |
| 107 | 107 | ||
| 108 | } // namespace Kernel | 108 | } // namespace Kernel |
diff --git a/src/core/hle/kernel/k_thread.cpp b/src/core/hle/kernel/k_thread.cpp index 21207fe99..84ff3c64b 100644 --- a/src/core/hle/kernel/k_thread.cpp +++ b/src/core/hle/kernel/k_thread.cpp | |||
| @@ -330,7 +330,7 @@ void KThread::Finalize() { | |||
| 330 | KThread* const waiter = std::addressof(*it); | 330 | KThread* const waiter = std::addressof(*it); |
| 331 | 331 | ||
| 332 | // The thread shouldn't be a kernel waiter. | 332 | // The thread shouldn't be a kernel waiter. |
| 333 | ASSERT(!IsKernelAddressKey(waiter->GetAddressKey())); | 333 | ASSERT(!waiter->GetAddressKeyIsKernel()); |
| 334 | 334 | ||
| 335 | // Clear the lock owner. | 335 | // Clear the lock owner. |
| 336 | waiter->SetLockOwner(nullptr); | 336 | waiter->SetLockOwner(nullptr); |
| @@ -763,19 +763,6 @@ void KThread::Continue() { | |||
| 763 | KScheduler::OnThreadStateChanged(kernel, this, old_state); | 763 | KScheduler::OnThreadStateChanged(kernel, this, old_state); |
| 764 | } | 764 | } |
| 765 | 765 | ||
| 766 | void KThread::WaitUntilSuspended() { | ||
| 767 | // Make sure we have a suspend requested. | ||
| 768 | ASSERT(IsSuspendRequested()); | ||
| 769 | |||
| 770 | // Loop until the thread is not executing on any core. | ||
| 771 | for (std::size_t i = 0; i < static_cast<std::size_t>(Core::Hardware::NUM_CPU_CORES); ++i) { | ||
| 772 | KThread* core_thread{}; | ||
| 773 | do { | ||
| 774 | core_thread = kernel.Scheduler(i).GetSchedulerCurrentThread(); | ||
| 775 | } while (core_thread == this); | ||
| 776 | } | ||
| 777 | } | ||
| 778 | |||
| 779 | Result KThread::SetActivity(Svc::ThreadActivity activity) { | 766 | Result KThread::SetActivity(Svc::ThreadActivity activity) { |
| 780 | // Lock ourselves. | 767 | // Lock ourselves. |
| 781 | KScopedLightLock lk(activity_pause_lock); | 768 | KScopedLightLock lk(activity_pause_lock); |
| @@ -897,7 +884,7 @@ void KThread::AddWaiterImpl(KThread* thread) { | |||
| 897 | } | 884 | } |
| 898 | 885 | ||
| 899 | // Keep track of how many kernel waiters we have. | 886 | // Keep track of how many kernel waiters we have. |
| 900 | if (IsKernelAddressKey(thread->GetAddressKey())) { | 887 | if (thread->GetAddressKeyIsKernel()) { |
| 901 | ASSERT((num_kernel_waiters++) >= 0); | 888 | ASSERT((num_kernel_waiters++) >= 0); |
| 902 | KScheduler::SetSchedulerUpdateNeeded(kernel); | 889 | KScheduler::SetSchedulerUpdateNeeded(kernel); |
| 903 | } | 890 | } |
| @@ -911,7 +898,7 @@ void KThread::RemoveWaiterImpl(KThread* thread) { | |||
| 911 | ASSERT(kernel.GlobalSchedulerContext().IsLocked()); | 898 | ASSERT(kernel.GlobalSchedulerContext().IsLocked()); |
| 912 | 899 | ||
| 913 | // Keep track of how many kernel waiters we have. | 900 | // Keep track of how many kernel waiters we have. |
| 914 | if (IsKernelAddressKey(thread->GetAddressKey())) { | 901 | if (thread->GetAddressKeyIsKernel()) { |
| 915 | ASSERT((num_kernel_waiters--) > 0); | 902 | ASSERT((num_kernel_waiters--) > 0); |
| 916 | KScheduler::SetSchedulerUpdateNeeded(kernel); | 903 | KScheduler::SetSchedulerUpdateNeeded(kernel); |
| 917 | } | 904 | } |
| @@ -987,7 +974,7 @@ KThread* KThread::RemoveWaiterByKey(s32* out_num_waiters, VAddr key) { | |||
| 987 | KThread* thread = std::addressof(*it); | 974 | KThread* thread = std::addressof(*it); |
| 988 | 975 | ||
| 989 | // Keep track of how many kernel waiters we have. | 976 | // Keep track of how many kernel waiters we have. |
| 990 | if (IsKernelAddressKey(thread->GetAddressKey())) { | 977 | if (thread->GetAddressKeyIsKernel()) { |
| 991 | ASSERT((num_kernel_waiters--) > 0); | 978 | ASSERT((num_kernel_waiters--) > 0); |
| 992 | KScheduler::SetSchedulerUpdateNeeded(kernel); | 979 | KScheduler::SetSchedulerUpdateNeeded(kernel); |
| 993 | } | 980 | } |
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h index 7cd94a340..9d771de0e 100644 --- a/src/core/hle/kernel/k_thread.h +++ b/src/core/hle/kernel/k_thread.h | |||
| @@ -214,8 +214,6 @@ public: | |||
| 214 | 214 | ||
| 215 | void Continue(); | 215 | void Continue(); |
| 216 | 216 | ||
| 217 | void WaitUntilSuspended(); | ||
| 218 | |||
| 219 | constexpr void SetSyncedIndex(s32 index) { | 217 | constexpr void SetSyncedIndex(s32 index) { |
| 220 | synced_index = index; | 218 | synced_index = index; |
| 221 | } | 219 | } |
| @@ -607,13 +605,30 @@ public: | |||
| 607 | return address_key_value; | 605 | return address_key_value; |
| 608 | } | 606 | } |
| 609 | 607 | ||
| 610 | void SetAddressKey(VAddr key) { | 608 | [[nodiscard]] bool GetAddressKeyIsKernel() const { |
| 609 | return address_key_is_kernel; | ||
| 610 | } | ||
| 611 | |||
| 612 | //! NB: intentional deviation from official kernel. | ||
| 613 | // | ||
| 614 | // Separate SetAddressKey into user and kernel versions | ||
| 615 | // to cope with arbitrary host pointers making their way | ||
| 616 | // into things. | ||
| 617 | |||
| 618 | void SetUserAddressKey(VAddr key) { | ||
| 611 | address_key = key; | 619 | address_key = key; |
| 620 | address_key_is_kernel = false; | ||
| 612 | } | 621 | } |
| 613 | 622 | ||
| 614 | void SetAddressKey(VAddr key, u32 val) { | 623 | void SetUserAddressKey(VAddr key, u32 val) { |
| 615 | address_key = key; | 624 | address_key = key; |
| 616 | address_key_value = val; | 625 | address_key_value = val; |
| 626 | address_key_is_kernel = false; | ||
| 627 | } | ||
| 628 | |||
| 629 | void SetKernelAddressKey(VAddr key) { | ||
| 630 | address_key = key; | ||
| 631 | address_key_is_kernel = true; | ||
| 617 | } | 632 | } |
| 618 | 633 | ||
| 619 | void ClearWaitQueue() { | 634 | void ClearWaitQueue() { |
| @@ -772,6 +787,7 @@ private: | |||
| 772 | bool debug_attached{}; | 787 | bool debug_attached{}; |
| 773 | s8 priority_inheritance_count{}; | 788 | s8 priority_inheritance_count{}; |
| 774 | bool resource_limit_release_hint{}; | 789 | bool resource_limit_release_hint{}; |
| 790 | bool address_key_is_kernel{}; | ||
| 775 | StackParameters stack_parameters{}; | 791 | StackParameters stack_parameters{}; |
| 776 | Common::SpinLock context_guard{}; | 792 | Common::SpinLock context_guard{}; |
| 777 | 793 | ||
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 1fb25f221..d9eafe261 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -1198,28 +1198,35 @@ void KernelCore::Suspend(bool suspended) { | |||
| 1198 | const bool should_suspend{exception_exited || suspended}; | 1198 | const bool should_suspend{exception_exited || suspended}; |
| 1199 | const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable; | 1199 | const auto activity = should_suspend ? ProcessActivity::Paused : ProcessActivity::Runnable; |
| 1200 | 1200 | ||
| 1201 | std::vector<KScopedAutoObject<KThread>> process_threads; | 1201 | //! This refers to the application process, not the current process. |
| 1202 | { | 1202 | KScopedAutoObject<KProcess> process = CurrentProcess(); |
| 1203 | KScopedSchedulerLock sl{*this}; | 1203 | if (process.IsNull()) { |
| 1204 | return; | ||
| 1205 | } | ||
| 1204 | 1206 | ||
| 1205 | if (auto* process = CurrentProcess(); process != nullptr) { | 1207 | // Set the new activity. |
| 1206 | process->SetActivity(activity); | 1208 | process->SetActivity(activity); |
| 1207 | 1209 | ||
| 1208 | if (!should_suspend) { | 1210 | // Wait for process execution to stop. |
| 1209 | // Runnable now; no need to wait. | 1211 | bool must_wait{should_suspend}; |
| 1210 | return; | 1212 | |
| 1211 | } | 1213 | // KernelCore::Suspend must be called from locked context, or we |
| 1214 | // could race another call to SetActivity, interfering with waiting. | ||
| 1215 | while (must_wait) { | ||
| 1216 | KScopedSchedulerLock sl{*this}; | ||
| 1217 | |||
| 1218 | // Assume that all threads have finished running. | ||
| 1219 | must_wait = false; | ||
| 1212 | 1220 | ||
| 1213 | for (auto* thread : process->GetThreadList()) { | 1221 | for (auto i = 0; i < static_cast<s32>(Core::Hardware::NUM_CPU_CORES); ++i) { |
| 1214 | process_threads.emplace_back(thread); | 1222 | if (Scheduler(i).GetSchedulerCurrentThread()->GetOwnerProcess() == |
| 1223 | process.GetPointerUnsafe()) { | ||
| 1224 | // A thread has not finished running yet. | ||
| 1225 | // Continue waiting. | ||
| 1226 | must_wait = true; | ||
| 1215 | } | 1227 | } |
| 1216 | } | 1228 | } |
| 1217 | } | 1229 | } |
| 1218 | |||
| 1219 | // Wait for execution to stop. | ||
| 1220 | for (auto& thread : process_threads) { | ||
| 1221 | thread->WaitUntilSuspended(); | ||
| 1222 | } | ||
| 1223 | } | 1230 | } |
| 1224 | 1231 | ||
| 1225 | void KernelCore::ShutdownCores() { | 1232 | void KernelCore::ShutdownCores() { |
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index aca442196..67fa5d71c 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp | |||
| @@ -1492,8 +1492,8 @@ static Result MapProcessMemory(Core::System& system, VAddr dst_address, Handle p | |||
| 1492 | KMemoryAttribute::All, KMemoryAttribute::None)); | 1492 | KMemoryAttribute::All, KMemoryAttribute::None)); |
| 1493 | 1493 | ||
| 1494 | // Map the group. | 1494 | // Map the group. |
| 1495 | R_TRY(dst_pt.MapPages(dst_address, pg, KMemoryState::SharedCode, | 1495 | R_TRY(dst_pt.MapPageGroup(dst_address, pg, KMemoryState::SharedCode, |
| 1496 | KMemoryPermission::UserReadWrite)); | 1496 | KMemoryPermission::UserReadWrite)); |
| 1497 | 1497 | ||
| 1498 | return ResultSuccess; | 1498 | return ResultSuccess; |
| 1499 | } | 1499 | } |
diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 4e605fae4..af9660b55 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp | |||
| @@ -440,7 +440,7 @@ struct Memory::Impl { | |||
| 440 | } | 440 | } |
| 441 | 441 | ||
| 442 | if (Settings::IsFastmemEnabled()) { | 442 | if (Settings::IsFastmemEnabled()) { |
| 443 | const bool is_read_enable = !Settings::IsGPULevelExtreme() || !cached; | 443 | const bool is_read_enable = Settings::IsGPULevelHigh() || !cached; |
| 444 | system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached); | 444 | system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached); |
| 445 | } | 445 | } |
| 446 | 446 | ||
diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp index 4159e5717..3775e2d35 100644 --- a/src/input_common/helpers/joycon_driver.cpp +++ b/src/input_common/helpers/joycon_driver.cpp | |||
| @@ -86,6 +86,7 @@ DriverResult JoyconDriver::InitializeDevice() { | |||
| 86 | 86 | ||
| 87 | // Get fixed joycon info | 87 | // Get fixed joycon info |
| 88 | generic_protocol->GetVersionNumber(version); | 88 | generic_protocol->GetVersionNumber(version); |
| 89 | generic_protocol->SetLowPowerMode(false); | ||
| 89 | generic_protocol->GetColor(color); | 90 | generic_protocol->GetColor(color); |
| 90 | if (handle_device_type == ControllerType::Pro) { | 91 | if (handle_device_type == ControllerType::Pro) { |
| 91 | // Some 3rd party controllers aren't pro controllers | 92 | // Some 3rd party controllers aren't pro controllers |
| @@ -324,6 +325,8 @@ DriverResult JoyconDriver::SetPollingMode() { | |||
| 324 | if (result != DriverResult::Success) { | 325 | if (result != DriverResult::Success) { |
| 325 | LOG_ERROR(Input, "Error enabling active mode"); | 326 | LOG_ERROR(Input, "Error enabling active mode"); |
| 326 | } | 327 | } |
| 328 | // Switch calls this function after enabling active mode | ||
| 329 | generic_protocol->TriggersElapsed(); | ||
| 327 | 330 | ||
| 328 | disable_input_thread = false; | 331 | disable_input_thread = false; |
| 329 | return result; | 332 | return result; |
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.cpp b/src/input_common/helpers/joycon_protocol/generic_functions.cpp index 52bb8b61a..63cfb1369 100644 --- a/src/input_common/helpers/joycon_protocol/generic_functions.cpp +++ b/src/input_common/helpers/joycon_protocol/generic_functions.cpp | |||
| @@ -19,6 +19,17 @@ DriverResult GenericProtocol::EnableActiveMode() { | |||
| 19 | return SetReportMode(ReportMode::STANDARD_FULL_60HZ); | 19 | return SetReportMode(ReportMode::STANDARD_FULL_60HZ); |
| 20 | } | 20 | } |
| 21 | 21 | ||
| 22 | DriverResult GenericProtocol::SetLowPowerMode(bool enable) { | ||
| 23 | ScopedSetBlocking sb(this); | ||
| 24 | const std::array<u8, 1> buffer{static_cast<u8>(enable ? 1 : 0)}; | ||
| 25 | return SendSubCommand(SubCommand::LOW_POWER_MODE, buffer); | ||
| 26 | } | ||
| 27 | |||
| 28 | DriverResult GenericProtocol::TriggersElapsed() { | ||
| 29 | ScopedSetBlocking sb(this); | ||
| 30 | return SendSubCommand(SubCommand::TRIGGERS_ELAPSED, {}); | ||
| 31 | } | ||
| 32 | |||
| 22 | DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) { | 33 | DriverResult GenericProtocol::GetDeviceInfo(DeviceInfo& device_info) { |
| 23 | ScopedSetBlocking sb(this); | 34 | ScopedSetBlocking sb(this); |
| 24 | std::vector<u8> output; | 35 | std::vector<u8> output; |
diff --git a/src/input_common/helpers/joycon_protocol/generic_functions.h b/src/input_common/helpers/joycon_protocol/generic_functions.h index 239bb7dbf..424831e81 100644 --- a/src/input_common/helpers/joycon_protocol/generic_functions.h +++ b/src/input_common/helpers/joycon_protocol/generic_functions.h | |||
| @@ -25,6 +25,12 @@ public: | |||
| 25 | /// Enables active mode. This mode will return the current status every 5-15ms | 25 | /// Enables active mode. This mode will return the current status every 5-15ms |
| 26 | DriverResult EnableActiveMode(); | 26 | DriverResult EnableActiveMode(); |
| 27 | 27 | ||
| 28 | /// Enables or disables the low power mode | ||
| 29 | DriverResult SetLowPowerMode(bool enable); | ||
| 30 | |||
| 31 | /// Unknown function used by the switch | ||
| 32 | DriverResult TriggersElapsed(); | ||
| 33 | |||
| 28 | /** | 34 | /** |
| 29 | * Sends a request to obtain the joycon firmware and mac from handle | 35 | * Sends a request to obtain the joycon firmware and mac from handle |
| 30 | * @returns controller device info | 36 | * @returns controller device info |
diff --git a/src/input_common/helpers/joycon_protocol/joycon_types.h b/src/input_common/helpers/joycon_protocol/joycon_types.h index e2d47349f..182d2c15b 100644 --- a/src/input_common/helpers/joycon_protocol/joycon_types.h +++ b/src/input_common/helpers/joycon_protocol/joycon_types.h | |||
| @@ -129,6 +129,7 @@ enum class SubCommand : u8 { | |||
| 129 | LOW_POWER_MODE = 0x08, | 129 | LOW_POWER_MODE = 0x08, |
| 130 | SPI_FLASH_READ = 0x10, | 130 | SPI_FLASH_READ = 0x10, |
| 131 | SPI_FLASH_WRITE = 0x11, | 131 | SPI_FLASH_WRITE = 0x11, |
| 132 | SPI_SECTOR_ERASE = 0x12, | ||
| 132 | RESET_MCU = 0x20, | 133 | RESET_MCU = 0x20, |
| 133 | SET_MCU_CONFIG = 0x21, | 134 | SET_MCU_CONFIG = 0x21, |
| 134 | SET_MCU_STATE = 0x22, | 135 | SET_MCU_STATE = 0x22, |
diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp index f3a0b3419..096c23b07 100644 --- a/src/input_common/helpers/stick_from_buttons.cpp +++ b/src/input_common/helpers/stick_from_buttons.cpp | |||
| @@ -11,6 +11,11 @@ namespace InputCommon { | |||
| 11 | 11 | ||
| 12 | class Stick final : public Common::Input::InputDevice { | 12 | class Stick final : public Common::Input::InputDevice { |
| 13 | public: | 13 | public: |
| 14 | // Some games such as EARTH DEFENSE FORCE: WORLD BROTHERS | ||
| 15 | // do not play nicely with the theoretical maximum range. | ||
| 16 | // Using a value one lower from the maximum emulates real stick behavior. | ||
| 17 | static constexpr float MAX_RANGE = 32766.0f / 32767.0f; | ||
| 18 | |||
| 14 | using Button = std::unique_ptr<Common::Input::InputDevice>; | 19 | using Button = std::unique_ptr<Common::Input::InputDevice>; |
| 15 | 20 | ||
| 16 | Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_, | 21 | Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, Button updater_, |
| @@ -196,7 +201,7 @@ public: | |||
| 196 | } | 201 | } |
| 197 | 202 | ||
| 198 | void UpdateStatus() { | 203 | void UpdateStatus() { |
| 199 | const float coef = modifier_status.value ? modifier_scale : 1.0f; | 204 | const float coef = modifier_status.value ? modifier_scale : MAX_RANGE; |
| 200 | 205 | ||
| 201 | bool r = right_status; | 206 | bool r = right_status; |
| 202 | bool l = left_status; | 207 | bool l = left_status; |
| @@ -290,7 +295,7 @@ public: | |||
| 290 | if (down_status) { | 295 | if (down_status) { |
| 291 | --y; | 296 | --y; |
| 292 | } | 297 | } |
| 293 | const float coef = modifier_status.value ? modifier_scale : 1.0f; | 298 | const float coef = modifier_status.value ? modifier_scale : MAX_RANGE; |
| 294 | status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); | 299 | status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); |
| 295 | status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); | 300 | status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); |
| 296 | return status; | 301 | return status; |
diff --git a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp index fb5799c42..c898ce12f 100644 --- a/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp +++ b/src/shader_recompiler/backend/spirv/emit_spirv_image.cpp | |||
| @@ -436,6 +436,10 @@ Id EmitImageFetch(EmitContext& ctx, IR::Inst* inst, const IR::Value& index, Id c | |||
| 436 | if (info.type == TextureType::Buffer) { | 436 | if (info.type == TextureType::Buffer) { |
| 437 | lod = Id{}; | 437 | lod = Id{}; |
| 438 | } | 438 | } |
| 439 | if (Sirit::ValidId(ms)) { | ||
| 440 | // This image is multisampled, lod must be implicit | ||
| 441 | lod = Id{}; | ||
| 442 | } | ||
| 439 | const ImageOperands operands(offset, lod, ms); | 443 | const ImageOperands operands(offset, lod, ms); |
| 440 | return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4], | 444 | return Emit(&EmitContext::OpImageSparseFetch, &EmitContext::OpImageFetch, ctx, inst, ctx.F32[4], |
| 441 | TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); | 445 | TextureImage(ctx, info, index), coords, operands.MaskOptional(), operands.Span()); |
diff --git a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp index a0c155fdb..3b97721e1 100644 --- a/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp +++ b/src/shader_recompiler/backend/spirv/spirv_emit_context.cpp | |||
| @@ -35,6 +35,7 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { | |||
| 35 | const spv::ImageFormat format{spv::ImageFormat::Unknown}; | 35 | const spv::ImageFormat format{spv::ImageFormat::Unknown}; |
| 36 | const Id type{ctx.F32[1]}; | 36 | const Id type{ctx.F32[1]}; |
| 37 | const bool depth{desc.is_depth}; | 37 | const bool depth{desc.is_depth}; |
| 38 | const bool ms{desc.is_multisample}; | ||
| 38 | switch (desc.type) { | 39 | switch (desc.type) { |
| 39 | case TextureType::Color1D: | 40 | case TextureType::Color1D: |
| 40 | return ctx.TypeImage(type, spv::Dim::Dim1D, depth, false, false, 1, format); | 41 | return ctx.TypeImage(type, spv::Dim::Dim1D, depth, false, false, 1, format); |
| @@ -42,9 +43,9 @@ Id ImageType(EmitContext& ctx, const TextureDescriptor& desc) { | |||
| 42 | return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format); | 43 | return ctx.TypeImage(type, spv::Dim::Dim1D, depth, true, false, 1, format); |
| 43 | case TextureType::Color2D: | 44 | case TextureType::Color2D: |
| 44 | case TextureType::Color2DRect: | 45 | case TextureType::Color2DRect: |
| 45 | return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, false, 1, format); | 46 | return ctx.TypeImage(type, spv::Dim::Dim2D, depth, false, ms, 1, format); |
| 46 | case TextureType::ColorArray2D: | 47 | case TextureType::ColorArray2D: |
| 47 | return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, false, 1, format); | 48 | return ctx.TypeImage(type, spv::Dim::Dim2D, depth, true, ms, 1, format); |
| 48 | case TextureType::Color3D: | 49 | case TextureType::Color3D: |
| 49 | return ctx.TypeImage(type, spv::Dim::Dim3D, depth, false, false, 1, format); | 50 | return ctx.TypeImage(type, spv::Dim::Dim3D, depth, false, false, 1, format); |
| 50 | case TextureType::ColorCube: | 51 | case TextureType::ColorCube: |
diff --git a/src/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index f5c86fcb1..9718c6921 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp | |||
| @@ -524,6 +524,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo | |||
| 524 | 524 | ||
| 525 | const auto& cbuf{texture_inst.cbuf}; | 525 | const auto& cbuf{texture_inst.cbuf}; |
| 526 | auto flags{inst->Flags<IR::TextureInstInfo>()}; | 526 | auto flags{inst->Flags<IR::TextureInstInfo>()}; |
| 527 | bool is_multisample{false}; | ||
| 527 | switch (inst->GetOpcode()) { | 528 | switch (inst->GetOpcode()) { |
| 528 | case IR::Opcode::ImageQueryDimensions: | 529 | case IR::Opcode::ImageQueryDimensions: |
| 529 | flags.type.Assign(ReadTextureType(env, cbuf)); | 530 | flags.type.Assign(ReadTextureType(env, cbuf)); |
| @@ -538,6 +539,12 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo | |||
| 538 | } | 539 | } |
| 539 | break; | 540 | break; |
| 540 | case IR::Opcode::ImageFetch: | 541 | case IR::Opcode::ImageFetch: |
| 542 | if (flags.type == TextureType::Color2D || flags.type == TextureType::Color2DRect || | ||
| 543 | flags.type == TextureType::ColorArray2D) { | ||
| 544 | is_multisample = !inst->Arg(4).IsEmpty(); | ||
| 545 | } else { | ||
| 546 | inst->SetArg(4, IR::U32{}); | ||
| 547 | } | ||
| 541 | if (flags.type != TextureType::Color1D) { | 548 | if (flags.type != TextureType::Color1D) { |
| 542 | break; | 549 | break; |
| 543 | } | 550 | } |
| @@ -613,6 +620,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo | |||
| 613 | index = descriptors.Add(TextureDescriptor{ | 620 | index = descriptors.Add(TextureDescriptor{ |
| 614 | .type = flags.type, | 621 | .type = flags.type, |
| 615 | .is_depth = flags.is_depth != 0, | 622 | .is_depth = flags.is_depth != 0, |
| 623 | .is_multisample = is_multisample, | ||
| 616 | .has_secondary = cbuf.has_secondary, | 624 | .has_secondary = cbuf.has_secondary, |
| 617 | .cbuf_index = cbuf.index, | 625 | .cbuf_index = cbuf.index, |
| 618 | .cbuf_offset = cbuf.offset, | 626 | .cbuf_offset = cbuf.offset, |
diff --git a/src/shader_recompiler/shader_info.h b/src/shader_recompiler/shader_info.h index f93181e1e..d308db942 100644 --- a/src/shader_recompiler/shader_info.h +++ b/src/shader_recompiler/shader_info.h | |||
| @@ -109,6 +109,7 @@ using ImageBufferDescriptors = boost::container::small_vector<ImageBufferDescrip | |||
| 109 | struct TextureDescriptor { | 109 | struct TextureDescriptor { |
| 110 | TextureType type; | 110 | TextureType type; |
| 111 | bool is_depth; | 111 | bool is_depth; |
| 112 | bool is_multisample; | ||
| 112 | bool has_secondary; | 113 | bool has_secondary; |
| 113 | u32 cbuf_index; | 114 | u32 cbuf_index; |
| 114 | u32 cbuf_offset; | 115 | u32 cbuf_offset; |
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index b474eb363..4742bcbe9 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -52,6 +52,8 @@ add_library(video_core STATIC | |||
| 52 | engines/puller.cpp | 52 | engines/puller.cpp |
| 53 | engines/puller.h | 53 | engines/puller.h |
| 54 | framebuffer_config.h | 54 | framebuffer_config.h |
| 55 | fsr.cpp | ||
| 56 | fsr.h | ||
| 55 | host1x/codecs/codec.cpp | 57 | host1x/codecs/codec.cpp |
| 56 | host1x/codecs/codec.h | 58 | host1x/codecs/codec.h |
| 57 | host1x/codecs/h264.cpp | 59 | host1x/codecs/h264.cpp |
| @@ -110,6 +112,8 @@ add_library(video_core STATIC | |||
| 110 | renderer_opengl/gl_device.h | 112 | renderer_opengl/gl_device.h |
| 111 | renderer_opengl/gl_fence_manager.cpp | 113 | renderer_opengl/gl_fence_manager.cpp |
| 112 | renderer_opengl/gl_fence_manager.h | 114 | renderer_opengl/gl_fence_manager.h |
| 115 | renderer_opengl/gl_fsr.cpp | ||
| 116 | renderer_opengl/gl_fsr.h | ||
| 113 | renderer_opengl/gl_graphics_pipeline.cpp | 117 | renderer_opengl/gl_graphics_pipeline.cpp |
| 114 | renderer_opengl/gl_graphics_pipeline.h | 118 | renderer_opengl/gl_graphics_pipeline.h |
| 115 | renderer_opengl/gl_rasterizer.cpp | 119 | renderer_opengl/gl_rasterizer.cpp |
diff --git a/src/video_core/fsr.cpp b/src/video_core/fsr.cpp new file mode 100644 index 000000000..5653c64fc --- /dev/null +++ b/src/video_core/fsr.cpp | |||
| @@ -0,0 +1,148 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <cmath> | ||
| 5 | #include "video_core/fsr.h" | ||
| 6 | |||
| 7 | namespace FSR { | ||
| 8 | namespace { | ||
| 9 | // Reimplementations of the constant generating functions in ffx_fsr1.h | ||
| 10 | // GCC generated a lot of warnings when using the official header. | ||
| 11 | u32 AU1_AH1_AF1(f32 f) { | ||
| 12 | static constexpr u32 base[512]{ | ||
| 13 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 14 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 15 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 16 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 17 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 18 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 19 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 20 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 21 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 22 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, | ||
| 23 | 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00, 0x2000, | ||
| 24 | 0x2400, 0x2800, 0x2c00, 0x3000, 0x3400, 0x3800, 0x3c00, 0x4000, 0x4400, 0x4800, 0x4c00, | ||
| 25 | 0x5000, 0x5400, 0x5800, 0x5c00, 0x6000, 0x6400, 0x6800, 0x6c00, 0x7000, 0x7400, 0x7800, | ||
| 26 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 27 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 28 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 29 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 30 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 31 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 32 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 33 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 34 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 35 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 36 | 0x7bff, 0x7bff, 0x7bff, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 37 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 38 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 39 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 40 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 41 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 42 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 43 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 44 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 45 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8001, 0x8002, 0x8004, 0x8008, | ||
| 46 | 0x8010, 0x8020, 0x8040, 0x8080, 0x8100, 0x8200, 0x8400, 0x8800, 0x8c00, 0x9000, 0x9400, | ||
| 47 | 0x9800, 0x9c00, 0xa000, 0xa400, 0xa800, 0xac00, 0xb000, 0xb400, 0xb800, 0xbc00, 0xc000, | ||
| 48 | 0xc400, 0xc800, 0xcc00, 0xd000, 0xd400, 0xd800, 0xdc00, 0xe000, 0xe400, 0xe800, 0xec00, | ||
| 49 | 0xf000, 0xf400, 0xf800, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 50 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 51 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 52 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 53 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 54 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 55 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 56 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 57 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 58 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 59 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 60 | }; | ||
| 61 | static constexpr s8 shift[512]{ | ||
| 62 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 63 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 64 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 65 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 66 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 67 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 68 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, 0x16, | ||
| 69 | 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 70 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 71 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 72 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 73 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 74 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 75 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 76 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 77 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 78 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 79 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 80 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 81 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 82 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 83 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 84 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 85 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, | ||
| 86 | 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 87 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 88 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 89 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 90 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 91 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 92 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 93 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 94 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 95 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 96 | 0x18, 0x18, | ||
| 97 | }; | ||
| 98 | const u32 u = Common::BitCast<u32>(f); | ||
| 99 | const u32 i = u >> 23; | ||
| 100 | return base[i] + ((u & 0x7fffff) >> shift[i]); | ||
| 101 | } | ||
| 102 | |||
| 103 | u32 AU1_AH2_AF2(f32 a[2]) { | ||
| 104 | return AU1_AH1_AF1(a[0]) + (AU1_AH1_AF1(a[1]) << 16); | ||
| 105 | } | ||
| 106 | |||
| 107 | void FsrEasuCon(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], f32 inputViewportInPixelsX, | ||
| 108 | f32 inputViewportInPixelsY, f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, | ||
| 109 | f32 outputSizeInPixelsX, f32 outputSizeInPixelsY) { | ||
| 110 | con0[0] = Common::BitCast<u32>(inputViewportInPixelsX / outputSizeInPixelsX); | ||
| 111 | con0[1] = Common::BitCast<u32>(inputViewportInPixelsY / outputSizeInPixelsY); | ||
| 112 | con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f); | ||
| 113 | con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f); | ||
| 114 | con1[0] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); | ||
| 115 | con1[1] = Common::BitCast<u32>(1.0f / inputSizeInPixelsY); | ||
| 116 | con1[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); | ||
| 117 | con1[3] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsY); | ||
| 118 | con2[0] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsX); | ||
| 119 | con2[1] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); | ||
| 120 | con2[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); | ||
| 121 | con2[3] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); | ||
| 122 | con3[0] = Common::BitCast<u32>(0.0f / inputSizeInPixelsX); | ||
| 123 | con3[1] = Common::BitCast<u32>(4.0f / inputSizeInPixelsY); | ||
| 124 | con3[2] = con3[3] = 0; | ||
| 125 | } | ||
| 126 | } // Anonymous namespace | ||
| 127 | |||
| 128 | void FsrEasuConOffset(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], | ||
| 129 | f32 inputViewportInPixelsX, f32 inputViewportInPixelsY, | ||
| 130 | f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, f32 outputSizeInPixelsX, | ||
| 131 | f32 outputSizeInPixelsY, f32 inputOffsetInPixelsX, f32 inputOffsetInPixelsY) { | ||
| 132 | FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, | ||
| 133 | inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); | ||
| 134 | con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f + | ||
| 135 | inputOffsetInPixelsX); | ||
| 136 | con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f + | ||
| 137 | inputOffsetInPixelsY); | ||
| 138 | } | ||
| 139 | |||
| 140 | void FsrRcasCon(u32* con, f32 sharpness) { | ||
| 141 | sharpness = std::exp2f(-sharpness); | ||
| 142 | f32 hSharp[2]{sharpness, sharpness}; | ||
| 143 | con[0] = Common::BitCast<u32>(sharpness); | ||
| 144 | con[1] = AU1_AH2_AF2(hSharp); | ||
| 145 | con[2] = 0; | ||
| 146 | con[3] = 0; | ||
| 147 | } | ||
| 148 | } // namespace FSR | ||
diff --git a/src/video_core/fsr.h b/src/video_core/fsr.h new file mode 100644 index 000000000..db0d4ec6f --- /dev/null +++ b/src/video_core/fsr.h | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include "common/bit_cast.h" | ||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace FSR { | ||
| 10 | // Reimplementations of the constant generating functions in ffx_fsr1.h | ||
| 11 | // GCC generated a lot of warnings when using the official header. | ||
| 12 | void FsrEasuConOffset(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], | ||
| 13 | f32 inputViewportInPixelsX, f32 inputViewportInPixelsY, | ||
| 14 | f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, f32 outputSizeInPixelsX, | ||
| 15 | f32 outputSizeInPixelsY, f32 inputOffsetInPixelsX, f32 inputOffsetInPixelsY); | ||
| 16 | |||
| 17 | void FsrRcasCon(u32* con, f32 sharpness); | ||
| 18 | |||
| 19 | } // namespace FSR | ||
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt index e968ae220..dad7b07d4 100644 --- a/src/video_core/host_shaders/CMakeLists.txt +++ b/src/video_core/host_shaders/CMakeLists.txt | |||
| @@ -3,12 +3,16 @@ | |||
| 3 | 3 | ||
| 4 | set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr) | 4 | set(FIDELITYFX_INCLUDE_DIR ${CMAKE_SOURCE_DIR}/externals/FidelityFX-FSR/ffx-fsr) |
| 5 | 5 | ||
| 6 | set(GLSL_INCLUDES | 6 | set(FIDELITYFX_FILES |
| 7 | fidelityfx_fsr.comp | ||
| 8 | ${FIDELITYFX_INCLUDE_DIR}/ffx_a.h | 7 | ${FIDELITYFX_INCLUDE_DIR}/ffx_a.h |
| 9 | ${FIDELITYFX_INCLUDE_DIR}/ffx_fsr1.h | 8 | ${FIDELITYFX_INCLUDE_DIR}/ffx_fsr1.h |
| 10 | ) | 9 | ) |
| 11 | 10 | ||
| 11 | set(GLSL_INCLUDES | ||
| 12 | fidelityfx_fsr.comp | ||
| 13 | ${FIDELITYFX_FILES} | ||
| 14 | ) | ||
| 15 | |||
| 12 | set(SHADER_FILES | 16 | set(SHADER_FILES |
| 13 | astc_decoder.comp | 17 | astc_decoder.comp |
| 14 | blit_color_float.frag | 18 | blit_color_float.frag |
| @@ -24,6 +28,9 @@ set(SHADER_FILES | |||
| 24 | fxaa.vert | 28 | fxaa.vert |
| 25 | opengl_convert_s8d24.comp | 29 | opengl_convert_s8d24.comp |
| 26 | opengl_copy_bc4.comp | 30 | opengl_copy_bc4.comp |
| 31 | opengl_fidelityfx_fsr.frag | ||
| 32 | opengl_fidelityfx_fsr_easu.frag | ||
| 33 | opengl_fidelityfx_fsr_rcas.frag | ||
| 27 | opengl_present.frag | 34 | opengl_present.frag |
| 28 | opengl_present.vert | 35 | opengl_present.vert |
| 29 | opengl_present_scaleforce.frag | 36 | opengl_present_scaleforce.frag |
| @@ -118,6 +125,25 @@ foreach(FILENAME IN ITEMS ${SHADER_FILES}) | |||
| 118 | endif() | 125 | endif() |
| 119 | endforeach() | 126 | endforeach() |
| 120 | 127 | ||
| 128 | foreach(FILEPATH IN ITEMS ${FIDELITYFX_FILES}) | ||
| 129 | get_filename_component(FILENAME ${FILEPATH} NAME) | ||
| 130 | string(REPLACE "." "_" HEADER_NAME ${FILENAME}) | ||
| 131 | set(SOURCE_FILE ${FILEPATH}) | ||
| 132 | set(SOURCE_HEADER_FILE ${SHADER_DIR}/${HEADER_NAME}.h) | ||
| 133 | add_custom_command( | ||
| 134 | OUTPUT | ||
| 135 | ${SOURCE_HEADER_FILE} | ||
| 136 | COMMAND | ||
| 137 | ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE} | ||
| 138 | MAIN_DEPENDENCY | ||
| 139 | ${SOURCE_FILE} | ||
| 140 | DEPENDS | ||
| 141 | ${INPUT_FILE} | ||
| 142 | # HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified | ||
| 143 | ) | ||
| 144 | set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE}) | ||
| 145 | endforeach() | ||
| 146 | |||
| 121 | set(SHADER_SOURCES ${SHADER_FILES}) | 147 | set(SHADER_SOURCES ${SHADER_FILES}) |
| 122 | list(APPEND SHADER_SOURCES ${GLSL_INCLUDES}) | 148 | list(APPEND SHADER_SOURCES ${GLSL_INCLUDES}) |
| 123 | 149 | ||
diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag new file mode 100644 index 000000000..16d22f58e --- /dev/null +++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr.frag | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | //!#version 460 core | ||
| 5 | #extension GL_ARB_separate_shader_objects : enable | ||
| 6 | #extension GL_ARB_shading_language_420pack : enable | ||
| 7 | |||
| 8 | #extension GL_AMD_gpu_shader_half_float : enable | ||
| 9 | #extension GL_NV_gpu_shader5 : enable | ||
| 10 | |||
| 11 | // FidelityFX Super Resolution Sample | ||
| 12 | // | ||
| 13 | // Copyright (c) 2021 Advanced Micro Devices, Inc. All rights reserved. | ||
| 14 | // Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| 15 | // of this software and associated documentation files(the "Software"), to deal | ||
| 16 | // in the Software without restriction, including without limitation the rights | ||
| 17 | // to use, copy, modify, merge, publish, distribute, sublicense, and / or sell | ||
| 18 | // copies of the Software, and to permit persons to whom the Software is | ||
| 19 | // furnished to do so, subject to the following conditions : | ||
| 20 | // The above copyright notice and this permission notice shall be included in | ||
| 21 | // all copies or substantial portions of the Software. | ||
| 22 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| 23 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| 24 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE | ||
| 25 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| 26 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| 27 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | ||
| 28 | // THE SOFTWARE. | ||
| 29 | |||
| 30 | layout (location = 0) uniform uvec4 constants[4]; | ||
| 31 | |||
| 32 | #define A_GPU 1 | ||
| 33 | #define A_GLSL 1 | ||
| 34 | |||
| 35 | #ifdef YUZU_USE_FP16 | ||
| 36 | #define A_HALF | ||
| 37 | #endif | ||
| 38 | #include "ffx_a.h" | ||
| 39 | |||
| 40 | #ifndef YUZU_USE_FP16 | ||
| 41 | layout (binding=0) uniform sampler2D InputTexture; | ||
| 42 | #if USE_EASU | ||
| 43 | #define FSR_EASU_F 1 | ||
| 44 | AF4 FsrEasuRF(AF2 p) { AF4 res = textureGather(InputTexture, p, 0); return res; } | ||
| 45 | AF4 FsrEasuGF(AF2 p) { AF4 res = textureGather(InputTexture, p, 1); return res; } | ||
| 46 | AF4 FsrEasuBF(AF2 p) { AF4 res = textureGather(InputTexture, p, 2); return res; } | ||
| 47 | #endif | ||
| 48 | #if USE_RCAS | ||
| 49 | #define FSR_RCAS_F | ||
| 50 | AF4 FsrRcasLoadF(ASU2 p) { return texelFetch(InputTexture, ASU2(p), 0); } | ||
| 51 | void FsrRcasInputF(inout AF1 r, inout AF1 g, inout AF1 b) {} | ||
| 52 | #endif | ||
| 53 | #else | ||
| 54 | layout (binding=0) uniform sampler2D InputTexture; | ||
| 55 | #if USE_EASU | ||
| 56 | #define FSR_EASU_H 1 | ||
| 57 | AH4 FsrEasuRH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 0)); return res; } | ||
| 58 | AH4 FsrEasuGH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 1)); return res; } | ||
| 59 | AH4 FsrEasuBH(AF2 p) { AH4 res = AH4(textureGather(InputTexture, p, 2)); return res; } | ||
| 60 | #endif | ||
| 61 | #if USE_RCAS | ||
| 62 | #define FSR_RCAS_H | ||
| 63 | AH4 FsrRcasLoadH(ASW2 p) { return AH4(texelFetch(InputTexture, ASU2(p), 0)); } | ||
| 64 | void FsrRcasInputH(inout AH1 r,inout AH1 g,inout AH1 b){} | ||
| 65 | #endif | ||
| 66 | #endif | ||
| 67 | |||
| 68 | #include "ffx_fsr1.h" | ||
| 69 | |||
| 70 | #if USE_RCAS | ||
| 71 | layout(location = 0) in vec2 frag_texcoord; | ||
| 72 | #endif | ||
| 73 | layout (location = 0) out vec4 frag_color; | ||
| 74 | |||
| 75 | void CurrFilter(AU2 pos) | ||
| 76 | { | ||
| 77 | #if USE_EASU | ||
| 78 | #ifndef YUZU_USE_FP16 | ||
| 79 | AF3 c; | ||
| 80 | FsrEasuF(c, pos, constants[0], constants[1], constants[2], constants[3]); | ||
| 81 | frag_color = AF4(c, 1.0); | ||
| 82 | #else | ||
| 83 | AH3 c; | ||
| 84 | FsrEasuH(c, pos, constants[0], constants[1], constants[2], constants[3]); | ||
| 85 | frag_color = AH4(c, 1.0); | ||
| 86 | #endif | ||
| 87 | #endif | ||
| 88 | #if USE_RCAS | ||
| 89 | #ifndef YUZU_USE_FP16 | ||
| 90 | AF3 c; | ||
| 91 | FsrRcasF(c.r, c.g, c.b, pos, constants[0]); | ||
| 92 | frag_color = AF4(c, 1.0); | ||
| 93 | #else | ||
| 94 | AH3 c; | ||
| 95 | FsrRcasH(c.r, c.g, c.b, pos, constants[0]); | ||
| 96 | frag_color = AH4(c, 1.0); | ||
| 97 | #endif | ||
| 98 | #endif | ||
| 99 | } | ||
| 100 | |||
| 101 | void main() | ||
| 102 | { | ||
| 103 | #if USE_RCAS | ||
| 104 | CurrFilter(AU2(frag_texcoord * vec2(textureSize(InputTexture, 0)))); | ||
| 105 | #else | ||
| 106 | CurrFilter(AU2(gl_FragCoord.xy)); | ||
| 107 | #endif | ||
| 108 | } | ||
diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr_easu.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr_easu.frag new file mode 100644 index 000000000..d39f80ac1 --- /dev/null +++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr_easu.frag | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #version 460 core | ||
| 5 | #extension GL_GOOGLE_include_directive : enable | ||
| 6 | |||
| 7 | #define USE_EASU 1 | ||
| 8 | |||
| 9 | #include "opengl_fidelityfx_fsr.frag" | ||
diff --git a/src/video_core/host_shaders/opengl_fidelityfx_fsr_rcas.frag b/src/video_core/host_shaders/opengl_fidelityfx_fsr_rcas.frag new file mode 100644 index 000000000..cfa78ddc7 --- /dev/null +++ b/src/video_core/host_shaders/opengl_fidelityfx_fsr_rcas.frag | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #version 460 core | ||
| 5 | #extension GL_GOOGLE_include_directive : enable | ||
| 6 | |||
| 7 | #define USE_RCAS 1 | ||
| 8 | |||
| 9 | #include "opengl_fidelityfx_fsr.frag" | ||
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index 3bcae3503..83924475b 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp | |||
| @@ -6,7 +6,6 @@ | |||
| 6 | #include "common/alignment.h" | 6 | #include "common/alignment.h" |
| 7 | #include "common/assert.h" | 7 | #include "common/assert.h" |
| 8 | #include "common/logging/log.h" | 8 | #include "common/logging/log.h" |
| 9 | #include "common/settings.h" | ||
| 10 | #include "core/core.h" | 9 | #include "core/core.h" |
| 11 | #include "core/device_memory.h" | 10 | #include "core/device_memory.h" |
| 12 | #include "core/hle/kernel/k_page_table.h" | 11 | #include "core/hle/kernel/k_page_table.h" |
| @@ -46,11 +45,6 @@ MemoryManager::MemoryManager(Core::System& system_, u64 address_space_bits_, u64 | |||
| 46 | big_page_table_cpu.resize(big_page_table_size); | 45 | big_page_table_cpu.resize(big_page_table_size); |
| 47 | big_page_continous.resize(big_page_table_size / continous_bits, 0); | 46 | big_page_continous.resize(big_page_table_size / continous_bits, 0); |
| 48 | entries.resize(page_table_size / 32, 0); | 47 | entries.resize(page_table_size / 32, 0); |
| 49 | if (!Settings::IsGPULevelExtreme() && Settings::IsFastmemEnabled()) { | ||
| 50 | fastmem_arena = system.DeviceMemory().buffer.VirtualBasePointer(); | ||
| 51 | } else { | ||
| 52 | fastmem_arena = nullptr; | ||
| 53 | } | ||
| 54 | } | 48 | } |
| 55 | 49 | ||
| 56 | MemoryManager::~MemoryManager() = default; | 50 | MemoryManager::~MemoryManager() = default; |
| @@ -360,7 +354,7 @@ inline void MemoryManager::MemoryOperation(GPUVAddr gpu_src_addr, std::size_t si | |||
| 360 | } | 354 | } |
| 361 | } | 355 | } |
| 362 | 356 | ||
| 363 | template <bool is_safe, bool use_fastmem> | 357 | template <bool is_safe> |
| 364 | void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, | 358 | void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, |
| 365 | [[maybe_unused]] VideoCommon::CacheType which) const { | 359 | [[maybe_unused]] VideoCommon::CacheType which) const { |
| 366 | auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index, | 360 | auto set_to_zero = [&]([[maybe_unused]] std::size_t page_index, |
| @@ -374,12 +368,8 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: | |||
| 374 | if constexpr (is_safe) { | 368 | if constexpr (is_safe) { |
| 375 | rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); | 369 | rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); |
| 376 | } | 370 | } |
| 377 | if constexpr (use_fastmem) { | 371 | u8* physical = memory.GetPointer(cpu_addr_base); |
| 378 | std::memcpy(dest_buffer, &fastmem_arena[cpu_addr_base], copy_amount); | 372 | std::memcpy(dest_buffer, physical, copy_amount); |
| 379 | } else { | ||
| 380 | u8* physical = memory.GetPointer(cpu_addr_base); | ||
| 381 | std::memcpy(dest_buffer, physical, copy_amount); | ||
| 382 | } | ||
| 383 | dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; | 373 | dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; |
| 384 | }; | 374 | }; |
| 385 | auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { | 375 | auto mapped_big = [&](std::size_t page_index, std::size_t offset, std::size_t copy_amount) { |
| @@ -388,15 +378,11 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: | |||
| 388 | if constexpr (is_safe) { | 378 | if constexpr (is_safe) { |
| 389 | rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); | 379 | rasterizer->FlushRegion(cpu_addr_base, copy_amount, which); |
| 390 | } | 380 | } |
| 391 | if constexpr (use_fastmem) { | 381 | if (!IsBigPageContinous(page_index)) [[unlikely]] { |
| 392 | std::memcpy(dest_buffer, &fastmem_arena[cpu_addr_base], copy_amount); | 382 | memory.ReadBlockUnsafe(cpu_addr_base, dest_buffer, copy_amount); |
| 393 | } else { | 383 | } else { |
| 394 | if (!IsBigPageContinous(page_index)) [[unlikely]] { | 384 | u8* physical = memory.GetPointer(cpu_addr_base); |
| 395 | memory.ReadBlockUnsafe(cpu_addr_base, dest_buffer, copy_amount); | 385 | std::memcpy(dest_buffer, physical, copy_amount); |
| 396 | } else { | ||
| 397 | u8* physical = memory.GetPointer(cpu_addr_base); | ||
| 398 | std::memcpy(dest_buffer, physical, copy_amount); | ||
| 399 | } | ||
| 400 | } | 386 | } |
| 401 | dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; | 387 | dest_buffer = static_cast<u8*>(dest_buffer) + copy_amount; |
| 402 | }; | 388 | }; |
| @@ -410,20 +396,12 @@ void MemoryManager::ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std: | |||
| 410 | 396 | ||
| 411 | void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, | 397 | void MemoryManager::ReadBlock(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, |
| 412 | VideoCommon::CacheType which) const { | 398 | VideoCommon::CacheType which) const { |
| 413 | if (fastmem_arena) [[likely]] { | 399 | ReadBlockImpl<true>(gpu_src_addr, dest_buffer, size, which); |
| 414 | ReadBlockImpl<true, true>(gpu_src_addr, dest_buffer, size, which); | ||
| 415 | return; | ||
| 416 | } | ||
| 417 | ReadBlockImpl<true, false>(gpu_src_addr, dest_buffer, size, which); | ||
| 418 | } | 400 | } |
| 419 | 401 | ||
| 420 | void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, | 402 | void MemoryManager::ReadBlockUnsafe(GPUVAddr gpu_src_addr, void* dest_buffer, |
| 421 | const std::size_t size) const { | 403 | const std::size_t size) const { |
| 422 | if (fastmem_arena) [[likely]] { | 404 | ReadBlockImpl<false>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); |
| 423 | ReadBlockImpl<false, true>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); | ||
| 424 | return; | ||
| 425 | } | ||
| 426 | ReadBlockImpl<false, false>(gpu_src_addr, dest_buffer, size, VideoCommon::CacheType::None); | ||
| 427 | } | 405 | } |
| 428 | 406 | ||
| 429 | template <bool is_safe> | 407 | template <bool is_safe> |
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h index 2936364f0..9ebfb6179 100644 --- a/src/video_core/memory_manager.h +++ b/src/video_core/memory_manager.h | |||
| @@ -141,7 +141,7 @@ private: | |||
| 141 | inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped, | 141 | inline void MemoryOperation(GPUVAddr gpu_src_addr, std::size_t size, FuncMapped&& func_mapped, |
| 142 | FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const; | 142 | FuncReserved&& func_reserved, FuncUnmapped&& func_unmapped) const; |
| 143 | 143 | ||
| 144 | template <bool is_safe, bool use_fastmem> | 144 | template <bool is_safe> |
| 145 | void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, | 145 | void ReadBlockImpl(GPUVAddr gpu_src_addr, void* dest_buffer, std::size_t size, |
| 146 | VideoCommon::CacheType which) const; | 146 | VideoCommon::CacheType which) const; |
| 147 | 147 | ||
| @@ -215,7 +215,6 @@ private: | |||
| 215 | 215 | ||
| 216 | std::vector<u64> big_page_continous; | 216 | std::vector<u64> big_page_continous; |
| 217 | std::vector<std::pair<VAddr, std::size_t>> page_stash{}; | 217 | std::vector<std::pair<VAddr, std::size_t>> page_stash{}; |
| 218 | u8* fastmem_arena{}; | ||
| 219 | 218 | ||
| 220 | constexpr static size_t continous_bits = 64; | 219 | constexpr static size_t continous_bits = 64; |
| 221 | 220 | ||
diff --git a/src/video_core/renderer_opengl/gl_fsr.cpp b/src/video_core/renderer_opengl/gl_fsr.cpp new file mode 100644 index 000000000..77262dcf1 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_fsr.cpp | |||
| @@ -0,0 +1,101 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include "common/settings.h" | ||
| 5 | #include "video_core/fsr.h" | ||
| 6 | #include "video_core/renderer_opengl/gl_fsr.h" | ||
| 7 | #include "video_core/renderer_opengl/gl_shader_manager.h" | ||
| 8 | #include "video_core/renderer_opengl/gl_shader_util.h" | ||
| 9 | |||
| 10 | namespace OpenGL { | ||
| 11 | using namespace FSR; | ||
| 12 | |||
| 13 | using FsrConstants = std::array<u32, 4 * 4>; | ||
| 14 | |||
| 15 | FSR::FSR(std::string_view fsr_vertex_source, std::string_view fsr_easu_source, | ||
| 16 | std::string_view fsr_rcas_source) | ||
| 17 | : fsr_vertex{CreateProgram(fsr_vertex_source, GL_VERTEX_SHADER)}, | ||
| 18 | fsr_easu_frag{CreateProgram(fsr_easu_source, GL_FRAGMENT_SHADER)}, | ||
| 19 | fsr_rcas_frag{CreateProgram(fsr_rcas_source, GL_FRAGMENT_SHADER)} { | ||
| 20 | glProgramUniform2f(fsr_vertex.handle, 0, 1.0f, 1.0f); | ||
| 21 | glProgramUniform2f(fsr_vertex.handle, 1, 0.0f, 0.0f); | ||
| 22 | } | ||
| 23 | |||
| 24 | FSR::~FSR() = default; | ||
| 25 | |||
| 26 | void FSR::Draw(ProgramManager& program_manager, const Common::Rectangle<u32>& screen, | ||
| 27 | u32 input_image_width, u32 input_image_height, | ||
| 28 | const Common::Rectangle<int>& crop_rect) { | ||
| 29 | |||
| 30 | const auto output_image_width = screen.GetWidth(); | ||
| 31 | const auto output_image_height = screen.GetHeight(); | ||
| 32 | |||
| 33 | if (fsr_intermediate_tex.handle) { | ||
| 34 | GLint fsr_tex_width, fsr_tex_height; | ||
| 35 | glGetTextureLevelParameteriv(fsr_intermediate_tex.handle, 0, GL_TEXTURE_WIDTH, | ||
| 36 | &fsr_tex_width); | ||
| 37 | glGetTextureLevelParameteriv(fsr_intermediate_tex.handle, 0, GL_TEXTURE_HEIGHT, | ||
| 38 | &fsr_tex_height); | ||
| 39 | if (static_cast<u32>(fsr_tex_width) != output_image_width || | ||
| 40 | static_cast<u32>(fsr_tex_height) != output_image_height) { | ||
| 41 | fsr_intermediate_tex.Release(); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | if (!fsr_intermediate_tex.handle) { | ||
| 45 | fsr_intermediate_tex.Create(GL_TEXTURE_2D); | ||
| 46 | glTextureStorage2D(fsr_intermediate_tex.handle, 1, GL_RGB16F, output_image_width, | ||
| 47 | output_image_height); | ||
| 48 | glNamedFramebufferTexture(fsr_framebuffer.handle, GL_COLOR_ATTACHMENT0, | ||
| 49 | fsr_intermediate_tex.handle, 0); | ||
| 50 | } | ||
| 51 | |||
| 52 | GLint old_draw_fb; | ||
| 53 | glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &old_draw_fb); | ||
| 54 | |||
| 55 | glFrontFace(GL_CW); | ||
| 56 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fsr_framebuffer.handle); | ||
| 57 | glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(output_image_width), | ||
| 58 | static_cast<GLfloat>(output_image_height)); | ||
| 59 | |||
| 60 | FsrConstants constants; | ||
| 61 | FsrEasuConOffset( | ||
| 62 | constants.data() + 0, constants.data() + 4, constants.data() + 8, constants.data() + 12, | ||
| 63 | |||
| 64 | static_cast<f32>(crop_rect.GetWidth()), static_cast<f32>(crop_rect.GetHeight()), | ||
| 65 | static_cast<f32>(input_image_width), static_cast<f32>(input_image_height), | ||
| 66 | static_cast<f32>(output_image_width), static_cast<f32>(output_image_height), | ||
| 67 | static_cast<f32>(crop_rect.left), static_cast<f32>(crop_rect.top)); | ||
| 68 | |||
| 69 | glProgramUniform4uiv(fsr_easu_frag.handle, 0, sizeof(constants), std::data(constants)); | ||
| 70 | |||
| 71 | program_manager.BindPresentPrograms(fsr_vertex.handle, fsr_easu_frag.handle); | ||
| 72 | glDrawArrays(GL_TRIANGLES, 0, 3); | ||
| 73 | |||
| 74 | glBindFramebuffer(GL_DRAW_FRAMEBUFFER, old_draw_fb); | ||
| 75 | glBindTextureUnit(0, fsr_intermediate_tex.handle); | ||
| 76 | |||
| 77 | const float sharpening = | ||
| 78 | static_cast<float>(Settings::values.fsr_sharpening_slider.GetValue()) / 100.0f; | ||
| 79 | |||
| 80 | FsrRcasCon(constants.data(), sharpening); | ||
| 81 | glProgramUniform4uiv(fsr_rcas_frag.handle, 0, sizeof(constants), std::data(constants)); | ||
| 82 | } | ||
| 83 | |||
| 84 | void FSR::InitBuffers() { | ||
| 85 | fsr_framebuffer.Create(); | ||
| 86 | } | ||
| 87 | |||
| 88 | void FSR::ReleaseBuffers() { | ||
| 89 | fsr_framebuffer.Release(); | ||
| 90 | fsr_intermediate_tex.Release(); | ||
| 91 | } | ||
| 92 | |||
| 93 | const OGLProgram& FSR::GetPresentFragmentProgram() const noexcept { | ||
| 94 | return fsr_rcas_frag; | ||
| 95 | } | ||
| 96 | |||
| 97 | bool FSR::AreBuffersInitialized() const noexcept { | ||
| 98 | return fsr_framebuffer.handle; | ||
| 99 | } | ||
| 100 | |||
| 101 | } // namespace OpenGL | ||
diff --git a/src/video_core/renderer_opengl/gl_fsr.h b/src/video_core/renderer_opengl/gl_fsr.h new file mode 100644 index 000000000..1f6ae3115 --- /dev/null +++ b/src/video_core/renderer_opengl/gl_fsr.h | |||
| @@ -0,0 +1,43 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <string_view> | ||
| 7 | |||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "common/math_util.h" | ||
| 10 | #include "video_core/fsr.h" | ||
| 11 | #include "video_core/renderer_opengl/gl_resource_manager.h" | ||
| 12 | |||
| 13 | namespace OpenGL { | ||
| 14 | |||
| 15 | class ProgramManager; | ||
| 16 | |||
| 17 | class FSR { | ||
| 18 | public: | ||
| 19 | explicit FSR(std::string_view fsr_vertex_source, std::string_view fsr_easu_source, | ||
| 20 | std::string_view fsr_rcas_source); | ||
| 21 | ~FSR(); | ||
| 22 | |||
| 23 | void Draw(ProgramManager& program_manager, const Common::Rectangle<u32>& screen, | ||
| 24 | u32 input_image_width, u32 input_image_height, | ||
| 25 | const Common::Rectangle<int>& crop_rect); | ||
| 26 | |||
| 27 | void InitBuffers(); | ||
| 28 | |||
| 29 | void ReleaseBuffers(); | ||
| 30 | |||
| 31 | [[nodiscard]] const OGLProgram& GetPresentFragmentProgram() const noexcept; | ||
| 32 | |||
| 33 | [[nodiscard]] bool AreBuffersInitialized() const noexcept; | ||
| 34 | |||
| 35 | private: | ||
| 36 | OGLFramebuffer fsr_framebuffer; | ||
| 37 | OGLProgram fsr_vertex; | ||
| 38 | OGLProgram fsr_easu_frag; | ||
| 39 | OGLProgram fsr_rcas_frag; | ||
| 40 | OGLTexture fsr_intermediate_tex; | ||
| 41 | }; | ||
| 42 | |||
| 43 | } // namespace OpenGL | ||
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp index de95f2634..2a74c1d05 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.cpp +++ b/src/video_core/renderer_opengl/renderer_opengl.cpp | |||
| @@ -17,8 +17,14 @@ | |||
| 17 | #include "core/frontend/emu_window.h" | 17 | #include "core/frontend/emu_window.h" |
| 18 | #include "core/memory.h" | 18 | #include "core/memory.h" |
| 19 | #include "core/telemetry_session.h" | 19 | #include "core/telemetry_session.h" |
| 20 | #include "video_core/host_shaders/ffx_a_h.h" | ||
| 21 | #include "video_core/host_shaders/ffx_fsr1_h.h" | ||
| 22 | #include "video_core/host_shaders/full_screen_triangle_vert.h" | ||
| 20 | #include "video_core/host_shaders/fxaa_frag.h" | 23 | #include "video_core/host_shaders/fxaa_frag.h" |
| 21 | #include "video_core/host_shaders/fxaa_vert.h" | 24 | #include "video_core/host_shaders/fxaa_vert.h" |
| 25 | #include "video_core/host_shaders/opengl_fidelityfx_fsr_easu_frag.h" | ||
| 26 | #include "video_core/host_shaders/opengl_fidelityfx_fsr_frag.h" | ||
| 27 | #include "video_core/host_shaders/opengl_fidelityfx_fsr_rcas_frag.h" | ||
| 22 | #include "video_core/host_shaders/opengl_present_frag.h" | 28 | #include "video_core/host_shaders/opengl_present_frag.h" |
| 23 | #include "video_core/host_shaders/opengl_present_scaleforce_frag.h" | 29 | #include "video_core/host_shaders/opengl_present_scaleforce_frag.h" |
| 24 | #include "video_core/host_shaders/opengl_present_vert.h" | 30 | #include "video_core/host_shaders/opengl_present_vert.h" |
| @@ -31,6 +37,7 @@ | |||
| 31 | #include "video_core/host_shaders/smaa_edge_detection_vert.h" | 37 | #include "video_core/host_shaders/smaa_edge_detection_vert.h" |
| 32 | #include "video_core/host_shaders/smaa_neighborhood_blending_frag.h" | 38 | #include "video_core/host_shaders/smaa_neighborhood_blending_frag.h" |
| 33 | #include "video_core/host_shaders/smaa_neighborhood_blending_vert.h" | 39 | #include "video_core/host_shaders/smaa_neighborhood_blending_vert.h" |
| 40 | #include "video_core/renderer_opengl/gl_fsr.h" | ||
| 34 | #include "video_core/renderer_opengl/gl_rasterizer.h" | 41 | #include "video_core/renderer_opengl/gl_rasterizer.h" |
| 35 | #include "video_core/renderer_opengl/gl_shader_manager.h" | 42 | #include "video_core/renderer_opengl/gl_shader_manager.h" |
| 36 | #include "video_core/renderer_opengl/gl_shader_util.h" | 43 | #include "video_core/renderer_opengl/gl_shader_util.h" |
| @@ -268,12 +275,17 @@ void RendererOpenGL::InitOpenGLObjects() { | |||
| 268 | fxaa_vertex = CreateProgram(HostShaders::FXAA_VERT, GL_VERTEX_SHADER); | 275 | fxaa_vertex = CreateProgram(HostShaders::FXAA_VERT, GL_VERTEX_SHADER); |
| 269 | fxaa_fragment = CreateProgram(HostShaders::FXAA_FRAG, GL_FRAGMENT_SHADER); | 276 | fxaa_fragment = CreateProgram(HostShaders::FXAA_FRAG, GL_FRAGMENT_SHADER); |
| 270 | 277 | ||
| 271 | const auto SmaaShader = [](std::string_view specialized_source, GLenum stage) { | 278 | const auto replace_include = [](std::string& shader_source, std::string_view include_name, |
| 272 | std::string shader_source{specialized_source}; | 279 | std::string_view include_content) { |
| 273 | constexpr std::string_view include_string = "#include \"opengl_smaa.glsl\""; | 280 | const std::string include_string = fmt::format("#include \"{}\"", include_name); |
| 274 | const std::size_t pos = shader_source.find(include_string); | 281 | const std::size_t pos = shader_source.find(include_string); |
| 275 | ASSERT(pos != std::string::npos); | 282 | ASSERT(pos != std::string::npos); |
| 276 | shader_source.replace(pos, include_string.size(), HostShaders::OPENGL_SMAA_GLSL); | 283 | shader_source.replace(pos, include_string.size(), include_content); |
| 284 | }; | ||
| 285 | |||
| 286 | const auto SmaaShader = [&](std::string_view specialized_source, GLenum stage) { | ||
| 287 | std::string shader_source{specialized_source}; | ||
| 288 | replace_include(shader_source, "opengl_smaa.glsl", HostShaders::OPENGL_SMAA_GLSL); | ||
| 277 | return CreateProgram(shader_source, stage); | 289 | return CreateProgram(shader_source, stage); |
| 278 | }; | 290 | }; |
| 279 | 291 | ||
| @@ -298,14 +310,32 @@ void RendererOpenGL::InitOpenGLObjects() { | |||
| 298 | CreateProgram(fmt::format("#version 460\n{}", HostShaders::OPENGL_PRESENT_SCALEFORCE_FRAG), | 310 | CreateProgram(fmt::format("#version 460\n{}", HostShaders::OPENGL_PRESENT_SCALEFORCE_FRAG), |
| 299 | GL_FRAGMENT_SHADER); | 311 | GL_FRAGMENT_SHADER); |
| 300 | 312 | ||
| 313 | std::string fsr_source{HostShaders::OPENGL_FIDELITYFX_FSR_FRAG}; | ||
| 314 | replace_include(fsr_source, "ffx_a.h", HostShaders::FFX_A_H); | ||
| 315 | replace_include(fsr_source, "ffx_fsr1.h", HostShaders::FFX_FSR1_H); | ||
| 316 | |||
| 317 | std::string fsr_easu_frag_source{HostShaders::OPENGL_FIDELITYFX_FSR_EASU_FRAG}; | ||
| 318 | std::string fsr_rcas_frag_source{HostShaders::OPENGL_FIDELITYFX_FSR_RCAS_FRAG}; | ||
| 319 | replace_include(fsr_easu_frag_source, "opengl_fidelityfx_fsr.frag", fsr_source); | ||
| 320 | replace_include(fsr_rcas_frag_source, "opengl_fidelityfx_fsr.frag", fsr_source); | ||
| 321 | |||
| 322 | fsr = std::make_unique<FSR>(HostShaders::FULL_SCREEN_TRIANGLE_VERT, fsr_easu_frag_source, | ||
| 323 | fsr_rcas_frag_source); | ||
| 324 | |||
| 301 | // Generate presentation sampler | 325 | // Generate presentation sampler |
| 302 | present_sampler.Create(); | 326 | present_sampler.Create(); |
| 303 | glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); | 327 | glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MIN_FILTER, GL_LINEAR); |
| 304 | glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR); | 328 | glSamplerParameteri(present_sampler.handle, GL_TEXTURE_MAG_FILTER, GL_LINEAR); |
| 329 | glSamplerParameteri(present_sampler.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||
| 330 | glSamplerParameteri(present_sampler.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||
| 331 | glSamplerParameteri(present_sampler.handle, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); | ||
| 305 | 332 | ||
| 306 | present_sampler_nn.Create(); | 333 | present_sampler_nn.Create(); |
| 307 | glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_MIN_FILTER, GL_NEAREST); | 334 | glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_MIN_FILTER, GL_NEAREST); |
| 308 | glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_MAG_FILTER, GL_NEAREST); | 335 | glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_MAG_FILTER, GL_NEAREST); |
| 336 | glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); | ||
| 337 | glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); | ||
| 338 | glSamplerParameteri(present_sampler_nn.handle, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); | ||
| 309 | 339 | ||
| 310 | // Generate VBO handle for drawing | 340 | // Generate VBO handle for drawing |
| 311 | vertex_buffer.Create(); | 341 | vertex_buffer.Create(); |
| @@ -525,6 +555,31 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { | |||
| 525 | 555 | ||
| 526 | glBindTextureUnit(0, aa_texture.handle); | 556 | glBindTextureUnit(0, aa_texture.handle); |
| 527 | } | 557 | } |
| 558 | glDisablei(GL_SCISSOR_TEST, 0); | ||
| 559 | |||
| 560 | if (Settings::values.scaling_filter.GetValue() == Settings::ScalingFilter::Fsr) { | ||
| 561 | if (!fsr->AreBuffersInitialized()) { | ||
| 562 | fsr->InitBuffers(); | ||
| 563 | } | ||
| 564 | |||
| 565 | auto crop_rect = framebuffer_crop_rect; | ||
| 566 | if (crop_rect.GetWidth() == 0) { | ||
| 567 | crop_rect.right = framebuffer_width; | ||
| 568 | } | ||
| 569 | if (crop_rect.GetHeight() == 0) { | ||
| 570 | crop_rect.bottom = framebuffer_height; | ||
| 571 | } | ||
| 572 | crop_rect = crop_rect.Scale(Settings::values.resolution_info.up_factor); | ||
| 573 | const auto fsr_input_width = Settings::values.resolution_info.ScaleUp(framebuffer_width); | ||
| 574 | const auto fsr_input_height = Settings::values.resolution_info.ScaleUp(framebuffer_height); | ||
| 575 | glBindSampler(0, present_sampler.handle); | ||
| 576 | fsr->Draw(program_manager, layout.screen, fsr_input_width, fsr_input_height, crop_rect); | ||
| 577 | } else { | ||
| 578 | if (fsr->AreBuffersInitialized()) { | ||
| 579 | fsr->ReleaseBuffers(); | ||
| 580 | } | ||
| 581 | } | ||
| 582 | |||
| 528 | const std::array ortho_matrix = | 583 | const std::array ortho_matrix = |
| 529 | MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height)); | 584 | MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height)); |
| 530 | 585 | ||
| @@ -540,10 +595,7 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { | |||
| 540 | case Settings::ScalingFilter::ScaleForce: | 595 | case Settings::ScalingFilter::ScaleForce: |
| 541 | return present_scaleforce_fragment.handle; | 596 | return present_scaleforce_fragment.handle; |
| 542 | case Settings::ScalingFilter::Fsr: | 597 | case Settings::ScalingFilter::Fsr: |
| 543 | LOG_WARNING( | 598 | return fsr->GetPresentFragmentProgram().handle; |
| 544 | Render_OpenGL, | ||
| 545 | "FidelityFX Super Resolution is not supported in OpenGL, changing to ScaleForce"); | ||
| 546 | return present_scaleforce_fragment.handle; | ||
| 547 | default: | 599 | default: |
| 548 | return present_bilinear_fragment.handle; | 600 | return present_bilinear_fragment.handle; |
| 549 | } | 601 | } |
| @@ -578,15 +630,18 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { | |||
| 578 | f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width); | 630 | f32 scale_u = static_cast<f32>(framebuffer_width) / static_cast<f32>(screen_info.texture.width); |
| 579 | f32 scale_v = | 631 | f32 scale_v = |
| 580 | static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height); | 632 | static_cast<f32>(framebuffer_height) / static_cast<f32>(screen_info.texture.height); |
| 581 | // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering | 633 | |
| 582 | // (e.g. handheld mode) on a 1920x1080 framebuffer. | 634 | if (Settings::values.scaling_filter.GetValue() != Settings::ScalingFilter::Fsr) { |
| 583 | if (framebuffer_crop_rect.GetWidth() > 0) { | 635 | // Scale the output by the crop width/height. This is commonly used with 1280x720 rendering |
| 584 | scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) / | 636 | // (e.g. handheld mode) on a 1920x1080 framebuffer. |
| 585 | static_cast<f32>(screen_info.texture.width); | 637 | if (framebuffer_crop_rect.GetWidth() > 0) { |
| 586 | } | 638 | scale_u = static_cast<f32>(framebuffer_crop_rect.GetWidth()) / |
| 587 | if (framebuffer_crop_rect.GetHeight() > 0) { | 639 | static_cast<f32>(screen_info.texture.width); |
| 588 | scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) / | 640 | } |
| 589 | static_cast<f32>(screen_info.texture.height); | 641 | if (framebuffer_crop_rect.GetHeight() > 0) { |
| 642 | scale_v = static_cast<f32>(framebuffer_crop_rect.GetHeight()) / | ||
| 643 | static_cast<f32>(screen_info.texture.height); | ||
| 644 | } | ||
| 590 | } | 645 | } |
| 591 | if (Settings::values.anti_aliasing.GetValue() == Settings::AntiAliasing::Fxaa && | 646 | if (Settings::values.anti_aliasing.GetValue() == Settings::AntiAliasing::Fxaa && |
| 592 | !screen_info.was_accelerated) { | 647 | !screen_info.was_accelerated) { |
| @@ -612,7 +667,6 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) { | |||
| 612 | } else { | 667 | } else { |
| 613 | glDisable(GL_FRAMEBUFFER_SRGB); | 668 | glDisable(GL_FRAMEBUFFER_SRGB); |
| 614 | } | 669 | } |
| 615 | glDisablei(GL_SCISSOR_TEST, 0); | ||
| 616 | glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width), | 670 | glViewportIndexedf(0, 0.0f, 0.0f, static_cast<GLfloat>(layout.width), |
| 617 | static_cast<GLfloat>(layout.height)); | 671 | static_cast<GLfloat>(layout.height)); |
| 618 | 672 | ||
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h index cc97d7b26..f1d5fd954 100644 --- a/src/video_core/renderer_opengl/renderer_opengl.h +++ b/src/video_core/renderer_opengl/renderer_opengl.h | |||
| @@ -10,6 +10,7 @@ | |||
| 10 | 10 | ||
| 11 | #include "video_core/renderer_base.h" | 11 | #include "video_core/renderer_base.h" |
| 12 | #include "video_core/renderer_opengl/gl_device.h" | 12 | #include "video_core/renderer_opengl/gl_device.h" |
| 13 | #include "video_core/renderer_opengl/gl_fsr.h" | ||
| 13 | #include "video_core/renderer_opengl/gl_rasterizer.h" | 14 | #include "video_core/renderer_opengl/gl_rasterizer.h" |
| 14 | #include "video_core/renderer_opengl/gl_resource_manager.h" | 15 | #include "video_core/renderer_opengl/gl_resource_manager.h" |
| 15 | #include "video_core/renderer_opengl/gl_shader_manager.h" | 16 | #include "video_core/renderer_opengl/gl_shader_manager.h" |
| @@ -141,6 +142,8 @@ private: | |||
| 141 | OGLTexture smaa_edges_tex; | 142 | OGLTexture smaa_edges_tex; |
| 142 | OGLTexture smaa_blend_tex; | 143 | OGLTexture smaa_blend_tex; |
| 143 | 144 | ||
| 145 | std::unique_ptr<FSR> fsr; | ||
| 146 | |||
| 144 | /// OpenGL framebuffer data | 147 | /// OpenGL framebuffer data |
| 145 | std::vector<u8> gl_framebuffer_data; | 148 | std::vector<u8> gl_framebuffer_data; |
| 146 | 149 | ||
diff --git a/src/video_core/renderer_vulkan/vk_fsr.cpp b/src/video_core/renderer_vulkan/vk_fsr.cpp index 33daa8c1c..df972cd54 100644 --- a/src/video_core/renderer_vulkan/vk_fsr.cpp +++ b/src/video_core/renderer_vulkan/vk_fsr.cpp | |||
| @@ -1,12 +1,11 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <cmath> | ||
| 5 | #include "common/bit_cast.h" | ||
| 6 | #include "common/common_types.h" | 4 | #include "common/common_types.h" |
| 7 | #include "common/div_ceil.h" | 5 | #include "common/div_ceil.h" |
| 8 | #include "common/settings.h" | 6 | #include "common/settings.h" |
| 9 | 7 | ||
| 8 | #include "video_core/fsr.h" | ||
| 10 | #include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16_comp_spv.h" | 9 | #include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp16_comp_spv.h" |
| 11 | #include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32_comp_spv.h" | 10 | #include "video_core/host_shaders/vulkan_fidelityfx_fsr_easu_fp32_comp_spv.h" |
| 12 | #include "video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16_comp_spv.h" | 11 | #include "video_core/host_shaders/vulkan_fidelityfx_fsr_rcas_fp16_comp_spv.h" |
| @@ -17,146 +16,7 @@ | |||
| 17 | #include "video_core/vulkan_common/vulkan_device.h" | 16 | #include "video_core/vulkan_common/vulkan_device.h" |
| 18 | 17 | ||
| 19 | namespace Vulkan { | 18 | namespace Vulkan { |
| 20 | namespace { | 19 | using namespace FSR; |
| 21 | // Reimplementations of the constant generating functions in ffx_fsr1.h | ||
| 22 | // GCC generated a lot of warnings when using the official header. | ||
| 23 | u32 AU1_AH1_AF1(f32 f) { | ||
| 24 | static constexpr u32 base[512]{ | ||
| 25 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 26 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 27 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 28 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 29 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 30 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 31 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 32 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 33 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, | ||
| 34 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 0x0002, 0x0004, 0x0008, 0x0010, 0x0020, 0x0040, | ||
| 35 | 0x0080, 0x0100, 0x0200, 0x0400, 0x0800, 0x0c00, 0x1000, 0x1400, 0x1800, 0x1c00, 0x2000, | ||
| 36 | 0x2400, 0x2800, 0x2c00, 0x3000, 0x3400, 0x3800, 0x3c00, 0x4000, 0x4400, 0x4800, 0x4c00, | ||
| 37 | 0x5000, 0x5400, 0x5800, 0x5c00, 0x6000, 0x6400, 0x6800, 0x6c00, 0x7000, 0x7400, 0x7800, | ||
| 38 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 39 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 40 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 41 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 42 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 43 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 44 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 45 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 46 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 47 | 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, 0x7bff, | ||
| 48 | 0x7bff, 0x7bff, 0x7bff, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 49 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 50 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 51 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 52 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 53 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 54 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 55 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 56 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, | ||
| 57 | 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8000, 0x8001, 0x8002, 0x8004, 0x8008, | ||
| 58 | 0x8010, 0x8020, 0x8040, 0x8080, 0x8100, 0x8200, 0x8400, 0x8800, 0x8c00, 0x9000, 0x9400, | ||
| 59 | 0x9800, 0x9c00, 0xa000, 0xa400, 0xa800, 0xac00, 0xb000, 0xb400, 0xb800, 0xbc00, 0xc000, | ||
| 60 | 0xc400, 0xc800, 0xcc00, 0xd000, 0xd400, 0xd800, 0xdc00, 0xe000, 0xe400, 0xe800, 0xec00, | ||
| 61 | 0xf000, 0xf400, 0xf800, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 62 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 63 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 64 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 65 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 66 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 67 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 68 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 69 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 70 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 71 | 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, 0xfbff, | ||
| 72 | }; | ||
| 73 | static constexpr s8 shift[512]{ | ||
| 74 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 75 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 76 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 77 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 78 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 79 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 80 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, 0x16, | ||
| 81 | 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 82 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 83 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 84 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 85 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 86 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 87 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 88 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 89 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 90 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 91 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 92 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 93 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 94 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 95 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 96 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 97 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x17, | ||
| 98 | 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x0f, 0x0e, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 99 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, | ||
| 100 | 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x0d, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 101 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 102 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 103 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 104 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 105 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 106 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 107 | 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, 0x18, | ||
| 108 | 0x18, 0x18, | ||
| 109 | }; | ||
| 110 | const u32 u = Common::BitCast<u32>(f); | ||
| 111 | const u32 i = u >> 23; | ||
| 112 | return base[i] + ((u & 0x7fffff) >> shift[i]); | ||
| 113 | } | ||
| 114 | |||
| 115 | u32 AU1_AH2_AF2(f32 a[2]) { | ||
| 116 | return AU1_AH1_AF1(a[0]) + (AU1_AH1_AF1(a[1]) << 16); | ||
| 117 | } | ||
| 118 | |||
| 119 | void FsrEasuCon(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], f32 inputViewportInPixelsX, | ||
| 120 | f32 inputViewportInPixelsY, f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, | ||
| 121 | f32 outputSizeInPixelsX, f32 outputSizeInPixelsY) { | ||
| 122 | con0[0] = Common::BitCast<u32>(inputViewportInPixelsX / outputSizeInPixelsX); | ||
| 123 | con0[1] = Common::BitCast<u32>(inputViewportInPixelsY / outputSizeInPixelsY); | ||
| 124 | con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f); | ||
| 125 | con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f); | ||
| 126 | con1[0] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); | ||
| 127 | con1[1] = Common::BitCast<u32>(1.0f / inputSizeInPixelsY); | ||
| 128 | con1[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); | ||
| 129 | con1[3] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsY); | ||
| 130 | con2[0] = Common::BitCast<u32>(-1.0f / inputSizeInPixelsX); | ||
| 131 | con2[1] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); | ||
| 132 | con2[2] = Common::BitCast<u32>(1.0f / inputSizeInPixelsX); | ||
| 133 | con2[3] = Common::BitCast<u32>(2.0f / inputSizeInPixelsY); | ||
| 134 | con3[0] = Common::BitCast<u32>(0.0f / inputSizeInPixelsX); | ||
| 135 | con3[1] = Common::BitCast<u32>(4.0f / inputSizeInPixelsY); | ||
| 136 | con3[2] = con3[3] = 0; | ||
| 137 | } | ||
| 138 | |||
| 139 | void FsrEasuConOffset(u32 con0[4], u32 con1[4], u32 con2[4], u32 con3[4], | ||
| 140 | f32 inputViewportInPixelsX, f32 inputViewportInPixelsY, | ||
| 141 | f32 inputSizeInPixelsX, f32 inputSizeInPixelsY, f32 outputSizeInPixelsX, | ||
| 142 | f32 outputSizeInPixelsY, f32 inputOffsetInPixelsX, f32 inputOffsetInPixelsY) { | ||
| 143 | FsrEasuCon(con0, con1, con2, con3, inputViewportInPixelsX, inputViewportInPixelsY, | ||
| 144 | inputSizeInPixelsX, inputSizeInPixelsY, outputSizeInPixelsX, outputSizeInPixelsY); | ||
| 145 | con0[2] = Common::BitCast<u32>(0.5f * inputViewportInPixelsX / outputSizeInPixelsX - 0.5f + | ||
| 146 | inputOffsetInPixelsX); | ||
| 147 | con0[3] = Common::BitCast<u32>(0.5f * inputViewportInPixelsY / outputSizeInPixelsY - 0.5f + | ||
| 148 | inputOffsetInPixelsY); | ||
| 149 | } | ||
| 150 | |||
| 151 | void FsrRcasCon(u32* con, f32 sharpness) { | ||
| 152 | sharpness = std::exp2f(-sharpness); | ||
| 153 | f32 hSharp[2]{sharpness, sharpness}; | ||
| 154 | con[0] = Common::BitCast<u32>(sharpness); | ||
| 155 | con[1] = AU1_AH2_AF2(hSharp); | ||
| 156 | con[2] = 0; | ||
| 157 | con[3] = 0; | ||
| 158 | } | ||
| 159 | } // Anonymous namespace | ||
| 160 | 20 | ||
| 161 | FSR::FSR(const Device& device_, MemoryAllocator& memory_allocator_, size_t image_count_, | 21 | FSR::FSR(const Device& device_, MemoryAllocator& memory_allocator_, size_t image_count_, |
| 162 | VkExtent2D output_size_) | 22 | VkExtent2D output_size_) |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 4301313cf..2aaefcc05 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -66,7 +66,6 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry_, | |||
| 66 | 66 | ||
| 67 | web_tab->SetWebServiceConfigEnabled(enable_web_config); | 67 | web_tab->SetWebServiceConfigEnabled(enable_web_config); |
| 68 | hotkeys_tab->Populate(registry); | 68 | hotkeys_tab->Populate(registry); |
| 69 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| 70 | 69 | ||
| 71 | input_tab->Initialize(input_subsystem); | 70 | input_tab->Initialize(input_subsystem); |
| 72 | 71 | ||
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index bb9910a53..a45ec69ec 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui | |||
| @@ -460,7 +460,7 @@ | |||
| 460 | </item> | 460 | </item> |
| 461 | <item> | 461 | <item> |
| 462 | <property name="text"> | 462 | <property name="text"> |
| 463 | <string>AMD FidelityFX™️ Super Resolution (Vulkan Only)</string> | 463 | <string>AMD FidelityFX™️ Super Resolution</string> |
| 464 | </property> | 464 | </property> |
| 465 | </item> | 465 | </item> |
| 466 | </widget> | 466 | </widget> |
diff --git a/src/yuzu/configuration/configure_motion_touch.cpp b/src/yuzu/configuration/configure_motion_touch.cpp index d1b870c72..fb1292f07 100644 --- a/src/yuzu/configuration/configure_motion_touch.cpp +++ b/src/yuzu/configuration/configure_motion_touch.cpp | |||
| @@ -89,7 +89,6 @@ ConfigureMotionTouch::ConfigureMotionTouch(QWidget* parent, | |||
| 89 | "using-a-controller-or-android-phone-for-motion-or-touch-input'><span " | 89 | "using-a-controller-or-android-phone-for-motion-or-touch-input'><span " |
| 90 | "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); | 90 | "style=\"text-decoration: underline; color:#039be5;\">Learn More</span></a>")); |
| 91 | 91 | ||
| 92 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| 93 | SetConfiguration(); | 92 | SetConfiguration(); |
| 94 | UpdateUiDisplay(); | 93 | UpdateUiDisplay(); |
| 95 | ConnectEvents(); | 94 | ConnectEvents(); |
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index 93db47cfd..7e757eafd 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp | |||
| @@ -66,8 +66,6 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st | |||
| 66 | 66 | ||
| 67 | setFocusPolicy(Qt::ClickFocus); | 67 | setFocusPolicy(Qt::ClickFocus); |
| 68 | setWindowTitle(tr("Properties")); | 68 | setWindowTitle(tr("Properties")); |
| 69 | // remove Help question mark button from the title bar | ||
| 70 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| 71 | 69 | ||
| 72 | addons_tab->SetTitleId(title_id); | 70 | addons_tab->SetTitleId(title_id); |
| 73 | 71 | ||
diff --git a/src/yuzu/configuration/configure_tas.cpp b/src/yuzu/configuration/configure_tas.cpp index 1edc5f1f3..5a545aa70 100644 --- a/src/yuzu/configuration/configure_tas.cpp +++ b/src/yuzu/configuration/configure_tas.cpp | |||
| @@ -17,7 +17,6 @@ ConfigureTasDialog::ConfigureTasDialog(QWidget* parent) | |||
| 17 | 17 | ||
| 18 | setFocusPolicy(Qt::ClickFocus); | 18 | setFocusPolicy(Qt::ClickFocus); |
| 19 | setWindowTitle(tr("TAS Configuration")); | 19 | setWindowTitle(tr("TAS Configuration")); |
| 20 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| 21 | 20 | ||
| 22 | connect(ui->tas_path_button, &QToolButton::pressed, this, | 21 | connect(ui->tas_path_button, &QToolButton::pressed, this, |
| 23 | [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); | 22 | [this] { SetDirectory(DirectoryTarget::TAS, ui->tas_path_edit); }); |
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index 19f3775a3..e2f55ebae 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp | |||
| @@ -20,9 +20,8 @@ ControllerDialog::ControllerDialog(Core::HID::HIDCore& hid_core_, | |||
| 20 | setWindowTitle(tr("Controller P1")); | 20 | setWindowTitle(tr("Controller P1")); |
| 21 | resize(500, 350); | 21 | resize(500, 350); |
| 22 | setMinimumSize(500, 350); | 22 | setMinimumSize(500, 350); |
| 23 | // Remove the "?" button from the titlebar and enable the maximize button | 23 | // Enable the maximize button |
| 24 | setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | | 24 | setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); |
| 25 | Qt::WindowMaximizeButtonHint); | ||
| 26 | 25 | ||
| 27 | widget = new PlayerControlPreview(this); | 26 | widget = new PlayerControlPreview(this); |
| 28 | refreshConfiguration(); | 27 | refreshConfiguration(); |
diff --git a/src/yuzu/debugger/profiler.cpp b/src/yuzu/debugger/profiler.cpp index d3e2d3c12..493ee0b17 100644 --- a/src/yuzu/debugger/profiler.cpp +++ b/src/yuzu/debugger/profiler.cpp | |||
| @@ -49,9 +49,8 @@ MicroProfileDialog::MicroProfileDialog(QWidget* parent) : QWidget(parent, Qt::Di | |||
| 49 | setObjectName(QStringLiteral("MicroProfile")); | 49 | setObjectName(QStringLiteral("MicroProfile")); |
| 50 | setWindowTitle(tr("&MicroProfile")); | 50 | setWindowTitle(tr("&MicroProfile")); |
| 51 | resize(1000, 600); | 51 | resize(1000, 600); |
| 52 | // Remove the "?" button from the titlebar and enable the maximize button | 52 | // Enable the maximize button |
| 53 | setWindowFlags((windowFlags() & ~Qt::WindowContextHelpButtonHint) | | 53 | setWindowFlags(windowFlags() | Qt::WindowMaximizeButtonHint); |
| 54 | Qt::WindowMaximizeButtonHint); | ||
| 55 | 54 | ||
| 56 | #if MICROPROFILE_ENABLED | 55 | #if MICROPROFILE_ENABLED |
| 57 | 56 | ||
diff --git a/src/yuzu/install_dialog.cpp b/src/yuzu/install_dialog.cpp index 84ec4fe13..673bbaa83 100644 --- a/src/yuzu/install_dialog.cpp +++ b/src/yuzu/install_dialog.cpp | |||
| @@ -46,7 +46,6 @@ InstallDialog::InstallDialog(QWidget* parent, const QStringList& files) : QDialo | |||
| 46 | vbox_layout->addLayout(hbox_layout); | 46 | vbox_layout->addLayout(hbox_layout); |
| 47 | 47 | ||
| 48 | setLayout(vbox_layout); | 48 | setLayout(vbox_layout); |
| 49 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| 50 | setWindowTitle(tr("Install Files to NAND")); | 49 | setWindowTitle(tr("Install Files to NAND")); |
| 51 | } | 50 | } |
| 52 | 51 | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 571eacf9f..42b7b64c8 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -983,11 +983,6 @@ void GMainWindow::InitializeWidgets() { | |||
| 983 | filter_status_button->setFocusPolicy(Qt::NoFocus); | 983 | filter_status_button->setFocusPolicy(Qt::NoFocus); |
| 984 | connect(filter_status_button, &QPushButton::clicked, this, | 984 | connect(filter_status_button, &QPushButton::clicked, this, |
| 985 | &GMainWindow::OnToggleAdaptingFilter); | 985 | &GMainWindow::OnToggleAdaptingFilter); |
| 986 | auto filter = Settings::values.scaling_filter.GetValue(); | ||
| 987 | if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && | ||
| 988 | filter == Settings::ScalingFilter::Fsr) { | ||
| 989 | Settings::values.scaling_filter.SetValue(Settings::ScalingFilter::NearestNeighbor); | ||
| 990 | } | ||
| 991 | UpdateFilterText(); | 986 | UpdateFilterText(); |
| 992 | filter_status_button->setCheckable(true); | 987 | filter_status_button->setCheckable(true); |
| 993 | filter_status_button->setChecked(true); | 988 | filter_status_button->setChecked(true); |
| @@ -2758,8 +2753,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 2758 | ui->action_Install_File_NAND->setEnabled(false); | 2753 | ui->action_Install_File_NAND->setEnabled(false); |
| 2759 | 2754 | ||
| 2760 | install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this); | 2755 | install_progress = new QProgressDialog(QString{}, tr("Cancel"), 0, total_size, this); |
| 2761 | install_progress->setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint & | 2756 | install_progress->setWindowFlags(windowFlags() & ~Qt::WindowMaximizeButtonHint); |
| 2762 | ~Qt::WindowMaximizeButtonHint); | ||
| 2763 | install_progress->setAttribute(Qt::WA_DeleteOnClose, true); | 2757 | install_progress->setAttribute(Qt::WA_DeleteOnClose, true); |
| 2764 | install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40); | 2758 | install_progress->setFixedWidth(installDialog.GetMinimumWidth() + 40); |
| 2765 | install_progress->show(); | 2759 | install_progress->show(); |
| @@ -3469,10 +3463,6 @@ void GMainWindow::OnToggleAdaptingFilter() { | |||
| 3469 | } else { | 3463 | } else { |
| 3470 | filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1); | 3464 | filter = static_cast<Settings::ScalingFilter>(static_cast<u32>(filter) + 1); |
| 3471 | } | 3465 | } |
| 3472 | if (Settings::values.renderer_backend.GetValue() == Settings::RendererBackend::OpenGL && | ||
| 3473 | filter == Settings::ScalingFilter::Fsr) { | ||
| 3474 | filter = Settings::ScalingFilter::NearestNeighbor; | ||
| 3475 | } | ||
| 3476 | Settings::values.scaling_filter.SetValue(filter); | 3466 | Settings::values.scaling_filter.SetValue(filter); |
| 3477 | filter_status_button->setChecked(true); | 3467 | filter_status_button->setChecked(true); |
| 3478 | UpdateFilterText(); | 3468 | UpdateFilterText(); |
| @@ -4456,6 +4446,11 @@ int main(int argc, char* argv[]) { | |||
| 4456 | } | 4446 | } |
| 4457 | #endif | 4447 | #endif |
| 4458 | 4448 | ||
| 4449 | #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) | ||
| 4450 | // Disables the "?" button on all dialogs. Disabled by default on Qt6. | ||
| 4451 | QCoreApplication::setAttribute(Qt::AA_DisableWindowContextHelpButton); | ||
| 4452 | #endif | ||
| 4453 | |||
| 4459 | // Enables the core to make the qt created contexts current on std::threads | 4454 | // Enables the core to make the qt created contexts current on std::threads |
| 4460 | QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); | 4455 | QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); |
| 4461 | QApplication app(argc, argv); | 4456 | QApplication app(argc, argv); |
diff --git a/src/yuzu/util/limitable_input_dialog.cpp b/src/yuzu/util/limitable_input_dialog.cpp index bbb370595..5f6a9c193 100644 --- a/src/yuzu/util/limitable_input_dialog.cpp +++ b/src/yuzu/util/limitable_input_dialog.cpp | |||
| @@ -16,8 +16,6 @@ LimitableInputDialog::LimitableInputDialog(QWidget* parent) : QDialog{parent} { | |||
| 16 | LimitableInputDialog::~LimitableInputDialog() = default; | 16 | LimitableInputDialog::~LimitableInputDialog() = default; |
| 17 | 17 | ||
| 18 | void LimitableInputDialog::CreateUI() { | 18 | void LimitableInputDialog::CreateUI() { |
| 19 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| 20 | |||
| 21 | text_label = new QLabel(this); | 19 | text_label = new QLabel(this); |
| 22 | text_entry = new QLineEdit(this); | 20 | text_entry = new QLineEdit(this); |
| 23 | text_label_invalid = new QLabel(this); | 21 | text_label_invalid = new QLabel(this); |
diff --git a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp index 4b10fa517..1670aa596 100644 --- a/src/yuzu/util/sequence_dialog/sequence_dialog.cpp +++ b/src/yuzu/util/sequence_dialog/sequence_dialog.cpp | |||
| @@ -8,7 +8,6 @@ | |||
| 8 | 8 | ||
| 9 | SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) { | 9 | SequenceDialog::SequenceDialog(QWidget* parent) : QDialog(parent) { |
| 10 | setWindowTitle(tr("Enter a hotkey")); | 10 | setWindowTitle(tr("Enter a hotkey")); |
| 11 | setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); | ||
| 12 | 11 | ||
| 13 | key_sequence = new QKeySequenceEdit; | 12 | key_sequence = new QKeySequenceEdit; |
| 14 | 13 | ||
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 6fcf04e1b..67d230462 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h | |||
| @@ -5,8 +5,8 @@ | |||
| 5 | 5 | ||
| 6 | namespace DefaultINI { | 6 | namespace DefaultINI { |
| 7 | 7 | ||
| 8 | const char* sdl2_config_file = R"( | 8 | const char* sdl2_config_file = |
| 9 | 9 | R"( | |
| 10 | [ControlsP0] | 10 | [ControlsP0] |
| 11 | # The input devices and parameters for each Switch native input | 11 | # The input devices and parameters for each Switch native input |
| 12 | # The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ... | 12 | # The config section determines the player number where the config will be applied on. For example "ControlsP0", "ControlsP1", ... |
| @@ -143,6 +143,8 @@ mouse_enabled = | |||
| 143 | # 0 (default): Disabled, 1: Enabled | 143 | # 0 (default): Disabled, 1: Enabled |
| 144 | keyboard_enabled = | 144 | keyboard_enabled = |
| 145 | 145 | ||
| 146 | )" | ||
| 147 | R"( | ||
| 146 | [Core] | 148 | [Core] |
| 147 | # Whether to use multi-core for CPU emulation | 149 | # Whether to use multi-core for CPU emulation |
| 148 | # 0: Disabled, 1 (default): Enabled | 150 | # 0: Disabled, 1 (default): Enabled |
| @@ -242,6 +244,8 @@ cpuopt_unsafe_fastmem_check = | |||
| 242 | # 0: Disabled, 1 (default): Enabled | 244 | # 0: Disabled, 1 (default): Enabled |
| 243 | cpuopt_unsafe_ignore_global_monitor = | 245 | cpuopt_unsafe_ignore_global_monitor = |
| 244 | 246 | ||
| 247 | )" | ||
| 248 | R"( | ||
| 245 | [Renderer] | 249 | [Renderer] |
| 246 | # Which backend API to use. | 250 | # Which backend API to use. |
| 247 | # 0: OpenGL, 1 (default): Vulkan | 251 | # 0: OpenGL, 1 (default): Vulkan |
| @@ -360,6 +364,8 @@ bg_red = | |||
| 360 | bg_blue = | 364 | bg_blue = |
| 361 | bg_green = | 365 | bg_green = |
| 362 | 366 | ||
| 367 | )" | ||
| 368 | R"( | ||
| 363 | [Audio] | 369 | [Audio] |
| 364 | # Which audio output engine to use. | 370 | # Which audio output engine to use. |
| 365 | # auto (default): Auto-select | 371 | # auto (default): Auto-select |