summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--CMakeLists.txt98
m---------externals/opus0
-rw-r--r--src/CMakeLists.txt61
-rw-r--r--src/common/CMakeLists.txt6
-rw-r--r--src/common/bit_field.h12
-rw-r--r--src/common/common_types.h7
-rw-r--r--src/common/memory_hook.cpp (renamed from src/core/memory_hook.cpp)6
-rw-r--r--src/common/memory_hook.h (renamed from src/core/memory_hook.h)4
-rw-r--r--src/common/page_table.cpp31
-rw-r--r--src/common/page_table.h84
-rw-r--r--src/common/swap.h174
-rw-r--r--src/common/thread_queue_list.h6
-rw-r--r--src/common/uint128.cpp45
-rw-r--r--src/common/uint128.h19
-rw-r--r--src/core/CMakeLists.txt10
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp5
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h4
-rw-r--r--src/core/core.cpp11
-rw-r--r--src/core/core.h4
-rw-r--r--src/core/core_timing_util.cpp6
-rw-r--r--src/core/core_timing_util.h3
-rw-r--r--src/core/file_sys/cheat_engine.cpp490
-rw-r--r--src/core/file_sys/cheat_engine.h234
-rw-r--r--src/core/file_sys/content_archive.h15
-rw-r--r--src/core/file_sys/errors.h3
-rw-r--r--src/core/file_sys/patch_manager.cpp79
-rw-r--r--src/core/file_sys/patch_manager.h9
-rw-r--r--src/core/file_sys/registered_cache.cpp2
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/file_sys/system_archive/system_version.cpp52
-rw-r--r--src/core/file_sys/system_archive/system_version.h16
-rw-r--r--src/core/hle/ipc.h44
-rw-r--r--src/core/hle/ipc_helpers.h48
-rw-r--r--src/core/hle/kernel/client_port.cpp9
-rw-r--r--src/core/hle/kernel/code_set.cpp12
-rw-r--r--src/core/hle/kernel/code_set.h90
-rw-r--r--src/core/hle/kernel/mutex.cpp35
-rw-r--r--src/core/hle/kernel/mutex.h20
-rw-r--r--src/core/hle/kernel/process.cpp26
-rw-r--r--src/core/hle/kernel/process.h65
-rw-r--r--src/core/hle/kernel/scheduler.cpp8
-rw-r--r--src/core/hle/kernel/server_port.cpp4
-rw-r--r--src/core/hle/kernel/server_port.h35
-rw-r--r--src/core/hle/kernel/svc.cpp49
-rw-r--r--src/core/hle/kernel/thread.cpp72
-rw-r--r--src/core/hle/kernel/thread.h23
-rw-r--r--src/core/hle/kernel/vm_manager.cpp26
-rw-r--r--src/core/hle/kernel/vm_manager.h20
-rw-r--r--src/core/hle/result.h18
-rw-r--r--src/core/hle/service/am/am.cpp86
-rw-r--r--src/core/hle/service/am/am.h16
-rw-r--r--src/core/hle/service/audio/hwopus.cpp82
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp5
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h1
-rw-r--r--src/core/hle/service/hid/controllers/debug_pad.h30
-rw-r--r--src/core/hle/service/hid/controllers/npad.h102
-rw-r--r--src/core/hle/service/hid/controllers/touchscreen.h4
-rw-r--r--src/core/hle/service/hid/hid.h3
-rw-r--r--src/core/hle/service/ldr/ldr.cpp8
-rw-r--r--src/core/hle/service/lm/lm.cpp2
-rw-r--r--src/core/hle/service/nvdrv/devices/nvdevice.h10
-rw-r--r--src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp17
-rw-r--r--src/core/hle/service/service.cpp3
-rw-r--r--src/core/hle/service/set/set_sys.cpp79
-rw-r--r--src/core/hle/service/set/set_sys.h2
-rw-r--r--src/core/hle/service/sm/sm.h2
-rw-r--r--src/core/hle/service/vi/vi.cpp38
-rw-r--r--src/core/hle/service/vi/vi.h40
-rw-r--r--src/core/hle/service/vi/vi_m.cpp12
-rw-r--r--src/core/hle/service/vi/vi_m.h19
-rw-r--r--src/core/hle/service/vi/vi_s.cpp12
-rw-r--r--src/core/hle/service/vi/vi_s.h19
-rw-r--r--src/core/hle/service/vi/vi_u.cpp12
-rw-r--r--src/core/hle/service/vi/vi_u.h19
-rw-r--r--src/core/loader/elf.cpp1
-rw-r--r--src/core/loader/linker.cpp147
-rw-r--r--src/core/loader/linker.h36
-rw-r--r--src/core/loader/nro.cpp1
-rw-r--r--src/core/loader/nro.h4
-rw-r--r--src/core/loader/nso.cpp114
-rw-r--r--src/core/loader/nso.h43
-rw-r--r--src/core/memory.cpp218
-rw-r--r--src/core/memory.h83
-rw-r--r--src/core/memory_setup.h19
-rw-r--r--src/input_common/CMakeLists.txt15
-rw-r--r--src/input_common/main.cpp23
-rw-r--r--src/input_common/main.h2
-rw-r--r--src/input_common/sdl/sdl.cpp636
-rw-r--r--src/input_common/sdl/sdl.h53
-rw-r--r--src/input_common/sdl/sdl_impl.cpp669
-rw-r--r--src/input_common/sdl/sdl_impl.h63
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/common/bit_field.cpp90
-rw-r--r--src/tests/core/arm/arm_test_common.cpp3
-rw-r--r--src/tests/core/arm/arm_test_common.h8
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/dma_pusher.cpp7
-rw-r--r--src/video_core/dma_pusher.h1
-rw-r--r--src/video_core/engines/kepler_memory.cpp15
-rw-r--r--src/video_core/engines/maxwell_3d.cpp42
-rw-r--r--src/video_core/engines/maxwell_dma.cpp37
-rw-r--r--src/video_core/gpu.cpp14
-rw-r--r--src/video_core/gpu.h17
-rw-r--r--src/video_core/gpu_asynch.cpp6
-rw-r--r--src/video_core/gpu_asynch.h6
-rw-r--r--src/video_core/gpu_synch.cpp6
-rw-r--r--src/video_core/gpu_synch.h6
-rw-r--r--src/video_core/gpu_thread.cpp136
-rw-r--r--src/video_core/gpu_thread.h132
-rw-r--r--src/video_core/memory_manager.cpp477
-rw-r--r--src/video_core/memory_manager.h163
-rw-r--r--src/video_core/morton.cpp324
-rw-r--r--src/video_core/morton.h6
-rw-r--r--src/video_core/rasterizer_cache.h74
-rw-r--r--src/video_core/rasterizer_interface.h9
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.cpp30
-rw-r--r--src/video_core/renderer_opengl/gl_buffer_cache.h33
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.cpp43
-rw-r--r--src/video_core/renderer_opengl/gl_global_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.cpp10
-rw-r--r--src/video_core/renderer_opengl/gl_primitive_assembler.h2
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp83
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h9
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp96
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h38
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp62
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h19
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp11
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.cpp30
-rw-r--r--src/video_core/renderer_vulkan/vk_buffer_cache.h34
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.cpp81
-rw-r--r--src/video_core/renderer_vulkan/vk_sampler_cache.h56
-rw-r--r--src/video_core/textures/decoders.cpp32
-rw-r--r--src/video_core/textures/decoders.h13
-rw-r--r--src/video_core/textures/texture.h55
-rw-r--r--src/yuzu/bootmanager.cpp5
-rw-r--r--src/yuzu/configuration/config.cpp448
-rw-r--r--src/yuzu/configuration/config.h5
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.cpp7
-rw-r--r--src/yuzu/debugger/graphics/graphics_surface.h2
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2.cpp13
-rw-r--r--src/yuzu_cmd/yuzu.cpp4
142 files changed, 4878 insertions, 2799 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 32cfa8580..ab18275d3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -104,90 +104,18 @@ endif()
message(STATUS "Target architecture: ${ARCHITECTURE}")
-# Configure compilation flags
+# Configure C++ standard
# ===========================
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
-if (NOT MSVC)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes")
- set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS}")
-
- if (MINGW)
- add_definitions(-DMINGW_HAS_SECURE_API)
-
- if (MINGW_STATIC_BUILD)
- add_definitions(-DQT_STATICPLUGIN)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
- endif()
- endif()
-else()
- # Silence "deprecation" warnings
- add_definitions(/D_CRT_SECURE_NO_WARNINGS /D_CRT_NONSTDC_NO_DEPRECATE /D_SCL_SECURE_NO_WARNINGS)
- # Avoid windows.h junk
- add_definitions(/DNOMINMAX)
- # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
- add_definitions(/DWIN32_LEAN_AND_MEAN)
-
- set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
-
- # Tweak optimization settings
- # As far as I can tell, there's no way to override the CMake defaults while leaving user
- # changes intact, so we'll just clobber everything and say sorry.
- message(STATUS "Cache compiler flags ignored, please edit CMakeLists.txt to change the flags.")
-
- # /W3 - Level 3 warnings
- # /MP - Multi-threaded compilation
- # /Zi - Output debugging information
- # /Zo - enhanced debug info for optimized builds
- # /permissive- - enables stricter C++ standards conformance checks
- set(CMAKE_C_FLAGS "/W3 /MP /Zi /Zo /permissive-" CACHE STRING "" FORCE)
- # /EHsc - C++-only exception handling semantics
- # /Zc:throwingNew - let codegen assume `operator new` will never return null
- # /Zc:inline - let codegen omit inline functions in object files
- set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} /EHsc /std:c++latest /Zc:throwingNew,inline" CACHE STRING "" FORCE)
-
- # /MDd - Multi-threaded Debug Runtime DLL
- set(CMAKE_C_FLAGS_DEBUG "/Od /MDd" CACHE STRING "" FORCE)
- set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG}" CACHE STRING "" FORCE)
-
- # /O2 - Optimization level 2
- # /GS- - No stack buffer overflow checks
- # /MD - Multi-threaded runtime DLL
- set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE)
- set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
-
- set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
- set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
-endif()
-
-# Fix GCC C++17 and Boost.ICL incompatibility (needed to build dynarmic)
-# See https://bugzilla.redhat.com/show_bug.cgi?id=1485641#c1
-if (CMAKE_COMPILER_IS_GNUCC)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-new-ttp-matching")
-endif()
-
-# Set file offset size to 64 bits.
-#
-# On modern Unixes, this is typically already the case. The lone exception is
-# glibc, which may default to 32 bits. glibc allows this to be configured
-# by setting _FILE_OFFSET_BITS.
-if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
- add_definitions(-D_FILE_OFFSET_BITS=64)
-endif()
-
-# CMake seems to only define _DEBUG on Windows
-set_property(DIRECTORY APPEND PROPERTY
- COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
-
# System imported libraries
# ======================
-find_package(Boost 1.63.0 QUIET)
+find_package(Boost 1.66.0 QUIET)
if (NOT Boost_FOUND)
- message(STATUS "Boost 1.63.0 or newer not found, falling back to externals")
+ message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
set(Boost_NO_SYSTEM_PATHS OFF)
@@ -332,25 +260,21 @@ endif()
# Platform-specific library requirements
# ======================================
-IF (APPLE)
- find_library(COCOA_LIBRARY Cocoa) # Umbrella framework for everything GUI-related
+if (APPLE)
+ # Umbrella framework for everything GUI-related
+ find_library(COCOA_LIBRARY Cocoa)
set(PLATFORM_LIBRARIES ${COCOA_LIBRARY} ${IOKIT_LIBRARY} ${COREVIDEO_LIBRARY})
-
- if (CMAKE_CXX_COMPILER_ID STREQUAL Clang)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libc++")
- endif()
-ELSEIF (WIN32)
+elseif (WIN32)
# WSAPoll and SHGetKnownFolderPath (AppData/Roaming) didn't exist before WinNT 6.x (Vista)
add_definitions(-D_WIN32_WINNT=0x0600 -DWINVER=0x0600)
set(PLATFORM_LIBRARIES winmm ws2_32)
- IF (MINGW)
+ if (MINGW)
# PSAPI is the Process Status API
set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version)
- ENDIF (MINGW)
-ELSEIF (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
+ endif()
+elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$")
set(PLATFORM_LIBRARIES rt)
-ENDIF (APPLE)
+endif()
# Setup a custom clang-format target (if clang-format can be found) that will run
# against all the src files. This should be used before making a pull request.
diff --git a/externals/opus b/externals/opus
-Subproject b2871922a12abb49579512d604cabc471a59ad9
+Subproject 562f8ba555c4181e1b57e82e496e4a959b9c019
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index f69d00a2b..6c99dd5e2 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -1,18 +1,79 @@
# Enable modules to include each other's files
include_directories(.)
+# CMake seems to only define _DEBUG on Windows
+set_property(DIRECTORY APPEND PROPERTY
+ COMPILE_DEFINITIONS $<$<CONFIG:Debug>:_DEBUG> $<$<NOT:$<CONFIG:Debug>>:NDEBUG>)
+
+# Set compilation flags
+if (MSVC)
+ set(CMAKE_CONFIGURATION_TYPES Debug Release CACHE STRING "" FORCE)
+
+ # Silence "deprecation" warnings
+ add_definitions(-D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_DEPRECATE -D_SCL_SECURE_NO_WARNINGS)
+
+ # Avoid windows.h junk
+ add_definitions(-DNOMINMAX)
+
+ # Avoid windows.h from including some usually unused libs like winsocks.h, since this might cause some redefinition errors.
+ add_definitions(-DWIN32_LEAN_AND_MEAN)
+
+ # /W3 - Level 3 warnings
+ # /MP - Multi-threaded compilation
+ # /Zi - Output debugging information
+ # /Zo - enhanced debug info for optimized builds
+ # /permissive- - enables stricter C++ standards conformance checks
+ # /EHsc - C++-only exception handling semantics
+ # /Zc:throwingNew - let codegen assume `operator new` will never return null
+ # /Zc:inline - let codegen omit inline functions in object files
+ add_compile_options(/W3 /MP /Zi /Zo /permissive- /EHsc /std:c++latest /Zc:throwingNew,inline)
+
+ # /GS- - No stack buffer overflow checks
+ add_compile_options("$<$<CONFIG:Release>:/GS->")
+
+ set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
+else()
+ add_compile_options("-Wno-attributes")
+
+ if (APPLE AND CMAKE_CXX_COMPILER_ID STREQUAL Clang)
+ add_compile_options("-stdlib=libc++")
+ endif()
+
+ # Set file offset size to 64 bits.
+ #
+ # On modern Unixes, this is typically already the case. The lone exception is
+ # glibc, which may default to 32 bits. glibc allows this to be configured
+ # by setting _FILE_OFFSET_BITS.
+ if(CMAKE_SYSTEM_NAME STREQUAL "Linux" OR MINGW)
+ add_definitions(-D_FILE_OFFSET_BITS=64)
+ endif()
+
+ if (MINGW)
+ add_definitions(-DMINGW_HAS_SECURE_API)
+
+ if (MINGW_STATIC_BUILD)
+ add_definitions(-DQT_STATICPLUGIN)
+ add_compile_options("-static")
+ endif()
+ endif()
+endif()
+
add_subdirectory(common)
add_subdirectory(core)
add_subdirectory(audio_core)
add_subdirectory(video_core)
add_subdirectory(input_common)
add_subdirectory(tests)
+
if (ENABLE_SDL2)
add_subdirectory(yuzu_cmd)
endif()
+
if (ENABLE_QT)
add_subdirectory(yuzu)
endif()
+
if (ENABLE_WEB_SERVICE)
add_subdirectory(web_service)
endif()
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 3d30f0e3e..43ae8a9e7 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -92,10 +92,14 @@ add_library(common STATIC
logging/text_formatter.cpp
logging/text_formatter.h
math_util.h
+ memory_hook.cpp
+ memory_hook.h
microprofile.cpp
microprofile.h
microprofileui.h
misc.cpp
+ page_table.cpp
+ page_table.h
param_package.cpp
param_package.h
quaternion.h
@@ -114,6 +118,8 @@ add_library(common STATIC
threadsafe_queue.h
timer.cpp
timer.h
+ uint128.cpp
+ uint128.h
vector_math.h
web_result.h
)
diff --git a/src/common/bit_field.h b/src/common/bit_field.h
index 7433c39ba..902e668e3 100644
--- a/src/common/bit_field.h
+++ b/src/common/bit_field.h
@@ -34,6 +34,7 @@
#include <limits>
#include <type_traits>
#include "common/common_funcs.h"
+#include "common/swap.h"
/*
* Abstract bitfield class
@@ -108,7 +109,7 @@
* symptoms.
*/
#pragma pack(1)
-template <std::size_t Position, std::size_t Bits, typename T>
+template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
struct BitField {
private:
// UnderlyingType is T for non-enum types and the underlying type of T if
@@ -121,6 +122,8 @@ private:
// We store the value as the unsigned type to avoid undefined behaviour on value shifting
using StorageType = std::make_unsigned_t<UnderlyingType>;
+ using StorageTypeWithEndian = typename AddEndian<StorageType, EndianTag>::type;
+
public:
/// Constants to allow limited introspection of fields if needed
static constexpr std::size_t position = Position;
@@ -170,7 +173,7 @@ public:
}
constexpr FORCE_INLINE void Assign(const T& value) {
- storage = (storage & ~mask) | FormatValue(value);
+ storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
}
constexpr T Value() const {
@@ -182,7 +185,7 @@ public:
}
private:
- StorageType storage;
+ StorageTypeWithEndian storage;
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
@@ -193,3 +196,6 @@ private:
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable in a BitField");
};
#pragma pack()
+
+template <std::size_t Position, std::size_t Bits, typename T>
+using BitFieldBE = BitField<Position, Bits, T, BETag>;
diff --git a/src/common/common_types.h b/src/common/common_types.h
index 6b1766dca..4cec89fbd 100644
--- a/src/common/common_types.h
+++ b/src/common/common_types.h
@@ -40,10 +40,9 @@ using s64 = std::int64_t; ///< 64-bit signed int
using f32 = float; ///< 32-bit floating point
using f64 = double; ///< 64-bit floating point
-// TODO: It would be nice to eventually replace these with strong types that prevent accidental
-// conversion between each other.
-using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
-using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
+using VAddr = u64; ///< Represents a pointer in the userspace virtual address space.
+using PAddr = u64; ///< Represents a pointer in the ARM11 physical address space.
+using GPUVAddr = u64; ///< Represents a pointer in the GPU virtual address space.
using u128 = std::array<std::uint64_t, 2>;
static_assert(sizeof(u128) == 16, "u128 must be 128 bits wide");
diff --git a/src/core/memory_hook.cpp b/src/common/memory_hook.cpp
index c61c6c1fb..3986986d6 100644
--- a/src/core/memory_hook.cpp
+++ b/src/common/memory_hook.cpp
@@ -2,10 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include "core/memory_hook.h"
+#include "common/memory_hook.h"
-namespace Memory {
+namespace Common {
MemoryHook::~MemoryHook() = default;
-} // namespace Memory
+} // namespace Common
diff --git a/src/core/memory_hook.h b/src/common/memory_hook.h
index 940777107..adaa4c2c5 100644
--- a/src/core/memory_hook.h
+++ b/src/common/memory_hook.h
@@ -9,7 +9,7 @@
#include "common/common_types.h"
-namespace Memory {
+namespace Common {
/**
* Memory hooks have two purposes:
@@ -44,4 +44,4 @@ public:
};
using MemoryHookPointer = std::shared_ptr<MemoryHook>;
-} // namespace Memory
+} // namespace Common
diff --git a/src/common/page_table.cpp b/src/common/page_table.cpp
new file mode 100644
index 000000000..69b7abc54
--- /dev/null
+++ b/src/common/page_table.cpp
@@ -0,0 +1,31 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/page_table.h"
+
+namespace Common {
+
+PageTable::PageTable(std::size_t page_size_in_bits) : page_size_in_bits{page_size_in_bits} {}
+
+PageTable::~PageTable() = default;
+
+void PageTable::Resize(std::size_t address_space_width_in_bits) {
+ const std::size_t num_page_table_entries = 1ULL
+ << (address_space_width_in_bits - page_size_in_bits);
+
+ pointers.resize(num_page_table_entries);
+ attributes.resize(num_page_table_entries);
+ backing_addr.resize(num_page_table_entries);
+
+ // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
+ // vector size is subsequently decreased (via resize), the vector might not automatically
+ // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
+ // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
+
+ pointers.shrink_to_fit();
+ attributes.shrink_to_fit();
+ backing_addr.shrink_to_fit();
+}
+
+} // namespace Common
diff --git a/src/common/page_table.h b/src/common/page_table.h
new file mode 100644
index 000000000..8b8ff0bb8
--- /dev/null
+++ b/src/common/page_table.h
@@ -0,0 +1,84 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include <boost/icl/interval_map.hpp>
+#include "common/common_types.h"
+#include "common/memory_hook.h"
+
+namespace Common {
+
+enum class PageType : u8 {
+ /// Page is unmapped and should cause an access error.
+ Unmapped,
+ /// Page is mapped to regular memory. This is the only type you can get pointers to.
+ Memory,
+ /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
+ /// invalidation
+ RasterizerCachedMemory,
+ /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
+ Special,
+ /// Page is allocated for use.
+ Allocated,
+};
+
+struct SpecialRegion {
+ enum class Type {
+ DebugHook,
+ IODevice,
+ } type;
+
+ MemoryHookPointer handler;
+
+ bool operator<(const SpecialRegion& other) const {
+ return std::tie(type, handler) < std::tie(other.type, other.handler);
+ }
+
+ bool operator==(const SpecialRegion& other) const {
+ return std::tie(type, handler) == std::tie(other.type, other.handler);
+ }
+};
+
+/**
+ * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
+ * mimics the way a real CPU page table works.
+ */
+struct PageTable {
+ explicit PageTable(std::size_t page_size_in_bits);
+ ~PageTable();
+
+ /**
+ * Resizes the page table to be able to accomodate enough pages within
+ * a given address space.
+ *
+ * @param address_space_width_in_bits The address size width in bits.
+ */
+ void Resize(std::size_t address_space_width_in_bits);
+
+ /**
+ * Vector of memory pointers backing each page. An entry can only be non-null if the
+ * corresponding entry in the `attributes` vector is of type `Memory`.
+ */
+ std::vector<u8*> pointers;
+
+ /**
+ * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
+ * of type `Special`.
+ */
+ boost::icl::interval_map<u64, std::set<SpecialRegion>> special_regions;
+
+ /**
+ * Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
+ * the corresponding entry in `pointers` MUST be set to null.
+ */
+ std::vector<PageType> attributes;
+
+ std::vector<u64> backing_addr;
+
+ const std::size_t page_size_in_bits{};
+};
+
+} // namespace Common
diff --git a/src/common/swap.h b/src/common/swap.h
index 0e219747f..b3eab1324 100644
--- a/src/common/swap.h
+++ b/src/common/swap.h
@@ -17,6 +17,8 @@
#pragma once
+#include <type_traits>
+
#if defined(_MSC_VER)
#include <cstdlib>
#elif defined(__linux__)
@@ -170,7 +172,7 @@ struct swap_struct_t {
using swapped_t = swap_struct_t;
protected:
- T value = T();
+ T value;
static T swap(T v) {
return F::swap(v);
@@ -605,52 +607,154 @@ struct swap_double_t {
}
};
-#if COMMON_LITTLE_ENDIAN
-using u16_le = u16;
-using u32_le = u32;
-using u64_le = u64;
+template <typename T>
+struct swap_enum_t {
+ static_assert(std::is_enum_v<T>);
+ using base = std::underlying_type_t<T>;
+
+public:
+ swap_enum_t() = default;
+ swap_enum_t(const T& v) : value(swap(v)) {}
+
+ swap_enum_t& operator=(const T& v) {
+ value = swap(v);
+ return *this;
+ }
+
+ operator T() const {
+ return swap(value);
+ }
+
+ explicit operator base() const {
+ return static_cast<base>(swap(value));
+ }
-using s16_le = s16;
-using s32_le = s32;
-using s64_le = s64;
+protected:
+ T value{};
+ // clang-format off
+ using swap_t = std::conditional_t<
+ std::is_same_v<base, u16>, swap_16_t<u16>, std::conditional_t<
+ std::is_same_v<base, s16>, swap_16_t<s16>, std::conditional_t<
+ std::is_same_v<base, u32>, swap_32_t<u32>, std::conditional_t<
+ std::is_same_v<base, s32>, swap_32_t<s32>, std::conditional_t<
+ std::is_same_v<base, u64>, swap_64_t<u64>, std::conditional_t<
+ std::is_same_v<base, s64>, swap_64_t<s64>, void>>>>>>;
+ // clang-format on
+ static T swap(T x) {
+ return static_cast<T>(swap_t::swap(static_cast<base>(x)));
+ }
+};
-using float_le = float;
-using double_le = double;
+struct SwapTag {}; // Use the different endianness from the system
+struct KeepTag {}; // Use the same endianness as the system
-using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
-using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
+template <typename T, typename Tag>
+struct AddEndian;
-using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
-using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
+// KeepTag specializations
-using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
-using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
+template <typename T>
+struct AddEndian<T, KeepTag> {
+ using type = T;
+};
-using float_be = swap_struct_t<float, swap_float_t<float>>;
-using double_be = swap_struct_t<double, swap_double_t<double>>;
-#else
+// SwapTag specializations
+
+template <>
+struct AddEndian<u8, SwapTag> {
+ using type = u8;
+};
+
+template <>
+struct AddEndian<u16, SwapTag> {
+ using type = swap_struct_t<u16, swap_16_t<u16>>;
+};
+
+template <>
+struct AddEndian<u32, SwapTag> {
+ using type = swap_struct_t<u32, swap_32_t<u32>>;
+};
-using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
-using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
+template <>
+struct AddEndian<u64, SwapTag> {
+ using type = swap_struct_t<u64, swap_64_t<u64>>;
+};
+
+template <>
+struct AddEndian<s8, SwapTag> {
+ using type = s8;
+};
-using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
-using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
+template <>
+struct AddEndian<s16, SwapTag> {
+ using type = swap_struct_t<s16, swap_16_t<s16>>;
+};
-using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
-using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
+template <>
+struct AddEndian<s32, SwapTag> {
+ using type = swap_struct_t<s32, swap_32_t<s32>>;
+};
+
+template <>
+struct AddEndian<s64, SwapTag> {
+ using type = swap_struct_t<s64, swap_64_t<s64>>;
+};
+
+template <>
+struct AddEndian<float, SwapTag> {
+ using type = swap_struct_t<float, swap_float_t<float>>;
+};
+
+template <>
+struct AddEndian<double, SwapTag> {
+ using type = swap_struct_t<double, swap_double_t<double>>;
+};
+
+template <typename T>
+struct AddEndian<T, SwapTag> {
+ static_assert(std::is_enum_v<T>);
+ using type = swap_enum_t<T>;
+};
-using float_le = swap_struct_t<float, swap_float_t<float>>;
-using double_le = swap_struct_t<double, swap_double_t<double>>;
+// Alias LETag/BETag as KeepTag/SwapTag depending on the system
+#if COMMON_LITTLE_ENDIAN
-using u16_be = u16;
-using u32_be = u32;
-using u64_be = u64;
+using LETag = KeepTag;
+using BETag = SwapTag;
-using s16_be = s16;
-using s32_be = s32;
-using s64_be = s64;
+#else
-using float_be = float;
-using double_be = double;
+using BETag = KeepTag;
+using LETag = SwapTag;
#endif
+
+// Aliases for LE types
+using u16_le = AddEndian<u16, LETag>::type;
+using u32_le = AddEndian<u32, LETag>::type;
+using u64_le = AddEndian<u64, LETag>::type;
+
+using s16_le = AddEndian<s16, LETag>::type;
+using s32_le = AddEndian<s32, LETag>::type;
+using s64_le = AddEndian<s64, LETag>::type;
+
+template <typename T>
+using enum_le = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, LETag>::type>;
+
+using float_le = AddEndian<float, LETag>::type;
+using double_le = AddEndian<double, LETag>::type;
+
+// Aliases for BE types
+using u16_be = AddEndian<u16, BETag>::type;
+using u32_be = AddEndian<u32, BETag>::type;
+using u64_be = AddEndian<u64, BETag>::type;
+
+using s16_be = AddEndian<s16, BETag>::type;
+using s32_be = AddEndian<s32, BETag>::type;
+using s64_be = AddEndian<s64, BETag>::type;
+
+template <typename T>
+using enum_be = std::enable_if_t<std::is_enum_v<T>, typename AddEndian<T, BETag>::type>;
+
+using float_be = AddEndian<float, BETag>::type;
+using double_be = AddEndian<double, BETag>::type;
diff --git a/src/common/thread_queue_list.h b/src/common/thread_queue_list.h
index e7594db68..791f99a8c 100644
--- a/src/common/thread_queue_list.h
+++ b/src/common/thread_queue_list.h
@@ -6,7 +6,6 @@
#include <array>
#include <deque>
-#include <boost/range/algorithm_ext/erase.hpp>
namespace Common {
@@ -111,8 +110,9 @@ struct ThreadQueueList {
}
void remove(Priority priority, const T& thread_id) {
- Queue* cur = &queues[priority];
- boost::remove_erase(cur->data, thread_id);
+ Queue* const cur = &queues[priority];
+ const auto iter = std::remove(cur->data.begin(), cur->data.end(), thread_id);
+ cur->data.erase(iter, cur->data.end());
}
void rotate(Priority priority) {
diff --git a/src/common/uint128.cpp b/src/common/uint128.cpp
new file mode 100644
index 000000000..32bf56730
--- /dev/null
+++ b/src/common/uint128.cpp
@@ -0,0 +1,45 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#ifdef _MSC_VER
+#include <intrin.h>
+
+#pragma intrinsic(_umul128)
+#endif
+#include <cstring>
+#include "common/uint128.h"
+
+namespace Common {
+
+u128 Multiply64Into128(u64 a, u64 b) {
+ u128 result;
+#ifdef _MSC_VER
+ result[0] = _umul128(a, b, &result[1]);
+#else
+ unsigned __int128 tmp = a;
+ tmp *= b;
+ std::memcpy(&result, &tmp, sizeof(u128));
+#endif
+ return result;
+}
+
+std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor) {
+ u64 remainder = dividend[0] % divisor;
+ u64 accum = dividend[0] / divisor;
+ if (dividend[1] == 0)
+ return {accum, remainder};
+ // We ignore dividend[1] / divisor as that overflows
+ const u64 first_segment = (dividend[1] % divisor) << 32;
+ accum += (first_segment / divisor) << 32;
+ const u64 second_segment = (first_segment % divisor) << 32;
+ accum += (second_segment / divisor);
+ remainder += second_segment % divisor;
+ if (remainder >= divisor) {
+ accum++;
+ remainder -= divisor;
+ }
+ return {accum, remainder};
+}
+
+} // namespace Common
diff --git a/src/common/uint128.h b/src/common/uint128.h
new file mode 100644
index 000000000..a3be2a2cb
--- /dev/null
+++ b/src/common/uint128.h
@@ -0,0 +1,19 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <utility>
+#include "common/common_types.h"
+
+namespace Common {
+
+// This function multiplies 2 u64 values and produces a u128 value;
+u128 Multiply64Into128(u64 a, u64 b);
+
+// This function divides a u128 by a u32 value and produces two u64 values:
+// the result of division and the remainder
+std::pair<u64, u64> Divide128On32(u128 dividend, u32 divisor);
+
+} // namespace Common
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index e8aefb6b4..9e23afe85 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -31,6 +31,8 @@ add_library(core STATIC
file_sys/bis_factory.h
file_sys/card_image.cpp
file_sys/card_image.h
+ file_sys/cheat_engine.cpp
+ file_sys/cheat_engine.h
file_sys/content_archive.cpp
file_sys/content_archive.h
file_sys/control_metadata.cpp
@@ -68,6 +70,8 @@ add_library(core STATIC
file_sys/system_archive/ng_word.h
file_sys/system_archive/system_archive.cpp
file_sys/system_archive/system_archive.h
+ file_sys/system_archive/system_version.cpp
+ file_sys/system_archive/system_version.h
file_sys/vfs.cpp
file_sys/vfs.h
file_sys/vfs_concat.cpp
@@ -107,6 +111,8 @@ add_library(core STATIC
hle/kernel/client_port.h
hle/kernel/client_session.cpp
hle/kernel/client_session.h
+ hle/kernel/code_set.cpp
+ hle/kernel/code_set.h
hle/kernel/errors.h
hle/kernel/handle_table.cpp
hle/kernel/handle_table.h
@@ -421,8 +427,6 @@ add_library(core STATIC
loader/deconstructed_rom_directory.h
loader/elf.cpp
loader/elf.h
- loader/linker.cpp
- loader/linker.h
loader/loader.cpp
loader/loader.h
loader/nax.cpp
@@ -439,8 +443,6 @@ add_library(core STATIC
loader/xci.h
memory.cpp
memory.h
- memory_hook.cpp
- memory_hook.h
memory_setup.h
perf_stats.cpp
perf_stats.h
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 9b7ca4030..4fdc12f11 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -12,6 +12,7 @@
#include "core/core.h"
#include "core/core_cpu.h"
#include "core/core_timing.h"
+#include "core/core_timing_util.h"
#include "core/gdbstub/gdbstub.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/svc.h"
@@ -119,7 +120,7 @@ public:
return std::max(parent.core_timing.GetDowncount(), 0);
}
u64 GetCNTPCT() override {
- return parent.core_timing.GetTicks();
+ return Timing::CpuCyclesToClockCycles(parent.core_timing.GetTicks());
}
ARM_Dynarmic& parent;
@@ -151,7 +152,7 @@ std::unique_ptr<Dynarmic::A64::Jit> ARM_Dynarmic::MakeJit() const {
config.tpidr_el0 = &cb->tpidr_el0;
config.dczid_el0 = 4;
config.ctr_el0 = 0x8444c004;
- config.cntfrq_el0 = 19200000; // Value from fusee.
+ config.cntfrq_el0 = Timing::CNTFREQ;
// Unpredictable instructions
config.define_unpredictable_behaviour = true;
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 6cc458296..aada1e862 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -12,7 +12,7 @@
#include "core/arm/exclusive_monitor.h"
#include "core/arm/unicorn/arm_unicorn.h"
-namespace Memory {
+namespace Common {
struct PageTable;
}
@@ -70,7 +70,7 @@ private:
Timing::CoreTiming& core_timing;
DynarmicExclusiveMonitor& exclusive_monitor;
- Memory::PageTable* current_page_table = nullptr;
+ Common::PageTable* current_page_table = nullptr;
};
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 89b3fb418..4fe77c25b 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -32,6 +32,7 @@
#include "core/perf_stats.h"
#include "core/settings.h"
#include "core/telemetry_session.h"
+#include "file_sys/cheat_engine.h"
#include "frontend/applets/profile_select.h"
#include "frontend/applets/software_keyboard.h"
#include "frontend/applets/web_browser.h"
@@ -205,6 +206,7 @@ struct System::Impl {
GDBStub::Shutdown();
Service::Shutdown();
service_manager.reset();
+ cheat_engine.reset();
telemetry_session.reset();
gpu_core.reset();
@@ -255,6 +257,8 @@ struct System::Impl {
CpuCoreManager cpu_core_manager;
bool is_powered_on = false;
+ std::unique_ptr<FileSys::CheatEngine> cheat_engine;
+
/// Frontend applets
std::unique_ptr<Core::Frontend::ProfileSelectApplet> profile_selector;
std::unique_ptr<Core::Frontend::SoftwareKeyboardApplet> software_keyboard;
@@ -453,6 +457,13 @@ Tegra::DebugContext* System::GetGPUDebugContext() const {
return impl->debug_context.get();
}
+void System::RegisterCheatList(const std::vector<FileSys::CheatList>& list,
+ const std::string& build_id, VAddr code_region_start,
+ VAddr code_region_end) {
+ impl->cheat_engine = std::make_unique<FileSys::CheatEngine>(*this, list, build_id,
+ code_region_start, code_region_end);
+}
+
void System::SetFilesystem(std::shared_ptr<FileSys::VfsFilesystem> vfs) {
impl->virtual_filesystem = std::move(vfs);
}
diff --git a/src/core/core.h b/src/core/core.h
index ba76a41d8..4d83b93cc 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -20,6 +20,7 @@ class WebBrowserApplet;
} // namespace Core::Frontend
namespace FileSys {
+class CheatList;
class VfsFilesystem;
} // namespace FileSys
@@ -253,6 +254,9 @@ public:
std::shared_ptr<FileSys::VfsFilesystem> GetFilesystem() const;
+ void RegisterCheatList(const std::vector<FileSys::CheatList>& list, const std::string& build_id,
+ VAddr code_region_start, VAddr code_region_end);
+
void SetProfileSelector(std::unique_ptr<Frontend::ProfileSelectApplet> applet);
const Frontend::ProfileSelectApplet& GetProfileSelector() const;
diff --git a/src/core/core_timing_util.cpp b/src/core/core_timing_util.cpp
index 88ff70233..7942f30d6 100644
--- a/src/core/core_timing_util.cpp
+++ b/src/core/core_timing_util.cpp
@@ -7,6 +7,7 @@
#include <cinttypes>
#include <limits>
#include "common/logging/log.h"
+#include "common/uint128.h"
namespace Core::Timing {
@@ -60,4 +61,9 @@ s64 nsToCycles(u64 ns) {
return (BASE_CLOCK_RATE * static_cast<s64>(ns)) / 1000000000;
}
+u64 CpuCyclesToClockCycles(u64 ticks) {
+ const u128 temporal = Common::Multiply64Into128(ticks, CNTFREQ);
+ return Common::Divide128On32(temporal, static_cast<u32>(BASE_CLOCK_RATE)).first;
+}
+
} // namespace Core::Timing
diff --git a/src/core/core_timing_util.h b/src/core/core_timing_util.h
index 513cfac1b..679aa3123 100644
--- a/src/core/core_timing_util.h
+++ b/src/core/core_timing_util.h
@@ -11,6 +11,7 @@ namespace Core::Timing {
// The below clock rate is based on Switch's clockspeed being widely known as 1.020GHz
// The exact value used is of course unverified.
constexpr u64 BASE_CLOCK_RATE = 1019215872; // Switch clock speed is 1020MHz un/docked
+constexpr u64 CNTFREQ = 19200000; // Value from fusee.
inline s64 msToCycles(int ms) {
// since ms is int there is no way to overflow
@@ -61,4 +62,6 @@ inline u64 cyclesToMs(s64 cycles) {
return cycles * 1000 / BASE_CLOCK_RATE;
}
+u64 CpuCyclesToClockCycles(u64 ticks);
+
} // namespace Core::Timing
diff --git a/src/core/file_sys/cheat_engine.cpp b/src/core/file_sys/cheat_engine.cpp
new file mode 100644
index 000000000..247fbc864
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.cpp
@@ -0,0 +1,490 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <locale>
+#include "common/hex_util.h"
+#include "common/microprofile.h"
+#include "common/swap.h"
+#include "core/core.h"
+#include "core/core_timing.h"
+#include "core/core_timing_util.h"
+#include "core/file_sys/cheat_engine.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/service/hid/controllers/npad.h"
+#include "core/hle/service/hid/hid.h"
+#include "core/hle/service/sm/sm.h"
+
+namespace FileSys {
+
+constexpr s64 CHEAT_ENGINE_TICKS = static_cast<s64>(Core::Timing::BASE_CLOCK_RATE / 60);
+constexpr u32 KEYPAD_BITMASK = 0x3FFFFFF;
+
+u64 Cheat::Address() const {
+ u64 out;
+ std::memcpy(&out, raw.data(), sizeof(u64));
+ return Common::swap64(out) & 0xFFFFFFFFFF;
+}
+
+u64 Cheat::ValueWidth(u64 offset) const {
+ return Value(offset, width);
+}
+
+u64 Cheat::Value(u64 offset, u64 width) const {
+ u64 out;
+ std::memcpy(&out, raw.data() + offset, sizeof(u64));
+ out = Common::swap64(out);
+ if (width == 8)
+ return out;
+ return out & ((1ull << (width * CHAR_BIT)) - 1);
+}
+
+u32 Cheat::KeypadValue() const {
+ u32 out;
+ std::memcpy(&out, raw.data(), sizeof(u32));
+ return Common::swap32(out) & 0x0FFFFFFF;
+}
+
+void CheatList::SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end,
+ VAddr heap_end, MemoryWriter writer, MemoryReader reader) {
+ this->main_region_begin = main_begin;
+ this->main_region_end = main_end;
+ this->heap_region_begin = heap_begin;
+ this->heap_region_end = heap_end;
+ this->writer = writer;
+ this->reader = reader;
+}
+
+MICROPROFILE_DEFINE(Cheat_Engine, "Add-Ons", "Cheat Engine", MP_RGB(70, 200, 70));
+
+void CheatList::Execute() {
+ MICROPROFILE_SCOPE(Cheat_Engine);
+
+ std::fill(scratch.begin(), scratch.end(), 0);
+ in_standard = false;
+ for (std::size_t i = 0; i < master_list.size(); ++i) {
+ LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, master_list[i].first);
+ current_block = i;
+ ExecuteBlock(master_list[i].second);
+ }
+
+ in_standard = true;
+ for (std::size_t i = 0; i < standard_list.size(); ++i) {
+ LOG_DEBUG(Common_Filesystem, "Executing block #{:08X} ({})", i, standard_list[i].first);
+ current_block = i;
+ ExecuteBlock(standard_list[i].second);
+ }
+}
+
+CheatList::CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard)
+ : master_list{std::move(master)}, standard_list{std::move(standard)}, system{&system_} {}
+
+bool CheatList::EvaluateConditional(const Cheat& cheat) const {
+ using ComparisonFunction = bool (*)(u64, u64);
+ constexpr std::array<ComparisonFunction, 6> comparison_functions{
+ [](u64 a, u64 b) { return a > b; }, [](u64 a, u64 b) { return a >= b; },
+ [](u64 a, u64 b) { return a < b; }, [](u64 a, u64 b) { return a <= b; },
+ [](u64 a, u64 b) { return a == b; }, [](u64 a, u64 b) { return a != b; },
+ };
+
+ if (cheat.type == CodeType::ConditionalInput) {
+ const auto applet_resource =
+ system->ServiceManager().GetService<Service::HID::Hid>("hid")->GetAppletResource();
+ if (applet_resource == nullptr) {
+ LOG_WARNING(
+ Common_Filesystem,
+ "Attempted to evaluate input conditional, but applet resource is not initialized!");
+ return false;
+ }
+
+ const auto press_state =
+ applet_resource
+ ->GetController<Service::HID::Controller_NPad>(Service::HID::HidController::NPad)
+ .GetAndResetPressState();
+ return ((press_state & cheat.KeypadValue()) & KEYPAD_BITMASK) != 0;
+ }
+
+ ASSERT(cheat.type == CodeType::Conditional);
+
+ const auto offset =
+ cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
+ ASSERT(static_cast<u8>(cheat.comparison_op.Value()) < 6);
+ auto* function = comparison_functions[static_cast<u8>(cheat.comparison_op.Value())];
+ const auto addr = cheat.Address() + offset;
+
+ return function(reader(cheat.width, SanitizeAddress(addr)), cheat.ValueWidth(8));
+}
+
+void CheatList::ProcessBlockPairs(const Block& block) {
+ block_pairs.clear();
+
+ u64 scope = 0;
+ std::map<u64, u64> pairs;
+
+ for (std::size_t i = 0; i < block.size(); ++i) {
+ const auto& cheat = block[i];
+
+ switch (cheat.type) {
+ case CodeType::Conditional:
+ case CodeType::ConditionalInput:
+ pairs.insert_or_assign(scope, i);
+ ++scope;
+ break;
+ case CodeType::EndConditional: {
+ --scope;
+ const auto idx = pairs.at(scope);
+ block_pairs.insert_or_assign(idx, i);
+ break;
+ }
+ case CodeType::Loop: {
+ if (cheat.end_of_loop) {
+ --scope;
+ const auto idx = pairs.at(scope);
+ block_pairs.insert_or_assign(idx, i);
+ } else {
+ pairs.insert_or_assign(scope, i);
+ ++scope;
+ }
+ break;
+ }
+ }
+ }
+}
+
+void CheatList::WriteImmediate(const Cheat& cheat) {
+ const auto offset =
+ cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
+ const auto& register_3 = scratch.at(cheat.register_3);
+
+ const auto addr = cheat.Address() + offset + register_3;
+ LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}", addr,
+ cheat.Value(8, cheat.width));
+ writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(8));
+}
+
+void CheatList::BeginConditional(const Cheat& cheat) {
+ if (EvaluateConditional(cheat)) {
+ return;
+ }
+
+ const auto iter = block_pairs.find(current_index);
+ ASSERT(iter != block_pairs.end());
+ current_index = iter->second - 1;
+}
+
+void CheatList::EndConditional(const Cheat& cheat) {
+ LOG_DEBUG(Common_Filesystem, "Ending conditional block.");
+}
+
+void CheatList::Loop(const Cheat& cheat) {
+ if (cheat.end_of_loop.Value())
+ ASSERT(!cheat.end_of_loop.Value());
+
+ auto& register_3 = scratch.at(cheat.register_3);
+ const auto iter = block_pairs.find(current_index);
+ ASSERT(iter != block_pairs.end());
+ ASSERT(iter->first < iter->second);
+
+ const s32 initial_value = static_cast<s32>(cheat.Value(4, sizeof(s32)));
+ for (s32 i = initial_value; i >= 0; --i) {
+ register_3 = static_cast<u64>(i);
+ for (std::size_t c = iter->first + 1; c < iter->second; ++c) {
+ current_index = c;
+ ExecuteSingleCheat(
+ (in_standard ? standard_list : master_list)[current_block].second[c]);
+ }
+ }
+
+ current_index = iter->second;
+}
+
+void CheatList::LoadImmediate(const Cheat& cheat) {
+ auto& register_3 = scratch.at(cheat.register_3);
+
+ LOG_DEBUG(Common_Filesystem, "setting register={:01X} equal to value={:016X}", cheat.register_3,
+ cheat.Value(4, 8));
+ register_3 = cheat.Value(4, 8);
+}
+
+void CheatList::LoadIndexed(const Cheat& cheat) {
+ const auto offset =
+ cheat.memory_type == MemoryType::MainNSO ? main_region_begin : heap_region_begin;
+ auto& register_3 = scratch.at(cheat.register_3);
+
+ const auto addr = (cheat.load_from_register.Value() ? register_3 : offset) + cheat.Address();
+ LOG_DEBUG(Common_Filesystem, "writing indexed value to register={:01X}, addr={:016X}",
+ cheat.register_3, addr);
+ register_3 = reader(cheat.width, SanitizeAddress(addr));
+}
+
+void CheatList::StoreIndexed(const Cheat& cheat) {
+ const auto& register_3 = scratch.at(cheat.register_3);
+
+ const auto addr =
+ register_3 + (cheat.add_additional_register.Value() ? scratch.at(cheat.register_6) : 0);
+ LOG_DEBUG(Common_Filesystem, "writing value={:016X} to addr={:016X}",
+ cheat.Value(4, cheat.width), addr);
+ writer(cheat.width, SanitizeAddress(addr), cheat.ValueWidth(4));
+}
+
+void CheatList::RegisterArithmetic(const Cheat& cheat) {
+ using ArithmeticFunction = u64 (*)(u64, u64);
+ constexpr std::array<ArithmeticFunction, 5> arithmetic_functions{
+ [](u64 a, u64 b) { return a + b; }, [](u64 a, u64 b) { return a - b; },
+ [](u64 a, u64 b) { return a * b; }, [](u64 a, u64 b) { return a << b; },
+ [](u64 a, u64 b) { return a >> b; },
+ };
+
+ using ArithmeticOverflowCheck = bool (*)(u64, u64);
+ constexpr std::array<ArithmeticOverflowCheck, 5> arithmetic_overflow_checks{
+ [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() - b); }, // a + b
+ [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() + b); }, // a - b
+ [](u64 a, u64 b) { return a > (std::numeric_limits<u64>::max() / b); }, // a * b
+ [](u64 a, u64 b) { return b >= 64 || (a & ~((1ull << (64 - b)) - 1)) != 0; }, // a << b
+ [](u64 a, u64 b) { return b >= 64 || (a & ((1ull << b) - 1)) != 0; }, // a >> b
+ };
+
+ static_assert(sizeof(arithmetic_functions) == sizeof(arithmetic_overflow_checks),
+ "Missing or have extra arithmetic overflow checks compared to functions!");
+
+ auto& register_3 = scratch.at(cheat.register_3);
+
+ ASSERT(static_cast<u8>(cheat.arithmetic_op.Value()) < 5);
+ auto* function = arithmetic_functions[static_cast<u8>(cheat.arithmetic_op.Value())];
+ auto* overflow_function =
+ arithmetic_overflow_checks[static_cast<u8>(cheat.arithmetic_op.Value())];
+ LOG_DEBUG(Common_Filesystem, "performing arithmetic with register={:01X}, value={:016X}",
+ cheat.register_3, cheat.ValueWidth(4));
+
+ if (overflow_function(register_3, cheat.ValueWidth(4))) {
+ LOG_WARNING(Common_Filesystem,
+ "overflow will occur when performing arithmetic operation={:02X} with operands "
+ "a={:016X}, b={:016X}!",
+ static_cast<u8>(cheat.arithmetic_op.Value()), register_3, cheat.ValueWidth(4));
+ }
+
+ register_3 = function(register_3, cheat.ValueWidth(4));
+}
+
+void CheatList::BeginConditionalInput(const Cheat& cheat) {
+ if (EvaluateConditional(cheat))
+ return;
+
+ const auto iter = block_pairs.find(current_index);
+ ASSERT(iter != block_pairs.end());
+ current_index = iter->second - 1;
+}
+
+VAddr CheatList::SanitizeAddress(VAddr in) const {
+ if ((in < main_region_begin || in >= main_region_end) &&
+ (in < heap_region_begin || in >= heap_region_end)) {
+ LOG_ERROR(Common_Filesystem,
+ "Cheat attempting to access memory at invalid address={:016X}, if this persists, "
+ "the cheat may be incorrect. However, this may be normal early in execution if "
+ "the game has not properly set up yet.",
+ in);
+ return 0; ///< Invalid addresses will hard crash
+ }
+
+ return in;
+}
+
+void CheatList::ExecuteSingleCheat(const Cheat& cheat) {
+ using CheatOperationFunction = void (CheatList::*)(const Cheat&);
+ constexpr std::array<CheatOperationFunction, 9> cheat_operation_functions{
+ &CheatList::WriteImmediate, &CheatList::BeginConditional,
+ &CheatList::EndConditional, &CheatList::Loop,
+ &CheatList::LoadImmediate, &CheatList::LoadIndexed,
+ &CheatList::StoreIndexed, &CheatList::RegisterArithmetic,
+ &CheatList::BeginConditionalInput,
+ };
+
+ const auto index = static_cast<u8>(cheat.type.Value());
+ ASSERT(index < sizeof(cheat_operation_functions));
+ const auto op = cheat_operation_functions[index];
+ (this->*op)(cheat);
+}
+
+void CheatList::ExecuteBlock(const Block& block) {
+ encountered_loops.clear();
+
+ ProcessBlockPairs(block);
+ for (std::size_t i = 0; i < block.size(); ++i) {
+ current_index = i;
+ ExecuteSingleCheat(block[i]);
+ i = current_index;
+ }
+}
+
+CheatParser::~CheatParser() = default;
+
+CheatList CheatParser::MakeCheatList(const Core::System& system, CheatList::ProgramSegment master,
+ CheatList::ProgramSegment standard) const {
+ return {system, std::move(master), std::move(standard)};
+}
+
+TextCheatParser::~TextCheatParser() = default;
+
+CheatList TextCheatParser::Parse(const Core::System& system, const std::vector<u8>& data) const {
+ std::stringstream ss;
+ ss.write(reinterpret_cast<const char*>(data.data()), data.size());
+
+ std::vector<std::string> lines;
+ std::string stream_line;
+ while (std::getline(ss, stream_line)) {
+ // Remove a trailing \r
+ if (!stream_line.empty() && stream_line.back() == '\r')
+ stream_line.pop_back();
+ lines.push_back(std::move(stream_line));
+ }
+
+ CheatList::ProgramSegment master_list;
+ CheatList::ProgramSegment standard_list;
+
+ for (std::size_t i = 0; i < lines.size(); ++i) {
+ auto line = lines[i];
+
+ if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
+ const auto master = line[0] == '{';
+ const auto begin = master ? line.find('{') : line.find('[');
+ const auto end = master ? line.rfind('}') : line.rfind(']');
+
+ ASSERT(begin != std::string::npos && end != std::string::npos);
+
+ const std::string patch_name{line.begin() + begin + 1, line.begin() + end};
+ CheatList::Block block{};
+
+ while (i < lines.size() - 1) {
+ line = lines[++i];
+ if (!line.empty() && (line[0] == '[' || line[0] == '{')) {
+ --i;
+ break;
+ }
+
+ if (line.size() < 8)
+ continue;
+
+ Cheat out{};
+ out.raw = ParseSingleLineCheat(line);
+ block.push_back(out);
+ }
+
+ (master ? master_list : standard_list).emplace_back(patch_name, block);
+ }
+ }
+
+ return MakeCheatList(system, master_list, standard_list);
+}
+
+std::array<u8, 16> TextCheatParser::ParseSingleLineCheat(const std::string& line) const {
+ std::array<u8, 16> out{};
+
+ if (line.size() < 8)
+ return out;
+
+ const auto word1 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data(), 8});
+ std::memcpy(out.data(), word1.data(), sizeof(u32));
+
+ if (line.size() < 17 || line[8] != ' ')
+ return out;
+
+ const auto word2 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 9, 8});
+ std::memcpy(out.data() + sizeof(u32), word2.data(), sizeof(u32));
+
+ if (line.size() < 26 || line[17] != ' ') {
+ // Perform shifting in case value is truncated early.
+ const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
+ if (type == CodeType::Loop || type == CodeType::LoadImmediate ||
+ type == CodeType::StoreIndexed || type == CodeType::RegisterArithmetic) {
+ std::memcpy(out.data() + 8, out.data() + 4, sizeof(u32));
+ std::memset(out.data() + 4, 0, sizeof(u32));
+ }
+
+ return out;
+ }
+
+ const auto word3 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 18, 8});
+ std::memcpy(out.data() + 2 * sizeof(u32), word3.data(), sizeof(u32));
+
+ if (line.size() < 35 || line[26] != ' ') {
+ // Perform shifting in case value is truncated early.
+ const auto type = static_cast<CodeType>((out[0] & 0xF0) >> 4);
+ if (type == CodeType::WriteImmediate || type == CodeType::Conditional) {
+ std::memcpy(out.data() + 12, out.data() + 8, sizeof(u32));
+ std::memset(out.data() + 8, 0, sizeof(u32));
+ }
+
+ return out;
+ }
+
+ const auto word4 = Common::HexStringToArray<sizeof(u32)>(std::string_view{line.data() + 27, 8});
+ std::memcpy(out.data() + 3 * sizeof(u32), word4.data(), sizeof(u32));
+
+ return out;
+}
+
+u64 MemoryReadImpl(u32 width, VAddr addr) {
+ switch (width) {
+ case 1:
+ return Memory::Read8(addr);
+ case 2:
+ return Memory::Read16(addr);
+ case 4:
+ return Memory::Read32(addr);
+ case 8:
+ return Memory::Read64(addr);
+ default:
+ UNREACHABLE();
+ return 0;
+ }
+}
+
+void MemoryWriteImpl(u32 width, VAddr addr, u64 value) {
+ switch (width) {
+ case 1:
+ Memory::Write8(addr, static_cast<u8>(value));
+ break;
+ case 2:
+ Memory::Write16(addr, static_cast<u16>(value));
+ break;
+ case 4:
+ Memory::Write32(addr, static_cast<u32>(value));
+ break;
+ case 8:
+ Memory::Write64(addr, value);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+CheatEngine::CheatEngine(Core::System& system, std::vector<CheatList> cheats_,
+ const std::string& build_id, VAddr code_region_start,
+ VAddr code_region_end)
+ : cheats{std::move(cheats_)}, core_timing{system.CoreTiming()} {
+ event = core_timing.RegisterEvent(
+ "CheatEngine::FrameCallback::" + build_id,
+ [this](u64 userdata, s64 cycles_late) { FrameCallback(userdata, cycles_late); });
+ core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS, event);
+
+ const auto& vm_manager = system.CurrentProcess()->VMManager();
+ for (auto& list : this->cheats) {
+ list.SetMemoryParameters(code_region_start, vm_manager.GetHeapRegionBaseAddress(),
+ code_region_end, vm_manager.GetHeapRegionEndAddress(),
+ &MemoryWriteImpl, &MemoryReadImpl);
+ }
+}
+
+CheatEngine::~CheatEngine() {
+ core_timing.UnscheduleEvent(event, 0);
+}
+
+void CheatEngine::FrameCallback(u64 userdata, s64 cycles_late) {
+ for (auto& list : cheats) {
+ list.Execute();
+ }
+
+ core_timing.ScheduleEvent(CHEAT_ENGINE_TICKS - cycles_late, event);
+}
+
+} // namespace FileSys
diff --git a/src/core/file_sys/cheat_engine.h b/src/core/file_sys/cheat_engine.h
new file mode 100644
index 000000000..ac22a82cb
--- /dev/null
+++ b/src/core/file_sys/cheat_engine.h
@@ -0,0 +1,234 @@
+// Copyright 2018 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <map>
+#include <set>
+#include <vector>
+#include "common/bit_field.h"
+#include "common/common_types.h"
+
+namespace Core {
+class System;
+}
+
+namespace Core::Timing {
+class CoreTiming;
+struct EventType;
+} // namespace Core::Timing
+
+namespace FileSys {
+
+enum class CodeType : u32 {
+ // 0TMR00AA AAAAAAAA YYYYYYYY YYYYYYYY
+ // Writes a T sized value Y to the address A added to the value of register R in memory domain M
+ WriteImmediate = 0,
+
+ // 1TMC00AA AAAAAAAA YYYYYYYY YYYYYYYY
+ // Compares the T sized value Y to the value at address A in memory domain M using the
+ // conditional function C. If success, continues execution. If failure, jumps to the matching
+ // EndConditional statement.
+ Conditional = 1,
+
+ // 20000000
+ // Terminates a Conditional or ConditionalInput block.
+ EndConditional = 2,
+
+ // 300R0000 VVVVVVVV
+ // Starts looping V times, storing the current count in register R.
+ // Loop block is terminated with a matching 310R0000.
+ Loop = 3,
+
+ // 400R0000 VVVVVVVV VVVVVVVV
+ // Sets the value of register R to the value V.
+ LoadImmediate = 4,
+
+ // 5TMRI0AA AAAAAAAA
+ // Sets the value of register R to the value of width T at address A in memory domain M, with
+ // the current value of R added to the address if I == 1.
+ LoadIndexed = 5,
+
+ // 6T0RIFG0 VVVVVVVV VVVVVVVV
+ // Writes the value V of width T to the memory address stored in register R. Adds the value of
+ // register G to the final calculation if F is nonzero. Increments the value of register R by T
+ // after operation if I is nonzero.
+ StoreIndexed = 6,
+
+ // 7T0RA000 VVVVVVVV
+ // Performs the arithmetic operation A on the value in register R and the value V of width T,
+ // storing the result in register R.
+ RegisterArithmetic = 7,
+
+ // 8KKKKKKK
+ // Checks to see if any of the buttons defined by the bitmask K are pressed. If any are,
+ // execution continues. If none are, execution skips to the next EndConditional command.
+ ConditionalInput = 8,
+};
+
+enum class MemoryType : u32 {
+ // Addressed relative to start of main NSO
+ MainNSO = 0,
+
+ // Addressed relative to start of heap
+ Heap = 1,
+};
+
+enum class ArithmeticOp : u32 {
+ Add = 0,
+ Sub = 1,
+ Mult = 2,
+ LShift = 3,
+ RShift = 4,
+};
+
+enum class ComparisonOp : u32 {
+ GreaterThan = 1,
+ GreaterThanEqual = 2,
+ LessThan = 3,
+ LessThanEqual = 4,
+ Equal = 5,
+ Inequal = 6,
+};
+
+union Cheat {
+ std::array<u8, 16> raw;
+
+ BitField<4, 4, CodeType> type;
+ BitField<0, 4, u32> width; // Can be 1, 2, 4, or 8. Measured in bytes.
+ BitField<0, 4, u32> end_of_loop;
+ BitField<12, 4, MemoryType> memory_type;
+ BitField<8, 4, u32> register_3;
+ BitField<8, 4, ComparisonOp> comparison_op;
+ BitField<20, 4, u32> load_from_register;
+ BitField<20, 4, u32> increment_register;
+ BitField<20, 4, ArithmeticOp> arithmetic_op;
+ BitField<16, 4, u32> add_additional_register;
+ BitField<28, 4, u32> register_6;
+
+ u64 Address() const;
+ u64 ValueWidth(u64 offset) const;
+ u64 Value(u64 offset, u64 width) const;
+ u32 KeypadValue() const;
+};
+
+class CheatParser;
+
+// Represents a full collection of cheats for a game. The Execute function should be called every
+// interval that all cheats should be executed. Clients should not directly instantiate this class
+// (hence private constructor), they should instead receive an instance from CheatParser, which
+// guarantees the list is always in an acceptable state.
+class CheatList {
+public:
+ friend class CheatParser;
+
+ using Block = std::vector<Cheat>;
+ using ProgramSegment = std::vector<std::pair<std::string, Block>>;
+
+ // (width in bytes, address, value)
+ using MemoryWriter = void (*)(u32, VAddr, u64);
+ // (width in bytes, address) -> value
+ using MemoryReader = u64 (*)(u32, VAddr);
+
+ void SetMemoryParameters(VAddr main_begin, VAddr heap_begin, VAddr main_end, VAddr heap_end,
+ MemoryWriter writer, MemoryReader reader);
+
+ void Execute();
+
+private:
+ CheatList(const Core::System& system_, ProgramSegment master, ProgramSegment standard);
+
+ void ProcessBlockPairs(const Block& block);
+ void ExecuteSingleCheat(const Cheat& cheat);
+
+ void ExecuteBlock(const Block& block);
+
+ bool EvaluateConditional(const Cheat& cheat) const;
+
+ // Individual cheat operations
+ void WriteImmediate(const Cheat& cheat);
+ void BeginConditional(const Cheat& cheat);
+ void EndConditional(const Cheat& cheat);
+ void Loop(const Cheat& cheat);
+ void LoadImmediate(const Cheat& cheat);
+ void LoadIndexed(const Cheat& cheat);
+ void StoreIndexed(const Cheat& cheat);
+ void RegisterArithmetic(const Cheat& cheat);
+ void BeginConditionalInput(const Cheat& cheat);
+
+ VAddr SanitizeAddress(VAddr in) const;
+
+ // Master Codes are defined as codes that cannot be disabled and are run prior to all
+ // others.
+ ProgramSegment master_list;
+ // All other codes
+ ProgramSegment standard_list;
+
+ bool in_standard = false;
+
+ // 16 (0x0-0xF) scratch registers that can be used by cheats
+ std::array<u64, 16> scratch{};
+
+ MemoryWriter writer = nullptr;
+ MemoryReader reader = nullptr;
+
+ u64 main_region_begin{};
+ u64 heap_region_begin{};
+ u64 main_region_end{};
+ u64 heap_region_end{};
+
+ u64 current_block{};
+ // The current index of the cheat within the current Block
+ u64 current_index{};
+
+ // The 'stack' of the program. When a conditional or loop statement is encountered, its index is
+ // pushed onto this queue. When a end block is encountered, the condition is checked.
+ std::map<u64, u64> block_pairs;
+
+ std::set<u64> encountered_loops;
+
+ const Core::System* system;
+};
+
+// Intermediary class that parses a text file or other disk format for storing cheats into a
+// CheatList object, that can be used for execution.
+class CheatParser {
+public:
+ virtual ~CheatParser();
+
+ virtual CheatList Parse(const Core::System& system, const std::vector<u8>& data) const = 0;
+
+protected:
+ CheatList MakeCheatList(const Core::System& system_, CheatList::ProgramSegment master,
+ CheatList::ProgramSegment standard) const;
+};
+
+// CheatParser implementation that parses text files
+class TextCheatParser final : public CheatParser {
+public:
+ ~TextCheatParser() override;
+
+ CheatList Parse(const Core::System& system, const std::vector<u8>& data) const override;
+
+private:
+ std::array<u8, 16> ParseSingleLineCheat(const std::string& line) const;
+};
+
+// Class that encapsulates a CheatList and manages its interaction with memory and CoreTiming
+class CheatEngine final {
+public:
+ CheatEngine(Core::System& system_, std::vector<CheatList> cheats_, const std::string& build_id,
+ VAddr code_region_start, VAddr code_region_end);
+ ~CheatEngine();
+
+private:
+ void FrameCallback(u64 userdata, s64 cycles_late);
+
+ std::vector<CheatList> cheats;
+
+ Core::Timing::EventType* event;
+ Core::Timing::CoreTiming& core_timing;
+};
+
+} // namespace FileSys
diff --git a/src/core/file_sys/content_archive.h b/src/core/file_sys/content_archive.h
index 5d4d05c82..15b9e6624 100644
--- a/src/core/file_sys/content_archive.h
+++ b/src/core/file_sys/content_archive.h
@@ -24,13 +24,26 @@ namespace FileSys {
union NCASectionHeader;
+/// Describes the type of content within an NCA archive.
enum class NCAContentType : u8 {
+ /// Executable-related data
Program = 0,
+
+ /// Metadata.
Meta = 1,
+
+ /// Access control data.
Control = 2,
+
+ /// Information related to the game manual
+ /// e.g. Legal information, etc.
Manual = 3,
+
+ /// System data.
Data = 4,
- Data_Unknown5 = 5, ///< Seems to be used on some system archives
+
+ /// Data that can be accessed by applications.
+ PublicData = 5,
};
enum class NCASectionCryptoType : u8 {
diff --git a/src/core/file_sys/errors.h b/src/core/file_sys/errors.h
index e4a4ee4ab..bb4654366 100644
--- a/src/core/file_sys/errors.h
+++ b/src/core/file_sys/errors.h
@@ -11,6 +11,9 @@ namespace FileSys {
constexpr ResultCode ERROR_PATH_NOT_FOUND{ErrorModule::FS, 1};
constexpr ResultCode ERROR_ENTITY_NOT_FOUND{ErrorModule::FS, 1002};
constexpr ResultCode ERROR_SD_CARD_NOT_FOUND{ErrorModule::FS, 2001};
+constexpr ResultCode ERROR_OUT_OF_BOUNDS{ErrorModule::FS, 3005};
+constexpr ResultCode ERROR_FAILED_MOUNT_ARCHIVE{ErrorModule::FS, 3223};
+constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::FS, 6001};
constexpr ResultCode ERROR_INVALID_OFFSET{ErrorModule::FS, 6061};
constexpr ResultCode ERROR_INVALID_SIZE{ErrorModule::FS, 6062};
diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp
index 61706966e..e11217708 100644
--- a/src/core/file_sys/patch_manager.cpp
+++ b/src/core/file_sys/patch_manager.cpp
@@ -7,6 +7,7 @@
#include <cstddef>
#include <cstring>
+#include "common/file_util.h"
#include "common/hex_util.h"
#include "common/logging/log.h"
#include "core/file_sys/content_archive.h"
@@ -19,6 +20,7 @@
#include "core/file_sys/vfs_vector.h"
#include "core/hle/service/filesystem/filesystem.h"
#include "core/loader/loader.h"
+#include "core/loader/nso.h"
#include "core/settings.h"
namespace FileSys {
@@ -31,14 +33,6 @@ constexpr std::array<const char*, 14> EXEFS_FILE_NAMES{
"subsdk3", "subsdk4", "subsdk5", "subsdk6", "subsdk7", "subsdk8", "subsdk9",
};
-struct NSOBuildHeader {
- u32_le magic;
- INSERT_PADDING_BYTES(0x3C);
- std::array<u8, 0x20> build_id;
- INSERT_PADDING_BYTES(0xA0);
-};
-static_assert(sizeof(NSOBuildHeader) == 0x100, "NSOBuildHeader has incorrect size.");
-
std::string FormatTitleVersion(u32 version, TitleVersionFormat format) {
std::array<u8, sizeof(u32)> bytes{};
bytes[0] = version % SINGLE_BYTE_MODULUS;
@@ -162,14 +156,16 @@ std::vector<VirtualFile> PatchManager::CollectPatches(const std::vector<VirtualD
}
std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
- if (nso.size() < 0x100)
+ if (nso.size() < sizeof(Loader::NSOHeader)) {
return nso;
+ }
- NSOBuildHeader header;
- std::memcpy(&header, nso.data(), sizeof(NSOBuildHeader));
+ Loader::NSOHeader header;
+ std::memcpy(&header, nso.data(), sizeof(header));
- if (header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
+ if (header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
return nso;
+ }
const auto build_id_raw = Common::HexArrayToString(header.build_id);
const auto build_id = build_id_raw.substr(0, build_id_raw.find_last_not_of('0') + 1);
@@ -212,9 +208,11 @@ std::vector<u8> PatchManager::PatchNSO(const std::vector<u8>& nso) const {
}
}
- if (out.size() < 0x100)
+ if (out.size() < sizeof(Loader::NSOHeader)) {
return nso;
- std::memcpy(out.data(), &header, sizeof(NSOBuildHeader));
+ }
+
+ std::memcpy(out.data(), &header, sizeof(header));
return out;
}
@@ -232,6 +230,57 @@ bool PatchManager::HasNSOPatch(const std::array<u8, 32>& build_id_) const {
return !CollectPatches(patch_dirs, build_id).empty();
}
+static std::optional<CheatList> ReadCheatFileFromFolder(const Core::System& system, u64 title_id,
+ const std::array<u8, 0x20>& build_id_,
+ const VirtualDir& base_path, bool upper) {
+ const auto build_id_raw = Common::HexArrayToString(build_id_, upper);
+ const auto build_id = build_id_raw.substr(0, sizeof(u64) * 2);
+ const auto file = base_path->GetFile(fmt::format("{}.txt", build_id));
+
+ if (file == nullptr) {
+ LOG_INFO(Common_Filesystem, "No cheats file found for title_id={:016X}, build_id={}",
+ title_id, build_id);
+ return std::nullopt;
+ }
+
+ std::vector<u8> data(file->GetSize());
+ if (file->Read(data.data(), data.size()) != data.size()) {
+ LOG_INFO(Common_Filesystem, "Failed to read cheats file for title_id={:016X}, build_id={}",
+ title_id, build_id);
+ return std::nullopt;
+ }
+
+ TextCheatParser parser;
+ return parser.Parse(system, data);
+}
+
+std::vector<CheatList> PatchManager::CreateCheatList(const Core::System& system,
+ const std::array<u8, 32>& build_id_) const {
+ const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
+ auto patch_dirs = load_dir->GetSubdirectories();
+ std::sort(patch_dirs.begin(), patch_dirs.end(),
+ [](const VirtualDir& l, const VirtualDir& r) { return l->GetName() < r->GetName(); });
+
+ std::vector<CheatList> out;
+ out.reserve(patch_dirs.size());
+ for (const auto& subdir : patch_dirs) {
+ auto cheats_dir = subdir->GetSubdirectory("cheats");
+ if (cheats_dir != nullptr) {
+ auto res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, true);
+ if (res.has_value()) {
+ out.push_back(std::move(*res));
+ continue;
+ }
+
+ res = ReadCheatFileFromFolder(system, title_id, build_id_, cheats_dir, false);
+ if (res.has_value())
+ out.push_back(std::move(*res));
+ }
+ }
+
+ return out;
+}
+
static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type) {
const auto load_dir = Service::FileSystem::GetModificationLoadRoot(title_id);
if ((type != ContentRecordType::Program && type != ContentRecordType::Data) ||
@@ -403,6 +452,8 @@ std::map<std::string, std::string, std::less<>> PatchManager::GetPatchVersionNam
}
if (IsDirValidAndNonEmpty(mod->GetSubdirectory("romfs")))
AppendCommaIfNotEmpty(types, "LayeredFS");
+ if (IsDirValidAndNonEmpty(mod->GetSubdirectory("cheats")))
+ AppendCommaIfNotEmpty(types, "Cheats");
if (types.empty())
continue;
diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h
index b8a1652fd..de2672c76 100644
--- a/src/core/file_sys/patch_manager.h
+++ b/src/core/file_sys/patch_manager.h
@@ -8,9 +8,14 @@
#include <memory>
#include <string>
#include "common/common_types.h"
+#include "core/file_sys/cheat_engine.h"
#include "core/file_sys/nca_metadata.h"
#include "core/file_sys/vfs.h"
+namespace Core {
+class System;
+}
+
namespace FileSys {
class NCA;
@@ -45,6 +50,10 @@ public:
// Used to prevent expensive copies in NSO loader.
bool HasNSOPatch(const std::array<u8, 0x20>& build_id) const;
+ // Creates a CheatList object with all
+ std::vector<CheatList> CreateCheatList(const Core::System& system,
+ const std::array<u8, 0x20>& build_id) const;
+
// Currently tracked RomFS patches:
// - Game Updates
// - LayeredFS
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp
index 128199063..1c6bacace 100644
--- a/src/core/file_sys/registered_cache.cpp
+++ b/src/core/file_sys/registered_cache.cpp
@@ -94,7 +94,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
case NCAContentType::Control:
return ContentRecordType::Control;
case NCAContentType::Data:
- case NCAContentType::Data_Unknown5:
+ case NCAContentType::PublicData:
return ContentRecordType::Data;
case NCAContentType::Manual:
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
index e3e79f40a..c9722ed77 100644
--- a/src/core/file_sys/system_archive/system_archive.cpp
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -6,6 +6,7 @@
#include "core/file_sys/romfs.h"
#include "core/file_sys/system_archive/ng_word.h"
#include "core/file_sys/system_archive/system_archive.h"
+#include "core/file_sys/system_archive/system_version.h"
namespace FileSys::SystemArchive {
@@ -30,7 +31,7 @@ constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHI
{0x0100000000000806, "NgWord", &NgWord1},
{0x0100000000000807, "SsidList", nullptr},
{0x0100000000000808, "Dictionary", nullptr},
- {0x0100000000000809, "SystemVersion", nullptr},
+ {0x0100000000000809, "SystemVersion", &SystemVersion},
{0x010000000000080A, "AvatarImage", nullptr},
{0x010000000000080B, "LocalNews", nullptr},
{0x010000000000080C, "Eula", nullptr},
diff --git a/src/core/file_sys/system_archive/system_version.cpp b/src/core/file_sys/system_archive/system_version.cpp
new file mode 100644
index 000000000..6e22f97b0
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_version.cpp
@@ -0,0 +1,52 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/file_sys/system_archive/system_version.h"
+#include "core/file_sys/vfs_vector.h"
+
+namespace FileSys::SystemArchive {
+
+namespace SystemVersionData {
+
+// This section should reflect the best system version to describe yuzu's HLE api.
+// TODO(DarkLordZach): Update when HLE gets better.
+
+constexpr u8 VERSION_MAJOR = 5;
+constexpr u8 VERSION_MINOR = 1;
+constexpr u8 VERSION_MICRO = 0;
+
+constexpr u8 REVISION_MAJOR = 3;
+constexpr u8 REVISION_MINOR = 0;
+
+constexpr char PLATFORM_STRING[] = "NX";
+constexpr char VERSION_HASH[] = "23f9df53e25709d756e0c76effcb2473bd3447dd";
+constexpr char DISPLAY_VERSION[] = "5.1.0";
+constexpr char DISPLAY_TITLE[] = "NintendoSDK Firmware for NX 5.1.0-3.0";
+
+} // namespace SystemVersionData
+
+std::string GetLongDisplayVersion() {
+ return SystemVersionData::DISPLAY_TITLE;
+}
+
+VirtualDir SystemVersion() {
+ VirtualFile file = std::make_shared<VectorVfsFile>(std::vector<u8>(0x100), "file");
+ file->WriteObject(SystemVersionData::VERSION_MAJOR, 0);
+ file->WriteObject(SystemVersionData::VERSION_MINOR, 1);
+ file->WriteObject(SystemVersionData::VERSION_MICRO, 2);
+ file->WriteObject(SystemVersionData::REVISION_MAJOR, 4);
+ file->WriteObject(SystemVersionData::REVISION_MINOR, 5);
+ file->WriteArray(SystemVersionData::PLATFORM_STRING,
+ std::min<u64>(sizeof(SystemVersionData::PLATFORM_STRING), 0x20ULL), 0x8);
+ file->WriteArray(SystemVersionData::VERSION_HASH,
+ std::min<u64>(sizeof(SystemVersionData::VERSION_HASH), 0x40ULL), 0x28);
+ file->WriteArray(SystemVersionData::DISPLAY_VERSION,
+ std::min<u64>(sizeof(SystemVersionData::DISPLAY_VERSION), 0x18ULL), 0x68);
+ file->WriteArray(SystemVersionData::DISPLAY_TITLE,
+ std::min<u64>(sizeof(SystemVersionData::DISPLAY_TITLE), 0x80ULL), 0x80);
+ return std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{file},
+ std::vector<VirtualDir>{}, "data");
+}
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_version.h b/src/core/file_sys/system_archive/system_version.h
new file mode 100644
index 000000000..deed79b26
--- /dev/null
+++ b/src/core/file_sys/system_archive/system_version.h
@@ -0,0 +1,16 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <string>
+#include "core/file_sys/vfs_types.h"
+
+namespace FileSys::SystemArchive {
+
+std::string GetLongDisplayVersion();
+
+VirtualDir SystemVersion();
+
+} // namespace FileSys::SystemArchive
diff --git a/src/core/hle/ipc.h b/src/core/hle/ipc.h
index 455d1f346..fae54bcc7 100644
--- a/src/core/hle/ipc.h
+++ b/src/core/hle/ipc.h
@@ -39,10 +39,10 @@ struct CommandHeader {
union {
u32_le raw_low;
BitField<0, 16, CommandType> type;
- BitField<16, 4, u32_le> num_buf_x_descriptors;
- BitField<20, 4, u32_le> num_buf_a_descriptors;
- BitField<24, 4, u32_le> num_buf_b_descriptors;
- BitField<28, 4, u32_le> num_buf_w_descriptors;
+ BitField<16, 4, u32> num_buf_x_descriptors;
+ BitField<20, 4, u32> num_buf_a_descriptors;
+ BitField<24, 4, u32> num_buf_b_descriptors;
+ BitField<28, 4, u32> num_buf_w_descriptors;
};
enum class BufferDescriptorCFlag : u32 {
@@ -53,28 +53,28 @@ struct CommandHeader {
union {
u32_le raw_high;
- BitField<0, 10, u32_le> data_size;
+ BitField<0, 10, u32> data_size;
BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags;
- BitField<31, 1, u32_le> enable_handle_descriptor;
+ BitField<31, 1, u32> enable_handle_descriptor;
};
};
static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect");
union HandleDescriptorHeader {
u32_le raw_high;
- BitField<0, 1, u32_le> send_current_pid;
- BitField<1, 4, u32_le> num_handles_to_copy;
- BitField<5, 4, u32_le> num_handles_to_move;
+ BitField<0, 1, u32> send_current_pid;
+ BitField<1, 4, u32> num_handles_to_copy;
+ BitField<5, 4, u32> num_handles_to_move;
};
static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect");
struct BufferDescriptorX {
union {
- BitField<0, 6, u32_le> counter_bits_0_5;
- BitField<6, 3, u32_le> address_bits_36_38;
- BitField<9, 3, u32_le> counter_bits_9_11;
- BitField<12, 4, u32_le> address_bits_32_35;
- BitField<16, 16, u32_le> size;
+ BitField<0, 6, u32> counter_bits_0_5;
+ BitField<6, 3, u32> address_bits_36_38;
+ BitField<9, 3, u32> counter_bits_9_11;
+ BitField<12, 4, u32> address_bits_32_35;
+ BitField<16, 16, u32> size;
};
u32_le address_bits_0_31;
@@ -103,10 +103,10 @@ struct BufferDescriptorABW {
u32_le address_bits_0_31;
union {
- BitField<0, 2, u32_le> flags;
- BitField<2, 3, u32_le> address_bits_36_38;
- BitField<24, 4, u32_le> size_bits_32_35;
- BitField<28, 4, u32_le> address_bits_32_35;
+ BitField<0, 2, u32> flags;
+ BitField<2, 3, u32> address_bits_36_38;
+ BitField<24, 4, u32> size_bits_32_35;
+ BitField<28, 4, u32> address_bits_32_35;
};
VAddr Address() const {
@@ -128,8 +128,8 @@ struct BufferDescriptorC {
u32_le address_bits_0_31;
union {
- BitField<0, 16, u32_le> address_bits_32_47;
- BitField<16, 16, u32_le> size;
+ BitField<0, 16, u32> address_bits_32_47;
+ BitField<16, 16, u32> size;
};
VAddr Address() const {
@@ -167,8 +167,8 @@ struct DomainMessageHeader {
struct {
union {
BitField<0, 8, CommandType> command;
- BitField<8, 8, u32_le> input_object_count;
- BitField<16, 16, u32_le> size;
+ BitField<8, 8, u32> input_object_count;
+ BitField<16, 16, u32> size;
};
u32_le object_id;
INSERT_PADDING_WORDS(2);
diff --git a/src/core/hle/ipc_helpers.h b/src/core/hle/ipc_helpers.h
index 079283830..68406eb63 100644
--- a/src/core/hle/ipc_helpers.h
+++ b/src/core/hle/ipc_helpers.h
@@ -19,9 +19,12 @@
#include "core/hle/kernel/hle_ipc.h"
#include "core/hle/kernel/object.h"
#include "core/hle/kernel/server_session.h"
+#include "core/hle/result.h"
namespace IPC {
+constexpr ResultCode ERR_REMOTE_PROCESS_DEAD{ErrorModule::HIPC, 301};
+
class RequestHelperBase {
protected:
Kernel::HLERequestContext* context = nullptr;
@@ -272,6 +275,20 @@ inline void ResponseBuilder::Push(u64 value) {
}
template <>
+inline void ResponseBuilder::Push(float value) {
+ u32 integral;
+ std::memcpy(&integral, &value, sizeof(u32));
+ Push(integral);
+}
+
+template <>
+inline void ResponseBuilder::Push(double value) {
+ u64 integral;
+ std::memcpy(&integral, &value, sizeof(u64));
+ Push(integral);
+}
+
+template <>
inline void ResponseBuilder::Push(bool value) {
Push(static_cast<u8>(value));
}
@@ -362,6 +379,11 @@ inline u32 RequestParser::Pop() {
return cmdbuf[index++];
}
+template <>
+inline s32 RequestParser::Pop() {
+ return static_cast<s32>(Pop<u32>());
+}
+
template <typename T>
void RequestParser::PopRaw(T& value) {
std::memcpy(&value, cmdbuf + index, sizeof(T));
@@ -393,11 +415,37 @@ inline u64 RequestParser::Pop() {
}
template <>
+inline s8 RequestParser::Pop() {
+ return static_cast<s8>(Pop<u8>());
+}
+
+template <>
+inline s16 RequestParser::Pop() {
+ return static_cast<s16>(Pop<u16>());
+}
+
+template <>
inline s64 RequestParser::Pop() {
return static_cast<s64>(Pop<u64>());
}
template <>
+inline float RequestParser::Pop() {
+ const u32 value = Pop<u32>();
+ float real;
+ std::memcpy(&real, &value, sizeof(real));
+ return real;
+}
+
+template <>
+inline double RequestParser::Pop() {
+ const u64 value = Pop<u64>();
+ float real;
+ std::memcpy(&real, &value, sizeof(real));
+ return real;
+}
+
+template <>
inline bool RequestParser::Pop() {
return Pop<u8>() != 0;
}
diff --git a/src/core/hle/kernel/client_port.cpp b/src/core/hle/kernel/client_port.cpp
index d4c91d529..aa432658e 100644
--- a/src/core/hle/kernel/client_port.cpp
+++ b/src/core/hle/kernel/client_port.cpp
@@ -33,10 +33,11 @@ ResultVal<SharedPtr<ClientSession>> ClientPort::Connect() {
// Create a new session pair, let the created sessions inherit the parent port's HLE handler.
auto sessions = ServerSession::CreateSessionPair(kernel, server_port->GetName(), this);
- if (server_port->hle_handler)
- server_port->hle_handler->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
- else
- server_port->pending_sessions.push_back(std::get<SharedPtr<ServerSession>>(sessions));
+ if (server_port->HasHLEHandler()) {
+ server_port->GetHLEHandler()->ClientConnected(std::get<SharedPtr<ServerSession>>(sessions));
+ } else {
+ server_port->AppendPendingSession(std::get<SharedPtr<ServerSession>>(sessions));
+ }
// Wake the threads waiting on the ServerPort
server_port->WakeupAllWaitingThreads();
diff --git a/src/core/hle/kernel/code_set.cpp b/src/core/hle/kernel/code_set.cpp
new file mode 100644
index 000000000..1f434e9af
--- /dev/null
+++ b/src/core/hle/kernel/code_set.cpp
@@ -0,0 +1,12 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/kernel/code_set.h"
+
+namespace Kernel {
+
+CodeSet::CodeSet() = default;
+CodeSet::~CodeSet() = default;
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/code_set.h b/src/core/hle/kernel/code_set.h
new file mode 100644
index 000000000..834fd23d2
--- /dev/null
+++ b/src/core/hle/kernel/code_set.h
@@ -0,0 +1,90 @@
+// Copyright 2019 yuzu emulator team
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <cstddef>
+#include <memory>
+#include <vector>
+
+#include "common/common_types.h"
+
+namespace Kernel {
+
+/**
+ * Represents executable data that may be loaded into a kernel process.
+ *
+ * A code set consists of three basic segments:
+ * - A code (AKA text) segment,
+ * - A read-only data segment (rodata)
+ * - A data segment
+ *
+ * The code segment is the portion of the object file that contains
+ * executable instructions.
+ *
+ * The read-only data segment in the portion of the object file that
+ * contains (as one would expect) read-only data, such as fixed constant
+ * values and data structures.
+ *
+ * The data segment is similar to the read-only data segment -- it contains
+ * variables and data structures that have predefined values, however,
+ * entities within this segment can be modified.
+ */
+struct CodeSet final {
+ /// A single segment within a code set.
+ struct Segment final {
+ /// The byte offset that this segment is located at.
+ std::size_t offset = 0;
+
+ /// The address to map this segment to.
+ VAddr addr = 0;
+
+ /// The size of this segment in bytes.
+ u32 size = 0;
+ };
+
+ explicit CodeSet();
+ ~CodeSet();
+
+ CodeSet(const CodeSet&) = delete;
+ CodeSet& operator=(const CodeSet&) = delete;
+
+ CodeSet(CodeSet&&) = default;
+ CodeSet& operator=(CodeSet&&) = default;
+
+ Segment& CodeSegment() {
+ return segments[0];
+ }
+
+ const Segment& CodeSegment() const {
+ return segments[0];
+ }
+
+ Segment& RODataSegment() {
+ return segments[1];
+ }
+
+ const Segment& RODataSegment() const {
+ return segments[1];
+ }
+
+ Segment& DataSegment() {
+ return segments[2];
+ }
+
+ const Segment& DataSegment() const {
+ return segments[2];
+ }
+
+ /// The overall data that backs this code set.
+ std::shared_ptr<std::vector<u8>> memory;
+
+ /// The segments that comprise this code set.
+ std::array<Segment, 3> segments;
+
+ /// The entry point address for this code set.
+ VAddr entrypoint = 0;
+};
+
+} // namespace Kernel
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 0743670ad..98e87313b 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -2,7 +2,6 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <map>
#include <utility>
#include <vector>
@@ -10,8 +9,11 @@
#include "core/core.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/object.h"
+#include "core/hle/kernel/process.h"
+#include "core/hle/kernel/scheduler.h"
#include "core/hle/kernel/thread.h"
#include "core/hle/result.h"
#include "core/memory.h"
@@ -57,41 +59,47 @@ static void TransferMutexOwnership(VAddr mutex_addr, SharedPtr<Thread> current_t
}
}
-ResultCode Mutex::TryAcquire(HandleTable& handle_table, VAddr address, Handle holding_thread_handle,
+Mutex::Mutex(Core::System& system) : system{system} {}
+Mutex::~Mutex() = default;
+
+ResultCode Mutex::TryAcquire(VAddr address, Handle holding_thread_handle,
Handle requesting_thread_handle) {
// The mutex address must be 4-byte aligned
if ((address % sizeof(u32)) != 0) {
return ERR_INVALID_ADDRESS;
}
+ const auto& handle_table = system.Kernel().CurrentProcess()->GetHandleTable();
+ Thread* const current_thread = system.CurrentScheduler().GetCurrentThread();
SharedPtr<Thread> holding_thread = handle_table.Get<Thread>(holding_thread_handle);
SharedPtr<Thread> requesting_thread = handle_table.Get<Thread>(requesting_thread_handle);
// TODO(Subv): It is currently unknown if it is possible to lock a mutex in behalf of another
// thread.
- ASSERT(requesting_thread == GetCurrentThread());
+ ASSERT(requesting_thread == current_thread);
- u32 addr_value = Memory::Read32(address);
+ const u32 addr_value = Memory::Read32(address);
// If the mutex isn't being held, just return success.
if (addr_value != (holding_thread_handle | Mutex::MutexHasWaitersFlag)) {
return RESULT_SUCCESS;
}
- if (holding_thread == nullptr)
+ if (holding_thread == nullptr) {
return ERR_INVALID_HANDLE;
+ }
// Wait until the mutex is released
- GetCurrentThread()->SetMutexWaitAddress(address);
- GetCurrentThread()->SetWaitHandle(requesting_thread_handle);
+ current_thread->SetMutexWaitAddress(address);
+ current_thread->SetWaitHandle(requesting_thread_handle);
- GetCurrentThread()->SetStatus(ThreadStatus::WaitMutex);
- GetCurrentThread()->InvalidateWakeupCallback();
+ current_thread->SetStatus(ThreadStatus::WaitMutex);
+ current_thread->InvalidateWakeupCallback();
// Update the lock holder thread's priority to prevent priority inversion.
- holding_thread->AddMutexWaiter(GetCurrentThread());
+ holding_thread->AddMutexWaiter(current_thread);
- Core::System::GetInstance().PrepareReschedule();
+ system.PrepareReschedule();
return RESULT_SUCCESS;
}
@@ -102,7 +110,8 @@ ResultCode Mutex::Release(VAddr address) {
return ERR_INVALID_ADDRESS;
}
- auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(GetCurrentThread(), address);
+ auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
+ auto [thread, num_waiters] = GetHighestPriorityMutexWaitingThread(current_thread, address);
// There are no more threads waiting for the mutex, release it completely.
if (thread == nullptr) {
@@ -111,7 +120,7 @@ ResultCode Mutex::Release(VAddr address) {
}
// Transfer the ownership of the mutex from the previous owner to the new one.
- TransferMutexOwnership(address, GetCurrentThread(), thread);
+ TransferMutexOwnership(address, current_thread, thread);
u32 mutex_value = thread->GetWaitHandle();
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
index 81e62d497..b904de2e8 100644
--- a/src/core/hle/kernel/mutex.h
+++ b/src/core/hle/kernel/mutex.h
@@ -5,32 +5,34 @@
#pragma once
#include "common/common_types.h"
-#include "core/hle/kernel/object.h"
union ResultCode;
-namespace Kernel {
+namespace Core {
+class System;
+}
-class HandleTable;
-class Thread;
+namespace Kernel {
class Mutex final {
public:
+ explicit Mutex(Core::System& system);
+ ~Mutex();
+
/// Flag that indicates that a mutex still has threads waiting for it.
static constexpr u32 MutexHasWaitersFlag = 0x40000000;
/// Mask of the bits in a mutex address value that contain the mutex owner.
static constexpr u32 MutexOwnerMask = 0xBFFFFFFF;
/// Attempts to acquire a mutex at the specified address.
- static ResultCode TryAcquire(HandleTable& handle_table, VAddr address,
- Handle holding_thread_handle, Handle requesting_thread_handle);
+ ResultCode TryAcquire(VAddr address, Handle holding_thread_handle,
+ Handle requesting_thread_handle);
/// Releases the mutex at the specified address.
- static ResultCode Release(VAddr address);
+ ResultCode Release(VAddr address);
private:
- Mutex() = default;
- ~Mutex() = default;
+ Core::System& system;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/process.cpp b/src/core/hle/kernel/process.cpp
index 7e8ba978c..0d782e4ba 100644
--- a/src/core/hle/kernel/process.cpp
+++ b/src/core/hle/kernel/process.cpp
@@ -9,6 +9,7 @@
#include "common/logging/log.h"
#include "core/core.h"
#include "core/file_sys/program_metadata.h"
+#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
@@ -31,7 +32,7 @@ namespace {
*/
void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
// Setup page table so we can write to memory
- SetCurrentPageTable(&owner_process.VMManager().page_table);
+ Memory::SetCurrentPageTable(&owner_process.VMManager().page_table);
// Initialize new "main" thread
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
@@ -50,9 +51,6 @@ void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_poi
}
} // Anonymous namespace
-CodeSet::CodeSet() = default;
-CodeSet::~CodeSet() = default;
-
SharedPtr<Process> Process::Create(Core::System& system, std::string&& name) {
auto& kernel = system.Kernel();
@@ -133,7 +131,7 @@ void Process::PrepareForTermination() {
if (thread->GetOwnerProcess() != this)
continue;
- if (thread == GetCurrentThread())
+ if (thread == system.CurrentScheduler().GetCurrentThread())
continue;
// TODO(Subv): When are the other running/ready threads terminated?
@@ -145,7 +143,6 @@ void Process::PrepareForTermination() {
}
};
- const auto& system = Core::System::GetInstance();
stop_threads(system.Scheduler(0).GetThreadList());
stop_threads(system.Scheduler(1).GetThreadList());
stop_threads(system.Scheduler(2).GetThreadList());
@@ -213,7 +210,7 @@ void Process::FreeTLSSlot(VAddr tls_address) {
}
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
- const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
+ const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions,
MemoryState memory_state) {
const auto vma = vm_manager
.MapMemoryBlock(segment.addr + base_addr, module_.memory,
@@ -223,18 +220,17 @@ void Process::LoadModule(CodeSet module_, VAddr base_addr) {
};
// Map CodeSet segments
- MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::CodeStatic);
- MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeMutable);
- MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeMutable);
+ MapSegment(module_.CodeSegment(), VMAPermission::ReadExecute, MemoryState::Code);
+ MapSegment(module_.RODataSegment(), VMAPermission::Read, MemoryState::CodeData);
+ MapSegment(module_.DataSegment(), VMAPermission::ReadWrite, MemoryState::CodeData);
// Clear instruction cache in CPU JIT
- Core::System::GetInstance().ArmInterface(0).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(1).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(2).ClearInstructionCache();
- Core::System::GetInstance().ArmInterface(3).ClearInstructionCache();
+ system.InvalidateCpuInstructionCaches();
}
-Process::Process(Core::System& system) : WaitObject{system.Kernel()}, address_arbiter{system} {}
+Process::Process(Core::System& system)
+ : WaitObject{system.Kernel()}, address_arbiter{system}, mutex{system}, system{system} {}
+
Process::~Process() = default;
void Process::Acquire(Thread* thread) {
diff --git a/src/core/hle/kernel/process.h b/src/core/hle/kernel/process.h
index 2a132c894..1bd7bf5c1 100644
--- a/src/core/hle/kernel/process.h
+++ b/src/core/hle/kernel/process.h
@@ -7,13 +7,13 @@
#include <array>
#include <bitset>
#include <cstddef>
-#include <memory>
#include <string>
#include <vector>
#include <boost/container/static_vector.hpp>
#include "common/common_types.h"
#include "core/hle/kernel/address_arbiter.h"
#include "core/hle/kernel/handle_table.h"
+#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/process_capability.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/kernel/wait_object.h"
@@ -33,6 +33,8 @@ class KernelCore;
class ResourceLimit;
class Thread;
+struct CodeSet;
+
struct AddressMapping {
// Address and size must be page-aligned
VAddr address;
@@ -65,46 +67,6 @@ enum class ProcessStatus {
DebugBreak,
};
-struct CodeSet final {
- struct Segment {
- std::size_t offset = 0;
- VAddr addr = 0;
- u32 size = 0;
- };
-
- explicit CodeSet();
- ~CodeSet();
-
- Segment& CodeSegment() {
- return segments[0];
- }
-
- const Segment& CodeSegment() const {
- return segments[0];
- }
-
- Segment& RODataSegment() {
- return segments[1];
- }
-
- const Segment& RODataSegment() const {
- return segments[1];
- }
-
- Segment& DataSegment() {
- return segments[2];
- }
-
- const Segment& DataSegment() const {
- return segments[2];
- }
-
- std::shared_ptr<std::vector<u8>> memory;
-
- std::array<Segment, 3> segments;
- VAddr entrypoint = 0;
-};
-
class Process final : public WaitObject {
public:
enum : u64 {
@@ -165,6 +127,16 @@ public:
return address_arbiter;
}
+ /// Gets a reference to the process' mutex lock.
+ Mutex& GetMutex() {
+ return mutex;
+ }
+
+ /// Gets a const reference to the process' mutex lock
+ const Mutex& GetMutex() const {
+ return mutex;
+ }
+
/// Gets the current status of the process
ProcessStatus GetStatus() const {
return status;
@@ -266,7 +238,7 @@ public:
void FreeTLSSlot(VAddr tls_address);
private:
- explicit Process(Core::System& kernel);
+ explicit Process(Core::System& system);
~Process() override;
/// Checks if the specified thread should wait until this process is available.
@@ -327,9 +299,18 @@ private:
/// Per-process address arbiter.
AddressArbiter address_arbiter;
+ /// The per-process mutex lock instance used for handling various
+ /// forms of services, such as lock arbitration, and condition
+ /// variable related facilities.
+ Mutex mutex;
+
/// Random values for svcGetInfo RandomEntropy
std::array<u64, RANDOM_ENTROPY_SIZE> random_entropy;
+ /// System context
+ Core::System& system;
+
+ /// Name of this process
std::string name;
};
diff --git a/src/core/hle/kernel/scheduler.cpp b/src/core/hle/kernel/scheduler.cpp
index 5fccfd9f4..cc189cc64 100644
--- a/src/core/hle/kernel/scheduler.cpp
+++ b/src/core/hle/kernel/scheduler.cpp
@@ -96,7 +96,7 @@ void Scheduler::SwitchContext(Thread* new_thread) {
auto* const thread_owner_process = current_thread->GetOwnerProcess();
if (previous_process != thread_owner_process) {
system.Kernel().MakeCurrentProcess(thread_owner_process);
- SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
+ Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
}
cpu_core.LoadContext(new_thread->GetContext());
@@ -199,8 +199,7 @@ void Scheduler::YieldWithoutLoadBalancing(Thread* thread) {
ASSERT(thread->GetPriority() < THREADPRIO_COUNT);
// Yield this thread -- sleep for zero time and force reschedule to different thread
- WaitCurrentThread_Sleep();
- GetCurrentThread()->WakeAfterDelay(0);
+ GetCurrentThread()->Sleep(0);
}
void Scheduler::YieldWithLoadBalancing(Thread* thread) {
@@ -215,8 +214,7 @@ void Scheduler::YieldWithLoadBalancing(Thread* thread) {
ASSERT(priority < THREADPRIO_COUNT);
// Sleep for zero time to be able to force reschedule to different thread
- WaitCurrentThread_Sleep();
- GetCurrentThread()->WakeAfterDelay(0);
+ GetCurrentThread()->Sleep(0);
Thread* suggested_thread = nullptr;
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index d6ceeb2da..0e1515c89 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -26,6 +26,10 @@ ResultVal<SharedPtr<ServerSession>> ServerPort::Accept() {
return MakeResult(std::move(session));
}
+void ServerPort::AppendPendingSession(SharedPtr<ServerSession> pending_session) {
+ pending_sessions.push_back(std::move(pending_session));
+}
+
bool ServerPort::ShouldWait(Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.empty();
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index e52f8245f..9bc667cf2 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -22,6 +22,8 @@ class SessionRequestHandler;
class ServerPort final : public WaitObject {
public:
+ using HLEHandler = std::shared_ptr<SessionRequestHandler>;
+
/**
* Creates a pair of ServerPort and an associated ClientPort.
*
@@ -51,22 +53,27 @@ public:
*/
ResultVal<SharedPtr<ServerSession>> Accept();
+ /// Whether or not this server port has an HLE handler available.
+ bool HasHLEHandler() const {
+ return hle_handler != nullptr;
+ }
+
+ /// Gets the HLE handler for this port.
+ HLEHandler GetHLEHandler() const {
+ return hle_handler;
+ }
+
/**
* Sets the HLE handler template for the port. ServerSessions crated by connecting to this port
* will inherit a reference to this handler.
*/
- void SetHleHandler(std::shared_ptr<SessionRequestHandler> hle_handler_) {
+ void SetHleHandler(HLEHandler hle_handler_) {
hle_handler = std::move(hle_handler_);
}
- std::string name; ///< Name of port (optional)
-
- /// ServerSessions waiting to be accepted by the port
- std::vector<SharedPtr<ServerSession>> pending_sessions;
-
- /// This session's HLE request handler template (optional)
- /// ServerSessions created from this port inherit a reference to this handler.
- std::shared_ptr<SessionRequestHandler> hle_handler;
+ /// Appends a ServerSession to the collection of ServerSessions
+ /// waiting to be accepted by this port.
+ void AppendPendingSession(SharedPtr<ServerSession> pending_session);
bool ShouldWait(Thread* thread) const override;
void Acquire(Thread* thread) override;
@@ -74,6 +81,16 @@ public:
private:
explicit ServerPort(KernelCore& kernel);
~ServerPort() override;
+
+ /// ServerSessions waiting to be accepted by the port
+ std::vector<SharedPtr<ServerSession>> pending_sessions;
+
+ /// This session's HLE request handler template (optional)
+ /// ServerSessions created from this port inherit a reference to this handler.
+ HLEHandler hle_handler;
+
+ /// Name of the port (optional)
+ std::string name;
};
} // namespace Kernel
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp
index bc89c4880..e5e7f99e1 100644
--- a/src/core/hle/kernel/svc.cpp
+++ b/src/core/hle/kernel/svc.cpp
@@ -552,9 +552,9 @@ static ResultCode ArbitrateLock(Handle holding_thread_handle, VAddr mutex_addr,
return ERR_INVALID_ADDRESS;
}
- auto& handle_table = Core::CurrentProcess()->GetHandleTable();
- return Mutex::TryAcquire(handle_table, mutex_addr, holding_thread_handle,
- requesting_thread_handle);
+ auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
+ return current_process->GetMutex().TryAcquire(mutex_addr, holding_thread_handle,
+ requesting_thread_handle);
}
/// Unlock a mutex
@@ -572,7 +572,8 @@ static ResultCode ArbitrateUnlock(VAddr mutex_addr) {
return ERR_INVALID_ADDRESS;
}
- return Mutex::Release(mutex_addr);
+ auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
+ return current_process->GetMutex().Release(mutex_addr);
}
enum class BreakType : u32 {
@@ -1285,10 +1286,14 @@ static ResultCode StartThread(Handle thread_handle) {
/// Called when a thread exits
static void ExitThread() {
- LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", Core::CurrentArmInterface().GetPC());
+ auto& system = Core::System::GetInstance();
- ExitCurrentThread();
- Core::System::GetInstance().PrepareReschedule();
+ LOG_TRACE(Kernel_SVC, "called, pc=0x{:08X}", system.CurrentArmInterface().GetPC());
+
+ auto* const current_thread = system.CurrentScheduler().GetCurrentThread();
+ current_thread->Stop();
+ system.CurrentScheduler().RemoveThread(current_thread);
+ system.PrepareReschedule();
}
/// Sleep the current thread
@@ -1301,32 +1306,32 @@ static void SleepThread(s64 nanoseconds) {
YieldAndWaitForLoadBalancing = -2,
};
+ auto& system = Core::System::GetInstance();
+ auto& scheduler = system.CurrentScheduler();
+ auto* const current_thread = scheduler.GetCurrentThread();
+
if (nanoseconds <= 0) {
- auto& scheduler{Core::System::GetInstance().CurrentScheduler()};
switch (static_cast<SleepType>(nanoseconds)) {
case SleepType::YieldWithoutLoadBalancing:
- scheduler.YieldWithoutLoadBalancing(GetCurrentThread());
+ scheduler.YieldWithoutLoadBalancing(current_thread);
break;
case SleepType::YieldWithLoadBalancing:
- scheduler.YieldWithLoadBalancing(GetCurrentThread());
+ scheduler.YieldWithLoadBalancing(current_thread);
break;
case SleepType::YieldAndWaitForLoadBalancing:
- scheduler.YieldAndWaitForLoadBalancing(GetCurrentThread());
+ scheduler.YieldAndWaitForLoadBalancing(current_thread);
break;
default:
UNREACHABLE_MSG("Unimplemented sleep yield type '{:016X}'!", nanoseconds);
}
} else {
- // Sleep current thread and check for next thread to schedule
- WaitCurrentThread_Sleep();
-
- // Create an event to wake the thread up after the specified nanosecond delay has passed
- GetCurrentThread()->WakeAfterDelay(nanoseconds);
+ current_thread->Sleep(nanoseconds);
}
// Reschedule all CPU cores
- for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i)
- Core::System::GetInstance().CpuCore(i).PrepareReschedule();
+ for (std::size_t i = 0; i < Core::NUM_CPU_CORES; ++i) {
+ system.CpuCore(i).PrepareReschedule();
+ }
}
/// Wait process wide key atomic
@@ -1337,11 +1342,15 @@ static ResultCode WaitProcessWideKeyAtomic(VAddr mutex_addr, VAddr condition_var
"called mutex_addr={:X}, condition_variable_addr={:X}, thread_handle=0x{:08X}, timeout={}",
mutex_addr, condition_variable_addr, thread_handle, nano_seconds);
- const auto& handle_table = Core::CurrentProcess()->GetHandleTable();
+ auto* const current_process = Core::System::GetInstance().Kernel().CurrentProcess();
+ const auto& handle_table = current_process->GetHandleTable();
SharedPtr<Thread> thread = handle_table.Get<Thread>(thread_handle);
ASSERT(thread);
- CASCADE_CODE(Mutex::Release(mutex_addr));
+ const auto release_result = current_process->GetMutex().Release(mutex_addr);
+ if (release_result.IsError()) {
+ return release_result;
+ }
SharedPtr<Thread> current_thread = GetCurrentThread();
current_thread->SetCondVarWaitAddress(condition_variable_addr);
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index eb54d6651..3b22e8e0d 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -7,8 +7,6 @@
#include <optional>
#include <vector>
-#include <boost/range/algorithm_ext/erase.hpp>
-
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
@@ -68,17 +66,6 @@ void Thread::Stop() {
owner_process->FreeTLSSlot(tls_address);
}
-void WaitCurrentThread_Sleep() {
- Thread* thread = GetCurrentThread();
- thread->SetStatus(ThreadStatus::WaitSleep);
-}
-
-void ExitCurrentThread() {
- Thread* thread = GetCurrentThread();
- thread->Stop();
- Core::System::GetInstance().CurrentScheduler().RemoveThread(thread);
-}
-
void Thread::WakeAfterDelay(s64 nanoseconds) {
// Don't schedule a wakeup if the thread wants to wait forever
if (nanoseconds == -1)
@@ -269,8 +256,8 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
if (thread->lock_owner == this) {
// If the thread is already waiting for this thread to release the mutex, ensure that the
// waiters list is consistent and return without doing anything.
- auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(itr != wait_mutex_threads.end());
+ const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
+ ASSERT(iter != wait_mutex_threads.end());
return;
}
@@ -278,11 +265,16 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
ASSERT(thread->lock_owner == nullptr);
// Ensure that the thread is not already in the list of mutex waiters
- auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(itr == wait_mutex_threads.end());
-
+ const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
+ ASSERT(iter == wait_mutex_threads.end());
+
+ // Keep the list in an ordered fashion
+ const auto insertion_point = std::find_if(
+ wait_mutex_threads.begin(), wait_mutex_threads.end(),
+ [&thread](const auto& entry) { return entry->GetPriority() > thread->GetPriority(); });
+ wait_mutex_threads.insert(insertion_point, thread);
thread->lock_owner = this;
- wait_mutex_threads.emplace_back(std::move(thread));
+
UpdatePriority();
}
@@ -290,32 +282,44 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
ASSERT(thread->lock_owner == this);
// Ensure that the thread is in the list of mutex waiters
- auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
- ASSERT(itr != wait_mutex_threads.end());
+ const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
+ ASSERT(iter != wait_mutex_threads.end());
+
+ wait_mutex_threads.erase(iter);
- boost::remove_erase(wait_mutex_threads, thread);
thread->lock_owner = nullptr;
UpdatePriority();
}
void Thread::UpdatePriority() {
- // Find the highest priority among all the threads that are waiting for this thread's lock
+ // If any of the threads waiting on the mutex have a higher priority
+ // (taking into account priority inheritance), then this thread inherits
+ // that thread's priority.
u32 new_priority = nominal_priority;
- for (const auto& thread : wait_mutex_threads) {
- if (thread->nominal_priority < new_priority)
- new_priority = thread->nominal_priority;
+ if (!wait_mutex_threads.empty()) {
+ if (wait_mutex_threads.front()->current_priority < new_priority) {
+ new_priority = wait_mutex_threads.front()->current_priority;
+ }
}
- if (new_priority == current_priority)
+ if (new_priority == current_priority) {
return;
+ }
scheduler->SetThreadPriority(this, new_priority);
-
current_priority = new_priority;
+ if (!lock_owner) {
+ return;
+ }
+
+ // Ensure that the thread is within the correct location in the waiting list.
+ auto old_owner = lock_owner;
+ lock_owner->RemoveMutexWaiter(this);
+ old_owner->AddMutexWaiter(this);
+
// Recursively update the priority of the thread that depends on the priority of this one.
- if (lock_owner)
- lock_owner->UpdatePriority();
+ lock_owner->UpdatePriority();
}
void Thread::ChangeCore(u32 core, u64 mask) {
@@ -391,6 +395,14 @@ void Thread::SetActivity(ThreadActivity value) {
}
}
+void Thread::Sleep(s64 nanoseconds) {
+ // Sleep current thread and check for next thread to schedule
+ SetStatus(ThreadStatus::WaitSleep);
+
+ // Create an event to wake the thread up after the specified nanosecond delay has passed
+ WakeAfterDelay(nanoseconds);
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
/**
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index c48b21aba..faad5f391 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -383,6 +383,9 @@ public:
void SetActivity(ThreadActivity value);
+ /// Sleeps this thread for the given amount of nanoseconds.
+ void Sleep(s64 nanoseconds);
+
private:
explicit Thread(KernelCore& kernel);
~Thread() override;
@@ -398,8 +401,14 @@ private:
VAddr entry_point = 0;
VAddr stack_top = 0;
- u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
- u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
+ /// Nominal thread priority, as set by the emulated application.
+ /// The nominal priority is the thread priority without priority
+ /// inheritance taken into account.
+ u32 nominal_priority = 0;
+
+ /// Current thread priority. This may change over the course of the
+ /// thread's lifetime in order to facilitate priority inheritance.
+ u32 current_priority = 0;
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
@@ -460,14 +469,4 @@ private:
*/
Thread* GetCurrentThread();
-/**
- * Waits the current thread on a sleep
- */
-void WaitCurrentThread_Sleep();
-
-/**
- * Stops the current thread and removes it from the thread_list
- */
-void ExitCurrentThread();
-
} // namespace Kernel
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 05c59af34..22bf55ce7 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -7,29 +7,29 @@
#include <utility>
#include "common/assert.h"
#include "common/logging/log.h"
+#include "common/memory_hook.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
#include "core/file_sys/program_metadata.h"
#include "core/hle/kernel/errors.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/memory.h"
-#include "core/memory_hook.h"
#include "core/memory_setup.h"
namespace Kernel {
namespace {
const char* GetMemoryStateName(MemoryState state) {
static constexpr const char* names[] = {
- "Unmapped", "Io",
- "Normal", "CodeStatic",
- "CodeMutable", "Heap",
- "Shared", "Unknown1",
- "ModuleCodeStatic", "ModuleCodeMutable",
- "IpcBuffer0", "Stack",
- "ThreadLocal", "TransferMemoryIsolated",
- "TransferMemory", "ProcessMemory",
- "Inaccessible", "IpcBuffer1",
- "IpcBuffer3", "KernelStack",
+ "Unmapped", "Io",
+ "Normal", "Code",
+ "CodeData", "Heap",
+ "Shared", "Unknown1",
+ "ModuleCode", "ModuleCodeData",
+ "IpcBuffer0", "Stack",
+ "ThreadLocal", "TransferMemoryIsolated",
+ "TransferMemory", "ProcessMemory",
+ "Inaccessible", "IpcBuffer1",
+ "IpcBuffer3", "KernelStack",
};
return names[ToSvcMemoryState(state)];
@@ -177,7 +177,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
MemoryState state,
- Memory::MemoryHookPointer mmio_handler) {
+ Common::MemoryHookPointer mmio_handler) {
// This is the appropriately sized VMA that will turn into our allocation.
CASCADE_RESULT(VMAIter vma_handle, CarveVMA(target, size));
VirtualMemoryArea& final_vma = vma_handle->second;
@@ -624,7 +624,7 @@ void VMManager::ClearPageTable() {
std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
page_table.special_regions.clear();
std::fill(page_table.attributes.begin(), page_table.attributes.end(),
- Memory::PageType::Unmapped);
+ Common::PageType::Unmapped);
}
VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index 88e0b3c02..7cdff6094 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -9,9 +9,10 @@
#include <tuple>
#include <vector>
#include "common/common_types.h"
+#include "common/memory_hook.h"
+#include "common/page_table.h"
#include "core/hle/result.h"
#include "core/memory.h"
-#include "core/memory_hook.h"
namespace FileSys {
enum class ProgramAddressSpaceType : u8;
@@ -164,12 +165,12 @@ enum class MemoryState : u32 {
Unmapped = 0x00,
Io = 0x01 | FlagMapped,
Normal = 0x02 | FlagMapped | FlagQueryPhysicalAddressAllowed,
- CodeStatic = 0x03 | CodeFlags | FlagMapProcess,
- CodeMutable = 0x04 | CodeFlags | FlagMapProcess | FlagCodeMemory,
+ Code = 0x03 | CodeFlags | FlagMapProcess,
+ CodeData = 0x04 | DataFlags | FlagMapProcess | FlagCodeMemory,
Heap = 0x05 | DataFlags | FlagCodeMemory,
Shared = 0x06 | FlagMapped | FlagMemoryPoolAllocated,
- ModuleCodeStatic = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
- ModuleCodeMutable = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
+ ModuleCode = 0x08 | CodeFlags | FlagModule | FlagMapProcess,
+ ModuleCodeData = 0x09 | DataFlags | FlagModule | FlagMapProcess | FlagCodeMemory,
IpcBuffer0 = 0x0A | FlagMapped | FlagQueryPhysicalAddressAllowed | FlagMemoryPoolAllocated |
IPCFlags | FlagSharedDevice | FlagSharedDeviceAligned,
@@ -290,7 +291,7 @@ struct VirtualMemoryArea {
// Settings for type = MMIO
/// Physical address of the register area this VMA maps to.
PAddr paddr = 0;
- Memory::MemoryHookPointer mmio_handler = nullptr;
+ Common::MemoryHookPointer mmio_handler = nullptr;
/// Tests if this area can be merged to the right with `next`.
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
@@ -368,7 +369,7 @@ public:
* @param mmio_handler The handler that will implement read and write for this MMIO region.
*/
ResultVal<VMAHandle> MapMMIO(VAddr target, PAddr paddr, u64 size, MemoryState state,
- Memory::MemoryHookPointer mmio_handler);
+ Common::MemoryHookPointer mmio_handler);
/// Unmaps a range of addresses, splitting VMAs as necessary.
ResultCode UnmapRange(VAddr target, u64 size);
@@ -509,7 +510,7 @@ public:
/// Each VMManager has its own page table, which is set as the main one when the owning process
/// is scheduled.
- Memory::PageTable page_table;
+ Common::PageTable page_table{Memory::PAGE_BITS};
private:
using VMAIter = VMAMap::iterator;
@@ -616,6 +617,9 @@ private:
VAddr new_map_region_base = 0;
VAddr new_map_region_end = 0;
+ VAddr main_code_region_base = 0;
+ VAddr main_code_region_end = 0;
+
VAddr tls_io_region_base = 0;
VAddr tls_io_region_end = 0;
diff --git a/src/core/hle/result.h b/src/core/hle/result.h
index 1ed144481..ab84f5ddc 100644
--- a/src/core/hle/result.h
+++ b/src/core/hle/result.h
@@ -13,14 +13,6 @@
// All the constants in this file come from http://switchbrew.org/index.php?title=Error_codes
/**
- * Detailed description of the error. Code 0 always means success.
- */
-enum class ErrorDescription : u32 {
- Success = 0,
- RemoteProcessDead = 301,
-};
-
-/**
* Identifies the module which caused the error. Error codes can be propagated through a call
* chain, meaning that this doesn't always correspond to the module where the API call made is
* contained.
@@ -120,7 +112,7 @@ enum class ErrorModule : u32 {
ShopN = 811,
};
-/// Encapsulates a CTR-OS error code, allowing it to be separated into its constituent fields.
+/// Encapsulates a Horizon OS error code, allowing it to be separated into its constituent fields.
union ResultCode {
u32 raw;
@@ -133,17 +125,9 @@ union ResultCode {
constexpr explicit ResultCode(u32 raw) : raw(raw) {}
- constexpr ResultCode(ErrorModule module, ErrorDescription description)
- : ResultCode(module, static_cast<u32>(description)) {}
-
constexpr ResultCode(ErrorModule module_, u32 description_)
: raw(module.FormatValue(module_) | description.FormatValue(description_)) {}
- constexpr ResultCode& operator=(const ResultCode& o) {
- raw = o.raw;
- return *this;
- }
-
constexpr bool IsSuccess() const {
return raw == 0;
}
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index 3f009d2b7..9c44e27c6 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -2,10 +2,10 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <array>
#include <cinttypes>
#include <cstring>
-#include <stack>
#include "audio_core/audio_renderer.h"
#include "core/core.h"
#include "core/file_sys/savedata_factory.h"
@@ -93,38 +93,84 @@ void IWindowController::AcquireForegroundRights(Kernel::HLERequestContext& ctx)
}
IAudioController::IAudioController() : ServiceFramework("IAudioController") {
+ // clang-format off
static const FunctionInfo functions[] = {
{0, &IAudioController::SetExpectedMasterVolume, "SetExpectedMasterVolume"},
- {1, &IAudioController::GetMainAppletExpectedMasterVolume,
- "GetMainAppletExpectedMasterVolume"},
- {2, &IAudioController::GetLibraryAppletExpectedMasterVolume,
- "GetLibraryAppletExpectedMasterVolume"},
- {3, nullptr, "ChangeMainAppletMasterVolume"},
- {4, nullptr, "SetTransparentVolumeRate"},
+ {1, &IAudioController::GetMainAppletExpectedMasterVolume, "GetMainAppletExpectedMasterVolume"},
+ {2, &IAudioController::GetLibraryAppletExpectedMasterVolume, "GetLibraryAppletExpectedMasterVolume"},
+ {3, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"},
+ {4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"},
};
+ // clang-format on
+
RegisterHandlers(functions);
}
IAudioController::~IAudioController() = default;
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ const float main_applet_volume_tmp = rp.Pop<float>();
+ const float library_applet_volume_tmp = rp.Pop<float>();
+
+ LOG_DEBUG(Service_AM, "called. main_applet_volume={}, library_applet_volume={}",
+ main_applet_volume_tmp, library_applet_volume_tmp);
+
+ // Ensure the volume values remain within the 0-100% range
+ main_applet_volume = std::clamp(main_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
+ library_applet_volume =
+ std::clamp(library_applet_volume_tmp, min_allowed_volume, max_allowed_volume);
+
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
}
void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(volume);
+ rb.Push(main_applet_volume);
}
void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_AM, "(STUBBED) called");
+ LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume);
IPC::ResponseBuilder rb{ctx, 3};
rb.Push(RESULT_SUCCESS);
- rb.Push(volume);
+ rb.Push(library_applet_volume);
+}
+
+void IAudioController::ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx) {
+ struct Parameters {
+ float volume;
+ s64 fade_time_ns;
+ };
+ static_assert(sizeof(Parameters) == 16);
+
+ IPC::RequestParser rp{ctx};
+ const auto parameters = rp.PopRaw<Parameters>();
+
+ LOG_DEBUG(Service_AM, "called. volume={}, fade_time_ns={}", parameters.volume,
+ parameters.fade_time_ns);
+
+ main_applet_volume = std::clamp(parameters.volume, min_allowed_volume, max_allowed_volume);
+ fade_time_ns = std::chrono::nanoseconds{parameters.fade_time_ns};
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+
+void IAudioController::SetTransparentAudioRate(Kernel::HLERequestContext& ctx) {
+ IPC::RequestParser rp{ctx};
+ const float transparent_volume_rate_tmp = rp.Pop<float>();
+
+ LOG_DEBUG(Service_AM, "called. transparent_volume_rate={}", transparent_volume_rate_tmp);
+
+ // Clamp volume range to 0-100%.
+ transparent_volume_rate =
+ std::clamp(transparent_volume_rate_tmp, min_allowed_volume, max_allowed_volume);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
}
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
@@ -169,7 +215,21 @@ IDisplayController::IDisplayController() : ServiceFramework("IDisplayController"
IDisplayController::~IDisplayController() = default;
-IDebugFunctions::IDebugFunctions() : ServiceFramework("IDebugFunctions") {}
+IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
+ // clang-format off
+ static const FunctionInfo functions[] = {
+ {0, nullptr, "NotifyMessageToHomeMenuForDebug"},
+ {1, nullptr, "OpenMainApplication"},
+ {10, nullptr, "EmulateButtonEvent"},
+ {20, nullptr, "InvalidateTransitionLayer"},
+ {30, nullptr, "RequestLaunchApplicationWithUserAndArgumentForDebug"},
+ {40, nullptr, "GetAppletResourceUsageInfo"},
+ };
+ // clang-format on
+
+ RegisterHandlers(functions);
+}
+
IDebugFunctions::~IDebugFunctions() = default;
ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger)
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index b6113cfdd..565dd8e9e 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -4,6 +4,7 @@
#pragma once
+#include <chrono>
#include <memory>
#include <queue>
#include "core/hle/kernel/writable_event.h"
@@ -81,8 +82,21 @@ private:
void SetExpectedMasterVolume(Kernel::HLERequestContext& ctx);
void GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
void GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx);
+ void ChangeMainAppletMasterVolume(Kernel::HLERequestContext& ctx);
+ void SetTransparentAudioRate(Kernel::HLERequestContext& ctx);
- u32 volume{100};
+ static constexpr float min_allowed_volume = 0.0f;
+ static constexpr float max_allowed_volume = 1.0f;
+
+ float main_applet_volume{0.25f};
+ float library_applet_volume{max_allowed_volume};
+ float transparent_volume_rate{min_allowed_volume};
+
+ // Volume transition fade time in nanoseconds.
+ // e.g. If the main applet volume was 0% and was changed to 50%
+ // with a fade of 50ns, then over the course of 50ns,
+ // the volume will gradually fade up to 50%
+ std::chrono::nanoseconds fade_time_ns{0};
};
class IDisplayController final : public ServiceFramework<IDisplayController> {
diff --git a/src/core/hle/service/audio/hwopus.cpp b/src/core/hle/service/audio/hwopus.cpp
index 377e12cfa..cb4a1160d 100644
--- a/src/core/hle/service/audio/hwopus.cpp
+++ b/src/core/hle/service/audio/hwopus.cpp
@@ -8,6 +8,7 @@
#include <vector>
#include <opus.h>
+#include <opus_multistream.h>
#include "common/assert.h"
#include "common/logging/log.h"
@@ -18,12 +19,12 @@
namespace Service::Audio {
namespace {
struct OpusDeleter {
- void operator()(void* ptr) const {
- operator delete(ptr);
+ void operator()(OpusMSDecoder* ptr) const {
+ opus_multistream_decoder_destroy(ptr);
}
};
-using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>;
+using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
struct OpusPacketHeader {
// Packet size in bytes.
@@ -33,7 +34,7 @@ struct OpusPacketHeader {
};
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
-class OpusDecoderStateBase {
+class OpusDecoderState {
public:
/// Describes extra behavior that may be asked of the decoding context.
enum class ExtraBehavior {
@@ -49,22 +50,13 @@ public:
Enabled,
};
- virtual ~OpusDecoderStateBase() = default;
-
- // Decodes interleaved Opus packets. Optionally allows reporting time taken to
- // perform the decoding, as well as any relevant extra behavior.
- virtual void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
- ExtraBehavior extra_behavior) = 0;
-};
-
-// Represents the decoder state for a non-multistream decoder.
-class OpusDecoderState final : public OpusDecoderStateBase {
-public:
explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
: decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
+ // Decodes interleaved Opus packets. Optionally allows reporting time taken to
+ // perform the decoding, as well as any relevant extra behavior.
void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
- ExtraBehavior extra_behavior) override {
+ ExtraBehavior extra_behavior) {
if (perf_time == PerfTime::Disabled) {
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
} else {
@@ -135,7 +127,7 @@ private:
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
const auto out_sample_count =
- opus_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
+ opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
if (out_sample_count < 0) {
LOG_ERROR(Audio,
"Incorrect sample count received from opus_decode, "
@@ -158,7 +150,7 @@ private:
void ResetDecoderContext() {
ASSERT(decoder != nullptr);
- opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
+ opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
}
OpusDecoderPtr decoder;
@@ -168,7 +160,7 @@ private:
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
public:
- explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state)
+ explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state)
: ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
// clang-format off
static const FunctionInfo functions[] = {
@@ -190,35 +182,51 @@ private:
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called");
- decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled,
- OpusDecoderStateBase::ExtraBehavior::None);
+ decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
+ OpusDecoderState::ExtraBehavior::None);
}
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called");
- decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
- OpusDecoderStateBase::ExtraBehavior::None);
+ decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
+ OpusDecoderState::ExtraBehavior::None);
}
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Audio, "called");
IPC::RequestParser rp{ctx};
- const auto extra_behavior = rp.Pop<bool>()
- ? OpusDecoderStateBase::ExtraBehavior::ResetContext
- : OpusDecoderStateBase::ExtraBehavior::None;
+ const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
+ : OpusDecoderState::ExtraBehavior::None;
- decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
- extra_behavior);
+ decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
}
- std::unique_ptr<OpusDecoderStateBase> decoder_state;
+ OpusDecoderState decoder_state;
};
std::size_t WorkerBufferSize(u32 channel_count) {
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid channel count");
- return opus_decoder_get_size(static_cast<int>(channel_count));
+ constexpr int num_streams = 1;
+ const int num_stereo_streams = channel_count == 2 ? 1 : 0;
+ return opus_multistream_decoder_get_size(num_streams, num_stereo_streams);
+}
+
+// Creates the mapping table that maps the input channels to the particular
+// output channels. In the stereo case, we map the left and right input channels
+// to the left and right output channels respectively.
+//
+// However, in the monophonic case, we only map the one available channel
+// to the sole output channel. We specify 255 for the would-be right channel
+// as this is a special value defined by Opus to indicate to the decoder to
+// ignore that channel.
+std::array<u8, 2> CreateMappingTable(u32 channel_count) {
+ if (channel_count == 2) {
+ return {{0, 1}};
+ }
+
+ return {{0, 255}};
}
} // Anonymous namespace
@@ -259,9 +267,15 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
const std::size_t worker_sz = WorkerBufferSize(channel_count);
ASSERT_MSG(buffer_sz >= worker_sz, "Worker buffer too large");
- OpusDecoderPtr decoder{static_cast<OpusDecoder*>(operator new(worker_sz))};
- if (const int err = opus_decoder_init(decoder.get(), sample_rate, channel_count)) {
- LOG_ERROR(Audio, "Failed to init opus decoder with error={}", err);
+ const int num_stereo_streams = channel_count == 2 ? 1 : 0;
+ const auto mapping_table = CreateMappingTable(channel_count);
+
+ int error = 0;
+ OpusDecoderPtr decoder{
+ opus_multistream_decoder_create(sample_rate, static_cast<int>(channel_count), 1,
+ num_stereo_streams, mapping_table.data(), &error)};
+ if (error != OPUS_OK || decoder == nullptr) {
+ LOG_ERROR(Audio, "Failed to create Opus decoder (error={}).", error);
IPC::ResponseBuilder rb{ctx, 2};
// TODO(ogniK): Use correct error code
rb.Push(ResultCode(-1));
@@ -271,7 +285,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
- std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count));
+ OpusDecoderState{std::move(decoder), sample_rate, channel_count});
}
HwOpus::HwOpus() : ServiceFramework("hwopus") {
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp
index 54959edd8..f03fb629c 100644
--- a/src/core/hle/service/filesystem/fsp_srv.cpp
+++ b/src/core/hle/service/filesystem/fsp_srv.cpp
@@ -733,7 +733,10 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
FSP_SRV::~FSP_SRV() = default;
void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_FS, "(STUBBED) called");
+ IPC::RequestParser rp{ctx};
+ current_process_id = rp.Pop<u64>();
+
+ LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
IPC::ResponseBuilder rb{ctx, 2};
rb.Push(RESULT_SUCCESS);
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h
index 3a5f4e200..d7572ba7a 100644
--- a/src/core/hle/service/filesystem/fsp_srv.h
+++ b/src/core/hle/service/filesystem/fsp_srv.h
@@ -32,6 +32,7 @@ private:
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
FileSys::VirtualFile romfs;
+ u64 current_process_id = 0;
};
} // namespace Service::FileSystem
diff --git a/src/core/hle/service/hid/controllers/debug_pad.h b/src/core/hle/service/hid/controllers/debug_pad.h
index 929035034..e584b92ec 100644
--- a/src/core/hle/service/hid/controllers/debug_pad.h
+++ b/src/core/hle/service/hid/controllers/debug_pad.h
@@ -41,20 +41,20 @@ private:
struct PadState {
union {
u32_le raw{};
- BitField<0, 1, u32_le> a;
- BitField<1, 1, u32_le> b;
- BitField<2, 1, u32_le> x;
- BitField<3, 1, u32_le> y;
- BitField<4, 1, u32_le> l;
- BitField<5, 1, u32_le> r;
- BitField<6, 1, u32_le> zl;
- BitField<7, 1, u32_le> zr;
- BitField<8, 1, u32_le> plus;
- BitField<9, 1, u32_le> minus;
- BitField<10, 1, u32_le> d_left;
- BitField<11, 1, u32_le> d_up;
- BitField<12, 1, u32_le> d_right;
- BitField<13, 1, u32_le> d_down;
+ BitField<0, 1, u32> a;
+ BitField<1, 1, u32> b;
+ BitField<2, 1, u32> x;
+ BitField<3, 1, u32> y;
+ BitField<4, 1, u32> l;
+ BitField<5, 1, u32> r;
+ BitField<6, 1, u32> zl;
+ BitField<7, 1, u32> zr;
+ BitField<8, 1, u32> plus;
+ BitField<9, 1, u32> minus;
+ BitField<10, 1, u32> d_left;
+ BitField<11, 1, u32> d_up;
+ BitField<12, 1, u32> d_right;
+ BitField<13, 1, u32> d_down;
};
};
static_assert(sizeof(PadState) == 0x4, "PadState is an invalid size");
@@ -62,7 +62,7 @@ private:
struct Attributes {
union {
u32_le raw{};
- BitField<0, 1, u32_le> connected;
+ BitField<0, 1, u32> connected;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 18c7a94e6..4ff50b3cd 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -39,13 +39,13 @@ public:
union {
u32_le raw{};
- BitField<0, 1, u32_le> pro_controller;
- BitField<1, 1, u32_le> handheld;
- BitField<2, 1, u32_le> joycon_dual;
- BitField<3, 1, u32_le> joycon_left;
- BitField<4, 1, u32_le> joycon_right;
+ BitField<0, 1, u32> pro_controller;
+ BitField<1, 1, u32> handheld;
+ BitField<2, 1, u32> joycon_dual;
+ BitField<3, 1, u32> joycon_left;
+ BitField<4, 1, u32> joycon_right;
- BitField<6, 1, u32_le> pokeball; // TODO(ogniK): Confirm when possible
+ BitField<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
};
};
static_assert(sizeof(NPadType) == 4, "NPadType is an invalid size");
@@ -150,43 +150,43 @@ private:
union {
u64_le raw{};
// Button states
- BitField<0, 1, u64_le> a;
- BitField<1, 1, u64_le> b;
- BitField<2, 1, u64_le> x;
- BitField<3, 1, u64_le> y;
- BitField<4, 1, u64_le> l_stick;
- BitField<5, 1, u64_le> r_stick;
- BitField<6, 1, u64_le> l;
- BitField<7, 1, u64_le> r;
- BitField<8, 1, u64_le> zl;
- BitField<9, 1, u64_le> zr;
- BitField<10, 1, u64_le> plus;
- BitField<11, 1, u64_le> minus;
+ BitField<0, 1, u64> a;
+ BitField<1, 1, u64> b;
+ BitField<2, 1, u64> x;
+ BitField<3, 1, u64> y;
+ BitField<4, 1, u64> l_stick;
+ BitField<5, 1, u64> r_stick;
+ BitField<6, 1, u64> l;
+ BitField<7, 1, u64> r;
+ BitField<8, 1, u64> zl;
+ BitField<9, 1, u64> zr;
+ BitField<10, 1, u64> plus;
+ BitField<11, 1, u64> minus;
// D-Pad
- BitField<12, 1, u64_le> d_left;
- BitField<13, 1, u64_le> d_up;
- BitField<14, 1, u64_le> d_right;
- BitField<15, 1, u64_le> d_down;
+ BitField<12, 1, u64> d_left;
+ BitField<13, 1, u64> d_up;
+ BitField<14, 1, u64> d_right;
+ BitField<15, 1, u64> d_down;
// Left JoyStick
- BitField<16, 1, u64_le> l_stick_left;
- BitField<17, 1, u64_le> l_stick_up;
- BitField<18, 1, u64_le> l_stick_right;
- BitField<19, 1, u64_le> l_stick_down;
+ BitField<16, 1, u64> l_stick_left;
+ BitField<17, 1, u64> l_stick_up;
+ BitField<18, 1, u64> l_stick_right;
+ BitField<19, 1, u64> l_stick_down;
// Right JoyStick
- BitField<20, 1, u64_le> r_stick_left;
- BitField<21, 1, u64_le> r_stick_up;
- BitField<22, 1, u64_le> r_stick_right;
- BitField<23, 1, u64_le> r_stick_down;
+ BitField<20, 1, u64> r_stick_left;
+ BitField<21, 1, u64> r_stick_up;
+ BitField<22, 1, u64> r_stick_right;
+ BitField<23, 1, u64> r_stick_down;
// Not always active?
- BitField<24, 1, u64_le> left_sl;
- BitField<25, 1, u64_le> left_sr;
+ BitField<24, 1, u64> left_sl;
+ BitField<25, 1, u64> left_sr;
- BitField<26, 1, u64_le> right_sl;
- BitField<27, 1, u64_le> right_sr;
+ BitField<26, 1, u64> right_sl;
+ BitField<27, 1, u64> right_sr;
};
};
static_assert(sizeof(ControllerPadState) == 8, "ControllerPadState is an invalid size");
@@ -200,12 +200,12 @@ private:
struct ConnectionState {
union {
u32_le raw{};
- BitField<0, 1, u32_le> IsConnected;
- BitField<1, 1, u32_le> IsWired;
- BitField<2, 1, u32_le> IsLeftJoyConnected;
- BitField<3, 1, u32_le> IsLeftJoyWired;
- BitField<4, 1, u32_le> IsRightJoyConnected;
- BitField<5, 1, u32_le> IsRightJoyWired;
+ BitField<0, 1, u32> IsConnected;
+ BitField<1, 1, u32> IsWired;
+ BitField<2, 1, u32> IsLeftJoyConnected;
+ BitField<3, 1, u32> IsLeftJoyWired;
+ BitField<4, 1, u32> IsRightJoyConnected;
+ BitField<5, 1, u32> IsRightJoyWired;
};
};
static_assert(sizeof(ConnectionState) == 4, "ConnectionState is an invalid size");
@@ -240,23 +240,23 @@ private:
struct NPadProperties {
union {
s64_le raw{};
- BitField<11, 1, s64_le> is_vertical;
- BitField<12, 1, s64_le> is_horizontal;
- BitField<13, 1, s64_le> use_plus;
- BitField<14, 1, s64_le> use_minus;
+ BitField<11, 1, s64> is_vertical;
+ BitField<12, 1, s64> is_horizontal;
+ BitField<13, 1, s64> use_plus;
+ BitField<14, 1, s64> use_minus;
};
};
struct NPadDevice {
union {
u32_le raw{};
- BitField<0, 1, s32_le> pro_controller;
- BitField<1, 1, s32_le> handheld;
- BitField<2, 1, s32_le> handheld_left;
- BitField<3, 1, s32_le> handheld_right;
- BitField<4, 1, s32_le> joycon_left;
- BitField<5, 1, s32_le> joycon_right;
- BitField<6, 1, s32_le> pokeball;
+ BitField<0, 1, s32> pro_controller;
+ BitField<1, 1, s32> handheld;
+ BitField<2, 1, s32> handheld_left;
+ BitField<3, 1, s32> handheld_right;
+ BitField<4, 1, s32> joycon_left;
+ BitField<5, 1, s32> joycon_right;
+ BitField<6, 1, s32> pokeball;
};
};
diff --git a/src/core/hle/service/hid/controllers/touchscreen.h b/src/core/hle/service/hid/controllers/touchscreen.h
index 012b6e0dd..76fc340e9 100644
--- a/src/core/hle/service/hid/controllers/touchscreen.h
+++ b/src/core/hle/service/hid/controllers/touchscreen.h
@@ -33,8 +33,8 @@ private:
struct Attributes {
union {
u32 raw{};
- BitField<0, 1, u32_le> start_touch;
- BitField<1, 1, u32_le> end_touch;
+ BitField<0, 1, u32> start_touch;
+ BitField<1, 1, u32> end_touch;
};
};
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 7cc58db4c..498602de5 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -4,6 +4,9 @@
#pragma once
+#include "core/hle/service/hid/controllers/controller_base.h"
+#include "core/hle/service/service.h"
+
#include "controllers/controller_base.h"
#include "core/hle/service/service.h"
diff --git a/src/core/hle/service/ldr/ldr.cpp b/src/core/hle/service/ldr/ldr.cpp
index 9df7ac50f..d65693fc7 100644
--- a/src/core/hle/service/ldr/ldr.cpp
+++ b/src/core/hle/service/ldr/ldr.cpp
@@ -319,15 +319,14 @@ public:
}
ASSERT(vm_manager
- .MirrorMemory(*map_address, nro_addr, nro_size,
- Kernel::MemoryState::ModuleCodeStatic)
+ .MirrorMemory(*map_address, nro_addr, nro_size, Kernel::MemoryState::ModuleCode)
.IsSuccess());
ASSERT(vm_manager.UnmapRange(nro_addr, nro_size).IsSuccess());
if (bss_size > 0) {
ASSERT(vm_manager
.MirrorMemory(*map_address + nro_size, bss_addr, bss_size,
- Kernel::MemoryState::ModuleCodeStatic)
+ Kernel::MemoryState::ModuleCode)
.IsSuccess());
ASSERT(vm_manager.UnmapRange(bss_addr, bss_size).IsSuccess());
}
@@ -388,8 +387,7 @@ public:
const auto& nro_size = iter->second.size;
ASSERT(vm_manager
- .MirrorMemory(heap_addr, mapped_addr, nro_size,
- Kernel::MemoryState::ModuleCodeStatic)
+ .MirrorMemory(heap_addr, mapped_addr, nro_size, Kernel::MemoryState::ModuleCode)
.IsSuccess());
ASSERT(vm_manager.UnmapRange(mapped_addr, nro_size).IsSuccess());
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp
index 1f462e087..2a61593e2 100644
--- a/src/core/hle/service/lm/lm.cpp
+++ b/src/core/hle/service/lm/lm.cpp
@@ -42,7 +42,7 @@ private:
union {
BitField<0, 16, Flags> flags;
BitField<16, 8, Severity> severity;
- BitField<24, 8, u32_le> verbosity;
+ BitField<24, 8, u32> verbosity;
};
u32_le payload_size;
diff --git a/src/core/hle/service/nvdrv/devices/nvdevice.h b/src/core/hle/service/nvdrv/devices/nvdevice.h
index 0f02a1a18..4f6042b00 100644
--- a/src/core/hle/service/nvdrv/devices/nvdevice.h
+++ b/src/core/hle/service/nvdrv/devices/nvdevice.h
@@ -19,11 +19,11 @@ public:
virtual ~nvdevice() = default;
union Ioctl {
u32_le raw;
- BitField<0, 8, u32_le> cmd;
- BitField<8, 8, u32_le> group;
- BitField<16, 14, u32_le> length;
- BitField<30, 1, u32_le> is_in;
- BitField<31, 1, u32_le> is_out;
+ BitField<0, 8, u32> cmd;
+ BitField<8, 8, u32> group;
+ BitField<16, 14, u32> length;
+ BitField<30, 1, u32> is_in;
+ BitField<31, 1, u32> is_out;
};
/**
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
index a34b9e753..af62d33d2 100644
--- a/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
+++ b/src/core/hle/service/nvdrv/devices/nvhost_as_gpu.cpp
@@ -10,6 +10,7 @@
#include "core/core.h"
#include "core/hle/service/nvdrv/devices/nvhost_as_gpu.h"
#include "core/hle/service/nvdrv/devices/nvmap.h"
+#include "core/memory.h"
#include "video_core/memory_manager.h"
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
@@ -88,7 +89,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
for (const auto& entry : entries) {
LOG_WARNING(Service_NVDRV, "remap entry, offset=0x{:X} handle=0x{:X} pages=0x{:X}",
entry.offset, entry.nvmap_handle, entry.pages);
- Tegra::GPUVAddr offset = static_cast<Tegra::GPUVAddr>(entry.offset) << 0x10;
+ GPUVAddr offset = static_cast<GPUVAddr>(entry.offset) << 0x10;
auto object = nvmap_dev->GetObject(entry.nvmap_handle);
if (!object) {
LOG_CRITICAL(Service_NVDRV, "nvmap {} is an invalid handle!", entry.nvmap_handle);
@@ -101,7 +102,7 @@ u32 nvhost_as_gpu::Remap(const std::vector<u8>& input, std::vector<u8>& output)
u64 size = static_cast<u64>(entry.pages) << 0x10;
ASSERT(size <= object->size);
- Tegra::GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size);
+ GPUVAddr returned = gpu.MemoryManager().MapBufferEx(object->addr, offset, size);
ASSERT(returned == offset);
}
std::memcpy(output.data(), entries.data(), output.size());
@@ -172,16 +173,8 @@ u32 nvhost_as_gpu::UnmapBuffer(const std::vector<u8>& input, std::vector<u8>& ou
return 0;
}
- auto& system_instance = Core::System::GetInstance();
-
- // Remove this memory region from the rasterizer cache.
- auto& gpu = system_instance.GPU();
- auto cpu_addr = gpu.MemoryManager().GpuToCpuAddress(params.offset);
- ASSERT(cpu_addr);
- gpu.FlushAndInvalidateRegion(*cpu_addr, itr->second.size);
-
- params.offset = gpu.MemoryManager().UnmapBuffer(params.offset, itr->second.size);
-
+ params.offset = Core::System::GetInstance().GPU().MemoryManager().UnmapBuffer(params.offset,
+ itr->second.size);
buffer_mappings.erase(itr->second.offset);
std::memcpy(output.data(), &params, output.size());
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 576fd6407..00806b0ed 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -11,7 +11,6 @@
#include "core/hle/ipc.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
-#include "core/hle/kernel/handle_table.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/server_port.h"
@@ -168,7 +167,7 @@ ResultCode ServiceFrameworkBase::HandleSyncRequest(Kernel::HLERequestContext& co
case IPC::CommandType::Close: {
IPC::ResponseBuilder rb{context, 2};
rb.Push(RESULT_SUCCESS);
- return ResultCode(ErrorModule::HIPC, ErrorDescription::RemoteProcessDead);
+ return IPC::ERR_REMOTE_PROCESS_DEAD;
}
case IPC::CommandType::ControlWithContext:
case IPC::CommandType::Control: {
diff --git a/src/core/hle/service/set/set_sys.cpp b/src/core/hle/service/set/set_sys.cpp
index c9b4da5b0..ecee554bf 100644
--- a/src/core/hle/service/set/set_sys.cpp
+++ b/src/core/hle/service/set/set_sys.cpp
@@ -2,13 +2,88 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/file_sys/errors.h"
+#include "core/file_sys/system_archive/system_version.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/client_port.h"
+#include "core/hle/service/filesystem/filesystem.h"
#include "core/hle/service/set/set_sys.h"
namespace Service::Set {
+namespace {
+constexpr u64 SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET = 0x05;
+
+enum class GetFirmwareVersionType {
+ Version1,
+ Version2,
+};
+
+void GetFirmwareVersionImpl(Kernel::HLERequestContext& ctx, GetFirmwareVersionType type) {
+ LOG_WARNING(Service_SET, "called - Using hardcoded firmware version '{}'",
+ FileSys::SystemArchive::GetLongDisplayVersion());
+
+ ASSERT_MSG(ctx.GetWriteBufferSize() == 0x100,
+ "FirmwareVersion output buffer must be 0x100 bytes in size!");
+
+ // Instead of using the normal procedure of checking for the real system archive and if it
+ // doesn't exist, synthesizing one, I feel that that would lead to strange bugs because a
+ // used is using a really old or really new SystemVersion title. The synthesized one ensures
+ // consistence (currently reports as 5.1.0-0.0)
+ const auto archive = FileSys::SystemArchive::SystemVersion();
+
+ const auto early_exit_failure = [&ctx](const std::string& desc, ResultCode code) {
+ LOG_ERROR(Service_SET, "General failure while attempting to resolve firmware version ({}).",
+ desc.c_str());
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(code);
+ };
+
+ if (archive == nullptr) {
+ early_exit_failure("The system version archive couldn't be synthesized.",
+ FileSys::ERROR_FAILED_MOUNT_ARCHIVE);
+ return;
+ }
+
+ const auto ver_file = archive->GetFile("file");
+ if (ver_file == nullptr) {
+ early_exit_failure("The system version archive didn't contain the file 'file'.",
+ FileSys::ERROR_INVALID_ARGUMENT);
+ return;
+ }
+
+ auto data = ver_file->ReadAllBytes();
+ if (data.size() != 0x100) {
+ early_exit_failure("The system version file 'file' was not the correct size.",
+ FileSys::ERROR_OUT_OF_BOUNDS);
+ return;
+ }
+
+ // If the command is GetFirmwareVersion (as opposed to GetFirmwareVersion2), hardware will
+ // zero out the REVISION_MINOR field.
+ if (type == GetFirmwareVersionType::Version1) {
+ data[SYSTEM_VERSION_FILE_MINOR_REVISION_OFFSET] = 0;
+ }
+
+ ctx.WriteBuffer(data);
+
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(RESULT_SUCCESS);
+}
+} // Anonymous namespace
+
+void SET_SYS::GetFirmwareVersion(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "called");
+ GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version1);
+}
+
+void SET_SYS::GetFirmwareVersion2(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_SET, "called");
+ GetFirmwareVersionImpl(ctx, GetFirmwareVersionType::Version2);
+}
+
void SET_SYS::GetColorSetId(Kernel::HLERequestContext& ctx) {
LOG_DEBUG(Service_SET, "called");
@@ -33,8 +108,8 @@ SET_SYS::SET_SYS() : ServiceFramework("set:sys") {
{0, nullptr, "SetLanguageCode"},
{1, nullptr, "SetNetworkSettings"},
{2, nullptr, "GetNetworkSettings"},
- {3, nullptr, "GetFirmwareVersion"},
- {4, nullptr, "GetFirmwareVersion2"},
+ {3, &SET_SYS::GetFirmwareVersion, "GetFirmwareVersion"},
+ {4, &SET_SYS::GetFirmwareVersion2, "GetFirmwareVersion2"},
{5, nullptr, "GetFirmwareVersionDigest"},
{7, nullptr, "GetLockScreenFlag"},
{8, nullptr, "SetLockScreenFlag"},
diff --git a/src/core/hle/service/set/set_sys.h b/src/core/hle/service/set/set_sys.h
index f602f3c77..13ee2cf46 100644
--- a/src/core/hle/service/set/set_sys.h
+++ b/src/core/hle/service/set/set_sys.h
@@ -20,6 +20,8 @@ private:
BasicBlack = 1,
};
+ void GetFirmwareVersion(Kernel::HLERequestContext& ctx);
+ void GetFirmwareVersion2(Kernel::HLERequestContext& ctx);
void GetColorSetId(Kernel::HLERequestContext& ctx);
void SetColorSetId(Kernel::HLERequestContext& ctx);
diff --git a/src/core/hle/service/sm/sm.h b/src/core/hle/service/sm/sm.h
index bef25433e..b9d6381b4 100644
--- a/src/core/hle/service/sm/sm.h
+++ b/src/core/hle/service/sm/sm.h
@@ -67,7 +67,7 @@ public:
if (port == nullptr) {
return nullptr;
}
- return std::static_pointer_cast<T>(port->hle_handler);
+ return std::static_pointer_cast<T>(port->GetHLEHandler());
}
void InvokeControlRequest(Kernel::HLERequestContext& context);
diff --git a/src/core/hle/service/vi/vi.cpp b/src/core/hle/service/vi/vi.cpp
index a975767bb..566cd6006 100644
--- a/src/core/hle/service/vi/vi.cpp
+++ b/src/core/hle/service/vi/vi.cpp
@@ -24,6 +24,7 @@
#include "core/hle/service/nvdrv/nvdrv.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "core/hle/service/nvflinger/nvflinger.h"
+#include "core/hle/service/service.h"
#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
#include "core/hle/service/vi/vi_s.h"
@@ -33,6 +34,7 @@
namespace Service::VI {
constexpr ResultCode ERR_OPERATION_FAILED{ErrorModule::VI, 1};
+constexpr ResultCode ERR_PERMISSION_DENIED{ErrorModule::VI, 5};
constexpr ResultCode ERR_UNSUPPORTED{ErrorModule::VI, 6};
constexpr ResultCode ERR_NOT_FOUND{ErrorModule::VI, 7};
@@ -1203,26 +1205,40 @@ IApplicationDisplayService::IApplicationDisplayService(
RegisterHandlers(functions);
}
-Module::Interface::Interface(std::shared_ptr<Module> module, const char* name,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : ServiceFramework(name), module(std::move(module)), nv_flinger(std::move(nv_flinger)) {}
+static bool IsValidServiceAccess(Permission permission, Policy policy) {
+ if (permission == Permission::User) {
+ return policy == Policy::User;
+ }
+
+ if (permission == Permission::System || permission == Permission::Manager) {
+ return policy == Policy::User || policy == Policy::Compositor;
+ }
-Module::Interface::~Interface() = default;
+ return false;
+}
-void Module::Interface::GetDisplayService(Kernel::HLERequestContext& ctx) {
- LOG_WARNING(Service_VI, "(STUBBED) called");
+void detail::GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger,
+ Permission permission) {
+ IPC::RequestParser rp{ctx};
+ const auto policy = rp.PopEnum<Policy>();
+
+ if (!IsValidServiceAccess(permission, policy)) {
+ IPC::ResponseBuilder rb{ctx, 2};
+ rb.Push(ERR_PERMISSION_DENIED);
+ return;
+ }
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
rb.Push(RESULT_SUCCESS);
- rb.PushIpcInterface<IApplicationDisplayService>(nv_flinger);
+ rb.PushIpcInterface<IApplicationDisplayService>(std::move(nv_flinger));
}
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger) {
- auto module = std::make_shared<Module>();
- std::make_shared<VI_M>(module, nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_S>(module, nv_flinger)->InstallAsService(service_manager);
- std::make_shared<VI_U>(module, nv_flinger)->InstallAsService(service_manager);
+ std::make_shared<VI_M>(nv_flinger)->InstallAsService(service_manager);
+ std::make_shared<VI_S>(nv_flinger)->InstallAsService(service_manager);
+ std::make_shared<VI_U>(nv_flinger)->InstallAsService(service_manager);
}
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi.h b/src/core/hle/service/vi/vi.h
index e3963502a..6b66f8b81 100644
--- a/src/core/hle/service/vi/vi.h
+++ b/src/core/hle/service/vi/vi.h
@@ -4,12 +4,21 @@
#pragma once
-#include "core/hle/service/service.h"
+#include <memory>
+#include "common/common_types.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
namespace Service::NVFlinger {
class NVFlinger;
}
+namespace Service::SM {
+class ServiceManager;
+}
+
namespace Service::VI {
enum class DisplayResolution : u32 {
@@ -19,22 +28,25 @@ enum class DisplayResolution : u32 {
UndockedHeight = 720,
};
-class Module final {
-public:
- class Interface : public ServiceFramework<Interface> {
- public:
- explicit Interface(std::shared_ptr<Module> module, const char* name,
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
- ~Interface() override;
-
- void GetDisplayService(Kernel::HLERequestContext& ctx);
+/// Permission level for a particular VI service instance
+enum class Permission {
+ User,
+ System,
+ Manager,
+};
- protected:
- std::shared_ptr<Module> module;
- std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
- };
+/// A policy type that may be requested via GetDisplayService and
+/// GetDisplayServiceWithProxyNameExchange
+enum class Policy {
+ User,
+ Compositor,
};
+namespace detail {
+void GetDisplayServiceImpl(Kernel::HLERequestContext& ctx,
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger, Permission permission);
+} // namespace detail
+
/// Registers all VI services with the specified service manager.
void InstallInterfaces(SM::ServiceManager& service_manager,
std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
diff --git a/src/core/hle/service/vi/vi_m.cpp b/src/core/hle/service/vi/vi_m.cpp
index 207c06b16..06070087f 100644
--- a/src/core/hle/service/vi/vi_m.cpp
+++ b/src/core/hle/service/vi/vi_m.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_m.h"
namespace Service::VI {
-VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : Module::Interface(std::move(module), "vi:m", std::move(nv_flinger)) {
+VI_M::VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : ServiceFramework{"vi:m"}, nv_flinger{std::move(nv_flinger)} {
static const FunctionInfo functions[] = {
{2, &VI_M::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -17,4 +19,10 @@ VI_M::VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
VI_M::~VI_M() = default;
+void VI_M::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::Manager);
+}
+
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_m.h b/src/core/hle/service/vi/vi_m.h
index 487d58d50..290e06689 100644
--- a/src/core/hle/service/vi/vi_m.h
+++ b/src/core/hle/service/vi/vi_m.h
@@ -4,14 +4,27 @@
#pragma once
-#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service::NVFlinger {
+class NVFlinger;
+}
namespace Service::VI {
-class VI_M final : public Module::Interface {
+class VI_M final : public ServiceFramework<VI_M> {
public:
- explicit VI_M(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_M(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
~VI_M() override;
+
+private:
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.cpp b/src/core/hle/service/vi/vi_s.cpp
index 920e6a1f6..57c596cc4 100644
--- a/src/core/hle/service/vi/vi_s.cpp
+++ b/src/core/hle/service/vi/vi_s.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_s.h"
namespace Service::VI {
-VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : Module::Interface(std::move(module), "vi:s", std::move(nv_flinger)) {
+VI_S::VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : ServiceFramework{"vi:s"}, nv_flinger{std::move(nv_flinger)} {
static const FunctionInfo functions[] = {
{1, &VI_S::GetDisplayService, "GetDisplayService"},
{3, nullptr, "GetDisplayServiceWithProxyNameExchange"},
@@ -17,4 +19,10 @@ VI_S::VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
VI_S::~VI_S() = default;
+void VI_S::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::System);
+}
+
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_s.h b/src/core/hle/service/vi/vi_s.h
index bbc31148f..47804dc0b 100644
--- a/src/core/hle/service/vi/vi_s.h
+++ b/src/core/hle/service/vi/vi_s.h
@@ -4,14 +4,27 @@
#pragma once
-#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service::NVFlinger {
+class NVFlinger;
+}
namespace Service::VI {
-class VI_S final : public Module::Interface {
+class VI_S final : public ServiceFramework<VI_S> {
public:
- explicit VI_S(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_S(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
~VI_S() override;
+
+private:
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.cpp b/src/core/hle/service/vi/vi_u.cpp
index d81e410d6..9d5ceb608 100644
--- a/src/core/hle/service/vi/vi_u.cpp
+++ b/src/core/hle/service/vi/vi_u.cpp
@@ -2,12 +2,14 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "common/logging/log.h"
+#include "core/hle/service/vi/vi.h"
#include "core/hle/service/vi/vi_u.h"
namespace Service::VI {
-VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
- : Module::Interface(std::move(module), "vi:u", std::move(nv_flinger)) {
+VI_U::VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger)
+ : ServiceFramework{"vi:u"}, nv_flinger{std::move(nv_flinger)} {
static const FunctionInfo functions[] = {
{0, &VI_U::GetDisplayService, "GetDisplayService"},
};
@@ -16,4 +18,10 @@ VI_U::VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger>
VI_U::~VI_U() = default;
+void VI_U::GetDisplayService(Kernel::HLERequestContext& ctx) {
+ LOG_DEBUG(Service_VI, "called");
+
+ detail::GetDisplayServiceImpl(ctx, nv_flinger, Permission::User);
+}
+
} // namespace Service::VI
diff --git a/src/core/hle/service/vi/vi_u.h b/src/core/hle/service/vi/vi_u.h
index b92f28c92..19bdb73b0 100644
--- a/src/core/hle/service/vi/vi_u.h
+++ b/src/core/hle/service/vi/vi_u.h
@@ -4,14 +4,27 @@
#pragma once
-#include "core/hle/service/vi/vi.h"
+#include "core/hle/service/service.h"
+
+namespace Kernel {
+class HLERequestContext;
+}
+
+namespace Service::NVFlinger {
+class NVFlinger;
+}
namespace Service::VI {
-class VI_U final : public Module::Interface {
+class VI_U final : public ServiceFramework<VI_U> {
public:
- explicit VI_U(std::shared_ptr<Module> module, std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
+ explicit VI_U(std::shared_ptr<NVFlinger::NVFlinger> nv_flinger);
~VI_U() override;
+
+private:
+ void GetDisplayService(Kernel::HLERequestContext& ctx);
+
+ std::shared_ptr<NVFlinger::NVFlinger> nv_flinger;
};
} // namespace Service::VI
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp
index 6057c7f26..8b1920f22 100644
--- a/src/core/loader/elf.cpp
+++ b/src/core/loader/elf.cpp
@@ -9,6 +9,7 @@
#include "common/common_types.h"
#include "common/file_util.h"
#include "common/logging/log.h"
+#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/elf.h"
diff --git a/src/core/loader/linker.cpp b/src/core/loader/linker.cpp
deleted file mode 100644
index 57ca8c3ee..000000000
--- a/src/core/loader/linker.cpp
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <vector>
-
-#include "common/common_funcs.h"
-#include "common/logging/log.h"
-#include "common/swap.h"
-#include "core/loader/linker.h"
-#include "core/memory.h"
-
-namespace Loader {
-
-enum class RelocationType : u32 { ABS64 = 257, GLOB_DAT = 1025, JUMP_SLOT = 1026, RELATIVE = 1027 };
-
-enum DynamicType : u32 {
- DT_NULL = 0,
- DT_PLTRELSZ = 2,
- DT_STRTAB = 5,
- DT_SYMTAB = 6,
- DT_RELA = 7,
- DT_RELASZ = 8,
- DT_STRSZ = 10,
- DT_JMPREL = 23,
-};
-
-struct Elf64_Rela {
- u64_le offset;
- RelocationType type;
- u32_le symbol;
- s64_le addend;
-};
-static_assert(sizeof(Elf64_Rela) == 0x18, "Elf64_Rela has incorrect size.");
-
-struct Elf64_Dyn {
- u64_le tag;
- u64_le value;
-};
-static_assert(sizeof(Elf64_Dyn) == 0x10, "Elf64_Dyn has incorrect size.");
-
-struct Elf64_Sym {
- u32_le name;
- INSERT_PADDING_BYTES(0x2);
- u16_le shndx;
- u64_le value;
- u64_le size;
-};
-static_assert(sizeof(Elf64_Sym) == 0x18, "Elf64_Sym has incorrect size.");
-
-void Linker::WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
- u64 relocation_offset, u64 size, VAddr load_base) {
- for (u64 i = 0; i < size; i += sizeof(Elf64_Rela)) {
- Elf64_Rela rela;
- std::memcpy(&rela, &program_image[relocation_offset + i], sizeof(Elf64_Rela));
-
- const Symbol& symbol = symbols[rela.symbol];
- switch (rela.type) {
- case RelocationType::RELATIVE: {
- const u64 value = load_base + rela.addend;
- if (!symbol.name.empty()) {
- exports[symbol.name] = value;
- }
- std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
- break;
- }
- case RelocationType::JUMP_SLOT:
- case RelocationType::GLOB_DAT:
- if (!symbol.value) {
- imports[symbol.name] = {rela.offset + load_base, 0};
- } else {
- exports[symbol.name] = symbol.value;
- std::memcpy(&program_image[rela.offset], &symbol.value, sizeof(u64));
- }
- break;
- case RelocationType::ABS64:
- if (!symbol.value) {
- imports[symbol.name] = {rela.offset + load_base, rela.addend};
- } else {
- const u64 value = symbol.value + rela.addend;
- exports[symbol.name] = value;
- std::memcpy(&program_image[rela.offset], &value, sizeof(u64));
- }
- break;
- default:
- LOG_CRITICAL(Loader, "Unknown relocation type: {}", static_cast<int>(rela.type));
- break;
- }
- }
-}
-
-void Linker::Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base) {
- std::map<u64, u64> dynamic;
- while (dynamic_section_offset < program_image.size()) {
- Elf64_Dyn dyn;
- std::memcpy(&dyn, &program_image[dynamic_section_offset], sizeof(Elf64_Dyn));
- dynamic_section_offset += sizeof(Elf64_Dyn);
-
- if (dyn.tag == DT_NULL) {
- break;
- }
- dynamic[dyn.tag] = dyn.value;
- }
-
- u64 offset = dynamic[DT_SYMTAB];
- std::vector<Symbol> symbols;
- while (offset < program_image.size()) {
- Elf64_Sym sym;
- std::memcpy(&sym, &program_image[offset], sizeof(Elf64_Sym));
- offset += sizeof(Elf64_Sym);
-
- if (sym.name >= dynamic[DT_STRSZ]) {
- break;
- }
-
- std::string name = reinterpret_cast<char*>(&program_image[dynamic[DT_STRTAB] + sym.name]);
- if (sym.value) {
- exports[name] = load_base + sym.value;
- symbols.emplace_back(std::move(name), load_base + sym.value);
- } else {
- symbols.emplace_back(std::move(name), 0);
- }
- }
-
- if (dynamic.find(DT_RELA) != dynamic.end()) {
- WriteRelocations(program_image, symbols, dynamic[DT_RELA], dynamic[DT_RELASZ], load_base);
- }
-
- if (dynamic.find(DT_JMPREL) != dynamic.end()) {
- WriteRelocations(program_image, symbols, dynamic[DT_JMPREL], dynamic[DT_PLTRELSZ],
- load_base);
- }
-}
-
-void Linker::ResolveImports() {
- // Resolve imports
- for (const auto& import : imports) {
- const auto& search = exports.find(import.first);
- if (search != exports.end()) {
- Memory::Write64(import.second.ea, search->second + import.second.addend);
- } else {
- LOG_ERROR(Loader, "Unresolved import: {}", import.first);
- }
- }
-}
-
-} // namespace Loader
diff --git a/src/core/loader/linker.h b/src/core/loader/linker.h
deleted file mode 100644
index 107625837..000000000
--- a/src/core/loader/linker.h
+++ /dev/null
@@ -1,36 +0,0 @@
-// Copyright 2018 yuzu emulator team
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include <map>
-#include <string>
-#include "common/common_types.h"
-
-namespace Loader {
-
-class Linker {
-protected:
- struct Symbol {
- Symbol(std::string&& name, u64 value) : name(std::move(name)), value(value) {}
- std::string name;
- u64 value;
- };
-
- struct Import {
- VAddr ea;
- s64 addend;
- };
-
- void WriteRelocations(std::vector<u8>& program_image, const std::vector<Symbol>& symbols,
- u64 relocation_offset, u64 size, VAddr load_base);
- void Relocate(std::vector<u8>& program_image, u32 dynamic_section_offset, VAddr load_base);
-
- void ResolveImports();
-
- std::map<std::string, Import> imports;
- std::map<std::string, VAddr> exports;
-};
-
-} // namespace Loader
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index 4fad0c0dd..5de02a94b 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -14,6 +14,7 @@
#include "core/file_sys/romfs_factory.h"
#include "core/file_sys/vfs_offset.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/hle/service/filesystem/filesystem.h"
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 013d629c0..85b0ed644 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -4,10 +4,10 @@
#pragma once
+#include <memory>
#include <string>
#include <vector>
#include "common/common_types.h"
-#include "core/loader/linker.h"
#include "core/loader/loader.h"
namespace FileSys {
@@ -21,7 +21,7 @@ class Process;
namespace Loader {
/// Loads an NRO file
-class AppLoader_NRO final : public AppLoader, Linker {
+class AppLoader_NRO final : public AppLoader {
public:
explicit AppLoader_NRO(FileSys::VirtualFile file);
~AppLoader_NRO() override;
diff --git a/src/core/loader/nso.cpp b/src/core/loader/nso.cpp
index 6ded0b707..714d85a59 100644
--- a/src/core/loader/nso.cpp
+++ b/src/core/loader/nso.cpp
@@ -7,10 +7,13 @@
#include <lz4.h>
#include "common/common_funcs.h"
#include "common/file_util.h"
+#include "common/hex_util.h"
#include "common/logging/log.h"
#include "common/swap.h"
+#include "core/core.h"
#include "core/file_sys/patch_manager.h"
#include "core/gdbstub/gdbstub.h"
+#include "core/hle/kernel/code_set.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/vm_manager.h"
#include "core/loader/nso.h"
@@ -18,36 +21,8 @@
#include "core/settings.h"
namespace Loader {
-
-struct NsoSegmentHeader {
- u32_le offset;
- u32_le location;
- u32_le size;
- union {
- u32_le alignment;
- u32_le bss_size;
- };
-};
-static_assert(sizeof(NsoSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size.");
-
-struct NsoHeader {
- u32_le magic;
- u32_le version;
- INSERT_PADDING_WORDS(1);
- u8 flags;
- std::array<NsoSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
- std::array<u8, 0x20> build_id;
- std::array<u32_le, 3> segments_compressed_size;
-
- bool IsSegmentCompressed(size_t segment_num) const {
- ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num);
- return ((flags >> segment_num) & 1);
- }
-};
-static_assert(sizeof(NsoHeader) == 0x6c, "NsoHeader has incorrect size.");
-static_assert(std::is_trivially_copyable_v<NsoHeader>, "NsoHeader isn't trivially copyable.");
-
-struct ModHeader {
+namespace {
+struct MODHeader {
u32_le magic;
u32_le dynamic_offset;
u32_le bss_start_offset;
@@ -56,25 +31,10 @@ struct ModHeader {
u32_le eh_frame_hdr_end_offset;
u32_le module_offset; // Offset to runtime-generated module object. typically equal to .bss base
};
-static_assert(sizeof(ModHeader) == 0x1c, "ModHeader has incorrect size.");
-
-AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
+static_assert(sizeof(MODHeader) == 0x1c, "MODHeader has incorrect size.");
-FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
- u32 magic = 0;
- if (file->ReadObject(&magic) != sizeof(magic)) {
- return FileType::Error;
- }
-
- if (Common::MakeMagic('N', 'S', 'O', '0') != magic) {
- return FileType::Error;
- }
-
- return FileType::NSO;
-}
-
-static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
- const NsoSegmentHeader& header) {
+std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
+ const NSOSegmentHeader& header) {
std::vector<u8> uncompressed_data(header.size);
const int bytes_uncompressed =
LZ4_decompress_safe(reinterpret_cast<const char*>(compressed_data.data()),
@@ -88,23 +48,47 @@ static std::vector<u8> DecompressSegment(const std::vector<u8>& compressed_data,
return uncompressed_data;
}
-static constexpr u32 PageAlignSize(u32 size) {
+constexpr u32 PageAlignSize(u32 size) {
return (size + Memory::PAGE_MASK) & ~Memory::PAGE_MASK;
}
+} // Anonymous namespace
+
+bool NSOHeader::IsSegmentCompressed(size_t segment_num) const {
+ ASSERT_MSG(segment_num < 3, "Invalid segment {}", segment_num);
+ return ((flags >> segment_num) & 1) != 0;
+}
+
+AppLoader_NSO::AppLoader_NSO(FileSys::VirtualFile file) : AppLoader(std::move(file)) {}
+
+FileType AppLoader_NSO::IdentifyType(const FileSys::VirtualFile& file) {
+ u32 magic = 0;
+ if (file->ReadObject(&magic) != sizeof(magic)) {
+ return FileType::Error;
+ }
+
+ if (Common::MakeMagic('N', 'S', 'O', '0') != magic) {
+ return FileType::Error;
+ }
+
+ return FileType::NSO;
+}
std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
const FileSys::VfsFile& file, VAddr load_base,
bool should_pass_arguments,
std::optional<FileSys::PatchManager> pm) {
- if (file.GetSize() < sizeof(NsoHeader))
+ if (file.GetSize() < sizeof(NSOHeader)) {
return {};
+ }
- NsoHeader nso_header{};
- if (sizeof(NsoHeader) != file.ReadObject(&nso_header))
+ NSOHeader nso_header{};
+ if (sizeof(NSOHeader) != file.ReadObject(&nso_header)) {
return {};
+ }
- if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0'))
+ if (nso_header.magic != Common::MakeMagic('N', 'S', 'O', '0')) {
return {};
+ }
// Build program image
Kernel::CodeSet codeset;
@@ -140,10 +124,10 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
std::memcpy(&module_offset, program_image.data() + 4, sizeof(u32));
// Read MOD header
- ModHeader mod_header{};
+ MODHeader mod_header{};
// Default .bss to size in segment header if MOD0 section doesn't exist
u32 bss_size{PageAlignSize(nso_header.segments[2].bss_size)};
- std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(ModHeader));
+ std::memcpy(&mod_header, program_image.data() + module_offset, sizeof(MODHeader));
const bool has_mod_header{mod_header.magic == Common::MakeMagic('M', 'O', 'D', '0')};
if (has_mod_header) {
// Resize program image to include .bss section and page align each section
@@ -155,13 +139,25 @@ std::optional<VAddr> AppLoader_NSO::LoadModule(Kernel::Process& process,
// Apply patches if necessary
if (pm && (pm->HasNSOPatch(nso_header.build_id) || Settings::values.dump_nso)) {
- std::vector<u8> pi_header(program_image.size() + 0x100);
- std::memcpy(pi_header.data(), &nso_header, sizeof(NsoHeader));
- std::memcpy(pi_header.data() + 0x100, program_image.data(), program_image.size());
+ std::vector<u8> pi_header(sizeof(NSOHeader) + program_image.size());
+ pi_header.insert(pi_header.begin(), reinterpret_cast<u8*>(&nso_header),
+ reinterpret_cast<u8*>(&nso_header) + sizeof(NSOHeader));
+ pi_header.insert(pi_header.begin() + sizeof(NSOHeader), program_image.begin(),
+ program_image.end());
pi_header = pm->PatchNSO(pi_header);
- std::memcpy(program_image.data(), pi_header.data() + 0x100, program_image.size());
+ std::copy(pi_header.begin() + sizeof(NSOHeader), pi_header.end(), program_image.begin());
+ }
+
+ // Apply cheats if they exist and the program has a valid title ID
+ if (pm) {
+ auto& system = Core::System::GetInstance();
+ const auto cheats = pm->CreateCheatList(system, nso_header.build_id);
+ if (!cheats.empty()) {
+ system.RegisterCheatList(cheats, Common::HexArrayToString(nso_header.build_id),
+ load_base, load_base + program_image.size());
+ }
}
// Load codeset for current process
diff --git a/src/core/loader/nso.h b/src/core/loader/nso.h
index 135b6ea5a..4674c3724 100644
--- a/src/core/loader/nso.h
+++ b/src/core/loader/nso.h
@@ -4,10 +4,12 @@
#pragma once
+#include <array>
#include <optional>
+#include <type_traits>
#include "common/common_types.h"
+#include "common/swap.h"
#include "core/file_sys/patch_manager.h"
-#include "core/loader/linker.h"
#include "core/loader/loader.h"
namespace Kernel {
@@ -16,6 +18,43 @@ class Process;
namespace Loader {
+struct NSOSegmentHeader {
+ u32_le offset;
+ u32_le location;
+ u32_le size;
+ union {
+ u32_le alignment;
+ u32_le bss_size;
+ };
+};
+static_assert(sizeof(NSOSegmentHeader) == 0x10, "NsoSegmentHeader has incorrect size.");
+
+struct NSOHeader {
+ using SHA256Hash = std::array<u8, 0x20>;
+
+ struct RODataRelativeExtent {
+ u32_le data_offset;
+ u32_le size;
+ };
+
+ u32_le magic;
+ u32_le version;
+ u32 reserved;
+ u32_le flags;
+ std::array<NSOSegmentHeader, 3> segments; // Text, RoData, Data (in that order)
+ std::array<u8, 0x20> build_id;
+ std::array<u32_le, 3> segments_compressed_size;
+ std::array<u8, 0x1C> padding;
+ RODataRelativeExtent api_info_extent;
+ RODataRelativeExtent dynstr_extent;
+ RODataRelativeExtent dynsyn_extent;
+ std::array<SHA256Hash, 3> segment_hashes;
+
+ bool IsSegmentCompressed(size_t segment_num) const;
+};
+static_assert(sizeof(NSOHeader) == 0x100, "NSOHeader has incorrect size.");
+static_assert(std::is_trivially_copyable_v<NSOHeader>, "NSOHeader must be trivially copyable.");
+
constexpr u64 NSO_ARGUMENT_DATA_ALLOCATION_SIZE = 0x9000;
struct NSOArgumentHeader {
@@ -26,7 +65,7 @@ struct NSOArgumentHeader {
static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
/// Loads an NSO file
-class AppLoader_NSO final : public AppLoader, Linker {
+class AppLoader_NSO final : public AppLoader {
public:
explicit AppLoader_NSO(FileSys::VirtualFile file);
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 6591c45d2..332c1037c 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -10,6 +10,7 @@
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "common/page_table.h"
#include "common/swap.h"
#include "core/arm/arm_interface.h"
#include "core/core.h"
@@ -18,13 +19,14 @@
#include "core/hle/lock.h"
#include "core/memory.h"
#include "core/memory_setup.h"
+#include "video_core/gpu.h"
#include "video_core/renderer_base.h"
namespace Memory {
-static PageTable* current_page_table = nullptr;
+static Common::PageTable* current_page_table = nullptr;
-void SetCurrentPageTable(PageTable* page_table) {
+void SetCurrentPageTable(Common::PageTable* page_table) {
current_page_table = page_table;
auto& system = Core::System::GetInstance();
@@ -36,39 +38,20 @@ void SetCurrentPageTable(PageTable* page_table) {
}
}
-PageTable* GetCurrentPageTable() {
+Common::PageTable* GetCurrentPageTable() {
return current_page_table;
}
-PageTable::PageTable() = default;
-
-PageTable::PageTable(std::size_t address_space_width_in_bits) {
- Resize(address_space_width_in_bits);
-}
-
-PageTable::~PageTable() = default;
-
-void PageTable::Resize(std::size_t address_space_width_in_bits) {
- const std::size_t num_page_table_entries = 1ULL << (address_space_width_in_bits - PAGE_BITS);
-
- pointers.resize(num_page_table_entries);
- attributes.resize(num_page_table_entries);
-
- // The default is a 39-bit address space, which causes an initial 1GB allocation size. If the
- // vector size is subsequently decreased (via resize), the vector might not automatically
- // actually reallocate/resize its underlying allocation, which wastes up to ~800 MB for
- // 36-bit titles. Call shrink_to_fit to reduce capacity to what's actually in use.
-
- pointers.shrink_to_fit();
- attributes.shrink_to_fit();
-}
-
-static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, PageType type) {
+static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
+ Common::PageType type) {
LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE,
(base + size) * PAGE_SIZE);
- RasterizerFlushVirtualRegion(base << PAGE_BITS, size * PAGE_SIZE,
- FlushMode::FlushAndInvalidate);
+ // During boot, current_page_table might not be set yet, in which case we need not flush
+ if (Core::System::GetInstance().IsPoweredOn()) {
+ Core::System::GetInstance().GPU().FlushAndInvalidateRegion(base << PAGE_BITS,
+ size * PAGE_SIZE);
+ }
VAddr end = base + size;
ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
@@ -88,41 +71,47 @@ static void MapPages(PageTable& page_table, VAddr base, u64 size, u8* memory, Pa
}
}
-void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target) {
+void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
- MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
}
-void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler) {
+void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer mmio_handler) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
- MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Special);
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- SpecialRegion region{SpecialRegion::Type::IODevice, std::move(mmio_handler)};
- page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
+ Common::SpecialRegion region{Common::SpecialRegion::Type::IODevice, std::move(mmio_handler)};
+ page_table.special_regions.add(
+ std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
}
-void UnmapRegion(PageTable& page_table, VAddr base, u64 size) {
+void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
- MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
+ MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, Common::PageType::Unmapped);
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
page_table.special_regions.erase(interval);
}
-void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
+void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook) {
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
- page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
+ Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
+ page_table.special_regions.add(
+ std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
}
-void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
+void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook) {
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
- SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
- page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region}));
+ Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
+ page_table.special_regions.subtract(
+ std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
}
/**
@@ -171,19 +160,19 @@ T Read(const VAddr vaddr) {
return value;
}
- PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
return 0;
- case PageType::Memory:
+ case Common::PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
break;
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Flush);
-
+ case Common::PageType::RasterizerCachedMemory: {
+ auto host_ptr{GetPointerFromVMA(vaddr)};
+ Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), sizeof(T));
T value;
- std::memcpy(&value, GetPointerFromVMA(vaddr), sizeof(T));
+ std::memcpy(&value, host_ptr, sizeof(T));
return value;
}
default:
@@ -201,18 +190,19 @@ void Write(const VAddr vaddr, const T data) {
return;
}
- PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
switch (type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
static_cast<u32>(data), vaddr);
return;
- case PageType::Memory:
+ case Common::PageType::Memory:
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
break;
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(vaddr, sizeof(T), FlushMode::Invalidate);
- std::memcpy(GetPointerFromVMA(vaddr), &data, sizeof(T));
+ case Common::PageType::RasterizerCachedMemory: {
+ auto host_ptr{GetPointerFromVMA(vaddr)};
+ Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T));
+ std::memcpy(host_ptr, &data, sizeof(T));
break;
}
default:
@@ -227,10 +217,10 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
if (page_pointer)
return true;
- if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory)
+ if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory)
return true;
- if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
+ if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special)
return false;
return false;
@@ -250,7 +240,8 @@ u8* GetPointer(const VAddr vaddr) {
return page_pointer + (vaddr & PAGE_MASK);
}
- if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
+ if (current_page_table->attributes[vaddr >> PAGE_BITS] ==
+ Common::PageType::RasterizerCachedMemory) {
return GetPointerFromVMA(vaddr);
}
@@ -284,20 +275,20 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
u64 num_pages = ((vaddr + size - 1) >> PAGE_BITS) - (vaddr >> PAGE_BITS) + 1;
for (unsigned i = 0; i < num_pages; ++i, vaddr += PAGE_SIZE) {
- PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
+ Common::PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
if (cached) {
// Switch page type to cached if now cached
switch (page_type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
// It is not necessary for a process to have this region mapped into its address
// space, for example, a system module need not have a VRAM mapping.
break;
- case PageType::Memory:
- page_type = PageType::RasterizerCachedMemory;
+ case Common::PageType::Memory:
+ page_type = Common::PageType::RasterizerCachedMemory;
current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
break;
- case PageType::RasterizerCachedMemory:
+ case Common::PageType::RasterizerCachedMemory:
// There can be more than one GPU region mapped per CPU region, so it's common that
// this area is already marked as cached.
break;
@@ -307,23 +298,23 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
} else {
// Switch page type to uncached if now uncached
switch (page_type) {
- case PageType::Unmapped:
+ case Common::PageType::Unmapped:
// It is not necessary for a process to have this region mapped into its address
// space, for example, a system module need not have a VRAM mapping.
break;
- case PageType::Memory:
+ case Common::PageType::Memory:
// There can be more than one GPU region mapped per CPU region, so it's common that
// this area is already unmarked as cached.
break;
- case PageType::RasterizerCachedMemory: {
+ case Common::PageType::RasterizerCachedMemory: {
u8* pointer = GetPointerFromVMA(vaddr & ~PAGE_MASK);
if (pointer == nullptr) {
// It's possible that this function has been called while updating the pagetable
// after unmapping a VMA. In that case the underlying VMA will no longer exist,
// and we should just leave the pagetable entry blank.
- page_type = PageType::Unmapped;
+ page_type = Common::PageType::Unmapped;
} else {
- page_type = PageType::Memory;
+ page_type = Common::PageType::Memory;
current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
}
break;
@@ -335,47 +326,6 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
}
}
-void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode) {
- auto& system_instance = Core::System::GetInstance();
-
- // Since pages are unmapped on shutdown after video core is shutdown, the renderer may be
- // null here
- if (!system_instance.IsPoweredOn()) {
- return;
- }
-
- const VAddr end = start + size;
-
- const auto CheckRegion = [&](VAddr region_start, VAddr region_end) {
- if (start >= region_end || end <= region_start) {
- // No overlap with region
- return;
- }
-
- const VAddr overlap_start = std::max(start, region_start);
- const VAddr overlap_end = std::min(end, region_end);
- const VAddr overlap_size = overlap_end - overlap_start;
-
- auto& gpu = system_instance.GPU();
- switch (mode) {
- case FlushMode::Flush:
- gpu.FlushRegion(overlap_start, overlap_size);
- break;
- case FlushMode::Invalidate:
- gpu.InvalidateRegion(overlap_start, overlap_size);
- break;
- case FlushMode::FlushAndInvalidate:
- gpu.FlushAndInvalidateRegion(overlap_start, overlap_size);
- break;
- }
- };
-
- const auto& vm_manager = Core::CurrentProcess()->VMManager();
-
- CheckRegion(vm_manager.GetCodeRegionBaseAddress(), vm_manager.GetCodeRegionEndAddress());
- CheckRegion(vm_manager.GetHeapRegionBaseAddress(), vm_manager.GetHeapRegionEndAddress());
-}
-
u8 Read8(const VAddr addr) {
return Read<u8>(addr);
}
@@ -406,24 +356,24 @@ void ReadBlock(const Kernel::Process& process, const VAddr src_addr, void* dest_
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped ReadBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, src_addr, size);
std::memset(dest_buffer, 0, copy_amount);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
std::memcpy(dest_buffer, src_ptr, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Flush);
- std::memcpy(dest_buffer, GetPointerFromVMA(process, current_vaddr), copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
+ std::memcpy(dest_buffer, host_ptr, copy_amount);
break;
}
default:
@@ -470,23 +420,23 @@ void WriteBlock(const Kernel::Process& process, const VAddr dest_addr, const voi
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, dest_addr, size);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
std::memcpy(dest_ptr, src_buffer, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Invalidate);
- std::memcpy(GetPointerFromVMA(process, current_vaddr), src_buffer, copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
+ std::memcpy(host_ptr, src_buffer, copy_amount);
break;
}
default:
@@ -516,23 +466,23 @@ void ZeroBlock(const Kernel::Process& process, const VAddr dest_addr, const std:
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, dest_addr, size);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
u8* dest_ptr = page_table.pointers[page_index] + page_offset;
std::memset(dest_ptr, 0, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Invalidate);
- std::memset(GetPointerFromVMA(process, current_vaddr), 0, copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), copy_amount);
+ std::memset(host_ptr, 0, copy_amount);
break;
}
default:
@@ -558,23 +508,23 @@ void CopyBlock(const Kernel::Process& process, VAddr dest_addr, VAddr src_addr,
const VAddr current_vaddr = static_cast<VAddr>((page_index << PAGE_BITS) + page_offset);
switch (page_table.attributes[page_index]) {
- case PageType::Unmapped: {
+ case Common::PageType::Unmapped: {
LOG_ERROR(HW_Memory,
"Unmapped CopyBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
current_vaddr, src_addr, size);
ZeroBlock(process, dest_addr, copy_amount);
break;
}
- case PageType::Memory: {
+ case Common::PageType::Memory: {
DEBUG_ASSERT(page_table.pointers[page_index]);
const u8* src_ptr = page_table.pointers[page_index] + page_offset;
WriteBlock(process, dest_addr, src_ptr, copy_amount);
break;
}
- case PageType::RasterizerCachedMemory: {
- RasterizerFlushVirtualRegion(current_vaddr, static_cast<u32>(copy_amount),
- FlushMode::Flush);
- WriteBlock(process, dest_addr, GetPointerFromVMA(process, current_vaddr), copy_amount);
+ case Common::PageType::RasterizerCachedMemory: {
+ const auto& host_ptr{GetPointerFromVMA(process, current_vaddr)};
+ Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), copy_amount);
+ WriteBlock(process, dest_addr, host_ptr, copy_amount);
break;
}
default:
diff --git a/src/core/memory.h b/src/core/memory.h
index 1acf5ce8c..1d38cdca8 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -6,11 +6,11 @@
#include <cstddef>
#include <string>
-#include <tuple>
-#include <vector>
-#include <boost/icl/interval_map.hpp>
#include "common/common_types.h"
-#include "core/memory_hook.h"
+
+namespace Common {
+struct PageTable;
+}
namespace Kernel {
class Process;
@@ -26,71 +26,6 @@ constexpr std::size_t PAGE_BITS = 12;
constexpr u64 PAGE_SIZE = 1ULL << PAGE_BITS;
constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
-enum class PageType : u8 {
- /// Page is unmapped and should cause an access error.
- Unmapped,
- /// Page is mapped to regular memory. This is the only type you can get pointers to.
- Memory,
- /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and
- /// invalidation
- RasterizerCachedMemory,
- /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions.
- Special,
-};
-
-struct SpecialRegion {
- enum class Type {
- DebugHook,
- IODevice,
- } type;
-
- MemoryHookPointer handler;
-
- bool operator<(const SpecialRegion& other) const {
- return std::tie(type, handler) < std::tie(other.type, other.handler);
- }
-
- bool operator==(const SpecialRegion& other) const {
- return std::tie(type, handler) == std::tie(other.type, other.handler);
- }
-};
-
-/**
- * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely
- * mimics the way a real CPU page table works.
- */
-struct PageTable {
- explicit PageTable();
- explicit PageTable(std::size_t address_space_width_in_bits);
- ~PageTable();
-
- /**
- * Resizes the page table to be able to accomodate enough pages within
- * a given address space.
- *
- * @param address_space_width_in_bits The address size width in bits.
- */
- void Resize(std::size_t address_space_width_in_bits);
-
- /**
- * Vector of memory pointers backing each page. An entry can only be non-null if the
- * corresponding entry in the `attributes` vector is of type `Memory`.
- */
- std::vector<u8*> pointers;
-
- /**
- * Contains MMIO handlers that back memory regions whose entries in the `attribute` vector is
- * of type `Special`.
- */
- boost::icl::interval_map<VAddr, std::set<SpecialRegion>> special_regions;
-
- /**
- * Vector of fine grained page attributes. If it is set to any value other than `Memory`, then
- * the corresponding entry in `pointers` MUST be set to null.
- */
- std::vector<PageType> attributes;
-};
-
/// Virtual user-space memory regions
enum : VAddr {
/// Read-only page containing kernel and system configuration values.
@@ -116,8 +51,8 @@ enum : VAddr {
};
/// Currently active page table
-void SetCurrentPageTable(PageTable* page_table);
-PageTable* GetCurrentPageTable();
+void SetCurrentPageTable(Common::PageTable* page_table);
+Common::PageTable* GetCurrentPageTable();
/// Determines if the given VAddr is valid for the specified process.
bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr);
@@ -161,10 +96,4 @@ enum class FlushMode {
*/
void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached);
-/**
- * Flushes and invalidates any externally cached rasterizer resources touching the given virtual
- * address region.
- */
-void RasterizerFlushVirtualRegion(VAddr start, u64 size, FlushMode mode);
-
} // namespace Memory
diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h
index 9a1a4f4be..5225ee8e2 100644
--- a/src/core/memory_setup.h
+++ b/src/core/memory_setup.h
@@ -5,7 +5,11 @@
#pragma once
#include "common/common_types.h"
-#include "core/memory_hook.h"
+#include "common/memory_hook.h"
+
+namespace Common {
+struct PageTable;
+}
namespace Memory {
@@ -17,7 +21,7 @@ namespace Memory {
* @param size The amount of bytes to map. Must be page-aligned.
* @param target Buffer with the memory backing the mapping. Must be of length at least `size`.
*/
-void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
+void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target);
/**
* Maps a region of the emulated process address space as a IO region.
@@ -26,11 +30,14 @@ void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
* @param size The amount of bytes to map. Must be page-aligned.
* @param mmio_handler The handler that backs the mapping.
*/
-void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler);
+void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer mmio_handler);
-void UnmapRegion(PageTable& page_table, VAddr base, u64 size);
+void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size);
-void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
-void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
+void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook);
+void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
+ Common::MemoryHookPointer hook);
} // namespace Memory
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt
index 1c7db28c0..5b4e032bd 100644
--- a/src/input_common/CMakeLists.txt
+++ b/src/input_common/CMakeLists.txt
@@ -7,15 +7,18 @@ add_library(input_common STATIC
main.h
motion_emu.cpp
motion_emu.h
-
- $<$<BOOL:${SDL2_FOUND}>:sdl/sdl.cpp sdl/sdl.h>
+ sdl/sdl.cpp
+ sdl/sdl.h
)
-create_target_directory_groups(input_common)
-
-target_link_libraries(input_common PUBLIC core PRIVATE common)
-
if(SDL2_FOUND)
+ target_sources(input_common PRIVATE
+ sdl/sdl_impl.cpp
+ sdl/sdl_impl.h
+ )
target_link_libraries(input_common PRIVATE SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
endif()
+
+create_target_directory_groups(input_common)
+target_link_libraries(input_common PUBLIC core PRIVATE common)
diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp
index 37f572853..8e66c1b15 100644
--- a/src/input_common/main.cpp
+++ b/src/input_common/main.cpp
@@ -17,10 +17,7 @@ namespace InputCommon {
static std::shared_ptr<Keyboard> keyboard;
static std::shared_ptr<MotionEmu> motion_emu;
-
-#ifdef HAVE_SDL2
-static std::thread poll_thread;
-#endif
+static std::unique_ptr<SDL::State> sdl;
void Init() {
keyboard = std::make_shared<Keyboard>();
@@ -30,15 +27,7 @@ void Init() {
motion_emu = std::make_shared<MotionEmu>();
Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu);
-#ifdef HAVE_SDL2
- SDL::Init();
-#endif
-}
-
-void StartJoystickEventHandler() {
-#ifdef HAVE_SDL2
- poll_thread = std::thread(SDL::PollLoop);
-#endif
+ sdl = SDL::Init();
}
void Shutdown() {
@@ -47,11 +36,7 @@ void Shutdown() {
Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button");
Input::UnregisterFactory<Input::MotionDevice>("motion_emu");
motion_emu.reset();
-
-#ifdef HAVE_SDL2
- SDL::Shutdown();
- poll_thread.join();
-#endif
+ sdl.reset();
}
Keyboard* GetKeyboard() {
@@ -88,7 +73,7 @@ namespace Polling {
std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) {
#ifdef HAVE_SDL2
- return SDL::Polling::GetPollers(type);
+ return sdl->GetPollers(type);
#else
return {};
#endif
diff --git a/src/input_common/main.h b/src/input_common/main.h
index 9eb13106e..77a0ce90b 100644
--- a/src/input_common/main.h
+++ b/src/input_common/main.h
@@ -20,8 +20,6 @@ void Init();
/// Deregisters all built-in input device factories and shuts them down.
void Shutdown();
-void StartJoystickEventHandler();
-
class Keyboard;
/// Gets the keyboard button device factory.
diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp
index faf3c1fa3..644db3448 100644
--- a/src/input_common/sdl/sdl.cpp
+++ b/src/input_common/sdl/sdl.cpp
@@ -1,631 +1,19 @@
-// Copyright 2017 Citra Emulator Project
+// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <atomic>
-#include <cmath>
-#include <functional>
-#include <iterator>
-#include <mutex>
-#include <string>
-#include <thread>
-#include <tuple>
-#include <unordered_map>
-#include <utility>
-#include <vector>
-#include <SDL.h>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/math_util.h"
-#include "common/param_package.h"
-#include "common/threadsafe_queue.h"
-#include "input_common/main.h"
#include "input_common/sdl/sdl.h"
+#ifdef HAVE_SDL2
+#include "input_common/sdl/sdl_impl.h"
+#endif
-namespace InputCommon {
+namespace InputCommon::SDL {
-namespace SDL {
-
-class SDLJoystick;
-class SDLButtonFactory;
-class SDLAnalogFactory;
-
-/// Map of GUID of a list of corresponding virtual Joysticks
-static std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
-static std::mutex joystick_map_mutex;
-
-static std::shared_ptr<SDLButtonFactory> button_factory;
-static std::shared_ptr<SDLAnalogFactory> analog_factory;
-
-/// Used by the Pollers during config
-static std::atomic<bool> polling;
-static Common::SPSCQueue<SDL_Event> event_queue;
-
-static std::atomic<bool> initialized = false;
-
-static std::string GetGUID(SDL_Joystick* joystick) {
- SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
- char guid_str[33];
- SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
- return guid_str;
-}
-
-class SDLJoystick {
-public:
- SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
- decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
- : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
-
- void SetButton(int button, bool value) {
- std::lock_guard<std::mutex> lock(mutex);
- state.buttons[button] = value;
- }
-
- bool GetButton(int button) const {
- std::lock_guard<std::mutex> lock(mutex);
- return state.buttons.at(button);
- }
-
- void SetAxis(int axis, Sint16 value) {
- std::lock_guard<std::mutex> lock(mutex);
- state.axes[axis] = value;
- }
-
- float GetAxis(int axis) const {
- std::lock_guard<std::mutex> lock(mutex);
- return state.axes.at(axis) / 32767.0f;
- }
-
- std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
- float x = GetAxis(axis_x);
- float y = GetAxis(axis_y);
- y = -y; // 3DS uses an y-axis inverse from SDL
-
- // Make sure the coordinates are in the unit circle,
- // otherwise normalize it.
- float r = x * x + y * y;
- if (r > 1.0f) {
- r = std::sqrt(r);
- x /= r;
- y /= r;
- }
-
- return std::make_tuple(x, y);
- }
-
- void SetHat(int hat, Uint8 direction) {
- std::lock_guard<std::mutex> lock(mutex);
- state.hats[hat] = direction;
- }
-
- bool GetHatDirection(int hat, Uint8 direction) const {
- std::lock_guard<std::mutex> lock(mutex);
- return (state.hats.at(hat) & direction) != 0;
- }
- /**
- * The guid of the joystick
- */
- const std::string& GetGUID() const {
- return guid;
- }
-
- /**
- * The number of joystick from the same type that were connected before this joystick
- */
- int GetPort() const {
- return port;
- }
-
- SDL_Joystick* GetSDLJoystick() const {
- return sdl_joystick.get();
- }
-
- void SetSDLJoystick(SDL_Joystick* joystick,
- decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
- sdl_joystick =
- std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
- }
-
-private:
- struct State {
- std::unordered_map<int, bool> buttons;
- std::unordered_map<int, Sint16> axes;
- std::unordered_map<int, Uint8> hats;
- } state;
- std::string guid;
- int port;
- std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
- mutable std::mutex mutex;
-};
-
-/**
- * Get the nth joystick with the corresponding GUID
- */
-static std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- const auto it = joystick_map.find(guid);
- if (it != joystick_map.end()) {
- while (it->second.size() <= port) {
- auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
- [](SDL_Joystick*) {});
- it->second.emplace_back(std::move(joystick));
- }
- return it->second[port];
- }
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
- return joystick_map[guid].emplace_back(std::move(joystick));
-}
-
-/**
- * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
- * it to a SDLJoystick with the same guid and that port
- */
-static std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
- const std::string guid = GetGUID(sdl_joystick);
- auto map_it = joystick_map.find(guid);
- if (map_it != joystick_map.end()) {
- auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
- [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
- return sdl_joystick == joystick->GetSDLJoystick();
- });
- if (vec_it != map_it->second.end()) {
- // This is the common case: There is already an existing SDL_Joystick maped to a
- // SDLJoystick. return the SDLJoystick
- return *vec_it;
- }
- // Search for a SDLJoystick without a mapped SDL_Joystick...
- auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
- [](const std::shared_ptr<SDLJoystick>& joystick) {
- return !joystick->GetSDLJoystick();
- });
- if (nullptr_it != map_it->second.end()) {
- // ... and map it
- (*nullptr_it)->SetSDLJoystick(sdl_joystick);
- return *nullptr_it;
- }
- // There is no SDLJoystick without a mapped SDL_Joystick
- // Create a new SDLJoystick
- auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
- return map_it->second.emplace_back(std::move(joystick));
- }
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
- return joystick_map[guid].emplace_back(std::move(joystick));
-}
-
-void InitJoystick(int joystick_index) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
- if (!sdl_joystick) {
- LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
- return;
- }
- std::string guid = GetGUID(sdl_joystick);
- if (joystick_map.find(guid) == joystick_map.end()) {
- auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
- joystick_map[guid].emplace_back(std::move(joystick));
- return;
- }
- auto& joystick_guid_list = joystick_map[guid];
- const auto it = std::find_if(
- joystick_guid_list.begin(), joystick_guid_list.end(),
- [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
- if (it != joystick_guid_list.end()) {
- (*it)->SetSDLJoystick(sdl_joystick);
- return;
- }
- auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
- joystick_guid_list.emplace_back(std::move(joystick));
-}
-
-void CloseJoystick(SDL_Joystick* sdl_joystick) {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- std::string guid = GetGUID(sdl_joystick);
- // This call to guid is save since the joystick is guranteed to be in that map
- auto& joystick_guid_list = joystick_map[guid];
- const auto joystick_it =
- std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
- [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
- return joystick->GetSDLJoystick() == sdl_joystick;
- });
- (*joystick_it)->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
-}
-
-void HandleGameControllerEvent(const SDL_Event& event) {
- switch (event.type) {
- case SDL_JOYBUTTONUP: {
- auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
- if (joystick) {
- joystick->SetButton(event.jbutton.button, false);
- }
- break;
- }
- case SDL_JOYBUTTONDOWN: {
- auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
- if (joystick) {
- joystick->SetButton(event.jbutton.button, true);
- }
- break;
- }
- case SDL_JOYHATMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
- if (joystick) {
- joystick->SetHat(event.jhat.hat, event.jhat.value);
- }
- break;
- }
- case SDL_JOYAXISMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
- if (joystick) {
- joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
- }
- break;
- }
- case SDL_JOYDEVICEREMOVED:
- LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
- CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
- break;
- case SDL_JOYDEVICEADDED:
- LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
- InitJoystick(event.jdevice.which);
- break;
- }
-}
-
-void CloseSDLJoysticks() {
- std::lock_guard<std::mutex> lock(joystick_map_mutex);
- joystick_map.clear();
-}
-
-void PollLoop() {
- if (SDL_Init(SDL_INIT_JOYSTICK) < 0) {
- LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
- return;
- }
-
- SDL_Event event;
- while (initialized) {
- // Wait for 10 ms or until an event happens
- if (SDL_WaitEventTimeout(&event, 10)) {
- // Don't handle the event if we are configuring
- if (polling) {
- event_queue.Push(event);
- } else {
- HandleGameControllerEvent(event);
- }
- }
- }
- CloseSDLJoysticks();
- SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
-}
-
-class SDLButton final : public Input::ButtonDevice {
-public:
- explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
- : joystick(std::move(joystick_)), button(button_) {}
-
- bool GetStatus() const override {
- return joystick->GetButton(button);
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int button;
-};
-
-class SDLDirectionButton final : public Input::ButtonDevice {
-public:
- explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
- : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
-
- bool GetStatus() const override {
- return joystick->GetHatDirection(hat, direction);
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int hat;
- Uint8 direction;
-};
-
-class SDLAxisButton final : public Input::ButtonDevice {
-public:
- explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
- bool trigger_if_greater_)
- : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
- trigger_if_greater(trigger_if_greater_) {}
-
- bool GetStatus() const override {
- float axis_value = joystick->GetAxis(axis);
- if (trigger_if_greater)
- return axis_value > threshold;
- return axis_value < threshold;
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int axis;
- float threshold;
- bool trigger_if_greater;
-};
-
-class SDLAnalog final : public Input::AnalogDevice {
-public:
- SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_)
- : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_) {}
-
- std::tuple<float, float> GetStatus() const override {
- return joystick->GetAnalog(axis_x, axis_y);
- }
-
-private:
- std::shared_ptr<SDLJoystick> joystick;
- int axis_x;
- int axis_y;
-};
-
-/// A button device factory that creates button devices from SDL joystick
-class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
-public:
- /**
- * Creates a button device from a joystick button
- * @param params contains parameters for creating the device:
- * - "guid": the guid of the joystick to bind
- * - "port": the nth joystick of the same type to bind
- * - "button"(optional): the index of the button to bind
- * - "hat"(optional): the index of the hat to bind as direction buttons
- * - "axis"(optional): the index of the axis to bind
- * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
- * "down", "left" or "right"
- * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
- * triggered if the axis value crosses
- * - "direction"(only used for axis): "+" means the button is triggered when the axis
- * value is greater than the threshold; "-" means the button is triggered when the axis
- * value is smaller than the threshold
- */
- std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
- const std::string guid = params.Get("guid", "0");
- const int port = params.Get("port", 0);
-
- auto joystick = GetSDLJoystickByGUID(guid, port);
-
- if (params.Has("hat")) {
- const int hat = params.Get("hat", 0);
- const std::string direction_name = params.Get("direction", "");
- Uint8 direction;
- if (direction_name == "up") {
- direction = SDL_HAT_UP;
- } else if (direction_name == "down") {
- direction = SDL_HAT_DOWN;
- } else if (direction_name == "left") {
- direction = SDL_HAT_LEFT;
- } else if (direction_name == "right") {
- direction = SDL_HAT_RIGHT;
- } else {
- direction = 0;
- }
- // This is necessary so accessing GetHat with hat won't crash
- joystick->SetHat(hat, SDL_HAT_CENTERED);
- return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
- }
-
- if (params.Has("axis")) {
- const int axis = params.Get("axis", 0);
- const float threshold = params.Get("threshold", 0.5f);
- const std::string direction_name = params.Get("direction", "");
- bool trigger_if_greater;
- if (direction_name == "+") {
- trigger_if_greater = true;
- } else if (direction_name == "-") {
- trigger_if_greater = false;
- } else {
- trigger_if_greater = true;
- LOG_ERROR(Input, "Unknown direction '{}'", direction_name);
- }
- // This is necessary so accessing GetAxis with axis won't crash
- joystick->SetAxis(axis, 0);
- return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
- }
-
- const int button = params.Get("button", 0);
- // This is necessary so accessing GetButton with button won't crash
- joystick->SetButton(button, false);
- return std::make_unique<SDLButton>(joystick, button);
- }
-};
-
-/// An analog device factory that creates analog devices from SDL joystick
-class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
-public:
- /**
- * Creates analog device from joystick axes
- * @param params contains parameters for creating the device:
- * - "guid": the guid of the joystick to bind
- * - "port": the nth joystick of the same type
- * - "axis_x": the index of the axis to be bind as x-axis
- * - "axis_y": the index of the axis to be bind as y-axis
- */
- std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
- const std::string guid = params.Get("guid", "0");
- const int port = params.Get("port", 0);
- const int axis_x = params.Get("axis_x", 0);
- const int axis_y = params.Get("axis_y", 1);
-
- auto joystick = GetSDLJoystickByGUID(guid, port);
-
- // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
- joystick->SetAxis(axis_x, 0);
- joystick->SetAxis(axis_y, 0);
- return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y);
- }
-};
-
-void Init() {
- using namespace Input;
- RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>());
- RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>());
- polling = false;
- initialized = true;
-}
-
-void Shutdown() {
- if (initialized) {
- using namespace Input;
- UnregisterFactory<ButtonDevice>("sdl");
- UnregisterFactory<AnalogDevice>("sdl");
- initialized = false;
- }
-}
-
-Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event) {
- Common::ParamPackage params({{"engine", "sdl"}});
- switch (event.type) {
- case SDL_JOYAXISMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis", event.jaxis.axis);
- if (event.jaxis.value > 0) {
- params.Set("direction", "+");
- params.Set("threshold", "0.5");
- } else {
- params.Set("direction", "-");
- params.Set("threshold", "-0.5");
- }
- break;
- }
- case SDL_JOYBUTTONUP: {
- auto joystick = GetSDLJoystickBySDLID(event.jbutton.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("button", event.jbutton.button);
- break;
- }
- case SDL_JOYHATMOTION: {
- auto joystick = GetSDLJoystickBySDLID(event.jhat.which);
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("hat", event.jhat.hat);
- switch (event.jhat.value) {
- case SDL_HAT_UP:
- params.Set("direction", "up");
- break;
- case SDL_HAT_DOWN:
- params.Set("direction", "down");
- break;
- case SDL_HAT_LEFT:
- params.Set("direction", "left");
- break;
- case SDL_HAT_RIGHT:
- params.Set("direction", "right");
- break;
- default:
- return {};
- }
- break;
- }
- }
- return params;
-}
-
-namespace Polling {
-
-class SDLPoller : public InputCommon::Polling::DevicePoller {
-public:
- void Start() override {
- event_queue.Clear();
- polling = true;
- }
-
- void Stop() override {
- polling = false;
- }
-};
-
-class SDLButtonPoller final : public SDLPoller {
-public:
- Common::ParamPackage GetNextInput() override {
- SDL_Event event;
- while (event_queue.Pop(event)) {
- switch (event.type) {
- case SDL_JOYAXISMOTION:
- if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
- break;
- }
- case SDL_JOYBUTTONUP:
- case SDL_JOYHATMOTION:
- return SDLEventToButtonParamPackage(event);
- }
- }
- return {};
- }
-};
-
-class SDLAnalogPoller final : public SDLPoller {
-public:
- void Start() override {
- SDLPoller::Start();
-
- // Reset stored axes
- analog_xaxis = -1;
- analog_yaxis = -1;
- analog_axes_joystick = -1;
- }
-
- Common::ParamPackage GetNextInput() override {
- SDL_Event event;
- while (event_queue.Pop(event)) {
- if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
- continue;
- }
- // An analog device needs two axes, so we need to store the axis for later and wait for
- // a second SDL event. The axes also must be from the same joystick.
- int axis = event.jaxis.axis;
- if (analog_xaxis == -1) {
- analog_xaxis = axis;
- analog_axes_joystick = event.jaxis.which;
- } else if (analog_yaxis == -1 && analog_xaxis != axis &&
- analog_axes_joystick == event.jaxis.which) {
- analog_yaxis = axis;
- }
- }
- Common::ParamPackage params;
- if (analog_xaxis != -1 && analog_yaxis != -1) {
- auto joystick = GetSDLJoystickBySDLID(event.jaxis.which);
- params.Set("engine", "sdl");
- params.Set("port", joystick->GetPort());
- params.Set("guid", joystick->GetGUID());
- params.Set("axis_x", analog_xaxis);
- params.Set("axis_y", analog_yaxis);
- analog_xaxis = -1;
- analog_yaxis = -1;
- analog_axes_joystick = -1;
- return params;
- }
- return params;
- }
-
-private:
- int analog_xaxis = -1;
- int analog_yaxis = -1;
- SDL_JoystickID analog_axes_joystick = -1;
-};
-
-std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
- InputCommon::Polling::DeviceType type) {
- std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
- switch (type) {
- case InputCommon::Polling::DeviceType::Analog:
- pollers.push_back(std::make_unique<SDLAnalogPoller>());
- break;
- case InputCommon::Polling::DeviceType::Button:
- pollers.push_back(std::make_unique<SDLButtonPoller>());
- break;
- }
- return pollers;
+std::unique_ptr<State> Init() {
+#ifdef HAVE_SDL2
+ return std::make_unique<SDLState>();
+#else
+ return std::make_unique<NullState>();
+#endif
}
-} // namespace Polling
-} // namespace SDL
-} // namespace InputCommon
+} // namespace InputCommon::SDL
diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h
index 0206860d3..d7f24c68a 100644
--- a/src/input_common/sdl/sdl.h
+++ b/src/input_common/sdl/sdl.h
@@ -1,4 +1,4 @@
-// Copyright 2017 Citra Emulator Project
+// Copyright 2018 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
@@ -7,45 +7,38 @@
#include <memory>
#include <vector>
#include "core/frontend/input.h"
+#include "input_common/main.h"
union SDL_Event;
+
namespace Common {
class ParamPackage;
-}
-namespace InputCommon {
-namespace Polling {
+} // namespace Common
+
+namespace InputCommon::Polling {
class DevicePoller;
enum class DeviceType;
-} // namespace Polling
-} // namespace InputCommon
-
-namespace InputCommon {
-namespace SDL {
-
-/// Initializes and registers SDL device factories
-void Init();
-
-/// Unresisters SDL device factories and shut them down.
-void Shutdown();
+} // namespace InputCommon::Polling
-/// Needs to be called before SDL_QuitSubSystem.
-void CloseSDLJoysticks();
+namespace InputCommon::SDL {
-/// Handle SDL_Events for joysticks from SDL_PollEvent
-void HandleGameControllerEvent(const SDL_Event& event);
+class State {
+public:
+ using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
-/// A Loop that calls HandleGameControllerEvent until Shutdown is called
-void PollLoop();
+ /// Unregisters SDL device factories and shut them down.
+ virtual ~State() = default;
-/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
-Common::ParamPackage SDLEventToButtonParamPackage(const SDL_Event& event);
+ virtual Pollers GetPollers(Polling::DeviceType type) = 0;
+};
-namespace Polling {
+class NullState : public State {
+public:
+ Pollers GetPollers(Polling::DeviceType type) override {
+ return {};
+ }
+};
-/// Get all DevicePoller that use the SDL backend for a specific device type
-std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
- InputCommon::Polling::DeviceType type);
+std::unique_ptr<State> Init();
-} // namespace Polling
-} // namespace SDL
-} // namespace InputCommon
+} // namespace InputCommon::SDL
diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp
new file mode 100644
index 000000000..b132d77f5
--- /dev/null
+++ b/src/input_common/sdl/sdl_impl.cpp
@@ -0,0 +1,669 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <atomic>
+#include <cmath>
+#include <functional>
+#include <iterator>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <tuple>
+#include <unordered_map>
+#include <utility>
+#include <vector>
+#include <SDL.h>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/param_package.h"
+#include "common/threadsafe_queue.h"
+#include "core/frontend/input.h"
+#include "input_common/sdl/sdl_impl.h"
+
+namespace InputCommon {
+
+namespace SDL {
+
+static std::string GetGUID(SDL_Joystick* joystick) {
+ SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick);
+ char guid_str[33];
+ SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str));
+ return guid_str;
+}
+
+/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice
+static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event);
+
+static int SDLEventWatcher(void* userdata, SDL_Event* event) {
+ SDLState* sdl_state = reinterpret_cast<SDLState*>(userdata);
+ // Don't handle the event if we are configuring
+ if (sdl_state->polling) {
+ sdl_state->event_queue.Push(*event);
+ } else {
+ sdl_state->HandleGameControllerEvent(*event);
+ }
+ return 0;
+}
+
+class SDLJoystick {
+public:
+ SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick,
+ decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose)
+ : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, deleter} {}
+
+ void SetButton(int button, bool value) {
+ std::lock_guard<std::mutex> lock(mutex);
+ state.buttons[button] = value;
+ }
+
+ bool GetButton(int button) const {
+ std::lock_guard<std::mutex> lock(mutex);
+ return state.buttons.at(button);
+ }
+
+ void SetAxis(int axis, Sint16 value) {
+ std::lock_guard<std::mutex> lock(mutex);
+ state.axes[axis] = value;
+ }
+
+ float GetAxis(int axis) const {
+ std::lock_guard<std::mutex> lock(mutex);
+ return state.axes.at(axis) / 32767.0f;
+ }
+
+ std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const {
+ float x = GetAxis(axis_x);
+ float y = GetAxis(axis_y);
+ y = -y; // 3DS uses an y-axis inverse from SDL
+
+ // Make sure the coordinates are in the unit circle,
+ // otherwise normalize it.
+ float r = x * x + y * y;
+ if (r > 1.0f) {
+ r = std::sqrt(r);
+ x /= r;
+ y /= r;
+ }
+
+ return std::make_tuple(x, y);
+ }
+
+ void SetHat(int hat, Uint8 direction) {
+ std::lock_guard<std::mutex> lock(mutex);
+ state.hats[hat] = direction;
+ }
+
+ bool GetHatDirection(int hat, Uint8 direction) const {
+ std::lock_guard<std::mutex> lock(mutex);
+ return (state.hats.at(hat) & direction) != 0;
+ }
+ /**
+ * The guid of the joystick
+ */
+ const std::string& GetGUID() const {
+ return guid;
+ }
+
+ /**
+ * The number of joystick from the same type that were connected before this joystick
+ */
+ int GetPort() const {
+ return port;
+ }
+
+ SDL_Joystick* GetSDLJoystick() const {
+ return sdl_joystick.get();
+ }
+
+ void SetSDLJoystick(SDL_Joystick* joystick,
+ decltype(&SDL_JoystickClose) deleter = &SDL_JoystickClose) {
+ sdl_joystick =
+ std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)>(joystick, deleter);
+ }
+
+private:
+ struct State {
+ std::unordered_map<int, bool> buttons;
+ std::unordered_map<int, Sint16> axes;
+ std::unordered_map<int, Uint8> hats;
+ } state;
+ std::string guid;
+ int port;
+ std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick;
+ mutable std::mutex mutex;
+};
+
+/**
+ * Get the nth joystick with the corresponding GUID
+ */
+std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) {
+ std::lock_guard<std::mutex> lock(joystick_map_mutex);
+ const auto it = joystick_map.find(guid);
+ if (it != joystick_map.end()) {
+ while (it->second.size() <= port) {
+ auto joystick = std::make_shared<SDLJoystick>(guid, it->second.size(), nullptr,
+ [](SDL_Joystick*) {});
+ it->second.emplace_back(std::move(joystick));
+ }
+ return it->second[port];
+ }
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, [](SDL_Joystick*) {});
+ return joystick_map[guid].emplace_back(std::move(joystick));
+}
+
+/**
+ * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so tie
+ * it to a SDLJoystick with the same guid and that port
+ */
+std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) {
+ auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id);
+ const std::string guid = GetGUID(sdl_joystick);
+ std::lock_guard<std::mutex> lock(joystick_map_mutex);
+ auto map_it = joystick_map.find(guid);
+ if (map_it != joystick_map.end()) {
+ auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(),
+ [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
+ return sdl_joystick == joystick->GetSDLJoystick();
+ });
+ if (vec_it != map_it->second.end()) {
+ // This is the common case: There is already an existing SDL_Joystick maped to a
+ // SDLJoystick. return the SDLJoystick
+ return *vec_it;
+ }
+ // Search for a SDLJoystick without a mapped SDL_Joystick...
+ auto nullptr_it = std::find_if(map_it->second.begin(), map_it->second.end(),
+ [](const std::shared_ptr<SDLJoystick>& joystick) {
+ return !joystick->GetSDLJoystick();
+ });
+ if (nullptr_it != map_it->second.end()) {
+ // ... and map it
+ (*nullptr_it)->SetSDLJoystick(sdl_joystick);
+ return *nullptr_it;
+ }
+ // There is no SDLJoystick without a mapped SDL_Joystick
+ // Create a new SDLJoystick
+ auto joystick = std::make_shared<SDLJoystick>(guid, map_it->second.size(), sdl_joystick);
+ return map_it->second.emplace_back(std::move(joystick));
+ }
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ return joystick_map[guid].emplace_back(std::move(joystick));
+}
+
+void SDLState::InitJoystick(int joystick_index) {
+ SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index);
+ if (!sdl_joystick) {
+ LOG_ERROR(Input, "failed to open joystick {}", joystick_index);
+ return;
+ }
+ std::string guid = GetGUID(sdl_joystick);
+ std::lock_guard<std::mutex> lock(joystick_map_mutex);
+ if (joystick_map.find(guid) == joystick_map.end()) {
+ auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick);
+ joystick_map[guid].emplace_back(std::move(joystick));
+ return;
+ }
+ auto& joystick_guid_list = joystick_map[guid];
+ const auto it = std::find_if(
+ joystick_guid_list.begin(), joystick_guid_list.end(),
+ [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); });
+ if (it != joystick_guid_list.end()) {
+ (*it)->SetSDLJoystick(sdl_joystick);
+ return;
+ }
+ auto joystick = std::make_shared<SDLJoystick>(guid, joystick_guid_list.size(), sdl_joystick);
+ joystick_guid_list.emplace_back(std::move(joystick));
+}
+
+void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) {
+ std::string guid = GetGUID(sdl_joystick);
+ std::shared_ptr<SDLJoystick> joystick;
+ {
+ std::lock_guard<std::mutex> lock(joystick_map_mutex);
+ // This call to guid is safe since the joystick is guaranteed to be in the map
+ auto& joystick_guid_list = joystick_map[guid];
+ const auto joystick_it =
+ std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(),
+ [&sdl_joystick](const std::shared_ptr<SDLJoystick>& joystick) {
+ return joystick->GetSDLJoystick() == sdl_joystick;
+ });
+ joystick = *joystick_it;
+ }
+ // Destruct SDL_Joystick outside the lock guard because SDL can internally call event calback
+ // which locks the mutex again
+ joystick->SetSDLJoystick(nullptr, [](SDL_Joystick*) {});
+}
+
+void SDLState::HandleGameControllerEvent(const SDL_Event& event) {
+ switch (event.type) {
+ case SDL_JOYBUTTONUP: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
+ joystick->SetButton(event.jbutton.button, false);
+ }
+ break;
+ }
+ case SDL_JOYBUTTONDOWN: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) {
+ joystick->SetButton(event.jbutton.button, true);
+ }
+ break;
+ }
+ case SDL_JOYHATMOTION: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) {
+ joystick->SetHat(event.jhat.hat, event.jhat.value);
+ }
+ break;
+ }
+ case SDL_JOYAXISMOTION: {
+ if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) {
+ joystick->SetAxis(event.jaxis.axis, event.jaxis.value);
+ }
+ break;
+ }
+ case SDL_JOYDEVICEREMOVED:
+ LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which);
+ CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which));
+ break;
+ case SDL_JOYDEVICEADDED:
+ LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which);
+ InitJoystick(event.jdevice.which);
+ break;
+ }
+}
+
+void SDLState::CloseJoysticks() {
+ std::lock_guard<std::mutex> lock(joystick_map_mutex);
+ joystick_map.clear();
+}
+
+class SDLButton final : public Input::ButtonDevice {
+public:
+ explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_)
+ : joystick(std::move(joystick_)), button(button_) {}
+
+ bool GetStatus() const override {
+ return joystick->GetButton(button);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int button;
+};
+
+class SDLDirectionButton final : public Input::ButtonDevice {
+public:
+ explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_)
+ : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {}
+
+ bool GetStatus() const override {
+ return joystick->GetHatDirection(hat, direction);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int hat;
+ Uint8 direction;
+};
+
+class SDLAxisButton final : public Input::ButtonDevice {
+public:
+ explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_,
+ bool trigger_if_greater_)
+ : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_),
+ trigger_if_greater(trigger_if_greater_) {}
+
+ bool GetStatus() const override {
+ float axis_value = joystick->GetAxis(axis);
+ if (trigger_if_greater)
+ return axis_value > threshold;
+ return axis_value < threshold;
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ int axis;
+ float threshold;
+ bool trigger_if_greater;
+};
+
+class SDLAnalog final : public Input::AnalogDevice {
+public:
+ SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_)
+ : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {}
+
+ std::tuple<float, float> GetStatus() const override {
+ const auto [x, y] = joystick->GetAnalog(axis_x, axis_y);
+ const float r = std::sqrt((x * x) + (y * y));
+ if (r > deadzone) {
+ return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone),
+ y / r * (r - deadzone) / (1 - deadzone));
+ }
+ return std::make_tuple<float, float>(0.0f, 0.0f);
+ }
+
+private:
+ std::shared_ptr<SDLJoystick> joystick;
+ const int axis_x;
+ const int axis_y;
+ const float deadzone;
+};
+
+/// A button device factory that creates button devices from SDL joystick
+class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> {
+public:
+ explicit SDLButtonFactory(SDLState& state_) : state(state_) {}
+
+ /**
+ * Creates a button device from a joystick button
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type to bind
+ * - "button"(optional): the index of the button to bind
+ * - "hat"(optional): the index of the hat to bind as direction buttons
+ * - "axis"(optional): the index of the axis to bind
+ * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up",
+ * "down", "left" or "right"
+ * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is
+ * triggered if the axis value crosses
+ * - "direction"(only used for axis): "+" means the button is triggered when the axis
+ * value is greater than the threshold; "-" means the button is triggered when the axis
+ * value is smaller than the threshold
+ */
+ std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ const int port = params.Get("port", 0);
+
+ auto joystick = state.GetSDLJoystickByGUID(guid, port);
+
+ if (params.Has("hat")) {
+ const int hat = params.Get("hat", 0);
+ const std::string direction_name = params.Get("direction", "");
+ Uint8 direction;
+ if (direction_name == "up") {
+ direction = SDL_HAT_UP;
+ } else if (direction_name == "down") {
+ direction = SDL_HAT_DOWN;
+ } else if (direction_name == "left") {
+ direction = SDL_HAT_LEFT;
+ } else if (direction_name == "right") {
+ direction = SDL_HAT_RIGHT;
+ } else {
+ direction = 0;
+ }
+ // This is necessary so accessing GetHat with hat won't crash
+ joystick->SetHat(hat, SDL_HAT_CENTERED);
+ return std::make_unique<SDLDirectionButton>(joystick, hat, direction);
+ }
+
+ if (params.Has("axis")) {
+ const int axis = params.Get("axis", 0);
+ const float threshold = params.Get("threshold", 0.5f);
+ const std::string direction_name = params.Get("direction", "");
+ bool trigger_if_greater;
+ if (direction_name == "+") {
+ trigger_if_greater = true;
+ } else if (direction_name == "-") {
+ trigger_if_greater = false;
+ } else {
+ trigger_if_greater = true;
+ LOG_ERROR(Input, "Unknown direction {}", direction_name);
+ }
+ // This is necessary so accessing GetAxis with axis won't crash
+ joystick->SetAxis(axis, 0);
+ return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater);
+ }
+
+ const int button = params.Get("button", 0);
+ // This is necessary so accessing GetButton with button won't crash
+ joystick->SetButton(button, false);
+ return std::make_unique<SDLButton>(joystick, button);
+ }
+
+private:
+ SDLState& state;
+};
+
+/// An analog device factory that creates analog devices from SDL joystick
+class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> {
+public:
+ explicit SDLAnalogFactory(SDLState& state_) : state(state_) {}
+ /**
+ * Creates analog device from joystick axes
+ * @param params contains parameters for creating the device:
+ * - "guid": the guid of the joystick to bind
+ * - "port": the nth joystick of the same type
+ * - "axis_x": the index of the axis to be bind as x-axis
+ * - "axis_y": the index of the axis to be bind as y-axis
+ */
+ std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override {
+ const std::string guid = params.Get("guid", "0");
+ const int port = params.Get("port", 0);
+ const int axis_x = params.Get("axis_x", 0);
+ const int axis_y = params.Get("axis_y", 1);
+ float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f);
+
+ auto joystick = state.GetSDLJoystickByGUID(guid, port);
+
+ // This is necessary so accessing GetAxis with axis_x and axis_y won't crash
+ joystick->SetAxis(axis_x, 0);
+ joystick->SetAxis(axis_y, 0);
+ return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone);
+ }
+
+private:
+ SDLState& state;
+};
+
+SDLState::SDLState() {
+ using namespace Input;
+ RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this));
+ RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this));
+
+ // If the frontend is going to manage the event loop, then we dont start one here
+ start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK);
+ if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) {
+ LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError());
+ return;
+ }
+ if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) {
+ LOG_ERROR(Input, "Failed to set Hint for background events", SDL_GetError());
+ }
+
+ SDL_AddEventWatch(&SDLEventWatcher, this);
+
+ initialized = true;
+ if (start_thread) {
+ poll_thread = std::thread([this] {
+ using namespace std::chrono_literals;
+ while (initialized) {
+ SDL_PumpEvents();
+ std::this_thread::sleep_for(10ms);
+ }
+ });
+ }
+ // Because the events for joystick connection happens before we have our event watcher added, we
+ // can just open all the joysticks right here
+ for (int i = 0; i < SDL_NumJoysticks(); ++i) {
+ InitJoystick(i);
+ }
+}
+
+SDLState::~SDLState() {
+ using namespace Input;
+ UnregisterFactory<ButtonDevice>("sdl");
+ UnregisterFactory<AnalogDevice>("sdl");
+
+ CloseJoysticks();
+ SDL_DelEventWatch(&SDLEventWatcher, this);
+
+ initialized = false;
+ if (start_thread) {
+ poll_thread.join();
+ SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
+ }
+}
+
+Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) {
+ Common::ParamPackage params({{"engine", "sdl"}});
+
+ switch (event.type) {
+ case SDL_JOYAXISMOTION: {
+ auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("axis", event.jaxis.axis);
+ if (event.jaxis.value > 0) {
+ params.Set("direction", "+");
+ params.Set("threshold", "0.5");
+ } else {
+ params.Set("direction", "-");
+ params.Set("threshold", "-0.5");
+ }
+ break;
+ }
+ case SDL_JOYBUTTONUP: {
+ auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which);
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("button", event.jbutton.button);
+ break;
+ }
+ case SDL_JOYHATMOTION: {
+ auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which);
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("hat", event.jhat.hat);
+ switch (event.jhat.value) {
+ case SDL_HAT_UP:
+ params.Set("direction", "up");
+ break;
+ case SDL_HAT_DOWN:
+ params.Set("direction", "down");
+ break;
+ case SDL_HAT_LEFT:
+ params.Set("direction", "left");
+ break;
+ case SDL_HAT_RIGHT:
+ params.Set("direction", "right");
+ break;
+ default:
+ return {};
+ }
+ break;
+ }
+ }
+ return params;
+}
+
+namespace Polling {
+
+class SDLPoller : public InputCommon::Polling::DevicePoller {
+public:
+ explicit SDLPoller(SDLState& state_) : state(state_) {}
+
+ void Start() override {
+ state.event_queue.Clear();
+ state.polling = true;
+ }
+
+ void Stop() override {
+ state.polling = false;
+ }
+
+protected:
+ SDLState& state;
+};
+
+class SDLButtonPoller final : public SDLPoller {
+public:
+ explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {}
+
+ Common::ParamPackage GetNextInput() override {
+ SDL_Event event;
+ while (state.event_queue.Pop(event)) {
+ switch (event.type) {
+ case SDL_JOYAXISMOTION:
+ if (std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ break;
+ }
+ case SDL_JOYBUTTONUP:
+ case SDL_JOYHATMOTION:
+ return SDLEventToButtonParamPackage(state, event);
+ }
+ }
+ return {};
+ }
+};
+
+class SDLAnalogPoller final : public SDLPoller {
+public:
+ explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {}
+
+ void Start() override {
+ SDLPoller::Start();
+
+ // Reset stored axes
+ analog_xaxis = -1;
+ analog_yaxis = -1;
+ analog_axes_joystick = -1;
+ }
+
+ Common::ParamPackage GetNextInput() override {
+ SDL_Event event;
+ while (state.event_queue.Pop(event)) {
+ if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) {
+ continue;
+ }
+ // An analog device needs two axes, so we need to store the axis for later and wait for
+ // a second SDL event. The axes also must be from the same joystick.
+ int axis = event.jaxis.axis;
+ if (analog_xaxis == -1) {
+ analog_xaxis = axis;
+ analog_axes_joystick = event.jaxis.which;
+ } else if (analog_yaxis == -1 && analog_xaxis != axis &&
+ analog_axes_joystick == event.jaxis.which) {
+ analog_yaxis = axis;
+ }
+ }
+ Common::ParamPackage params;
+ if (analog_xaxis != -1 && analog_yaxis != -1) {
+ auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which);
+ params.Set("engine", "sdl");
+ params.Set("port", joystick->GetPort());
+ params.Set("guid", joystick->GetGUID());
+ params.Set("axis_x", analog_xaxis);
+ params.Set("axis_y", analog_yaxis);
+ analog_xaxis = -1;
+ analog_yaxis = -1;
+ analog_axes_joystick = -1;
+ return params;
+ }
+ return params;
+ }
+
+private:
+ int analog_xaxis = -1;
+ int analog_yaxis = -1;
+ SDL_JoystickID analog_axes_joystick = -1;
+};
+} // namespace Polling
+
+SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
+ Pollers pollers;
+
+ switch (type) {
+ case InputCommon::Polling::DeviceType::Analog:
+ pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
+ break;
+ case InputCommon::Polling::DeviceType::Button:
+ pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
+ break;
+ }
+
+ return pollers;
+}
+
+} // namespace SDL
+} // namespace InputCommon
diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h
new file mode 100644
index 000000000..2579741d6
--- /dev/null
+++ b/src/input_common/sdl/sdl_impl.h
@@ -0,0 +1,63 @@
+// Copyright 2018 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <atomic>
+#include <memory>
+#include <thread>
+#include "common/threadsafe_queue.h"
+#include "input_common/sdl/sdl.h"
+
+union SDL_Event;
+using SDL_Joystick = struct _SDL_Joystick;
+using SDL_JoystickID = s32;
+
+namespace InputCommon::SDL {
+
+class SDLJoystick;
+class SDLButtonFactory;
+class SDLAnalogFactory;
+
+class SDLState : public State {
+public:
+ /// Initializes and registers SDL device factories
+ SDLState();
+
+ /// Unregisters SDL device factories and shut them down.
+ ~SDLState() override;
+
+ /// Handle SDL_Events for joysticks from SDL_PollEvent
+ void HandleGameControllerEvent(const SDL_Event& event);
+
+ std::shared_ptr<SDLJoystick> GetSDLJoystickBySDLID(SDL_JoystickID sdl_id);
+ std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port);
+
+ /// Get all DevicePoller that use the SDL backend for a specific device type
+ Pollers GetPollers(Polling::DeviceType type) override;
+
+ /// Used by the Pollers during config
+ std::atomic<bool> polling = false;
+ Common::SPSCQueue<SDL_Event> event_queue;
+
+private:
+ void InitJoystick(int joystick_index);
+ void CloseJoystick(SDL_Joystick* sdl_joystick);
+
+ /// Needs to be called before SDL_QuitSubSystem.
+ void CloseJoysticks();
+
+ /// Map of GUID of a list of corresponding virtual Joysticks
+ std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map;
+ std::mutex joystick_map_mutex;
+
+ std::shared_ptr<SDLButtonFactory> button_factory;
+ std::shared_ptr<SDLAnalogFactory> analog_factory;
+
+ bool start_thread = false;
+ std::atomic<bool> initialized = false;
+
+ std::thread poll_thread;
+};
+} // namespace InputCommon::SDL
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
index 37f09ce5f..d0284bdf4 100644
--- a/src/tests/CMakeLists.txt
+++ b/src/tests/CMakeLists.txt
@@ -1,4 +1,5 @@
add_executable(tests
+ common/bit_field.cpp
common/param_package.cpp
common/ring_buffer.cpp
core/arm/arm_test_common.cpp
diff --git a/src/tests/common/bit_field.cpp b/src/tests/common/bit_field.cpp
new file mode 100644
index 000000000..8ca1889f9
--- /dev/null
+++ b/src/tests/common/bit_field.cpp
@@ -0,0 +1,90 @@
+// Copyright 2019 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+#include <cstring>
+#include <type_traits>
+#include <catch2/catch.hpp>
+#include "common/bit_field.h"
+
+TEST_CASE("BitField", "[common]") {
+ enum class TestEnum : u32 {
+ A = 0b10111101,
+ B = 0b10101110,
+ C = 0b00001111,
+ };
+
+ union LEBitField {
+ u32_le raw;
+ BitField<0, 6, u32> a;
+ BitField<6, 4, s32> b;
+ BitField<10, 8, TestEnum> c;
+ BitField<18, 14, u32> d;
+ } le_bitfield;
+
+ union BEBitField {
+ u32_be raw;
+ BitFieldBE<0, 6, u32> a;
+ BitFieldBE<6, 4, s32> b;
+ BitFieldBE<10, 8, TestEnum> c;
+ BitFieldBE<18, 14, u32> d;
+ } be_bitfield;
+
+ static_assert(sizeof(LEBitField) == sizeof(u32));
+ static_assert(sizeof(BEBitField) == sizeof(u32));
+ static_assert(std::is_trivially_copyable_v<LEBitField>);
+ static_assert(std::is_trivially_copyable_v<BEBitField>);
+
+ std::array<u8, 4> raw{{
+ 0b01101100,
+ 0b11110110,
+ 0b10111010,
+ 0b11101100,
+ }};
+
+ std::memcpy(&le_bitfield, &raw, sizeof(raw));
+ std::memcpy(&be_bitfield, &raw, sizeof(raw));
+
+ // bit fields: 11101100101110'10111101'1001'101100
+ REQUIRE(le_bitfield.raw == 0b11101100'10111010'11110110'01101100);
+ REQUIRE(le_bitfield.a == 0b101100);
+ REQUIRE(le_bitfield.b == -7); // 1001 as two's complement
+ REQUIRE(le_bitfield.c == TestEnum::A);
+ REQUIRE(le_bitfield.d == 0b11101100101110);
+
+ le_bitfield.a.Assign(0b000111);
+ le_bitfield.b.Assign(-1);
+ le_bitfield.c.Assign(TestEnum::C);
+ le_bitfield.d.Assign(0b01010101010101);
+ std::memcpy(&raw, &le_bitfield, sizeof(raw));
+ // bit fields: 01010101010101'00001111'1111'000111
+ REQUIRE(le_bitfield.raw == 0b01010101'01010100'00111111'11000111);
+ REQUIRE(raw == std::array<u8, 4>{{
+ 0b11000111,
+ 0b00111111,
+ 0b01010100,
+ 0b01010101,
+ }});
+
+ // bit fields: 01101100111101'10101110'1011'101100
+ REQUIRE(be_bitfield.raw == 0b01101100'11110110'10111010'11101100);
+ REQUIRE(be_bitfield.a == 0b101100);
+ REQUIRE(be_bitfield.b == -5); // 1011 as two's complement
+ REQUIRE(be_bitfield.c == TestEnum::B);
+ REQUIRE(be_bitfield.d == 0b01101100111101);
+
+ be_bitfield.a.Assign(0b000111);
+ be_bitfield.b.Assign(-1);
+ be_bitfield.c.Assign(TestEnum::C);
+ be_bitfield.d.Assign(0b01010101010101);
+ std::memcpy(&raw, &be_bitfield, sizeof(raw));
+ // bit fields: 01010101010101'00001111'1111'000111
+ REQUIRE(be_bitfield.raw == 0b01010101'01010100'00111111'11000111);
+ REQUIRE(raw == std::array<u8, 4>{{
+ 0b01010101,
+ 0b01010100,
+ 0b00111111,
+ 0b11000111,
+ }});
+}
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp
index 6fe56833d..3e1a735c3 100644
--- a/src/tests/core/arm/arm_test_common.cpp
+++ b/src/tests/core/arm/arm_test_common.cpp
@@ -4,6 +4,7 @@
#include <algorithm>
+#include "common/page_table.h"
#include "core/core.h"
#include "core/hle/kernel/process.h"
#include "core/memory.h"
@@ -22,7 +23,7 @@ TestEnvironment::TestEnvironment(bool mutable_memory_)
std::fill(page_table->pointers.begin(), page_table->pointers.end(), nullptr);
page_table->special_regions.clear();
std::fill(page_table->attributes.begin(), page_table->attributes.end(),
- Memory::PageType::Unmapped);
+ Common::PageType::Unmapped);
Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
diff --git a/src/tests/core/arm/arm_test_common.h b/src/tests/core/arm/arm_test_common.h
index 0b7539601..d145dbfcc 100644
--- a/src/tests/core/arm/arm_test_common.h
+++ b/src/tests/core/arm/arm_test_common.h
@@ -9,10 +9,10 @@
#include <vector>
#include "common/common_types.h"
+#include "common/memory_hook.h"
#include "core/hle/kernel/kernel.h"
-#include "core/memory_hook.h"
-namespace Memory {
+namespace Common {
struct PageTable;
}
@@ -58,7 +58,7 @@ public:
private:
friend struct TestMemory;
- struct TestMemory final : Memory::MemoryHook {
+ struct TestMemory final : Common::MemoryHook {
explicit TestMemory(TestEnvironment* env_) : env(env_) {}
TestEnvironment* env;
@@ -86,7 +86,7 @@ private:
bool mutable_memory;
std::shared_ptr<TestMemory> test_memory;
std::vector<WriteRecord> write_records;
- Memory::PageTable* page_table = nullptr;
+ Common::PageTable* page_table = nullptr;
Kernel::KernelCore kernel;
};
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 0c3038c52..14b76680f 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -123,6 +123,8 @@ if (ENABLE_VULKAN)
renderer_vulkan/vk_memory_manager.h
renderer_vulkan/vk_resource_manager.cpp
renderer_vulkan/vk_resource_manager.h
+ renderer_vulkan/vk_sampler_cache.cpp
+ renderer_vulkan/vk_sampler_cache.h
renderer_vulkan/vk_scheduler.cpp
renderer_vulkan/vk_scheduler.h
renderer_vulkan/vk_stream_buffer.cpp
diff --git a/src/video_core/dma_pusher.cpp b/src/video_core/dma_pusher.cpp
index bff1a37ff..8b1bea1ae 100644
--- a/src/video_core/dma_pusher.cpp
+++ b/src/video_core/dma_pusher.cpp
@@ -55,12 +55,9 @@ bool DmaPusher::Step() {
}
// Push buffer non-empty, read a word
- const auto address = gpu.MemoryManager().GpuToCpuAddress(dma_get);
- ASSERT_MSG(address, "Invalid GPU address");
-
command_headers.resize(command_list_header.size);
-
- Memory::ReadBlock(*address, command_headers.data(), command_list_header.size * sizeof(u32));
+ gpu.MemoryManager().ReadBlock(dma_get, command_headers.data(),
+ command_list_header.size * sizeof(u32));
for (const CommandHeader& command_header : command_headers) {
diff --git a/src/video_core/dma_pusher.h b/src/video_core/dma_pusher.h
index 27a36348c..6ab06518f 100644
--- a/src/video_core/dma_pusher.h
+++ b/src/video_core/dma_pusher.h
@@ -9,7 +9,6 @@
#include "common/bit_field.h"
#include "common/common_types.h"
-#include "video_core/memory_manager.h"
namespace Tegra {
diff --git a/src/video_core/engines/kepler_memory.cpp b/src/video_core/engines/kepler_memory.cpp
index aae2a4019..e259bf46b 100644
--- a/src/video_core/engines/kepler_memory.cpp
+++ b/src/video_core/engines/kepler_memory.cpp
@@ -9,6 +9,7 @@
#include "video_core/engines/kepler_memory.h"
#include "video_core/engines/maxwell_3d.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
namespace Tegra::Engines {
@@ -40,17 +41,13 @@ void KeplerMemory::ProcessData(u32 data) {
ASSERT_MSG(regs.exec.linear, "Non-linear uploads are not supported");
ASSERT(regs.dest.x == 0 && regs.dest.y == 0 && regs.dest.z == 0);
- const GPUVAddr address = regs.dest.Address();
- const auto dest_address =
- memory_manager.GpuToCpuAddress(address + state.write_offset * sizeof(u32));
- ASSERT_MSG(dest_address, "Invalid GPU address");
-
// We have to invalidate the destination region to evict any outdated surfaces from the cache.
- // We do this before actually writing the new data because the destination address might contain
- // a dirty surface that will have to be written back to memory.
- Core::System::GetInstance().GPU().InvalidateRegion(*dest_address, sizeof(u32));
+ // We do this before actually writing the new data because the destination address might
+ // contain a dirty surface that will have to be written back to memory.
+ const GPUVAddr address{regs.dest.Address() + state.write_offset * sizeof(u32)};
+ rasterizer.InvalidateRegion(ToCacheAddr(memory_manager.GetPointer(address)), sizeof(u32));
+ memory_manager.Write<u32>(address, data);
- Memory::Write32(*dest_address, data);
system.GPU().Maxwell3D().dirty_flags.OnMemoryWrite();
state.write_offset++;
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index 144e7fa82..defcfbd3f 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -270,11 +270,9 @@ void Maxwell3D::ProcessMacroBind(u32 data) {
}
void Maxwell3D::ProcessQueryGet() {
- GPUVAddr sequence_address = regs.query.QueryAddress();
+ const GPUVAddr sequence_address{regs.query.QueryAddress()};
// Since the sequence address is given as a GPU VAddr, we have to convert it to an application
// VAddr before writing.
- const auto address = memory_manager.GpuToCpuAddress(sequence_address);
- ASSERT_MSG(address, "Invalid GPU address");
// TODO(Subv): Support the other query units.
ASSERT_MSG(regs.query.query_get.unit == Regs::QueryUnit::Crop,
@@ -309,7 +307,7 @@ void Maxwell3D::ProcessQueryGet() {
// Write the current query sequence to the sequence address.
// TODO(Subv): Find out what happens if you use a long query type but mark it as a short
// query.
- Memory::Write32(*address, sequence);
+ memory_manager.Write<u32>(sequence_address, sequence);
} else {
// Write the 128-bit result structure in long mode. Note: We emulate an infinitely fast
// GPU, this command may actually take a while to complete in real hardware due to GPU
@@ -318,7 +316,7 @@ void Maxwell3D::ProcessQueryGet() {
query_result.value = result;
// TODO(Subv): Generate a real GPU timestamp and write it here instead of CoreTiming
query_result.timestamp = system.CoreTiming().GetTicks();
- Memory::WriteBlock(*address, &query_result, sizeof(query_result));
+ memory_manager.WriteBlock(sequence_address, &query_result, sizeof(query_result));
}
dirty_flags.OnMemoryWrite();
break;
@@ -393,10 +391,12 @@ void Maxwell3D::ProcessCBData(u32 value) {
// Don't allow writing past the end of the buffer.
ASSERT(regs.const_buffer.cb_pos + sizeof(u32) <= regs.const_buffer.cb_size);
- const auto address = memory_manager.GpuToCpuAddress(buffer_address + regs.const_buffer.cb_pos);
- ASSERT_MSG(address, "Invalid GPU address");
+ const GPUVAddr address{buffer_address + regs.const_buffer.cb_pos};
+
+ u8* ptr{memory_manager.GetPointer(address)};
+ rasterizer.InvalidateRegion(ToCacheAddr(ptr), sizeof(u32));
+ memory_manager.Write<u32>(address, value);
- Memory::Write32(*address, value);
dirty_flags.OnMemoryWrite();
// Increment the current buffer position.
@@ -404,14 +404,10 @@ void Maxwell3D::ProcessCBData(u32 value) {
}
Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
- const GPUVAddr tic_base_address = regs.tic.TICAddress();
-
- const GPUVAddr tic_address_gpu = tic_base_address + tic_index * sizeof(Texture::TICEntry);
- const auto tic_address_cpu = memory_manager.GpuToCpuAddress(tic_address_gpu);
- ASSERT_MSG(tic_address_cpu, "Invalid GPU address");
+ const GPUVAddr tic_address_gpu{regs.tic.TICAddress() + tic_index * sizeof(Texture::TICEntry)};
Texture::TICEntry tic_entry;
- Memory::ReadBlock(*tic_address_cpu, &tic_entry, sizeof(Texture::TICEntry));
+ memory_manager.ReadBlock(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
ASSERT_MSG(tic_entry.header_version == Texture::TICHeaderVersion::BlockLinear ||
tic_entry.header_version == Texture::TICHeaderVersion::Pitch,
@@ -429,14 +425,10 @@ Texture::TICEntry Maxwell3D::GetTICEntry(u32 tic_index) const {
}
Texture::TSCEntry Maxwell3D::GetTSCEntry(u32 tsc_index) const {
- const GPUVAddr tsc_base_address = regs.tsc.TSCAddress();
-
- const GPUVAddr tsc_address_gpu = tsc_base_address + tsc_index * sizeof(Texture::TSCEntry);
- const auto tsc_address_cpu = memory_manager.GpuToCpuAddress(tsc_address_gpu);
- ASSERT_MSG(tsc_address_cpu, "Invalid GPU address");
+ const GPUVAddr tsc_address_gpu{regs.tsc.TSCAddress() + tsc_index * sizeof(Texture::TSCEntry)};
Texture::TSCEntry tsc_entry;
- Memory::ReadBlock(*tsc_address_cpu, &tsc_entry, sizeof(Texture::TSCEntry));
+ memory_manager.ReadBlock(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
return tsc_entry;
}
@@ -455,10 +447,7 @@ std::vector<Texture::FullTextureInfo> Maxwell3D::GetStageTextures(Regs::ShaderSt
for (GPUVAddr current_texture = tex_info_buffer.address + TextureInfoOffset;
current_texture < tex_info_buffer_end; current_texture += sizeof(Texture::TextureHandle)) {
- const auto address = memory_manager.GpuToCpuAddress(current_texture);
- ASSERT_MSG(address, "Invalid GPU address");
-
- const Texture::TextureHandle tex_handle{Memory::Read32(*address)};
+ const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(current_texture)};
Texture::FullTextureInfo tex_info{};
// TODO(Subv): Use the shader to determine which textures are actually accessed.
@@ -493,10 +482,7 @@ Texture::FullTextureInfo Maxwell3D::GetStageTexture(Regs::ShaderStage stage,
ASSERT(tex_info_address < tex_info_buffer.address + tex_info_buffer.size);
- const auto tex_address_cpu = memory_manager.GpuToCpuAddress(tex_info_address);
- ASSERT_MSG(tex_address_cpu, "Invalid GPU address");
-
- const Texture::TextureHandle tex_handle{Memory::Read32(*tex_address_cpu)};
+ const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(tex_info_address)};
Texture::FullTextureInfo tex_info{};
tex_info.index = static_cast<u32>(offset);
diff --git a/src/video_core/engines/maxwell_dma.cpp b/src/video_core/engines/maxwell_dma.cpp
index 9dfea5999..5cca5c29a 100644
--- a/src/video_core/engines/maxwell_dma.cpp
+++ b/src/video_core/engines/maxwell_dma.cpp
@@ -9,6 +9,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
#include "video_core/textures/decoders.h"
namespace Tegra::Engines {
@@ -42,11 +43,6 @@ void MaxwellDMA::HandleCopy() {
const GPUVAddr source = regs.src_address.Address();
const GPUVAddr dest = regs.dst_address.Address();
- const auto source_cpu = memory_manager.GpuToCpuAddress(source);
- const auto dest_cpu = memory_manager.GpuToCpuAddress(dest);
- ASSERT_MSG(source_cpu, "Invalid source GPU address");
- ASSERT_MSG(dest_cpu, "Invalid destination GPU address");
-
// TODO(Subv): Perform more research and implement all features of this engine.
ASSERT(regs.exec.enable_swizzle == 0);
ASSERT(regs.exec.query_mode == Regs::QueryMode::None);
@@ -69,7 +65,7 @@ void MaxwellDMA::HandleCopy() {
// buffer of length `x_count`, otherwise we copy a 2D image of dimensions (x_count,
// y_count).
if (!regs.exec.enable_2d) {
- Memory::CopyBlock(*dest_cpu, *source_cpu, regs.x_count);
+ memory_manager.CopyBlock(dest, source, regs.x_count);
return;
}
@@ -78,9 +74,9 @@ void MaxwellDMA::HandleCopy() {
// rectangle. There is no need to manually flush/invalidate the regions because
// CopyBlock does that for us.
for (u32 line = 0; line < regs.y_count; ++line) {
- const VAddr source_line = *source_cpu + line * regs.src_pitch;
- const VAddr dest_line = *dest_cpu + line * regs.dst_pitch;
- Memory::CopyBlock(dest_line, source_line, regs.x_count);
+ const GPUVAddr source_line = source + line * regs.src_pitch;
+ const GPUVAddr dest_line = dest + line * regs.dst_pitch;
+ memory_manager.CopyBlock(dest_line, source_line, regs.x_count);
}
return;
}
@@ -89,15 +85,28 @@ void MaxwellDMA::HandleCopy() {
const std::size_t copy_size = regs.x_count * regs.y_count;
+ auto source_ptr{memory_manager.GetPointer(source)};
+ auto dst_ptr{memory_manager.GetPointer(dest)};
+
+ if (!source_ptr) {
+ LOG_ERROR(HW_GPU, "source_ptr is invalid");
+ return;
+ }
+
+ if (!dst_ptr) {
+ LOG_ERROR(HW_GPU, "dst_ptr is invalid");
+ return;
+ }
+
const auto FlushAndInvalidate = [&](u32 src_size, u64 dst_size) {
// TODO(Subv): For now, manually flush the regions until we implement GPU-accelerated
// copying.
- Core::System::GetInstance().GPU().FlushRegion(*source_cpu, src_size);
+ rasterizer.FlushRegion(ToCacheAddr(source_ptr), src_size);
// We have to invalidate the destination region to evict any outdated surfaces from the
// cache. We do this before actually writing the new data because the destination address
// might contain a dirty surface that will have to be written back to memory.
- Core::System::GetInstance().GPU().InvalidateRegion(*dest_cpu, dst_size);
+ rasterizer.InvalidateRegion(ToCacheAddr(dst_ptr), dst_size);
};
if (regs.exec.is_dst_linear && !regs.exec.is_src_linear) {
@@ -110,8 +119,8 @@ void MaxwellDMA::HandleCopy() {
copy_size * src_bytes_per_pixel);
Texture::UnswizzleSubrect(regs.x_count, regs.y_count, regs.dst_pitch,
- regs.src_params.size_x, src_bytes_per_pixel, *source_cpu,
- *dest_cpu, regs.src_params.BlockHeight(), regs.src_params.pos_x,
+ regs.src_params.size_x, src_bytes_per_pixel, source_ptr, dst_ptr,
+ regs.src_params.BlockHeight(), regs.src_params.pos_x,
regs.src_params.pos_y);
} else {
ASSERT(regs.dst_params.size_z == 1);
@@ -124,7 +133,7 @@ void MaxwellDMA::HandleCopy() {
// If the input is linear and the output is tiled, swizzle the input and copy it over.
Texture::SwizzleSubrect(regs.x_count, regs.y_count, regs.src_pitch, regs.dst_params.size_x,
- src_bpp, *dest_cpu, *source_cpu, regs.dst_params.BlockHeight());
+ src_bpp, dst_ptr, source_ptr, regs.dst_params.BlockHeight());
}
}
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index 08abf8ac9..267a03f2d 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -12,6 +12,7 @@
#include "video_core/engines/maxwell_3d.h"
#include "video_core/engines/maxwell_dma.h"
#include "video_core/gpu.h"
+#include "video_core/memory_manager.h"
#include "video_core/renderer_base.h"
namespace Tegra {
@@ -274,7 +275,6 @@ void GPU::ProcessSemaphoreTriggerMethod() {
const auto op =
static_cast<GpuSemaphoreOperation>(regs.semaphore_trigger & semaphoreOperationMask);
if (op == GpuSemaphoreOperation::WriteLong) {
- auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
struct Block {
u32 sequence;
u32 zeros = 0;
@@ -286,11 +286,9 @@ void GPU::ProcessSemaphoreTriggerMethod() {
// TODO(Kmather73): Generate a real GPU timestamp and write it here instead of
// CoreTiming
block.timestamp = Core::System::GetInstance().CoreTiming().GetTicks();
- Memory::WriteBlock(*address, &block, sizeof(block));
+ memory_manager->WriteBlock(regs.smaphore_address.SmaphoreAddress(), &block, sizeof(block));
} else {
- const auto address =
- memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
- const u32 word = Memory::Read32(*address);
+ const u32 word{memory_manager->Read<u32>(regs.smaphore_address.SmaphoreAddress())};
if ((op == GpuSemaphoreOperation::AcquireEqual && word == regs.semaphore_sequence) ||
(op == GpuSemaphoreOperation::AcquireGequal &&
static_cast<s32>(word - regs.semaphore_sequence) > 0) ||
@@ -317,13 +315,11 @@ void GPU::ProcessSemaphoreTriggerMethod() {
}
void GPU::ProcessSemaphoreRelease() {
- const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
- Memory::Write32(*address, regs.semaphore_release);
+ memory_manager->Write<u32>(regs.smaphore_address.SmaphoreAddress(), regs.semaphore_release);
}
void GPU::ProcessSemaphoreAcquire() {
- const auto address = memory_manager->GpuToCpuAddress(regs.smaphore_address.SmaphoreAddress());
- const u32 word = Memory::Read32(*address);
+ const u32 word = memory_manager->Read<u32>(regs.smaphore_address.SmaphoreAddress());
const auto value = regs.semaphore_acquire;
if (word != value) {
regs.acquire_active = true;
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index 56a203275..c1830ac8d 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -9,7 +9,11 @@
#include "common/common_types.h"
#include "core/hle/service/nvflinger/buffer_queue.h"
#include "video_core/dma_pusher.h"
-#include "video_core/memory_manager.h"
+
+using CacheAddr = std::uintptr_t;
+inline CacheAddr ToCacheAddr(const void* host_ptr) {
+ return reinterpret_cast<CacheAddr>(host_ptr);
+}
namespace Core {
class System;
@@ -119,6 +123,8 @@ enum class EngineID {
MAXWELL_DMA_COPY_A = 0xB0B5,
};
+class MemoryManager;
+
class GPU {
public:
explicit GPU(Core::System& system, VideoCore::RendererBase& renderer);
@@ -209,13 +215,13 @@ public:
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
- virtual void FlushRegion(VAddr addr, u64 size) = 0;
+ virtual void FlushRegion(CacheAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be invalidated
- virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
+ virtual void InvalidateRegion(CacheAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
- virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
+ virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
private:
void ProcessBindMethod(const MethodCall& method_call);
@@ -239,9 +245,8 @@ protected:
private:
std::unique_ptr<Tegra::MemoryManager> memory_manager;
- /// Mapping of command subchannels to their bound engine ids.
+ /// Mapping of command subchannels to their bound engine ids
std::array<EngineID, 8> bound_engines = {};
-
/// 3D engine
std::unique_ptr<Engines::Maxwell3D> maxwell_3d;
/// 2D engine
diff --git a/src/video_core/gpu_asynch.cpp b/src/video_core/gpu_asynch.cpp
index ad0a747e3..8b355cf7b 100644
--- a/src/video_core/gpu_asynch.cpp
+++ b/src/video_core/gpu_asynch.cpp
@@ -22,15 +22,15 @@ void GPUAsynch::SwapBuffers(
gpu_thread.SwapBuffers(std::move(framebuffer));
}
-void GPUAsynch::FlushRegion(VAddr addr, u64 size) {
+void GPUAsynch::FlushRegion(CacheAddr addr, u64 size) {
gpu_thread.FlushRegion(addr, size);
}
-void GPUAsynch::InvalidateRegion(VAddr addr, u64 size) {
+void GPUAsynch::InvalidateRegion(CacheAddr addr, u64 size) {
gpu_thread.InvalidateRegion(addr, size);
}
-void GPUAsynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void GPUAsynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
gpu_thread.FlushAndInvalidateRegion(addr, size);
}
diff --git a/src/video_core/gpu_asynch.h b/src/video_core/gpu_asynch.h
index e6a807aba..1dcc61a6c 100644
--- a/src/video_core/gpu_asynch.h
+++ b/src/video_core/gpu_asynch.h
@@ -26,9 +26,9 @@ public:
void PushGPUEntries(Tegra::CommandList&& entries) override;
void SwapBuffers(
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
- void FlushRegion(VAddr addr, u64 size) override;
- void InvalidateRegion(VAddr addr, u64 size) override;
- void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
+ void FlushRegion(CacheAddr addr, u64 size) override;
+ void InvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
private:
GPUThread::ThreadManager gpu_thread;
diff --git a/src/video_core/gpu_synch.cpp b/src/video_core/gpu_synch.cpp
index 4c00b96c7..2cfc900ed 100644
--- a/src/video_core/gpu_synch.cpp
+++ b/src/video_core/gpu_synch.cpp
@@ -22,15 +22,15 @@ void GPUSynch::SwapBuffers(
renderer.SwapBuffers(std::move(framebuffer));
}
-void GPUSynch::FlushRegion(VAddr addr, u64 size) {
+void GPUSynch::FlushRegion(CacheAddr addr, u64 size) {
renderer.Rasterizer().FlushRegion(addr, size);
}
-void GPUSynch::InvalidateRegion(VAddr addr, u64 size) {
+void GPUSynch::InvalidateRegion(CacheAddr addr, u64 size) {
renderer.Rasterizer().InvalidateRegion(addr, size);
}
-void GPUSynch::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void GPUSynch::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
renderer.Rasterizer().FlushAndInvalidateRegion(addr, size);
}
diff --git a/src/video_core/gpu_synch.h b/src/video_core/gpu_synch.h
index 7d5a241ff..766b5631c 100644
--- a/src/video_core/gpu_synch.h
+++ b/src/video_core/gpu_synch.h
@@ -21,9 +21,9 @@ public:
void PushGPUEntries(Tegra::CommandList&& entries) override;
void SwapBuffers(
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) override;
- void FlushRegion(VAddr addr, u64 size) override;
- void InvalidateRegion(VAddr addr, u64 size) override;
- void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
+ void FlushRegion(CacheAddr addr, u64 size) override;
+ void InvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
};
} // namespace VideoCommon
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index c5bdd2a17..086b2f625 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -5,7 +5,6 @@
#include "common/assert.h"
#include "common/microprofile.h"
#include "core/frontend/scope_acquire_window_context.h"
-#include "core/settings.h"
#include "video_core/dma_pusher.h"
#include "video_core/gpu.h"
#include "video_core/gpu_thread.h"
@@ -13,38 +12,13 @@
namespace VideoCommon::GPUThread {
-/// Executes a single GPU thread command
-static void ExecuteCommand(CommandData* command, VideoCore::RendererBase& renderer,
- Tegra::DmaPusher& dma_pusher) {
- if (const auto submit_list = std::get_if<SubmitListCommand>(command)) {
- dma_pusher.Push(std::move(submit_list->entries));
- dma_pusher.DispatchCalls();
- } else if (const auto data = std::get_if<SwapBuffersCommand>(command)) {
- renderer.SwapBuffers(data->framebuffer);
- } else if (const auto data = std::get_if<FlushRegionCommand>(command)) {
- renderer.Rasterizer().FlushRegion(data->addr, data->size);
- } else if (const auto data = std::get_if<InvalidateRegionCommand>(command)) {
- renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
- } else if (const auto data = std::get_if<FlushAndInvalidateRegionCommand>(command)) {
- renderer.Rasterizer().FlushAndInvalidateRegion(data->addr, data->size);
- } else {
- UNREACHABLE();
- }
-}
-
/// Runs the GPU thread
static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher,
SynchState& state) {
-
MicroProfileOnThreadCreate("GpuThread");
- auto WaitForWakeup = [&]() {
- std::unique_lock<std::mutex> lock{state.signal_mutex};
- state.signal_condition.wait(lock, [&] { return !state.is_idle || !state.is_running; });
- };
-
// Wait for first GPU command before acquiring the window context
- WaitForWakeup();
+ state.WaitForCommands();
// If emulation was stopped during disk shader loading, abort before trying to acquire context
if (!state.is_running) {
@@ -53,100 +27,72 @@ static void RunThread(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_p
Core::Frontend::ScopeAcquireWindowContext acquire_context{renderer.GetRenderWindow()};
+ CommandDataContainer next;
while (state.is_running) {
- if (!state.is_running) {
- return;
- }
-
- {
- // Thread has been woken up, so make the previous write queue the next read queue
- std::lock_guard<std::mutex> lock{state.signal_mutex};
- std::swap(state.push_queue, state.pop_queue);
- }
-
- // Execute all of the GPU commands
- while (!state.pop_queue->empty()) {
- ExecuteCommand(&state.pop_queue->front(), renderer, dma_pusher);
- state.pop_queue->pop();
+ state.WaitForCommands();
+ while (!state.queue.Empty()) {
+ state.queue.Pop(next);
+ if (const auto submit_list = std::get_if<SubmitListCommand>(&next.data)) {
+ dma_pusher.Push(std::move(submit_list->entries));
+ dma_pusher.DispatchCalls();
+ } else if (const auto data = std::get_if<SwapBuffersCommand>(&next.data)) {
+ state.DecrementFramesCounter();
+ renderer.SwapBuffers(std::move(data->framebuffer));
+ } else if (const auto data = std::get_if<FlushRegionCommand>(&next.data)) {
+ renderer.Rasterizer().FlushRegion(data->addr, data->size);
+ } else if (const auto data = std::get_if<InvalidateRegionCommand>(&next.data)) {
+ renderer.Rasterizer().InvalidateRegion(data->addr, data->size);
+ } else if (const auto data = std::get_if<EndProcessingCommand>(&next.data)) {
+ return;
+ } else {
+ UNREACHABLE();
+ }
}
-
- state.UpdateIdleState();
-
- // Signal that the GPU thread has finished processing commands
- if (state.is_idle) {
- state.idle_condition.notify_one();
- }
-
- // Wait for CPU thread to send more GPU commands
- WaitForWakeup();
}
}
ThreadManager::ThreadManager(VideoCore::RendererBase& renderer, Tegra::DmaPusher& dma_pusher)
: renderer{renderer}, dma_pusher{dma_pusher}, thread{RunThread, std::ref(renderer),
- std::ref(dma_pusher), std::ref(state)},
- thread_id{thread.get_id()} {}
+ std::ref(dma_pusher), std::ref(state)} {}
ThreadManager::~ThreadManager() {
- {
- // Notify GPU thread that a shutdown is pending
- std::lock_guard<std::mutex> lock{state.signal_mutex};
- state.is_running = false;
- }
-
- state.signal_condition.notify_one();
+ // Notify GPU thread that a shutdown is pending
+ PushCommand(EndProcessingCommand());
thread.join();
}
void ThreadManager::SubmitList(Tegra::CommandList&& entries) {
- if (entries.empty()) {
- return;
- }
-
- PushCommand(SubmitListCommand(std::move(entries)), false, false);
+ PushCommand(SubmitListCommand(std::move(entries)));
}
void ThreadManager::SwapBuffers(
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer) {
- PushCommand(SwapBuffersCommand(std::move(framebuffer)), true, false);
+ state.IncrementFramesCounter();
+ PushCommand(SwapBuffersCommand(std::move(framebuffer)));
+ state.WaitForFrames();
}
-void ThreadManager::FlushRegion(VAddr addr, u64 size) {
- // Block the CPU when using accurate emulation
- PushCommand(FlushRegionCommand(addr, size), Settings::values.use_accurate_gpu_emulation, false);
+void ThreadManager::FlushRegion(CacheAddr addr, u64 size) {
+ PushCommand(FlushRegionCommand(addr, size));
}
-void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
- PushCommand(InvalidateRegionCommand(addr, size), true, true);
+void ThreadManager::InvalidateRegion(CacheAddr addr, u64 size) {
+ if (state.queue.Empty()) {
+ // It's quicker to invalidate a single region on the CPU if the queue is already empty
+ renderer.Rasterizer().InvalidateRegion(addr, size);
+ } else {
+ PushCommand(InvalidateRegionCommand(addr, size));
+ }
}
-void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void ThreadManager::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
+ // Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
InvalidateRegion(addr, size);
}
-void ThreadManager::PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu) {
- {
- std::lock_guard<std::mutex> lock{state.signal_mutex};
-
- if ((allow_on_cpu && state.is_idle) || IsGpuThread()) {
- // Execute the command synchronously on the current thread
- ExecuteCommand(&command_data, renderer, dma_pusher);
- return;
- }
-
- // Push the command to the GPU thread
- state.UpdateIdleState();
- state.push_queue->emplace(command_data);
- }
-
- // Signal the GPU thread that commands are pending
- state.signal_condition.notify_one();
-
- if (wait_for_idle) {
- // Wait for the GPU to be idle (all commands to be executed)
- std::unique_lock<std::mutex> lock{state.idle_mutex};
- state.idle_condition.wait(lock, [this] { return static_cast<bool>(state.is_idle); });
- }
+void ThreadManager::PushCommand(CommandData&& command_data) {
+ state.queue.Push(CommandDataContainer(std::move(command_data)));
+ state.SignalCommands();
}
} // namespace VideoCommon::GPUThread
diff --git a/src/video_core/gpu_thread.h b/src/video_core/gpu_thread.h
index edb148b14..8cd7db1c6 100644
--- a/src/video_core/gpu_thread.h
+++ b/src/video_core/gpu_thread.h
@@ -13,6 +13,9 @@
#include <thread>
#include <variant>
+#include "common/threadsafe_queue.h"
+#include "video_core/gpu.h"
+
namespace Tegra {
struct FramebufferConfig;
class DmaPusher;
@@ -24,6 +27,9 @@ class RendererBase;
namespace VideoCommon::GPUThread {
+/// Command to signal to the GPU thread that processing has ended
+struct EndProcessingCommand final {};
+
/// Command to signal to the GPU thread that a command list is ready for processing
struct SubmitListCommand final {
explicit SubmitListCommand(Tegra::CommandList&& entries) : entries{std::move(entries)} {}
@@ -36,59 +42,110 @@ struct SwapBuffersCommand final {
explicit SwapBuffersCommand(std::optional<const Tegra::FramebufferConfig> framebuffer)
: framebuffer{std::move(framebuffer)} {}
- std::optional<const Tegra::FramebufferConfig> framebuffer;
+ std::optional<Tegra::FramebufferConfig> framebuffer;
};
/// Command to signal to the GPU thread to flush a region
struct FlushRegionCommand final {
- explicit constexpr FlushRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {}
+ explicit constexpr FlushRegionCommand(CacheAddr addr, u64 size) : addr{addr}, size{size} {}
- const VAddr addr;
- const u64 size;
+ CacheAddr addr;
+ u64 size;
};
/// Command to signal to the GPU thread to invalidate a region
struct InvalidateRegionCommand final {
- explicit constexpr InvalidateRegionCommand(VAddr addr, u64 size) : addr{addr}, size{size} {}
+ explicit constexpr InvalidateRegionCommand(CacheAddr addr, u64 size) : addr{addr}, size{size} {}
- const VAddr addr;
- const u64 size;
+ CacheAddr addr;
+ u64 size;
};
/// Command to signal to the GPU thread to flush and invalidate a region
struct FlushAndInvalidateRegionCommand final {
- explicit constexpr FlushAndInvalidateRegionCommand(VAddr addr, u64 size)
+ explicit constexpr FlushAndInvalidateRegionCommand(CacheAddr addr, u64 size)
: addr{addr}, size{size} {}
- const VAddr addr;
- const u64 size;
+ CacheAddr addr;
+ u64 size;
};
-using CommandData = std::variant<SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
- InvalidateRegionCommand, FlushAndInvalidateRegionCommand>;
+using CommandData =
+ std::variant<EndProcessingCommand, SubmitListCommand, SwapBuffersCommand, FlushRegionCommand,
+ InvalidateRegionCommand, FlushAndInvalidateRegionCommand>;
+
+struct CommandDataContainer {
+ CommandDataContainer() = default;
+
+ CommandDataContainer(CommandData&& data) : data{std::move(data)} {}
+
+ CommandDataContainer& operator=(const CommandDataContainer& t) {
+ data = std::move(t.data);
+ return *this;
+ }
+
+ CommandData data;
+};
/// Struct used to synchronize the GPU thread
struct SynchState final {
- std::atomic<bool> is_running{true};
- std::atomic<bool> is_idle{true};
- std::condition_variable signal_condition;
- std::mutex signal_mutex;
- std::condition_variable idle_condition;
- std::mutex idle_mutex;
-
- // We use two queues for sending commands to the GPU thread, one for writing (push_queue) to and
- // one for reading from (pop_queue). These are swapped whenever the current pop_queue becomes
- // empty. This allows for efficient thread-safe access, as it does not require any copies.
-
- using CommandQueue = std::queue<CommandData>;
- std::array<CommandQueue, 2> command_queues;
- CommandQueue* push_queue{&command_queues[0]};
- CommandQueue* pop_queue{&command_queues[1]};
-
- void UpdateIdleState() {
- std::lock_guard<std::mutex> lock{idle_mutex};
- is_idle = command_queues[0].empty() && command_queues[1].empty();
+ std::atomic_bool is_running{true};
+ std::atomic_int queued_frame_count{};
+ std::mutex frames_mutex;
+ std::mutex commands_mutex;
+ std::condition_variable commands_condition;
+ std::condition_variable frames_condition;
+
+ void IncrementFramesCounter() {
+ std::lock_guard<std::mutex> lock{frames_mutex};
+ ++queued_frame_count;
+ }
+
+ void DecrementFramesCounter() {
+ {
+ std::lock_guard<std::mutex> lock{frames_mutex};
+ --queued_frame_count;
+
+ if (queued_frame_count) {
+ return;
+ }
+ }
+ frames_condition.notify_one();
}
+
+ void WaitForFrames() {
+ {
+ std::lock_guard<std::mutex> lock{frames_mutex};
+ if (!queued_frame_count) {
+ return;
+ }
+ }
+
+ // Wait for the GPU to be idle (all commands to be executed)
+ {
+ std::unique_lock<std::mutex> lock{frames_mutex};
+ frames_condition.wait(lock, [this] { return !queued_frame_count; });
+ }
+ }
+
+ void SignalCommands() {
+ {
+ std::unique_lock<std::mutex> lock{commands_mutex};
+ if (queue.Empty()) {
+ return;
+ }
+ }
+
+ commands_condition.notify_one();
+ }
+
+ void WaitForCommands() {
+ std::unique_lock<std::mutex> lock{commands_mutex};
+ commands_condition.wait(lock, [this] { return !queue.Empty(); });
+ }
+
+ using CommandQueue = Common::SPSCQueue<CommandDataContainer>;
+ CommandQueue queue;
};
/// Class used to manage the GPU thread
@@ -105,22 +162,17 @@ public:
std::optional<std::reference_wrapper<const Tegra::FramebufferConfig>> framebuffer);
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
- void FlushRegion(VAddr addr, u64 size);
+ void FlushRegion(CacheAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be invalidated
- void InvalidateRegion(VAddr addr, u64 size);
+ void InvalidateRegion(CacheAddr addr, u64 size);
/// Notify rasterizer that any caches of the specified region should be flushed and invalidated
- void FlushAndInvalidateRegion(VAddr addr, u64 size);
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size);
private:
/// Pushes a command to be executed by the GPU thread
- void PushCommand(CommandData&& command_data, bool wait_for_idle, bool allow_on_cpu);
-
- /// Returns true if this is called by the GPU thread
- bool IsGpuThread() const {
- return std::this_thread::get_id() == thread_id;
- }
+ void PushCommand(CommandData&& command_data);
private:
SynchState state;
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp
index 54abe5298..e76b59842 100644
--- a/src/video_core/memory_manager.cpp
+++ b/src/video_core/memory_manager.cpp
@@ -5,181 +5,446 @@
#include "common/alignment.h"
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/core.h"
+#include "core/memory.h"
+#include "video_core/gpu.h"
#include "video_core/memory_manager.h"
+#include "video_core/rasterizer_interface.h"
+#include "video_core/renderer_base.h"
namespace Tegra {
MemoryManager::MemoryManager() {
- // Mark the first page as reserved, so that 0 is not a valid GPUVAddr. Otherwise, games might
- // try to use 0 as a valid address, which is also used to mean nullptr. This fixes a bug with
- // Undertale using 0 for a render target.
- PageSlot(0) = static_cast<u64>(PageStatus::Reserved);
+ std::fill(page_table.pointers.begin(), page_table.pointers.end(), nullptr);
+ std::fill(page_table.attributes.begin(), page_table.attributes.end(),
+ Common::PageType::Unmapped);
+ page_table.Resize(address_space_width);
+
+ // Initialize the map with a single free region covering the entire managed space.
+ VirtualMemoryArea initial_vma;
+ initial_vma.size = address_space_end;
+ vma_map.emplace(initial_vma.base, initial_vma);
+
+ UpdatePageTableForVMA(initial_vma);
}
GPUVAddr MemoryManager::AllocateSpace(u64 size, u64 align) {
- const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, align, PageStatus::Unmapped)};
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+ const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
- ASSERT_MSG(gpu_addr, "unable to find available GPU memory");
+ AllocateMemory(gpu_addr, 0, aligned_size);
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(*gpu_addr + offset)};
+ return gpu_addr;
+}
- ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
+GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
- slot = static_cast<u64>(PageStatus::Allocated);
- }
+ AllocateMemory(gpu_addr, 0, aligned_size);
- return *gpu_addr;
+ return gpu_addr;
}
-GPUVAddr MemoryManager::AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align) {
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(gpu_addr + offset)};
+GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+ const GPUVAddr gpu_addr{FindFreeRegion(address_space_base, aligned_size)};
- ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
+ MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr);
- slot = static_cast<u64>(PageStatus::Allocated);
- }
+ return gpu_addr;
+}
+
+GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
+ ASSERT((gpu_addr & page_mask) == 0);
+
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+
+ MapBackingMemory(gpu_addr, Memory::GetPointer(cpu_addr), aligned_size, cpu_addr);
return gpu_addr;
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, u64 size) {
- const std::optional<GPUVAddr> gpu_addr{FindFreeBlock(0, size, PAGE_SIZE, PageStatus::Unmapped)};
+GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
+ ASSERT((gpu_addr & page_mask) == 0);
- ASSERT_MSG(gpu_addr, "unable to find available GPU memory");
+ const u64 aligned_size{Common::AlignUp(size, page_size)};
+ const CacheAddr cache_addr{ToCacheAddr(GetPointer(gpu_addr))};
+
+ Core::System::GetInstance().Renderer().Rasterizer().FlushAndInvalidateRegion(cache_addr,
+ aligned_size);
+ UnmapRange(gpu_addr, aligned_size);
+
+ return gpu_addr;
+}
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(*gpu_addr + offset)};
+GPUVAddr MemoryManager::FindFreeRegion(GPUVAddr region_start, u64 size) {
+ // Find the first Free VMA.
+ const VMAHandle vma_handle{std::find_if(vma_map.begin(), vma_map.end(), [&](const auto& vma) {
+ if (vma.second.type != VirtualMemoryArea::Type::Unmapped) {
+ return false;
+ }
- ASSERT(slot == static_cast<u64>(PageStatus::Unmapped));
+ const VAddr vma_end{vma.second.base + vma.second.size};
+ return vma_end > region_start && vma_end >= region_start + size;
+ })};
- slot = cpu_addr + offset;
+ if (vma_handle == vma_map.end()) {
+ return {};
}
- const MappedRegion region{cpu_addr, *gpu_addr, size};
- mapped_regions.push_back(region);
+ return std::max(region_start, vma_handle->second.base);
+}
- return *gpu_addr;
+bool MemoryManager::IsAddressValid(GPUVAddr addr) const {
+ return (addr >> page_bits) < page_table.pointers.size();
}
-GPUVAddr MemoryManager::MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & PAGE_MASK) == 0);
+std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr addr) {
+ if (!IsAddressValid(addr)) {
+ return {};
+ }
- if (PageSlot(gpu_addr) != static_cast<u64>(PageStatus::Allocated)) {
- // Page has been already mapped. In this case, we must find a new area of memory to use that
- // is different than the specified one. Super Mario Odyssey hits this scenario when changing
- // areas, but we do not want to overwrite the old pages.
- // TODO(bunnei): We need to write a hardware test to confirm this behavior.
+ VAddr cpu_addr{page_table.backing_addr[addr >> page_bits]};
+ if (cpu_addr) {
+ return cpu_addr + (addr & page_mask);
+ }
- LOG_ERROR(HW_GPU, "attempting to map addr 0x{:016X}, which is not available!", gpu_addr);
+ return {};
+}
- const std::optional<GPUVAddr> new_gpu_addr{
- FindFreeBlock(gpu_addr, size, PAGE_SIZE, PageStatus::Allocated)};
+template <typename T>
+T MemoryManager::Read(GPUVAddr addr) {
+ if (!IsAddressValid(addr)) {
+ return {};
+ }
- ASSERT_MSG(new_gpu_addr, "unable to find available GPU memory");
+ const u8* page_pointer{page_table.pointers[addr >> page_bits]};
+ if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ T value;
+ std::memcpy(&value, &page_pointer[addr & page_mask], sizeof(T));
+ return value;
+ }
- gpu_addr = *new_gpu_addr;
+ switch (page_table.attributes[addr >> page_bits]) {
+ case Common::PageType::Unmapped:
+ LOG_ERROR(HW_GPU, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, addr);
+ return 0;
+ case Common::PageType::Memory:
+ ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr);
+ break;
+ default:
+ UNREACHABLE();
}
+ return {};
+}
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(gpu_addr + offset)};
+template <typename T>
+void MemoryManager::Write(GPUVAddr addr, T data) {
+ if (!IsAddressValid(addr)) {
+ return;
+ }
- ASSERT(slot == static_cast<u64>(PageStatus::Allocated));
+ u8* page_pointer{page_table.pointers[addr >> page_bits]};
+ if (page_pointer) {
+ // NOTE: Avoid adding any extra logic to this fast-path block
+ std::memcpy(&page_pointer[addr & page_mask], &data, sizeof(T));
+ return;
+ }
- slot = cpu_addr + offset;
+ switch (page_table.attributes[addr >> page_bits]) {
+ case Common::PageType::Unmapped:
+ LOG_ERROR(HW_GPU, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
+ static_cast<u32>(data), addr);
+ return;
+ case Common::PageType::Memory:
+ ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", addr);
+ break;
+ default:
+ UNREACHABLE();
}
+}
- const MappedRegion region{cpu_addr, gpu_addr, size};
- mapped_regions.push_back(region);
+template u8 MemoryManager::Read<u8>(GPUVAddr addr);
+template u16 MemoryManager::Read<u16>(GPUVAddr addr);
+template u32 MemoryManager::Read<u32>(GPUVAddr addr);
+template u64 MemoryManager::Read<u64>(GPUVAddr addr);
+template void MemoryManager::Write<u8>(GPUVAddr addr, u8 data);
+template void MemoryManager::Write<u16>(GPUVAddr addr, u16 data);
+template void MemoryManager::Write<u32>(GPUVAddr addr, u32 data);
+template void MemoryManager::Write<u64>(GPUVAddr addr, u64 data);
+
+u8* MemoryManager::GetPointer(GPUVAddr addr) {
+ if (!IsAddressValid(addr)) {
+ return {};
+ }
- return gpu_addr;
+ u8* page_pointer{page_table.pointers[addr >> page_bits]};
+ if (page_pointer) {
+ return page_pointer + (addr & page_mask);
+ }
+
+ LOG_ERROR(HW_GPU, "Unknown GetPointer @ 0x{:016X}", addr);
+ return {};
}
-GPUVAddr MemoryManager::UnmapBuffer(GPUVAddr gpu_addr, u64 size) {
- ASSERT((gpu_addr & PAGE_MASK) == 0);
+void MemoryManager::ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size) {
+ std::memcpy(dest_buffer, GetPointer(src_addr), size);
+}
+void MemoryManager::WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size) {
+ std::memcpy(GetPointer(dest_addr), src_buffer, size);
+}
- for (u64 offset{}; offset < size; offset += PAGE_SIZE) {
- VAddr& slot{PageSlot(gpu_addr + offset)};
+void MemoryManager::CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size) {
+ std::memcpy(GetPointer(dest_addr), GetPointer(src_addr), size);
+}
- ASSERT(slot != static_cast<u64>(PageStatus::Allocated) &&
- slot != static_cast<u64>(PageStatus::Unmapped));
+void MemoryManager::MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
+ VAddr backing_addr) {
+ LOG_DEBUG(HW_GPU, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * page_size,
+ (base + size) * page_size);
+
+ const VAddr end{base + size};
+ ASSERT_MSG(end <= page_table.pointers.size(), "out of range mapping at {:016X}",
+ base + page_table.pointers.size());
+
+ std::fill(page_table.attributes.begin() + base, page_table.attributes.begin() + end, type);
+
+ if (memory == nullptr) {
+ std::fill(page_table.pointers.begin() + base, page_table.pointers.begin() + end, memory);
+ std::fill(page_table.backing_addr.begin() + base, page_table.backing_addr.begin() + end,
+ backing_addr);
+ } else {
+ while (base != end) {
+ page_table.pointers[base] = memory;
+ page_table.backing_addr[base] = backing_addr;
+
+ base += 1;
+ memory += page_size;
+ backing_addr += page_size;
+ }
+ }
+}
- slot = static_cast<u64>(PageStatus::Unmapped);
+void MemoryManager::MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr) {
+ ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size);
+ ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base);
+ MapPages(base / page_size, size / page_size, target, Common::PageType::Memory, backing_addr);
+}
+
+void MemoryManager::UnmapRegion(GPUVAddr base, u64 size) {
+ ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: {:016X}", size);
+ ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: {:016X}", base);
+ MapPages(base / page_size, size / page_size, nullptr, Common::PageType::Unmapped);
+}
+
+bool VirtualMemoryArea::CanBeMergedWith(const VirtualMemoryArea& next) const {
+ ASSERT(base + size == next.base);
+ if (type != next.type) {
+ return {};
+ }
+ if (type == VirtualMemoryArea::Type::Allocated && (offset + size != next.offset)) {
+ return {};
+ }
+ if (type == VirtualMemoryArea::Type::Mapped && backing_memory + size != next.backing_memory) {
+ return {};
+ }
+ return true;
+}
+
+MemoryManager::VMAHandle MemoryManager::FindVMA(GPUVAddr target) const {
+ if (target >= address_space_end) {
+ return vma_map.end();
+ } else {
+ return std::prev(vma_map.upper_bound(target));
}
+}
- // Delete the region mappings that are contained within the unmapped region
- mapped_regions.erase(std::remove_if(mapped_regions.begin(), mapped_regions.end(),
- [&](const MappedRegion& region) {
- return region.gpu_addr <= gpu_addr &&
- region.gpu_addr + region.size < gpu_addr + size;
- }),
- mapped_regions.end());
- return gpu_addr;
+MemoryManager::VMAIter MemoryManager::Allocate(VMAIter vma_handle) {
+ VirtualMemoryArea& vma{vma_handle->second};
+
+ vma.type = VirtualMemoryArea::Type::Allocated;
+ vma.backing_addr = 0;
+ vma.backing_memory = {};
+ UpdatePageTableForVMA(vma);
+
+ return MergeAdjacent(vma_handle);
}
-GPUVAddr MemoryManager::GetRegionEnd(GPUVAddr region_start) const {
- for (const auto& region : mapped_regions) {
- const GPUVAddr region_end{region.gpu_addr + region.size};
- if (region_start >= region.gpu_addr && region_start < region_end) {
- return region_end;
- }
+MemoryManager::VMAHandle MemoryManager::AllocateMemory(GPUVAddr target, std::size_t offset,
+ u64 size) {
+
+ // This is the appropriately sized VMA that will turn into our allocation.
+ VMAIter vma_handle{CarveVMA(target, size)};
+ VirtualMemoryArea& vma{vma_handle->second};
+
+ ASSERT(vma.size == size);
+
+ vma.offset = offset;
+
+ return Allocate(vma_handle);
+}
+
+MemoryManager::VMAHandle MemoryManager::MapBackingMemory(GPUVAddr target, u8* memory, u64 size,
+ VAddr backing_addr) {
+ // This is the appropriately sized VMA that will turn into our allocation.
+ VMAIter vma_handle{CarveVMA(target, size)};
+ VirtualMemoryArea& vma{vma_handle->second};
+
+ ASSERT(vma.size == size);
+
+ vma.type = VirtualMemoryArea::Type::Mapped;
+ vma.backing_memory = memory;
+ vma.backing_addr = backing_addr;
+ UpdatePageTableForVMA(vma);
+
+ return MergeAdjacent(vma_handle);
+}
+
+void MemoryManager::UnmapRange(GPUVAddr target, u64 size) {
+ VMAIter vma{CarveVMARange(target, size)};
+ const VAddr target_end{target + size};
+ const VMAIter end{vma_map.end()};
+
+ // The comparison against the end of the range must be done using addresses since VMAs can be
+ // merged during this process, causing invalidation of the iterators.
+ while (vma != end && vma->second.base < target_end) {
+ // Unmapped ranges return to allocated state and can be reused
+ // This behavior is used by Super Mario Odyssey, Sonic Forces, and likely other games
+ vma = std::next(Allocate(vma));
}
- return {};
+
+ ASSERT(FindVMA(target)->second.size >= size);
}
-std::optional<GPUVAddr> MemoryManager::FindFreeBlock(GPUVAddr region_start, u64 size, u64 align,
- PageStatus status) {
- GPUVAddr gpu_addr{region_start};
- u64 free_space{};
- align = (align + PAGE_MASK) & ~PAGE_MASK;
-
- while (gpu_addr + free_space < MAX_ADDRESS) {
- if (PageSlot(gpu_addr + free_space) == static_cast<u64>(status)) {
- free_space += PAGE_SIZE;
- if (free_space >= size) {
- return gpu_addr;
- }
- } else {
- gpu_addr += free_space + PAGE_SIZE;
- free_space = 0;
- gpu_addr = Common::AlignUp(gpu_addr, align);
- }
+MemoryManager::VMAIter MemoryManager::StripIterConstness(const VMAHandle& iter) {
+ // This uses a neat C++ trick to convert a const_iterator to a regular iterator, given
+ // non-const access to its container.
+ return vma_map.erase(iter, iter); // Erases an empty range of elements
+}
+
+MemoryManager::VMAIter MemoryManager::CarveVMA(GPUVAddr base, u64 size) {
+ ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
+ ASSERT_MSG((base & page_mask) == 0, "non-page aligned base: 0x{:016X}", base);
+
+ VMAIter vma_handle{StripIterConstness(FindVMA(base))};
+ if (vma_handle == vma_map.end()) {
+ // Target address is outside the managed range
+ return {};
}
- return {};
+ const VirtualMemoryArea& vma{vma_handle->second};
+ if (vma.type == VirtualMemoryArea::Type::Mapped) {
+ // Region is already allocated
+ return {};
+ }
+
+ const VAddr start_in_vma{base - vma.base};
+ const VAddr end_in_vma{start_in_vma + size};
+
+ ASSERT_MSG(end_in_vma <= vma.size, "region size 0x{:016X} is less than required size 0x{:016X}",
+ vma.size, end_in_vma);
+
+ if (end_in_vma < vma.size) {
+ // Split VMA at the end of the allocated region
+ SplitVMA(vma_handle, end_in_vma);
+ }
+ if (start_in_vma != 0) {
+ // Split VMA at the start of the allocated region
+ vma_handle = SplitVMA(vma_handle, start_in_vma);
+ }
+
+ return vma_handle;
}
-std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) {
- const VAddr base_addr{PageSlot(gpu_addr)};
+MemoryManager::VMAIter MemoryManager::CarveVMARange(GPUVAddr target, u64 size) {
+ ASSERT_MSG((size & page_mask) == 0, "non-page aligned size: 0x{:016X}", size);
+ ASSERT_MSG((target & page_mask) == 0, "non-page aligned base: 0x{:016X}", target);
- if (base_addr == static_cast<u64>(PageStatus::Allocated) ||
- base_addr == static_cast<u64>(PageStatus::Unmapped) ||
- base_addr == static_cast<u64>(PageStatus::Reserved)) {
+ const VAddr target_end{target + size};
+ ASSERT(target_end >= target);
+ ASSERT(size > 0);
+
+ VMAIter begin_vma{StripIterConstness(FindVMA(target))};
+ const VMAIter i_end{vma_map.lower_bound(target_end)};
+ if (std::any_of(begin_vma, i_end, [](const auto& entry) {
+ return entry.second.type == VirtualMemoryArea::Type::Unmapped;
+ })) {
return {};
}
- return base_addr + (gpu_addr & PAGE_MASK);
+ if (target != begin_vma->second.base) {
+ begin_vma = SplitVMA(begin_vma, target - begin_vma->second.base);
+ }
+
+ VMAIter end_vma{StripIterConstness(FindVMA(target_end))};
+ if (end_vma != vma_map.end() && target_end != end_vma->second.base) {
+ end_vma = SplitVMA(end_vma, target_end - end_vma->second.base);
+ }
+
+ return begin_vma;
}
-std::vector<GPUVAddr> MemoryManager::CpuToGpuAddress(VAddr cpu_addr) const {
- std::vector<GPUVAddr> results;
- for (const auto& region : mapped_regions) {
- if (cpu_addr >= region.cpu_addr && cpu_addr < (region.cpu_addr + region.size)) {
- const u64 offset{cpu_addr - region.cpu_addr};
- results.push_back(region.gpu_addr + offset);
+MemoryManager::VMAIter MemoryManager::SplitVMA(VMAIter vma_handle, u64 offset_in_vma) {
+ VirtualMemoryArea& old_vma{vma_handle->second};
+ VirtualMemoryArea new_vma{old_vma}; // Make a copy of the VMA
+
+ // For now, don't allow no-op VMA splits (trying to split at a boundary) because it's probably
+ // a bug. This restriction might be removed later.
+ ASSERT(offset_in_vma < old_vma.size);
+ ASSERT(offset_in_vma > 0);
+
+ old_vma.size = offset_in_vma;
+ new_vma.base += offset_in_vma;
+ new_vma.size -= offset_in_vma;
+
+ switch (new_vma.type) {
+ case VirtualMemoryArea::Type::Unmapped:
+ break;
+ case VirtualMemoryArea::Type::Allocated:
+ new_vma.offset += offset_in_vma;
+ break;
+ case VirtualMemoryArea::Type::Mapped:
+ new_vma.backing_memory += offset_in_vma;
+ break;
+ }
+
+ ASSERT(old_vma.CanBeMergedWith(new_vma));
+
+ return vma_map.emplace_hint(std::next(vma_handle), new_vma.base, new_vma);
+}
+
+MemoryManager::VMAIter MemoryManager::MergeAdjacent(VMAIter iter) {
+ const VMAIter next_vma{std::next(iter)};
+ if (next_vma != vma_map.end() && iter->second.CanBeMergedWith(next_vma->second)) {
+ iter->second.size += next_vma->second.size;
+ vma_map.erase(next_vma);
+ }
+
+ if (iter != vma_map.begin()) {
+ VMAIter prev_vma{std::prev(iter)};
+ if (prev_vma->second.CanBeMergedWith(iter->second)) {
+ prev_vma->second.size += iter->second.size;
+ vma_map.erase(iter);
+ iter = prev_vma;
}
}
- return results;
+
+ return iter;
}
-VAddr& MemoryManager::PageSlot(GPUVAddr gpu_addr) {
- auto& block{page_table[(gpu_addr >> (PAGE_BITS + PAGE_TABLE_BITS)) & PAGE_TABLE_MASK]};
- if (!block) {
- block = std::make_unique<PageBlock>();
- block->fill(static_cast<VAddr>(PageStatus::Unmapped));
+void MemoryManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) {
+ switch (vma.type) {
+ case VirtualMemoryArea::Type::Unmapped:
+ UnmapRegion(vma.base, vma.size);
+ break;
+ case VirtualMemoryArea::Type::Allocated:
+ MapMemoryRegion(vma.base, vma.size, nullptr, vma.backing_addr);
+ break;
+ case VirtualMemoryArea::Type::Mapped:
+ MapMemoryRegion(vma.base, vma.size, vma.backing_memory, vma.backing_addr);
+ break;
}
- return (*block)[(gpu_addr >> PAGE_BITS) & PAGE_BLOCK_MASK];
}
} // namespace Tegra
diff --git a/src/video_core/memory_manager.h b/src/video_core/memory_manager.h
index fb03497ca..34744bb27 100644
--- a/src/video_core/memory_manager.h
+++ b/src/video_core/memory_manager.h
@@ -1,67 +1,148 @@
-// Copyright 2018 yuzu emulator team
+// Copyright 2018 yuzu emulator team
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
-#include <array>
-#include <memory>
+#include <map>
#include <optional>
-#include <vector>
#include "common/common_types.h"
+#include "common/page_table.h"
namespace Tegra {
-/// Virtual addresses in the GPU's memory map are 64 bit.
-using GPUVAddr = u64;
+/**
+ * Represents a VMA in an address space. A VMA is a contiguous region of virtual addressing space
+ * with homogeneous attributes across its extents. In this particular implementation each VMA is
+ * also backed by a single host memory allocation.
+ */
+struct VirtualMemoryArea {
+ enum class Type : u8 {
+ Unmapped,
+ Allocated,
+ Mapped,
+ };
+
+ /// Virtual base address of the region.
+ GPUVAddr base{};
+ /// Size of the region.
+ u64 size{};
+ /// Memory area mapping type.
+ Type type{Type::Unmapped};
+ /// CPU memory mapped address corresponding to this memory area.
+ VAddr backing_addr{};
+ /// Offset into the backing_memory the mapping starts from.
+ std::size_t offset{};
+ /// Pointer backing this VMA.
+ u8* backing_memory{};
+
+ /// Tests if this area can be merged to the right with `next`.
+ bool CanBeMergedWith(const VirtualMemoryArea& next) const;
+};
class MemoryManager final {
public:
MemoryManager();
GPUVAddr AllocateSpace(u64 size, u64 align);
- GPUVAddr AllocateSpace(GPUVAddr gpu_addr, u64 size, u64 align);
+ GPUVAddr AllocateSpace(GPUVAddr addr, u64 size, u64 align);
GPUVAddr MapBufferEx(VAddr cpu_addr, u64 size);
- GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr gpu_addr, u64 size);
- GPUVAddr UnmapBuffer(GPUVAddr gpu_addr, u64 size);
- GPUVAddr GetRegionEnd(GPUVAddr region_start) const;
- std::optional<VAddr> GpuToCpuAddress(GPUVAddr gpu_addr);
- std::vector<GPUVAddr> CpuToGpuAddress(VAddr cpu_addr) const;
+ GPUVAddr MapBufferEx(VAddr cpu_addr, GPUVAddr addr, u64 size);
+ GPUVAddr UnmapBuffer(GPUVAddr addr, u64 size);
+ std::optional<VAddr> GpuToCpuAddress(GPUVAddr addr);
+
+ template <typename T>
+ T Read(GPUVAddr addr);
+
+ template <typename T>
+ void Write(GPUVAddr addr, T data);
- static constexpr u64 PAGE_BITS = 16;
- static constexpr u64 PAGE_SIZE = 1 << PAGE_BITS;
- static constexpr u64 PAGE_MASK = PAGE_SIZE - 1;
+ u8* GetPointer(GPUVAddr addr);
+
+ void ReadBlock(GPUVAddr src_addr, void* dest_buffer, std::size_t size);
+ void WriteBlock(GPUVAddr dest_addr, const void* src_buffer, std::size_t size);
+ void CopyBlock(GPUVAddr dest_addr, GPUVAddr src_addr, std::size_t size);
private:
- enum class PageStatus : u64 {
- Unmapped = 0xFFFFFFFFFFFFFFFFULL,
- Allocated = 0xFFFFFFFFFFFFFFFEULL,
- Reserved = 0xFFFFFFFFFFFFFFFDULL,
- };
+ using VMAMap = std::map<GPUVAddr, VirtualMemoryArea>;
+ using VMAHandle = VMAMap::const_iterator;
+ using VMAIter = VMAMap::iterator;
- std::optional<GPUVAddr> FindFreeBlock(GPUVAddr region_start, u64 size, u64 align,
- PageStatus status);
- VAddr& PageSlot(GPUVAddr gpu_addr);
-
- static constexpr u64 MAX_ADDRESS{0x10000000000ULL};
- static constexpr u64 PAGE_TABLE_BITS{10};
- static constexpr u64 PAGE_TABLE_SIZE{1 << PAGE_TABLE_BITS};
- static constexpr u64 PAGE_TABLE_MASK{PAGE_TABLE_SIZE - 1};
- static constexpr u64 PAGE_BLOCK_BITS{14};
- static constexpr u64 PAGE_BLOCK_SIZE{1 << PAGE_BLOCK_BITS};
- static constexpr u64 PAGE_BLOCK_MASK{PAGE_BLOCK_SIZE - 1};
-
- using PageBlock = std::array<VAddr, PAGE_BLOCK_SIZE>;
- std::array<std::unique_ptr<PageBlock>, PAGE_TABLE_SIZE> page_table{};
-
- struct MappedRegion {
- VAddr cpu_addr;
- GPUVAddr gpu_addr;
- u64 size;
- };
+ bool IsAddressValid(GPUVAddr addr) const;
+ void MapPages(GPUVAddr base, u64 size, u8* memory, Common::PageType type,
+ VAddr backing_addr = 0);
+ void MapMemoryRegion(GPUVAddr base, u64 size, u8* target, VAddr backing_addr);
+ void UnmapRegion(GPUVAddr base, u64 size);
+
+ /// Finds the VMA in which the given address is included in, or `vma_map.end()`.
+ VMAHandle FindVMA(GPUVAddr target) const;
+
+ VMAHandle AllocateMemory(GPUVAddr target, std::size_t offset, u64 size);
+
+ /**
+ * Maps an unmanaged host memory pointer at a given address.
+ *
+ * @param target The guest address to start the mapping at.
+ * @param memory The memory to be mapped.
+ * @param size Size of the mapping.
+ * @param state MemoryState tag to attach to the VMA.
+ */
+ VMAHandle MapBackingMemory(GPUVAddr target, u8* memory, u64 size, VAddr backing_addr);
+
+ /// Unmaps a range of addresses, splitting VMAs as necessary.
+ void UnmapRange(GPUVAddr target, u64 size);
+
+ /// Converts a VMAHandle to a mutable VMAIter.
+ VMAIter StripIterConstness(const VMAHandle& iter);
+
+ /// Marks as the specfied VMA as allocated.
+ VMAIter Allocate(VMAIter vma);
+
+ /**
+ * Carves a VMA of a specific size at the specified address by splitting Free VMAs while doing
+ * the appropriate error checking.
+ */
+ VMAIter CarveVMA(GPUVAddr base, u64 size);
+
+ /**
+ * Splits the edges of the given range of non-Free VMAs so that there is a VMA split at each
+ * end of the range.
+ */
+ VMAIter CarveVMARange(GPUVAddr base, u64 size);
+
+ /**
+ * Splits a VMA in two, at the specified offset.
+ * @returns the right side of the split, with the original iterator becoming the left side.
+ */
+ VMAIter SplitVMA(VMAIter vma, u64 offset_in_vma);
+
+ /**
+ * Checks for and merges the specified VMA with adjacent ones if possible.
+ * @returns the merged VMA or the original if no merging was possible.
+ */
+ VMAIter MergeAdjacent(VMAIter vma);
+
+ /// Updates the pages corresponding to this VMA so they match the VMA's attributes.
+ void UpdatePageTableForVMA(const VirtualMemoryArea& vma);
+
+ /// Finds a free (unmapped region) of the specified size starting at the specified address.
+ GPUVAddr FindFreeRegion(GPUVAddr region_start, u64 size);
+
+private:
+ static constexpr u64 page_bits{16};
+ static constexpr u64 page_size{1 << page_bits};
+ static constexpr u64 page_mask{page_size - 1};
+
+ /// Address space in bits, this is fairly arbitrary but sufficiently large.
+ static constexpr u32 address_space_width{39};
+ /// Start address for mapping, this is fairly arbitrary but must be non-zero.
+ static constexpr GPUVAddr address_space_base{0x100000};
+ /// End of address space, based on address space in bits.
+ static constexpr GPUVAddr address_space_end{1ULL << address_space_width};
- std::vector<MappedRegion> mapped_regions;
+ Common::PageTable page_table{page_bits};
+ VMAMap vma_map;
};
} // namespace Tegra
diff --git a/src/video_core/morton.cpp b/src/video_core/morton.cpp
index b68f4fb13..3e91cbc83 100644
--- a/src/video_core/morton.cpp
+++ b/src/video_core/morton.cpp
@@ -6,7 +6,6 @@
#include <cstring>
#include "common/assert.h"
#include "common/common_types.h"
-#include "core/memory.h"
#include "video_core/morton.h"
#include "video_core/surface.h"
#include "video_core/textures/decoders.h"
@@ -16,12 +15,12 @@ namespace VideoCore {
using Surface::GetBytesPerPixel;
using Surface::PixelFormat;
-using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, std::size_t, VAddr);
+using MortonCopyFn = void (*)(u32, u32, u32, u32, u32, u32, u8*, u8*);
using ConversionArray = std::array<MortonCopyFn, Surface::MaxPixelFormat>;
template <bool morton_to_linear, PixelFormat format>
static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth, u32 depth,
- u32 tile_width_spacing, u8* buffer, std::size_t buffer_size, VAddr addr) {
+ u32 tile_width_spacing, u8* buffer, u8* addr) {
constexpr u32 bytes_per_pixel = GetBytesPerPixel(format);
// With the BCn formats (DXT and DXN), each 4x4 tile is swizzled instead of just individual
@@ -34,150 +33,146 @@ static void MortonCopy(u32 stride, u32 block_height, u32 height, u32 block_depth
stride, height, depth, block_height, block_depth,
tile_width_spacing);
} else {
- Tegra::Texture::CopySwizzledData(
- (stride + tile_size_x - 1) / tile_size_x, (height + tile_size_y - 1) / tile_size_y,
- depth, bytes_per_pixel, bytes_per_pixel, Memory::GetPointer(addr), buffer, false,
- block_height, block_depth, tile_width_spacing);
+ Tegra::Texture::CopySwizzledData((stride + tile_size_x - 1) / tile_size_x,
+ (height + tile_size_y - 1) / tile_size_y, depth,
+ bytes_per_pixel, bytes_per_pixel, addr, buffer, false,
+ block_height, block_depth, tile_width_spacing);
}
}
static constexpr ConversionArray morton_to_linear_fns = {
- // clang-format off
- MortonCopy<true, PixelFormat::ABGR8U>,
- MortonCopy<true, PixelFormat::ABGR8S>,
- MortonCopy<true, PixelFormat::ABGR8UI>,
- MortonCopy<true, PixelFormat::B5G6R5U>,
- MortonCopy<true, PixelFormat::A2B10G10R10U>,
- MortonCopy<true, PixelFormat::A1B5G5R5U>,
- MortonCopy<true, PixelFormat::R8U>,
- MortonCopy<true, PixelFormat::R8UI>,
- MortonCopy<true, PixelFormat::RGBA16F>,
- MortonCopy<true, PixelFormat::RGBA16U>,
- MortonCopy<true, PixelFormat::RGBA16UI>,
- MortonCopy<true, PixelFormat::R11FG11FB10F>,
- MortonCopy<true, PixelFormat::RGBA32UI>,
- MortonCopy<true, PixelFormat::DXT1>,
- MortonCopy<true, PixelFormat::DXT23>,
- MortonCopy<true, PixelFormat::DXT45>,
- MortonCopy<true, PixelFormat::DXN1>,
- MortonCopy<true, PixelFormat::DXN2UNORM>,
- MortonCopy<true, PixelFormat::DXN2SNORM>,
- MortonCopy<true, PixelFormat::BC7U>,
- MortonCopy<true, PixelFormat::BC6H_UF16>,
- MortonCopy<true, PixelFormat::BC6H_SF16>,
- MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
- MortonCopy<true, PixelFormat::BGRA8>,
- MortonCopy<true, PixelFormat::RGBA32F>,
- MortonCopy<true, PixelFormat::RG32F>,
- MortonCopy<true, PixelFormat::R32F>,
- MortonCopy<true, PixelFormat::R16F>,
- MortonCopy<true, PixelFormat::R16U>,
- MortonCopy<true, PixelFormat::R16S>,
- MortonCopy<true, PixelFormat::R16UI>,
- MortonCopy<true, PixelFormat::R16I>,
- MortonCopy<true, PixelFormat::RG16>,
- MortonCopy<true, PixelFormat::RG16F>,
- MortonCopy<true, PixelFormat::RG16UI>,
- MortonCopy<true, PixelFormat::RG16I>,
- MortonCopy<true, PixelFormat::RG16S>,
- MortonCopy<true, PixelFormat::RGB32F>,
- MortonCopy<true, PixelFormat::RGBA8_SRGB>,
- MortonCopy<true, PixelFormat::RG8U>,
- MortonCopy<true, PixelFormat::RG8S>,
- MortonCopy<true, PixelFormat::RG32UI>,
- MortonCopy<true, PixelFormat::R32UI>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
- MortonCopy<true, PixelFormat::BGRA8_SRGB>,
- MortonCopy<true, PixelFormat::DXT1_SRGB>,
- MortonCopy<true, PixelFormat::DXT23_SRGB>,
- MortonCopy<true, PixelFormat::DXT45_SRGB>,
- MortonCopy<true, PixelFormat::BC7U_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X5>,
- MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
- MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
- MortonCopy<true, PixelFormat::Z32F>,
- MortonCopy<true, PixelFormat::Z16>,
- MortonCopy<true, PixelFormat::Z24S8>,
- MortonCopy<true, PixelFormat::S8Z24>,
- MortonCopy<true, PixelFormat::Z32FS8>,
- // clang-format on
+ MortonCopy<true, PixelFormat::ABGR8U>,
+ MortonCopy<true, PixelFormat::ABGR8S>,
+ MortonCopy<true, PixelFormat::ABGR8UI>,
+ MortonCopy<true, PixelFormat::B5G6R5U>,
+ MortonCopy<true, PixelFormat::A2B10G10R10U>,
+ MortonCopy<true, PixelFormat::A1B5G5R5U>,
+ MortonCopy<true, PixelFormat::R8U>,
+ MortonCopy<true, PixelFormat::R8UI>,
+ MortonCopy<true, PixelFormat::RGBA16F>,
+ MortonCopy<true, PixelFormat::RGBA16U>,
+ MortonCopy<true, PixelFormat::RGBA16UI>,
+ MortonCopy<true, PixelFormat::R11FG11FB10F>,
+ MortonCopy<true, PixelFormat::RGBA32UI>,
+ MortonCopy<true, PixelFormat::DXT1>,
+ MortonCopy<true, PixelFormat::DXT23>,
+ MortonCopy<true, PixelFormat::DXT45>,
+ MortonCopy<true, PixelFormat::DXN1>,
+ MortonCopy<true, PixelFormat::DXN2UNORM>,
+ MortonCopy<true, PixelFormat::DXN2SNORM>,
+ MortonCopy<true, PixelFormat::BC7U>,
+ MortonCopy<true, PixelFormat::BC6H_UF16>,
+ MortonCopy<true, PixelFormat::BC6H_SF16>,
+ MortonCopy<true, PixelFormat::ASTC_2D_4X4>,
+ MortonCopy<true, PixelFormat::BGRA8>,
+ MortonCopy<true, PixelFormat::RGBA32F>,
+ MortonCopy<true, PixelFormat::RG32F>,
+ MortonCopy<true, PixelFormat::R32F>,
+ MortonCopy<true, PixelFormat::R16F>,
+ MortonCopy<true, PixelFormat::R16U>,
+ MortonCopy<true, PixelFormat::R16S>,
+ MortonCopy<true, PixelFormat::R16UI>,
+ MortonCopy<true, PixelFormat::R16I>,
+ MortonCopy<true, PixelFormat::RG16>,
+ MortonCopy<true, PixelFormat::RG16F>,
+ MortonCopy<true, PixelFormat::RG16UI>,
+ MortonCopy<true, PixelFormat::RG16I>,
+ MortonCopy<true, PixelFormat::RG16S>,
+ MortonCopy<true, PixelFormat::RGB32F>,
+ MortonCopy<true, PixelFormat::RGBA8_SRGB>,
+ MortonCopy<true, PixelFormat::RG8U>,
+ MortonCopy<true, PixelFormat::RG8S>,
+ MortonCopy<true, PixelFormat::RG32UI>,
+ MortonCopy<true, PixelFormat::R32UI>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X8>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X5>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X4>,
+ MortonCopy<true, PixelFormat::BGRA8_SRGB>,
+ MortonCopy<true, PixelFormat::DXT1_SRGB>,
+ MortonCopy<true, PixelFormat::DXT23_SRGB>,
+ MortonCopy<true, PixelFormat::DXT45_SRGB>,
+ MortonCopy<true, PixelFormat::BC7U_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_4X4_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X8_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_8X5_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X4_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X5>,
+ MortonCopy<true, PixelFormat::ASTC_2D_5X5_SRGB>,
+ MortonCopy<true, PixelFormat::ASTC_2D_10X8>,
+ MortonCopy<true, PixelFormat::ASTC_2D_10X8_SRGB>,
+ MortonCopy<true, PixelFormat::Z32F>,
+ MortonCopy<true, PixelFormat::Z16>,
+ MortonCopy<true, PixelFormat::Z24S8>,
+ MortonCopy<true, PixelFormat::S8Z24>,
+ MortonCopy<true, PixelFormat::Z32FS8>,
};
static constexpr ConversionArray linear_to_morton_fns = {
- // clang-format off
- MortonCopy<false, PixelFormat::ABGR8U>,
- MortonCopy<false, PixelFormat::ABGR8S>,
- MortonCopy<false, PixelFormat::ABGR8UI>,
- MortonCopy<false, PixelFormat::B5G6R5U>,
- MortonCopy<false, PixelFormat::A2B10G10R10U>,
- MortonCopy<false, PixelFormat::A1B5G5R5U>,
- MortonCopy<false, PixelFormat::R8U>,
- MortonCopy<false, PixelFormat::R8UI>,
- MortonCopy<false, PixelFormat::RGBA16F>,
- MortonCopy<false, PixelFormat::RGBA16U>,
- MortonCopy<false, PixelFormat::RGBA16UI>,
- MortonCopy<false, PixelFormat::R11FG11FB10F>,
- MortonCopy<false, PixelFormat::RGBA32UI>,
- MortonCopy<false, PixelFormat::DXT1>,
- MortonCopy<false, PixelFormat::DXT23>,
- MortonCopy<false, PixelFormat::DXT45>,
- MortonCopy<false, PixelFormat::DXN1>,
- MortonCopy<false, PixelFormat::DXN2UNORM>,
- MortonCopy<false, PixelFormat::DXN2SNORM>,
- MortonCopy<false, PixelFormat::BC7U>,
- MortonCopy<false, PixelFormat::BC6H_UF16>,
- MortonCopy<false, PixelFormat::BC6H_SF16>,
- // TODO(Subv): Swizzling ASTC formats are not supported
- nullptr,
- MortonCopy<false, PixelFormat::BGRA8>,
- MortonCopy<false, PixelFormat::RGBA32F>,
- MortonCopy<false, PixelFormat::RG32F>,
- MortonCopy<false, PixelFormat::R32F>,
- MortonCopy<false, PixelFormat::R16F>,
- MortonCopy<false, PixelFormat::R16U>,
- MortonCopy<false, PixelFormat::R16S>,
- MortonCopy<false, PixelFormat::R16UI>,
- MortonCopy<false, PixelFormat::R16I>,
- MortonCopy<false, PixelFormat::RG16>,
- MortonCopy<false, PixelFormat::RG16F>,
- MortonCopy<false, PixelFormat::RG16UI>,
- MortonCopy<false, PixelFormat::RG16I>,
- MortonCopy<false, PixelFormat::RG16S>,
- MortonCopy<false, PixelFormat::RGB32F>,
- MortonCopy<false, PixelFormat::RGBA8_SRGB>,
- MortonCopy<false, PixelFormat::RG8U>,
- MortonCopy<false, PixelFormat::RG8S>,
- MortonCopy<false, PixelFormat::RG32UI>,
- MortonCopy<false, PixelFormat::R32UI>,
- nullptr,
- nullptr,
- nullptr,
- MortonCopy<false, PixelFormat::BGRA8_SRGB>,
- MortonCopy<false, PixelFormat::DXT1_SRGB>,
- MortonCopy<false, PixelFormat::DXT23_SRGB>,
- MortonCopy<false, PixelFormat::DXT45_SRGB>,
- MortonCopy<false, PixelFormat::BC7U_SRGB>,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- nullptr,
- MortonCopy<false, PixelFormat::Z32F>,
- MortonCopy<false, PixelFormat::Z16>,
- MortonCopy<false, PixelFormat::Z24S8>,
- MortonCopy<false, PixelFormat::S8Z24>,
- MortonCopy<false, PixelFormat::Z32FS8>,
- // clang-format on
+ MortonCopy<false, PixelFormat::ABGR8U>,
+ MortonCopy<false, PixelFormat::ABGR8S>,
+ MortonCopy<false, PixelFormat::ABGR8UI>,
+ MortonCopy<false, PixelFormat::B5G6R5U>,
+ MortonCopy<false, PixelFormat::A2B10G10R10U>,
+ MortonCopy<false, PixelFormat::A1B5G5R5U>,
+ MortonCopy<false, PixelFormat::R8U>,
+ MortonCopy<false, PixelFormat::R8UI>,
+ MortonCopy<false, PixelFormat::RGBA16F>,
+ MortonCopy<false, PixelFormat::RGBA16U>,
+ MortonCopy<false, PixelFormat::RGBA16UI>,
+ MortonCopy<false, PixelFormat::R11FG11FB10F>,
+ MortonCopy<false, PixelFormat::RGBA32UI>,
+ MortonCopy<false, PixelFormat::DXT1>,
+ MortonCopy<false, PixelFormat::DXT23>,
+ MortonCopy<false, PixelFormat::DXT45>,
+ MortonCopy<false, PixelFormat::DXN1>,
+ MortonCopy<false, PixelFormat::DXN2UNORM>,
+ MortonCopy<false, PixelFormat::DXN2SNORM>,
+ MortonCopy<false, PixelFormat::BC7U>,
+ MortonCopy<false, PixelFormat::BC6H_UF16>,
+ MortonCopy<false, PixelFormat::BC6H_SF16>,
+ // TODO(Subv): Swizzling ASTC formats are not supported
+ nullptr,
+ MortonCopy<false, PixelFormat::BGRA8>,
+ MortonCopy<false, PixelFormat::RGBA32F>,
+ MortonCopy<false, PixelFormat::RG32F>,
+ MortonCopy<false, PixelFormat::R32F>,
+ MortonCopy<false, PixelFormat::R16F>,
+ MortonCopy<false, PixelFormat::R16U>,
+ MortonCopy<false, PixelFormat::R16S>,
+ MortonCopy<false, PixelFormat::R16UI>,
+ MortonCopy<false, PixelFormat::R16I>,
+ MortonCopy<false, PixelFormat::RG16>,
+ MortonCopy<false, PixelFormat::RG16F>,
+ MortonCopy<false, PixelFormat::RG16UI>,
+ MortonCopy<false, PixelFormat::RG16I>,
+ MortonCopy<false, PixelFormat::RG16S>,
+ MortonCopy<false, PixelFormat::RGB32F>,
+ MortonCopy<false, PixelFormat::RGBA8_SRGB>,
+ MortonCopy<false, PixelFormat::RG8U>,
+ MortonCopy<false, PixelFormat::RG8S>,
+ MortonCopy<false, PixelFormat::RG32UI>,
+ MortonCopy<false, PixelFormat::R32UI>,
+ nullptr,
+ nullptr,
+ nullptr,
+ MortonCopy<false, PixelFormat::BGRA8_SRGB>,
+ MortonCopy<false, PixelFormat::DXT1_SRGB>,
+ MortonCopy<false, PixelFormat::DXT23_SRGB>,
+ MortonCopy<false, PixelFormat::DXT45_SRGB>,
+ MortonCopy<false, PixelFormat::BC7U_SRGB>,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ nullptr,
+ MortonCopy<false, PixelFormat::Z32F>,
+ MortonCopy<false, PixelFormat::Z16>,
+ MortonCopy<false, PixelFormat::Z24S8>,
+ MortonCopy<false, PixelFormat::S8Z24>,
+ MortonCopy<false, PixelFormat::Z32FS8>,
};
static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFormat format) {
@@ -191,45 +186,6 @@ static MortonCopyFn GetSwizzleFunction(MortonSwizzleMode mode, Surface::PixelFor
return morton_to_linear_fns[static_cast<std::size_t>(format)];
}
-/// 8x8 Z-Order coordinate from 2D coordinates
-static u32 MortonInterleave(u32 x, u32 y) {
- static const u32 xlut[] = {0x00, 0x01, 0x04, 0x05, 0x10, 0x11, 0x14, 0x15};
- static const u32 ylut[] = {0x00, 0x02, 0x08, 0x0a, 0x20, 0x22, 0x28, 0x2a};
- return xlut[x % 8] + ylut[y % 8];
-}
-
-/// Calculates the offset of the position of the pixel in Morton order
-static u32 GetMortonOffset(u32 x, u32 y, u32 bytes_per_pixel) {
- // Images are split into 8x8 tiles. Each tile is composed of four 4x4 subtiles each
- // of which is composed of four 2x2 subtiles each of which is composed of four texels.
- // Each structure is embedded into the next-bigger one in a diagonal pattern, e.g.
- // texels are laid out in a 2x2 subtile like this:
- // 2 3
- // 0 1
- //
- // The full 8x8 tile has the texels arranged like this:
- //
- // 42 43 46 47 58 59 62 63
- // 40 41 44 45 56 57 60 61
- // 34 35 38 39 50 51 54 55
- // 32 33 36 37 48 49 52 53
- // 10 11 14 15 26 27 30 31
- // 08 09 12 13 24 25 28 29
- // 02 03 06 07 18 19 22 23
- // 00 01 04 05 16 17 20 21
- //
- // This pattern is what's called Z-order curve, or Morton order.
-
- const unsigned int block_height = 8;
- const unsigned int coarse_x = x & ~7;
-
- u32 i = MortonInterleave(x, y);
-
- const unsigned int offset = coarse_x * block_height;
-
- return (i + offset) * bytes_per_pixel;
-}
-
static u32 MortonInterleave128(u32 x, u32 y) {
// 128x128 Z-Order coordinate from 2D coordinates
static constexpr u32 xlut[] = {
@@ -325,14 +281,14 @@ static u32 GetMortonOffset128(u32 x, u32 y, u32 bytes_per_pixel) {
void MortonSwizzle(MortonSwizzleMode mode, Surface::PixelFormat format, u32 stride,
u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
- u8* buffer, std::size_t buffer_size, VAddr addr) {
-
+ u8* buffer, u8* addr) {
GetSwizzleFunction(mode, format)(stride, block_height, height, block_depth, depth,
- tile_width_spacing, buffer, buffer_size, addr);
+ tile_width_spacing, buffer, addr);
}
-void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel,
- u8* morton_data, u8* linear_data, bool morton_to_linear) {
+void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
+ u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data) {
+ const bool morton_to_linear = mode == MortonSwizzleMode::MortonToLinear;
u8* data_ptrs[2];
for (u32 y = 0; y < height; ++y) {
for (u32 x = 0; x < width; ++x) {
diff --git a/src/video_core/morton.h b/src/video_core/morton.h
index 065f59ce3..ee5b45555 100644
--- a/src/video_core/morton.h
+++ b/src/video_core/morton.h
@@ -13,9 +13,9 @@ enum class MortonSwizzleMode { MortonToLinear, LinearToMorton };
void MortonSwizzle(MortonSwizzleMode mode, VideoCore::Surface::PixelFormat format, u32 stride,
u32 block_height, u32 height, u32 block_depth, u32 depth, u32 tile_width_spacing,
- u8* buffer, std::size_t buffer_size, VAddr addr);
+ u8* buffer, u8* addr);
-void MortonCopyPixels128(u32 width, u32 height, u32 bytes_per_pixel, u32 linear_bytes_per_pixel,
- u8* morton_data, u8* linear_data, bool morton_to_linear);
+void MortonCopyPixels128(MortonSwizzleMode mode, u32 width, u32 height, u32 bytes_per_pixel,
+ u32 linear_bytes_per_pixel, u8* morton_data, u8* linear_data);
} // namespace VideoCore
diff --git a/src/video_core/rasterizer_cache.h b/src/video_core/rasterizer_cache.h
index a7bcf26fb..9fc9f3056 100644
--- a/src/video_core/rasterizer_cache.h
+++ b/src/video_core/rasterizer_cache.h
@@ -4,6 +4,7 @@
#pragma once
+#include <mutex>
#include <set>
#include <unordered_map>
@@ -12,14 +13,26 @@
#include "common/common_types.h"
#include "core/settings.h"
+#include "video_core/gpu.h"
#include "video_core/rasterizer_interface.h"
class RasterizerCacheObject {
public:
+ explicit RasterizerCacheObject(const u8* host_ptr)
+ : host_ptr{host_ptr}, cache_addr{ToCacheAddr(host_ptr)} {}
+
virtual ~RasterizerCacheObject();
+ CacheAddr GetCacheAddr() const {
+ return cache_addr;
+ }
+
+ const u8* GetHostPtr() const {
+ return host_ptr;
+ }
+
/// Gets the address of the shader in guest memory, required for cache management
- virtual VAddr GetAddr() const = 0;
+ virtual VAddr GetCpuAddr() const = 0;
/// Gets the size of the shader in guest memory, required for cache management
virtual std::size_t GetSizeInBytes() const = 0;
@@ -58,6 +71,8 @@ private:
bool is_registered{}; ///< Whether the object is currently registered with the cache
bool is_dirty{}; ///< Whether the object is dirty (out of sync with guest memory)
u64 last_modified_ticks{}; ///< When the object was last modified, used for in-order flushing
+ CacheAddr cache_addr{}; ///< Cache address memory, unique from emulated virtual address space
+ const u8* host_ptr{}; ///< Pointer to the memory backing this cached region
};
template <class T>
@@ -68,7 +83,9 @@ public:
explicit RasterizerCache(VideoCore::RasterizerInterface& rasterizer) : rasterizer{rasterizer} {}
/// Write any cached resources overlapping the specified region back to memory
- void FlushRegion(Tegra::GPUVAddr addr, size_t size) {
+ void FlushRegion(CacheAddr addr, std::size_t size) {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
FlushObject(object);
@@ -76,7 +93,9 @@ public:
}
/// Mark the specified region as being invalidated
- void InvalidateRegion(VAddr addr, u64 size) {
+ void InvalidateRegion(CacheAddr addr, u64 size) {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+
const auto& objects{GetSortedObjectsFromRegion(addr, size)};
for (auto& object : objects) {
if (!object->IsRegistered()) {
@@ -89,48 +108,60 @@ public:
/// Invalidates everything in the cache
void InvalidateAll() {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+
while (interval_cache.begin() != interval_cache.end()) {
Unregister(*interval_cache.begin()->second.begin());
}
}
protected:
- /// Tries to get an object from the cache with the specified address
- T TryGet(VAddr addr) const {
+ /// Tries to get an object from the cache with the specified cache address
+ T TryGet(CacheAddr addr) const {
const auto iter = map_cache.find(addr);
if (iter != map_cache.end())
return iter->second;
return nullptr;
}
+ T TryGet(const void* addr) const {
+ const auto iter = map_cache.find(ToCacheAddr(addr));
+ if (iter != map_cache.end())
+ return iter->second;
+ return nullptr;
+ }
+
/// Register an object into the cache
- void Register(const T& object) {
+ virtual void Register(const T& object) {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+
object->SetIsRegistered(true);
interval_cache.add({GetInterval(object), ObjectSet{object}});
- map_cache.insert({object->GetAddr(), object});
- rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), 1);
+ map_cache.insert({object->GetCacheAddr(), object});
+ rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), 1);
}
/// Unregisters an object from the cache
- void Unregister(const T& object) {
- object->SetIsRegistered(false);
- rasterizer.UpdatePagesCachedCount(object->GetAddr(), object->GetSizeInBytes(), -1);
- // Only flush if use_accurate_gpu_emulation is enabled, as it incurs a performance hit
- if (Settings::values.use_accurate_gpu_emulation) {
- FlushObject(object);
- }
+ virtual void Unregister(const T& object) {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+ object->SetIsRegistered(false);
+ rasterizer.UpdatePagesCachedCount(object->GetCpuAddr(), object->GetSizeInBytes(), -1);
interval_cache.subtract({GetInterval(object), ObjectSet{object}});
- map_cache.erase(object->GetAddr());
+ map_cache.erase(object->GetCacheAddr());
}
/// Returns a ticks counter used for tracking when cached objects were last modified
u64 GetModifiedTicks() {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+
return ++modified_ticks;
}
/// Flushes the specified object, updating appropriate cache state as needed
void FlushObject(const T& object) {
+ std::lock_guard<std::recursive_mutex> lock{mutex};
+
if (!object->IsDirty()) {
return;
}
@@ -140,7 +171,7 @@ protected:
private:
/// Returns a list of cached objects from the specified memory region, ordered by access time
- std::vector<T> GetSortedObjectsFromRegion(VAddr addr, u64 size) {
+ std::vector<T> GetSortedObjectsFromRegion(CacheAddr addr, u64 size) {
if (size == 0) {
return {};
}
@@ -164,17 +195,18 @@ private:
}
using ObjectSet = std::set<T>;
- using ObjectCache = std::unordered_map<VAddr, T>;
- using IntervalCache = boost::icl::interval_map<VAddr, ObjectSet>;
+ using ObjectCache = std::unordered_map<CacheAddr, T>;
+ using IntervalCache = boost::icl::interval_map<CacheAddr, ObjectSet>;
using ObjectInterval = typename IntervalCache::interval_type;
static auto GetInterval(const T& object) {
- return ObjectInterval::right_open(object->GetAddr(),
- object->GetAddr() + object->GetSizeInBytes());
+ return ObjectInterval::right_open(object->GetCacheAddr(),
+ object->GetCacheAddr() + object->GetSizeInBytes());
}
ObjectCache map_cache;
IntervalCache interval_cache; ///< Cache of objects
u64 modified_ticks{}; ///< Counter of cache state ticks, used for in-order flushing
VideoCore::RasterizerInterface& rasterizer;
+ std::recursive_mutex mutex;
};
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 6a1dc9cf6..d7b86df38 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -9,7 +9,6 @@
#include "common/common_types.h"
#include "video_core/engines/fermi_2d.h"
#include "video_core/gpu.h"
-#include "video_core/memory_manager.h"
namespace VideoCore {
@@ -35,14 +34,14 @@ public:
virtual void FlushAll() = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
- virtual void FlushRegion(VAddr addr, u64 size) = 0;
+ virtual void FlushRegion(CacheAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be invalidated
- virtual void InvalidateRegion(VAddr addr, u64 size) = 0;
+ virtual void InvalidateRegion(CacheAddr addr, u64 size) = 0;
/// Notify rasterizer that any caches of the specified region should be flushed to Switch memory
/// and invalidated
- virtual void FlushAndInvalidateRegion(VAddr addr, u64 size) = 0;
+ virtual void FlushAndInvalidateRegion(CacheAddr addr, u64 size) = 0;
/// Attempt to use a faster method to perform a surface copy
virtual bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
@@ -63,7 +62,7 @@ public:
}
/// Increase/decrease the number of object in pages touching the specified region
- virtual void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {}
+ virtual void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {}
/// Initialize disk cached resources for the game being emulated
virtual void LoadDiskResources(const std::atomic_bool& stop_loading = false,
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.cpp b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
index b3062e5ba..f75c65825 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.cpp
@@ -13,24 +13,28 @@
namespace OpenGL {
+CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset,
+ std::size_t alignment, u8* host_ptr)
+ : cpu_addr{cpu_addr}, size{size}, offset{offset}, alignment{alignment}, RasterizerCacheObject{
+ host_ptr} {}
+
OGLBufferCache::OGLBufferCache(RasterizerOpenGL& rasterizer, std::size_t size)
: RasterizerCache{rasterizer}, stream_buffer(size, true) {}
-GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size,
- std::size_t alignment, bool cache) {
+GLintptr OGLBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment,
+ bool cache) {
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
- const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
- ASSERT_MSG(cpu_addr, "Invalid GPU address");
// Cache management is a big overhead, so only cache entries with a given size.
// TODO: Figure out which size is the best for given games.
cache &= size >= 2048;
+ const auto& host_ptr{memory_manager.GetPointer(gpu_addr)};
if (cache) {
- auto entry = TryGet(*cpu_addr);
+ auto entry = TryGet(host_ptr);
if (entry) {
- if (entry->size >= size && entry->alignment == alignment) {
- return entry->offset;
+ if (entry->GetSize() >= size && entry->GetAlignment() == alignment) {
+ return entry->GetOffset();
}
Unregister(entry);
}
@@ -39,17 +43,17 @@ GLintptr OGLBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size
AlignBuffer(alignment);
const GLintptr uploaded_offset = buffer_offset;
- Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
+ if (!host_ptr) {
+ return uploaded_offset;
+ }
+ std::memcpy(buffer_ptr, host_ptr, size);
buffer_ptr += size;
buffer_offset += size;
if (cache) {
- auto entry = std::make_shared<CachedBufferEntry>();
- entry->offset = uploaded_offset;
- entry->size = size;
- entry->alignment = alignment;
- entry->addr = *cpu_addr;
+ auto entry = std::make_shared<CachedBufferEntry>(
+ *memory_manager.GpuToCpuAddress(gpu_addr), size, uploaded_offset, alignment, host_ptr);
Register(entry);
}
diff --git a/src/video_core/renderer_opengl/gl_buffer_cache.h b/src/video_core/renderer_opengl/gl_buffer_cache.h
index c11acfb79..fc33aa433 100644
--- a/src/video_core/renderer_opengl/gl_buffer_cache.h
+++ b/src/video_core/renderer_opengl/gl_buffer_cache.h
@@ -17,22 +17,39 @@ namespace OpenGL {
class RasterizerOpenGL;
-struct CachedBufferEntry final : public RasterizerCacheObject {
- VAddr GetAddr() const override {
- return addr;
+class CachedBufferEntry final : public RasterizerCacheObject {
+public:
+ explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, GLintptr offset,
+ std::size_t alignment, u8* host_ptr);
+
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
return size;
}
+ std::size_t GetSize() const {
+ return size;
+ }
+
+ GLintptr GetOffset() const {
+ return offset;
+ }
+
+ std::size_t GetAlignment() const {
+ return alignment;
+ }
+
// We do not have to flush this cache as things in it are never modified by us.
void Flush() override {}
- VAddr addr;
- std::size_t size;
- GLintptr offset;
- std::size_t alignment;
+private:
+ VAddr cpu_addr{};
+ std::size_t size{};
+ GLintptr offset{};
+ std::size_t alignment{};
};
class OGLBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
@@ -41,7 +58,7 @@ public:
/// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
/// allocated.
- GLintptr UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
+ GLintptr UploadMemory(GPUVAddr gpu_addr, std::size_t size, std::size_t alignment = 4,
bool cache = true);
/// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
diff --git a/src/video_core/renderer_opengl/gl_global_cache.cpp b/src/video_core/renderer_opengl/gl_global_cache.cpp
index c7f32feaa..0fbfbad55 100644
--- a/src/video_core/renderer_opengl/gl_global_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_global_cache.cpp
@@ -7,7 +7,6 @@
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/core.h"
-#include "core/memory.h"
#include "video_core/renderer_opengl/gl_global_cache.h"
#include "video_core/renderer_opengl/gl_rasterizer.h"
#include "video_core/renderer_opengl/gl_shader_decompiler.h"
@@ -15,12 +14,13 @@
namespace OpenGL {
-CachedGlobalRegion::CachedGlobalRegion(VAddr addr, u32 size) : addr{addr}, size{size} {
+CachedGlobalRegion::CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr)
+ : cpu_addr{cpu_addr}, size{size}, RasterizerCacheObject{host_ptr} {
buffer.Create();
// Bind and unbind the buffer so it gets allocated by the driver
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
glBindBuffer(GL_SHADER_STORAGE_BUFFER, 0);
- LabelGLObject(GL_BUFFER, buffer.handle, addr, "GlobalMemory");
+ LabelGLObject(GL_BUFFER, buffer.handle, cpu_addr, "GlobalMemory");
}
void CachedGlobalRegion::Reload(u32 size_) {
@@ -35,10 +35,10 @@ void CachedGlobalRegion::Reload(u32 size_) {
// TODO(Rodrigo): Get rid of Memory::GetPointer with a staging buffer
glBindBuffer(GL_SHADER_STORAGE_BUFFER, buffer.handle);
- glBufferData(GL_SHADER_STORAGE_BUFFER, size, Memory::GetPointer(addr), GL_DYNAMIC_DRAW);
+ glBufferData(GL_SHADER_STORAGE_BUFFER, size, GetHostPtr(), GL_DYNAMIC_DRAW);
}
-GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32 size) const {
+GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const {
const auto search{reserve.find(addr)};
if (search == reserve.end()) {
return {};
@@ -46,19 +46,22 @@ GlobalRegion GlobalRegionCacheOpenGL::TryGetReservedGlobalRegion(VAddr addr, u32
return search->second;
}
-GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(VAddr addr, u32 size) {
- GlobalRegion region{TryGetReservedGlobalRegion(addr, size)};
+GlobalRegion GlobalRegionCacheOpenGL::GetUncachedGlobalRegion(GPUVAddr addr, u32 size,
+ u8* host_ptr) {
+ GlobalRegion region{TryGetReservedGlobalRegion(ToCacheAddr(host_ptr), size)};
if (!region) {
// No reserved surface available, create a new one and reserve it
- region = std::make_shared<CachedGlobalRegion>(addr, size);
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ const auto cpu_addr = *memory_manager.GpuToCpuAddress(addr);
+ region = std::make_shared<CachedGlobalRegion>(cpu_addr, size, host_ptr);
ReserveGlobalRegion(region);
}
region->Reload(size);
return region;
}
-void GlobalRegionCacheOpenGL::ReserveGlobalRegion(const GlobalRegion& region) {
- reserve[region->GetAddr()] = region;
+void GlobalRegionCacheOpenGL::ReserveGlobalRegion(GlobalRegion region) {
+ reserve.insert_or_assign(region->GetCacheAddr(), std::move(region));
}
GlobalRegionCacheOpenGL::GlobalRegionCacheOpenGL(RasterizerOpenGL& rasterizer)
@@ -69,22 +72,20 @@ GlobalRegion GlobalRegionCacheOpenGL::GetGlobalRegion(
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage) {
auto& gpu{Core::System::GetInstance().GPU()};
- const auto cbufs = gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)];
- const auto cbuf_addr = gpu.MemoryManager().GpuToCpuAddress(
- cbufs.const_buffers[global_region.GetCbufIndex()].address + global_region.GetCbufOffset());
- ASSERT(cbuf_addr);
-
- const auto actual_addr_gpu = Memory::Read64(*cbuf_addr);
- const auto size = Memory::Read32(*cbuf_addr + 8);
- const auto actual_addr = gpu.MemoryManager().GpuToCpuAddress(actual_addr_gpu);
- ASSERT(actual_addr);
+ auto& memory_manager{gpu.MemoryManager()};
+ const auto cbufs{gpu.Maxwell3D().state.shader_stages[static_cast<u64>(stage)]};
+ const auto addr{cbufs.const_buffers[global_region.GetCbufIndex()].address +
+ global_region.GetCbufOffset()};
+ const auto actual_addr{memory_manager.Read<u64>(addr)};
+ const auto size{memory_manager.Read<u32>(addr + 8)};
// Look up global region in the cache based on address
- GlobalRegion region = TryGet(*actual_addr);
+ const auto& host_ptr{memory_manager.GetPointer(actual_addr)};
+ GlobalRegion region{TryGet(host_ptr)};
if (!region) {
// No global region found - create a new one
- region = GetUncachedGlobalRegion(*actual_addr, size);
+ region = GetUncachedGlobalRegion(actual_addr, size, host_ptr);
Register(region);
}
diff --git a/src/video_core/renderer_opengl/gl_global_cache.h b/src/video_core/renderer_opengl/gl_global_cache.h
index 37830bb7c..5a21ab66f 100644
--- a/src/video_core/renderer_opengl/gl_global_cache.h
+++ b/src/video_core/renderer_opengl/gl_global_cache.h
@@ -27,15 +27,13 @@ using GlobalRegion = std::shared_ptr<CachedGlobalRegion>;
class CachedGlobalRegion final : public RasterizerCacheObject {
public:
- explicit CachedGlobalRegion(VAddr addr, u32 size);
+ explicit CachedGlobalRegion(VAddr cpu_addr, u32 size, u8* host_ptr);
- /// Gets the address of the shader in guest memory, required for cache management
- VAddr GetAddr() const {
- return addr;
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
- /// Gets the size of the shader in guest memory, required for cache management
- std::size_t GetSizeInBytes() const {
+ std::size_t GetSizeInBytes() const override {
return size;
}
@@ -53,9 +51,8 @@ public:
}
private:
- VAddr addr{};
+ VAddr cpu_addr{};
u32 size{};
-
OGLBuffer buffer;
};
@@ -68,11 +65,11 @@ public:
Tegra::Engines::Maxwell3D::Regs::ShaderStage stage);
private:
- GlobalRegion TryGetReservedGlobalRegion(VAddr addr, u32 size) const;
- GlobalRegion GetUncachedGlobalRegion(VAddr addr, u32 size);
- void ReserveGlobalRegion(const GlobalRegion& region);
+ GlobalRegion TryGetReservedGlobalRegion(CacheAddr addr, u32 size) const;
+ GlobalRegion GetUncachedGlobalRegion(GPUVAddr addr, u32 size, u8* host_ptr);
+ void ReserveGlobalRegion(GlobalRegion region);
- std::unordered_map<VAddr, GlobalRegion> reserve;
+ std::unordered_map<CacheAddr, GlobalRegion> reserve;
};
} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
index 77d5cedd2..2bcbd3da2 100644
--- a/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.cpp
@@ -40,16 +40,12 @@ GLintptr PrimitiveAssembler::MakeQuadArray(u32 first, u32 count) {
return index_offset;
}
-GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size,
- u32 count) {
+GLintptr PrimitiveAssembler::MakeQuadIndexed(GPUVAddr gpu_addr, std::size_t index_size, u32 count) {
const std::size_t map_size{CalculateQuadSize(count)};
auto [dst_pointer, index_offset] = buffer_cache.ReserveMemory(map_size);
auto& memory_manager = Core::System::GetInstance().GPU().MemoryManager();
- const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr)};
- ASSERT_MSG(cpu_addr, "Invalid GPU address");
-
- const u8* source{Memory::GetPointer(*cpu_addr)};
+ const u8* source{memory_manager.GetPointer(gpu_addr)};
for (u32 primitive = 0; primitive < count / 4; ++primitive) {
for (std::size_t i = 0; i < TRIANGLES_PER_QUAD; ++i) {
@@ -64,4 +60,4 @@ GLintptr PrimitiveAssembler::MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size
return index_offset;
}
-} // namespace OpenGL \ No newline at end of file
+} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_primitive_assembler.h b/src/video_core/renderer_opengl/gl_primitive_assembler.h
index a8cb88eb5..0e2e7dc36 100644
--- a/src/video_core/renderer_opengl/gl_primitive_assembler.h
+++ b/src/video_core/renderer_opengl/gl_primitive_assembler.h
@@ -24,7 +24,7 @@ public:
GLintptr MakeQuadArray(u32 first, u32 count);
- GLintptr MakeQuadIndexed(Tegra::GPUVAddr gpu_addr, std::size_t index_size, u32 count);
+ GLintptr MakeQuadIndexed(GPUVAddr gpu_addr, std::size_t index_size, u32 count);
private:
OGLBufferCache& buffer_cache;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 824863561..e06dfe43f 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -102,8 +102,9 @@ struct FramebufferCacheKey {
RasterizerOpenGL::RasterizerOpenGL(Core::Frontend::EmuWindow& window, Core::System& system,
ScreenInfo& info)
- : res_cache{*this}, shader_cache{*this, system}, global_cache{*this}, emu_window{window},
- screen_info{info}, buffer_cache(*this, STREAM_BUFFER_SIZE) {
+ : res_cache{*this}, shader_cache{*this, system}, global_cache{*this},
+ emu_window{window}, system{system}, screen_info{info},
+ buffer_cache(*this, STREAM_BUFFER_SIZE) {
// Create sampler objects
for (std::size_t i = 0; i < texture_samplers.size(); ++i) {
texture_samplers[i].Create();
@@ -138,7 +139,7 @@ void RasterizerOpenGL::CheckExtensions() {
}
GLuint RasterizerOpenGL::SetupVertexFormat() {
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
if (!gpu.dirty_flags.vertex_attrib_format) {
@@ -207,7 +208,7 @@ GLuint RasterizerOpenGL::SetupVertexFormat() {
}
void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
if (gpu.dirty_flags.vertex_array.none())
@@ -224,8 +225,8 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
if (!vertex_array.IsEnabled())
continue;
- const Tegra::GPUVAddr start = vertex_array.StartAddress();
- const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
+ const GPUVAddr start = vertex_array.StartAddress();
+ const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
ASSERT(end > start);
const u64 size = end - start + 1;
@@ -248,7 +249,7 @@ void RasterizerOpenGL::SetupVertexBuffer(GLuint vao) {
}
DrawParameters RasterizerOpenGL::SetupDraw() {
- const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ const auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
const bool is_indexed = accelerate_draw == AccelDraw::Indexed;
@@ -297,7 +298,7 @@ DrawParameters RasterizerOpenGL::SetupDraw() {
void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
MICROPROFILE_SCOPE(OpenGL_Shader);
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
BaseBindings base_bindings;
std::array<bool, Maxwell::NumClipDistances> clip_distances{};
@@ -413,15 +414,15 @@ void RasterizerOpenGL::SetupCachedFramebuffer(const FramebufferCacheKey& fbkey,
}
std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
std::size_t size = 0;
for (u32 index = 0; index < Maxwell::NumVertexArrays; ++index) {
if (!regs.vertex_array[index].IsEnabled())
continue;
- const Tegra::GPUVAddr start = regs.vertex_array[index].StartAddress();
- const Tegra::GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
+ const GPUVAddr start = regs.vertex_array[index].StartAddress();
+ const GPUVAddr end = regs.vertex_array_limit[index].LimitAddress();
ASSERT(end > start);
size += end - start + 1;
@@ -431,7 +432,7 @@ std::size_t RasterizerOpenGL::CalculateVertexArraysSize() const {
}
std::size_t RasterizerOpenGL::CalculateIndexBufferSize() const {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
return static_cast<std::size_t>(regs.index_array.count) *
static_cast<std::size_t>(regs.index_array.FormatSizeInBytes());
@@ -448,7 +449,7 @@ static constexpr auto RangeFromInterval(Map& map, const Interval& interval) {
return boost::make_iterator_range(map.equal_range(interval));
}
-void RasterizerOpenGL::UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) {
+void RasterizerOpenGL::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
const u64 page_start{addr >> Memory::PAGE_BITS};
const u64 page_end{(addr + size + Memory::PAGE_SIZE - 1) >> Memory::PAGE_BITS};
@@ -487,7 +488,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
OpenGLState& current_state, bool using_color_fb, bool using_depth_fb, bool preserve_contents,
std::optional<std::size_t> single_color_target) {
MICROPROFILE_SCOPE(OpenGL_Framebuffer);
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
const FramebufferConfigState fb_config_state{using_color_fb, using_depth_fb, preserve_contents,
@@ -581,7 +582,7 @@ void RasterizerOpenGL::Clear() {
const auto prev_state{state};
SCOPE_EXIT({ prev_state.Apply(); });
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
bool use_color{};
bool use_depth{};
bool use_stencil{};
@@ -672,7 +673,7 @@ void RasterizerOpenGL::DrawArrays() {
return;
MICROPROFILE_SCOPE(OpenGL_Drawing);
- auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
+ auto& gpu = system.GPU().Maxwell3D();
const auto& regs = gpu.regs;
ConfigureFramebuffers(state);
@@ -746,20 +747,26 @@ void RasterizerOpenGL::DrawArrays() {
void RasterizerOpenGL::FlushAll() {}
-void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) {
+void RasterizerOpenGL::FlushRegion(CacheAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ if (!addr || !size) {
+ return;
+ }
res_cache.FlushRegion(addr, size);
}
-void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size) {
+void RasterizerOpenGL::InvalidateRegion(CacheAddr addr, u64 size) {
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
+ if (!addr || !size) {
+ return;
+ }
res_cache.InvalidateRegion(addr, size);
shader_cache.InvalidateRegion(addr, size);
global_cache.InvalidateRegion(addr, size);
buffer_cache.InvalidateRegion(addr, size);
}
-void RasterizerOpenGL::FlushAndInvalidateRegion(VAddr addr, u64 size) {
+void RasterizerOpenGL::FlushAndInvalidateRegion(CacheAddr addr, u64 size) {
FlushRegion(addr, size);
InvalidateRegion(addr, size);
}
@@ -781,7 +788,7 @@ bool RasterizerOpenGL::AccelerateDisplay(const Tegra::FramebufferConfig& config,
MICROPROFILE_SCOPE(OpenGL_CacheManagement);
- const auto& surface{res_cache.TryFindFramebufferSurface(framebuffer_addr)};
+ const auto& surface{res_cache.TryFindFramebufferSurface(Memory::GetPointer(framebuffer_addr))};
if (!surface) {
return {};
}
@@ -892,7 +899,7 @@ void RasterizerOpenGL::SetupConstBuffers(Tegra::Engines::Maxwell3D::Regs::Shader
const Shader& shader, GLuint program_handle,
BaseBindings base_bindings) {
MICROPROFILE_SCOPE(OpenGL_UBO);
- const auto& gpu = Core::System::GetInstance().GPU();
+ const auto& gpu = system.GPU();
const auto& maxwell3d = gpu.Maxwell3D();
const auto& shader_stage = maxwell3d.state.shader_stages[static_cast<std::size_t>(stage)];
const auto& entries = shader->GetShaderEntries().const_buffers;
@@ -971,7 +978,7 @@ void RasterizerOpenGL::SetupGlobalRegions(Tegra::Engines::Maxwell3D::Regs::Shade
void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader,
GLuint program_handle, BaseBindings base_bindings) {
MICROPROFILE_SCOPE(OpenGL_Texture);
- const auto& gpu = Core::System::GetInstance().GPU();
+ const auto& gpu = system.GPU();
const auto& maxwell3d = gpu.Maxwell3D();
const auto& entries = shader->GetShaderEntries().samplers;
@@ -998,7 +1005,7 @@ void RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& s
}
void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const bool geometry_shaders_enabled =
regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry));
const std::size_t viewport_count =
@@ -1021,7 +1028,7 @@ void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
void RasterizerOpenGL::SyncClipEnabled(
const std::array<bool, Maxwell::Regs::NumClipDistances>& clip_mask) {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const std::array<bool, Maxwell::Regs::NumClipDistances> reg_state{
regs.clip_distance_enabled.c0 != 0, regs.clip_distance_enabled.c1 != 0,
regs.clip_distance_enabled.c2 != 0, regs.clip_distance_enabled.c3 != 0,
@@ -1038,7 +1045,7 @@ void RasterizerOpenGL::SyncClipCoef() {
}
void RasterizerOpenGL::SyncCullMode() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.cull.enabled = regs.cull.enabled != 0;
@@ -1062,14 +1069,14 @@ void RasterizerOpenGL::SyncCullMode() {
}
void RasterizerOpenGL::SyncPrimitiveRestart() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.primitive_restart.enabled = regs.primitive_restart.enabled;
state.primitive_restart.index = regs.primitive_restart.index;
}
void RasterizerOpenGL::SyncDepthTestState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.depth.test_enabled = regs.depth_test_enable != 0;
state.depth.write_mask = regs.depth_write_enabled ? GL_TRUE : GL_FALSE;
@@ -1081,7 +1088,7 @@ void RasterizerOpenGL::SyncDepthTestState() {
}
void RasterizerOpenGL::SyncStencilTestState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.stencil.test_enabled = regs.stencil_enable != 0;
if (!regs.stencil_enable) {
@@ -1115,7 +1122,7 @@ void RasterizerOpenGL::SyncStencilTestState() {
}
void RasterizerOpenGL::SyncColorMask() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const std::size_t count =
regs.independent_blend_enable ? Tegra::Engines::Maxwell3D::Regs::NumRenderTargets : 1;
for (std::size_t i = 0; i < count; i++) {
@@ -1129,18 +1136,18 @@ void RasterizerOpenGL::SyncColorMask() {
}
void RasterizerOpenGL::SyncMultiSampleState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.multisample_control.alpha_to_coverage = regs.multisample_control.alpha_to_coverage != 0;
state.multisample_control.alpha_to_one = regs.multisample_control.alpha_to_one != 0;
}
void RasterizerOpenGL::SyncFragmentColorClampState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.fragment_color_clamp.enabled = regs.frag_color_clamp != 0;
}
void RasterizerOpenGL::SyncBlendState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.blend_color.red = regs.blend_color.r;
state.blend_color.green = regs.blend_color.g;
@@ -1182,7 +1189,7 @@ void RasterizerOpenGL::SyncBlendState() {
}
void RasterizerOpenGL::SyncLogicOpState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.logic_op.enabled = regs.logic_op.enable != 0;
@@ -1196,7 +1203,7 @@ void RasterizerOpenGL::SyncLogicOpState() {
}
void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
const bool geometry_shaders_enabled =
regs.IsShaderConfigEnabled(static_cast<size_t>(Maxwell::ShaderProgram::Geometry));
const std::size_t viewport_count =
@@ -1218,17 +1225,17 @@ void RasterizerOpenGL::SyncScissorTest(OpenGLState& current_state) {
}
void RasterizerOpenGL::SyncTransformFeedback() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
UNIMPLEMENTED_IF_MSG(regs.tfb_enabled != 0, "Transform feedbacks are not implemented");
}
void RasterizerOpenGL::SyncPointState() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.point.size = regs.point_size;
}
void RasterizerOpenGL::SyncPolygonOffset() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
state.polygon_offset.fill_enable = regs.polygon_offset_fill_enable != 0;
state.polygon_offset.line_enable = regs.polygon_offset_line_enable != 0;
state.polygon_offset.point_enable = regs.polygon_offset_point_enable != 0;
@@ -1238,7 +1245,7 @@ void RasterizerOpenGL::SyncPolygonOffset() {
}
void RasterizerOpenGL::CheckAlphaTests() {
- const auto& regs = Core::System::GetInstance().GPU().Maxwell3D().regs;
+ const auto& regs = system.GPU().Maxwell3D().regs;
UNIMPLEMENTED_IF_MSG(regs.alpha_test_enabled != 0 && regs.rt_control.count > 1,
"Alpha Testing is enabled with more than one rendertarget");
}
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 7e63f8008..30f3e8acb 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -57,9 +57,9 @@ public:
void DrawArrays() override;
void Clear() override;
void FlushAll() override;
- void FlushRegion(VAddr addr, u64 size) override;
- void InvalidateRegion(VAddr addr, u64 size) override;
- void FlushAndInvalidateRegion(VAddr addr, u64 size) override;
+ void FlushRegion(CacheAddr addr, u64 size) override;
+ void InvalidateRegion(CacheAddr addr, u64 size) override;
+ void FlushAndInvalidateRegion(CacheAddr addr, u64 size) override;
bool AccelerateSurfaceCopy(const Tegra::Engines::Fermi2D::Regs::Surface& src,
const Tegra::Engines::Fermi2D::Regs::Surface& dst,
const Common::Rectangle<u32>& src_rect,
@@ -67,7 +67,7 @@ public:
bool AccelerateDisplay(const Tegra::FramebufferConfig& config, VAddr framebuffer_addr,
u32 pixel_stride) override;
bool AccelerateDrawBatch(bool is_indexed) override;
- void UpdatePagesCachedCount(Tegra::GPUVAddr addr, u64 size, int delta) override;
+ void UpdatePagesCachedCount(VAddr addr, u64 size, int delta) override;
void LoadDiskResources(const std::atomic_bool& stop_loading,
const VideoCore::DiskResourceLoadCallback& callback) override;
@@ -215,6 +215,7 @@ private:
GlobalRegionCacheOpenGL global_cache;
Core::Frontend::EmuWindow& emu_window;
+ Core::System& system;
ScreenInfo& screen_info;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index e9eb6e921..0235317c0 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -55,12 +55,11 @@ static void ApplyTextureDefaults(GLuint texture, u32 max_mip_level) {
}
}
-void SurfaceParams::InitCacheParameters(Tegra::GPUVAddr gpu_addr_) {
+void SurfaceParams::InitCacheParameters(GPUVAddr gpu_addr_) {
auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
- const auto cpu_addr{memory_manager.GpuToCpuAddress(gpu_addr_)};
- addr = cpu_addr ? *cpu_addr : 0;
gpu_addr = gpu_addr_;
+ host_ptr = memory_manager.GetPointer(gpu_addr_);
size_in_bytes = SizeInBytesRaw();
if (IsPixelFormatASTC(pixel_format)) {
@@ -223,7 +222,7 @@ std::size_t SurfaceParams::InnerMemorySize(bool force_gl, bool layer_only,
}
/*static*/ SurfaceParams SurfaceParams::CreateForDepthBuffer(
- u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
+ u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format,
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
SurfaceParams params{};
@@ -446,7 +445,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
params.MipBlockDepth(mip_level), 1, params.tile_width_spacing,
- gl_buffer.data() + offset_gl, gl_size, params.addr + offset);
+ gl_buffer.data() + offset_gl, params.host_ptr + offset);
offset += layer_size;
offset_gl += gl_size;
}
@@ -455,7 +454,7 @@ void SwizzleFunc(const MortonSwizzleMode& mode, const SurfaceParams& params,
MortonSwizzle(mode, params.pixel_format, params.MipWidth(mip_level),
params.MipBlockHeight(mip_level), params.MipHeight(mip_level),
params.MipBlockDepth(mip_level), depth, params.tile_width_spacing,
- gl_buffer.data(), gl_buffer.size(), params.addr + offset);
+ gl_buffer.data(), params.host_ptr + offset);
}
}
@@ -513,9 +512,9 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
"reinterpretation but the texture is tiled.");
}
const std::size_t remaining_size = dst_params.size_in_bytes - src_params.size_in_bytes;
-
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
glBufferSubData(GL_PIXEL_PACK_BUFFER, src_params.size_in_bytes, remaining_size,
- Memory::GetPointer(dst_params.addr + src_params.size_in_bytes));
+ memory_manager.GetPointer(dst_params.gpu_addr + src_params.size_in_bytes));
}
glBindBuffer(GL_PIXEL_PACK_BUFFER, 0);
@@ -563,8 +562,14 @@ void RasterizerCacheOpenGL::CopySurface(const Surface& src_surface, const Surfac
}
CachedSurface::CachedSurface(const SurfaceParams& params)
- : params(params), gl_target(SurfaceTargetToGL(params.target)),
- cached_size_in_bytes(params.size_in_bytes) {
+ : params{params}, gl_target{SurfaceTargetToGL(params.target)},
+ cached_size_in_bytes{params.size_in_bytes}, RasterizerCacheObject{params.host_ptr} {
+
+ const auto optional_cpu_addr{
+ Core::System::GetInstance().GPU().MemoryManager().GpuToCpuAddress(params.gpu_addr)};
+ ASSERT_MSG(optional_cpu_addr, "optional_cpu_addr is invalid");
+ cpu_addr = *optional_cpu_addr;
+
texture.Create(gl_target);
// TODO(Rodrigo): Using params.GetRect() returns a different size than using its Mip*(0)
@@ -603,19 +608,7 @@ CachedSurface::CachedSurface(const SurfaceParams& params)
ApplyTextureDefaults(texture.handle, params.max_mip_level);
- OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.addr, params.IdentityString());
-
- // Clamp size to mapped GPU memory region
- // TODO(bunnei): Super Mario Odyssey maps a 0x40000 byte region and then uses it for a 0x80000
- // R32F render buffer. We do not yet know if this is a game bug or something else, but this
- // check is necessary to prevent flushing from overwriting unmapped memory.
-
- auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
- const u64 max_size{memory_manager.GetRegionEnd(params.gpu_addr) - params.gpu_addr};
- if (cached_size_in_bytes > max_size) {
- LOG_ERROR(HW_GPU, "Surface size {} exceeds region size {}", params.size_in_bytes, max_size);
- cached_size_in_bytes = max_size;
- }
+ OpenGL::LabelGLObject(GL_TEXTURE, texture.handle, params.gpu_addr, params.IdentityString());
}
MICROPROFILE_DEFINE(OpenGL_SurfaceLoad, "OpenGL", "Surface Load", MP_RGB(128, 192, 64));
@@ -633,10 +626,9 @@ void CachedSurface::LoadGLBuffer() {
const u32 bpp = params.GetFormatBpp() / 8;
const u32 copy_size = params.width * bpp;
if (params.pitch == copy_size) {
- std::memcpy(gl_buffer[0].data(), Memory::GetPointer(params.addr),
- params.size_in_bytes_gl);
+ std::memcpy(gl_buffer[0].data(), params.host_ptr, params.size_in_bytes_gl);
} else {
- const u8* start = Memory::GetPointer(params.addr);
+ const u8* start{params.host_ptr};
u8* write_to = gl_buffer[0].data();
for (u32 h = params.height; h > 0; h--) {
std::memcpy(write_to, start, copy_size);
@@ -680,8 +672,6 @@ void CachedSurface::FlushGLBuffer() {
glPixelStorei(GL_PACK_ROW_LENGTH, 0);
Tegra::Texture::ConvertFromHostToGuest(gl_buffer[0].data(), params.pixel_format, params.width,
params.height, params.depth, true, true);
- const u8* const texture_src_data = Memory::GetPointer(params.addr);
- ASSERT(texture_src_data);
if (params.is_tiled) {
ASSERT_MSG(params.block_width == 1, "Block width is defined as {} on texture type {}",
params.block_width, static_cast<u32>(params.target));
@@ -691,9 +681,9 @@ void CachedSurface::FlushGLBuffer() {
const u32 bpp = params.GetFormatBpp() / 8;
const u32 copy_size = params.width * bpp;
if (params.pitch == copy_size) {
- std::memcpy(Memory::GetPointer(params.addr), gl_buffer[0].data(), GetSizeInBytes());
+ std::memcpy(params.host_ptr, gl_buffer[0].data(), GetSizeInBytes());
} else {
- u8* start = Memory::GetPointer(params.addr);
+ u8* start{params.host_ptr};
const u8* read_to = gl_buffer[0].data();
for (u32 h = params.height; h > 0; h--) {
std::memcpy(start, read_to, copy_size);
@@ -927,12 +917,12 @@ void RasterizerCacheOpenGL::LoadSurface(const Surface& surface) {
}
Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool preserve_contents) {
- if (params.addr == 0 || params.height * params.width == 0) {
+ if (!params.IsValid()) {
return {};
}
// Look up surface in the cache based on address
- Surface surface{TryGet(params.addr)};
+ Surface surface{TryGet(params.host_ptr)};
if (surface) {
if (surface->GetSurfaceParams().IsCompatibleSurface(params)) {
// Use the cached surface as-is unless it's not synced with memory
@@ -943,7 +933,7 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
// If surface parameters changed and we care about keeping the previous data, recreate
// the surface from the old one
Surface new_surface{RecreateSurface(surface, params)};
- UnregisterSurface(surface);
+ Unregister(surface);
Register(new_surface);
if (new_surface->IsUploaded()) {
RegisterReinterpretSurface(new_surface);
@@ -951,7 +941,7 @@ Surface RasterizerCacheOpenGL::GetSurface(const SurfaceParams& params, bool pres
return new_surface;
} else {
// Delete the old surface before creating a new one to prevent collisions.
- UnregisterSurface(surface);
+ Unregister(surface);
}
}
@@ -981,14 +971,16 @@ void RasterizerCacheOpenGL::FastLayeredCopySurface(const Surface& src_surface,
const Surface& dst_surface) {
const auto& init_params{src_surface->GetSurfaceParams()};
const auto& dst_params{dst_surface->GetSurfaceParams()};
- VAddr address = init_params.addr;
- const std::size_t layer_size = dst_params.LayerMemorySize();
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ GPUVAddr address{init_params.gpu_addr};
+ const std::size_t layer_size{dst_params.LayerMemorySize()};
for (u32 layer = 0; layer < dst_params.depth; layer++) {
for (u32 mipmap = 0; mipmap < dst_params.max_mip_level; mipmap++) {
- const VAddr sub_address = address + dst_params.GetMipmapLevelOffset(mipmap);
- const Surface& copy = TryGet(sub_address);
- if (!copy)
+ const GPUVAddr sub_address{address + dst_params.GetMipmapLevelOffset(mipmap)};
+ const Surface& copy{TryGet(memory_manager.GetPointer(sub_address))};
+ if (!copy) {
continue;
+ }
const auto& src_params{copy->GetSurfaceParams()};
const u32 width{std::min(src_params.width, dst_params.MipWidth(mipmap))};
const u32 height{std::min(src_params.height, dst_params.MipHeight(mipmap))};
@@ -1163,7 +1155,8 @@ void RasterizerCacheOpenGL::AccurateCopySurface(const Surface& src_surface,
const auto& dst_params{dst_surface->GetSurfaceParams()};
// Flush enough memory for both the source and destination surface
- FlushRegion(src_params.addr, std::max(src_params.MemorySize(), dst_params.MemorySize()));
+ FlushRegion(ToCacheAddr(src_params.host_ptr),
+ std::max(src_params.MemorySize(), dst_params.MemorySize()));
LoadSurface(dst_surface);
}
@@ -1215,8 +1208,8 @@ Surface RasterizerCacheOpenGL::RecreateSurface(const Surface& old_surface,
return new_surface;
}
-Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(VAddr addr) const {
- return TryGet(addr);
+Surface RasterizerCacheOpenGL::TryFindFramebufferSurface(const u8* host_ptr) const {
+ return TryGet(host_ptr);
}
void RasterizerCacheOpenGL::ReserveSurface(const Surface& surface) {
@@ -1243,9 +1236,9 @@ static std::optional<u32> TryFindBestMipMap(std::size_t memory, const SurfacePar
return {};
}
-static std::optional<u32> TryFindBestLayer(VAddr addr, const SurfaceParams params, u32 mipmap) {
- const std::size_t size = params.LayerMemorySize();
- VAddr start = params.addr + params.GetMipmapLevelOffset(mipmap);
+static std::optional<u32> TryFindBestLayer(GPUVAddr addr, const SurfaceParams params, u32 mipmap) {
+ const std::size_t size{params.LayerMemorySize()};
+ GPUVAddr start{params.gpu_addr + params.GetMipmapLevelOffset(mipmap)};
for (u32 i = 0; i < params.depth; i++) {
if (start == addr) {
return {i};
@@ -1267,7 +1260,7 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa
src_params.height == dst_params.MipHeight(*level) &&
src_params.block_height >= dst_params.MipBlockHeight(*level)) {
const std::optional<u32> slot =
- TryFindBestLayer(render_surface->GetAddr(), dst_params, *level);
+ TryFindBestLayer(render_surface->GetSurfaceParams().gpu_addr, dst_params, *level);
if (slot.has_value()) {
glCopyImageSubData(render_surface->Texture().handle,
SurfaceTargetToGL(src_params.target), 0, 0, 0, 0,
@@ -1283,8 +1276,8 @@ static bool LayerFitReinterpretSurface(RasterizerCacheOpenGL& cache, const Surfa
}
static bool IsReinterpretInvalid(const Surface render_surface, const Surface blitted_surface) {
- const VAddr bound1 = blitted_surface->GetAddr() + blitted_surface->GetMemorySize();
- const VAddr bound2 = render_surface->GetAddr() + render_surface->GetMemorySize();
+ const VAddr bound1 = blitted_surface->GetCpuAddr() + blitted_surface->GetMemorySize();
+ const VAddr bound2 = render_surface->GetCpuAddr() + render_surface->GetMemorySize();
if (bound2 > bound1)
return true;
const auto& dst_params = blitted_surface->GetSurfaceParams();
@@ -1302,12 +1295,12 @@ static bool IsReinterpretInvalidSecond(const Surface render_surface,
bool RasterizerCacheOpenGL::PartialReinterpretSurface(Surface triggering_surface,
Surface intersect) {
if (IsReinterpretInvalid(triggering_surface, intersect)) {
- UnregisterSurface(intersect);
+ Unregister(intersect);
return false;
}
if (!LayerFitReinterpretSurface(*this, triggering_surface, intersect)) {
if (IsReinterpretInvalidSecond(triggering_surface, intersect)) {
- UnregisterSurface(intersect);
+ Unregister(intersect);
return false;
}
FlushObject(intersect);
@@ -1327,7 +1320,8 @@ void RasterizerCacheOpenGL::SignalPreDrawCall() {
void RasterizerCacheOpenGL::SignalPostDrawCall() {
for (u32 i = 0; i < Maxwell::NumRenderTargets; i++) {
if (current_color_buffers[i] != nullptr) {
- Surface intersect = CollideOnReinterpretedSurface(current_color_buffers[i]->GetAddr());
+ Surface intersect =
+ CollideOnReinterpretedSurface(current_color_buffers[i]->GetCacheAddr());
if (intersect != nullptr) {
PartialReinterpretSurface(current_color_buffers[i], intersect);
texception = true;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index 9cf6f50be..c644271d0 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -109,6 +109,11 @@ struct SurfaceParams {
return size;
}
+ /// Returns true if the parameters constitute a valid rasterizer surface.
+ bool IsValid() const {
+ return gpu_addr && host_ptr && height && width;
+ }
+
/// Returns the exact size of the memory occupied by a layer in a texture in VRAM, including
/// mipmaps.
std::size_t LayerMemorySize() const {
@@ -210,7 +215,7 @@ struct SurfaceParams {
/// Creates SurfaceParams for a depth buffer configuration
static SurfaceParams CreateForDepthBuffer(
- u32 zeta_width, u32 zeta_height, Tegra::GPUVAddr zeta_address, Tegra::DepthFormat format,
+ u32 zeta_width, u32 zeta_height, GPUVAddr zeta_address, Tegra::DepthFormat format,
u32 block_width, u32 block_height, u32 block_depth,
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
@@ -232,7 +237,7 @@ struct SurfaceParams {
}
/// Initializes parameters for caching, should be called after everything has been initialized
- void InitCacheParameters(Tegra::GPUVAddr gpu_addr);
+ void InitCacheParameters(GPUVAddr gpu_addr);
std::string TargetName() const {
switch (target) {
@@ -296,8 +301,8 @@ struct SurfaceParams {
bool is_array;
bool srgb_conversion;
// Parameters used for caching
- VAddr addr;
- Tegra::GPUVAddr gpu_addr;
+ u8* host_ptr;
+ GPUVAddr gpu_addr;
std::size_t size_in_bytes;
std::size_t size_in_bytes_gl;
@@ -345,10 +350,10 @@ class RasterizerOpenGL;
class CachedSurface final : public RasterizerCacheObject {
public:
- CachedSurface(const SurfaceParams& params);
+ explicit CachedSurface(const SurfaceParams& params);
- VAddr GetAddr() const override {
- return params.addr;
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
@@ -432,6 +437,7 @@ private:
std::size_t memory_size;
bool reinterpreted = false;
bool must_reload = false;
+ VAddr cpu_addr{};
};
class RasterizerCacheOpenGL final : public RasterizerCache<Surface> {
@@ -449,7 +455,7 @@ public:
Surface GetColorBufferSurface(std::size_t index, bool preserve_contents);
/// Tries to find a framebuffer using on the provided CPU address
- Surface TryFindFramebufferSurface(VAddr addr) const;
+ Surface TryFindFramebufferSurface(const u8* host_ptr) const;
/// Copies the contents of one surface to another
void FermiCopySurface(const Tegra::Engines::Fermi2D::Regs::Surface& src_config,
@@ -506,12 +512,12 @@ private:
std::array<Surface, Maxwell::NumRenderTargets> current_color_buffers;
Surface last_depth_buffer;
- using SurfaceIntervalCache = boost::icl::interval_map<VAddr, Surface>;
+ using SurfaceIntervalCache = boost::icl::interval_map<CacheAddr, Surface>;
using SurfaceInterval = typename SurfaceIntervalCache::interval_type;
static auto GetReinterpretInterval(const Surface& object) {
- return SurfaceInterval::right_open(object->GetAddr() + 1,
- object->GetAddr() + object->GetMemorySize() - 1);
+ return SurfaceInterval::right_open(object->GetCacheAddr() + 1,
+ object->GetCacheAddr() + object->GetMemorySize() - 1);
}
// Reinterpreted surfaces are very fragil as the game may keep rendering into them.
@@ -523,7 +529,7 @@ private:
reinterpret_surface->MarkReinterpreted();
}
- Surface CollideOnReinterpretedSurface(VAddr addr) const {
+ Surface CollideOnReinterpretedSurface(CacheAddr addr) const {
const SurfaceInterval interval{addr};
for (auto& pair :
boost::make_iterator_range(reinterpreted_surfaces.equal_range(interval))) {
@@ -532,13 +538,17 @@ private:
return nullptr;
}
+ void Register(const Surface& object) {
+ RasterizerCache<Surface>::Register(object);
+ }
+
/// Unregisters an object from the cache
- void UnregisterSurface(const Surface& object) {
+ void Unregister(const Surface& object) {
if (object->IsReinterpreted()) {
auto interval = GetReinterpretInterval(object);
reinterpreted_surfaces.erase(interval);
}
- Unregister(object);
+ RasterizerCache<Surface>::Unregister(object);
}
};
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index 4883e4f62..1f8eca6f0 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -32,19 +32,16 @@ struct UnspecializedShader {
namespace {
/// Gets the address for the specified shader stage program
-VAddr GetShaderAddress(Maxwell::ShaderProgram program) {
- const auto& gpu = Core::System::GetInstance().GPU().Maxwell3D();
- const auto& shader_config = gpu.regs.shader_config[static_cast<std::size_t>(program)];
- const auto address = gpu.memory_manager.GpuToCpuAddress(gpu.regs.code_address.CodeAddress() +
- shader_config.offset);
- ASSERT_MSG(address, "Invalid GPU address");
- return *address;
+GPUVAddr GetShaderAddress(Maxwell::ShaderProgram program) {
+ const auto& gpu{Core::System::GetInstance().GPU().Maxwell3D()};
+ const auto& shader_config{gpu.regs.shader_config[static_cast<std::size_t>(program)]};
+ return gpu.regs.code_address.CodeAddress() + shader_config.offset;
}
/// Gets the shader program code from memory for the specified address
-ProgramCode GetShaderCode(VAddr addr) {
+ProgramCode GetShaderCode(const u8* host_ptr) {
ProgramCode program_code(VideoCommon::Shader::MAX_PROGRAM_LENGTH);
- Memory::ReadBlock(addr, program_code.data(), program_code.size() * sizeof(u64));
+ std::memcpy(program_code.data(), host_ptr, program_code.size() * sizeof(u64));
return program_code;
}
@@ -214,12 +211,13 @@ std::set<GLenum> GetSupportedFormats() {
} // namespace
-CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- ProgramCode&& program_code, ProgramCode&& program_code_b)
- : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
- disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
+ ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr)
+ : host_ptr{host_ptr}, cpu_addr{cpu_addr}, unique_identifier{unique_identifier},
+ program_type{program_type}, disk_cache{disk_cache},
+ precompiled_programs{precompiled_programs}, RasterizerCacheObject{host_ptr} {
const std::size_t code_size = CalculateProgramSize(program_code);
const std::size_t code_size_b =
@@ -243,12 +241,13 @@ CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderPro
disk_cache.SaveRaw(raw);
}
-CachedShader::CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+CachedShader::CachedShader(VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- GLShader::ProgramResult result)
- : addr{addr}, unique_identifier{unique_identifier}, program_type{program_type},
- disk_cache{disk_cache}, precompiled_programs{precompiled_programs} {
+ GLShader::ProgramResult result, u8* host_ptr)
+ : cpu_addr{cpu_addr}, unique_identifier{unique_identifier}, program_type{program_type},
+ disk_cache{disk_cache}, precompiled_programs{precompiled_programs}, RasterizerCacheObject{
+ host_ptr} {
code = std::move(result.first);
entries = result.second;
@@ -271,7 +270,7 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(GLenum primitive
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
}
- LabelGLObject(GL_PROGRAM, program->handle, addr);
+ LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
}
handle = program->handle;
@@ -323,7 +322,7 @@ GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program, BaseBind
disk_cache.SaveUsage(GetUsage(primitive_mode, base_bindings));
}
- LabelGLObject(GL_PROGRAM, target_program->handle, addr, debug_name);
+ LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name);
return target_program->handle;
};
@@ -486,29 +485,32 @@ Shader ShaderCacheOpenGL::GetStageProgram(Maxwell::ShaderProgram program) {
return last_shaders[static_cast<u32>(program)];
}
- const VAddr program_addr{GetShaderAddress(program)};
+ auto& memory_manager{Core::System::GetInstance().GPU().MemoryManager()};
+ const GPUVAddr program_addr{GetShaderAddress(program)};
// Look up shader in the cache based on address
- Shader shader{TryGet(program_addr)};
+ const auto& host_ptr{memory_manager.GetPointer(program_addr)};
+ Shader shader{TryGet(host_ptr)};
if (!shader) {
// No shader found - create a new one
- ProgramCode program_code = GetShaderCode(program_addr);
+ ProgramCode program_code{GetShaderCode(host_ptr)};
ProgramCode program_code_b;
if (program == Maxwell::ShaderProgram::VertexA) {
- program_code_b = GetShaderCode(GetShaderAddress(Maxwell::ShaderProgram::VertexB));
+ program_code_b = GetShaderCode(
+ memory_manager.GetPointer(GetShaderAddress(Maxwell::ShaderProgram::VertexB)));
}
const u64 unique_identifier = GetUniqueIdentifier(program, program_code, program_code_b);
-
+ const VAddr cpu_addr{*memory_manager.GpuToCpuAddress(program_addr)};
const auto found = precompiled_shaders.find(unique_identifier);
if (found != precompiled_shaders.end()) {
shader =
- std::make_shared<CachedShader>(program_addr, unique_identifier, program, disk_cache,
- precompiled_programs, found->second);
+ std::make_shared<CachedShader>(cpu_addr, unique_identifier, program, disk_cache,
+ precompiled_programs, found->second, host_ptr);
} else {
shader = std::make_shared<CachedShader>(
- program_addr, unique_identifier, program, disk_cache, precompiled_programs,
- std::move(program_code), std::move(program_code_b));
+ cpu_addr, unique_identifier, program, disk_cache, precompiled_programs,
+ std::move(program_code), std::move(program_code_b), host_ptr);
}
Register(shader);
}
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 97eed192f..fd1c85115 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -39,18 +39,18 @@ using PrecompiledShaders = std::unordered_map<u64, GLShader::ProgramResult>;
class CachedShader final : public RasterizerCacheObject {
public:
- explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+ explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- ProgramCode&& program_code, ProgramCode&& program_code_b);
+ ProgramCode&& program_code, ProgramCode&& program_code_b, u8* host_ptr);
- explicit CachedShader(VAddr addr, u64 unique_identifier, Maxwell::ShaderProgram program_type,
- ShaderDiskCacheOpenGL& disk_cache,
+ explicit CachedShader(VAddr cpu_addr, u64 unique_identifier,
+ Maxwell::ShaderProgram program_type, ShaderDiskCacheOpenGL& disk_cache,
const PrecompiledPrograms& precompiled_programs,
- GLShader::ProgramResult result);
+ GLShader::ProgramResult result, u8* host_ptr);
- VAddr GetAddr() const override {
- return addr;
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
@@ -91,7 +91,8 @@ private:
ShaderDiskCacheUsage GetUsage(GLenum primitive_mode, BaseBindings base_bindings) const;
- VAddr addr{};
+ u8* host_ptr{};
+ VAddr cpu_addr{};
u64 unique_identifier{};
Maxwell::ShaderProgram program_type{};
ShaderDiskCacheOpenGL& disk_cache;
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 8b510b6ae..5e3d862c6 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -164,12 +164,13 @@ void RendererOpenGL::LoadFBToScreenInfo(const Tegra::FramebufferConfig& framebuf
// Reset the screen info's display texture to its own permanent texture
screen_info.display_texture = screen_info.texture.resource.handle;
- Memory::RasterizerFlushVirtualRegion(framebuffer_addr, size_in_bytes,
- Memory::FlushMode::Flush);
+ rasterizer->FlushRegion(ToCacheAddr(Memory::GetPointer(framebuffer_addr)), size_in_bytes);
- VideoCore::MortonCopyPixels128(framebuffer.width, framebuffer.height, bytes_per_pixel, 4,
- Memory::GetPointer(framebuffer_addr),
- gl_framebuffer_data.data(), true);
+ constexpr u32 linear_bpp = 4;
+ VideoCore::MortonCopyPixels128(VideoCore::MortonSwizzleMode::MortonToLinear,
+ framebuffer.width, framebuffer.height, bytes_per_pixel,
+ linear_bpp, Memory::GetPointer(framebuffer_addr),
+ gl_framebuffer_data.data());
glPixelStorei(GL_UNPACK_ROW_LENGTH, static_cast<GLint>(framebuffer.stride));
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
index 4a33a6c84..eac51ecb3 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.cpp
@@ -17,6 +17,11 @@
namespace Vulkan {
+CachedBufferEntry::CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset,
+ std::size_t alignment, u8* host_ptr)
+ : cpu_addr{cpu_addr}, size{size}, offset{offset}, alignment{alignment}, RasterizerCacheObject{
+ host_ptr} {}
+
VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager,
VideoCore::RasterizerInterface& rasterizer, const VKDevice& device,
VKMemoryManager& memory_manager, VKScheduler& scheduler, u64 size)
@@ -34,19 +39,20 @@ VKBufferCache::VKBufferCache(Tegra::MemoryManager& tegra_memory_manager,
VKBufferCache::~VKBufferCache() = default;
-u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 alignment,
- bool cache) {
+u64 VKBufferCache::UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment, bool cache) {
const auto cpu_addr{tegra_memory_manager.GpuToCpuAddress(gpu_addr)};
- ASSERT(cpu_addr);
+ ASSERT_MSG(cpu_addr, "Invalid GPU address");
// Cache management is a big overhead, so only cache entries with a given size.
// TODO: Figure out which size is the best for given games.
cache &= size >= 2048;
+ const auto& host_ptr{Memory::GetPointer(*cpu_addr)};
if (cache) {
- if (auto entry = TryGet(*cpu_addr); entry) {
- if (entry->size >= size && entry->alignment == alignment) {
- return entry->offset;
+ auto entry = TryGet(host_ptr);
+ if (entry) {
+ if (entry->GetSize() >= size && entry->GetAlignment() == alignment) {
+ return entry->GetOffset();
}
Unregister(entry);
}
@@ -55,17 +61,17 @@ u64 VKBufferCache::UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64
AlignBuffer(alignment);
const u64 uploaded_offset = buffer_offset;
- Memory::ReadBlock(*cpu_addr, buffer_ptr, size);
+ if (!host_ptr) {
+ return uploaded_offset;
+ }
+ std::memcpy(buffer_ptr, host_ptr, size);
buffer_ptr += size;
buffer_offset += size;
if (cache) {
- auto entry = std::make_shared<CachedBufferEntry>();
- entry->offset = uploaded_offset;
- entry->size = size;
- entry->alignment = alignment;
- entry->addr = *cpu_addr;
+ auto entry = std::make_shared<CachedBufferEntry>(*cpu_addr, size, uploaded_offset,
+ alignment, host_ptr);
Register(entry);
}
diff --git a/src/video_core/renderer_vulkan/vk_buffer_cache.h b/src/video_core/renderer_vulkan/vk_buffer_cache.h
index d8e916f31..08b786aad 100644
--- a/src/video_core/renderer_vulkan/vk_buffer_cache.h
+++ b/src/video_core/renderer_vulkan/vk_buffer_cache.h
@@ -24,22 +24,39 @@ class VKFence;
class VKMemoryManager;
class VKStreamBuffer;
-struct CachedBufferEntry final : public RasterizerCacheObject {
- VAddr GetAddr() const override {
- return addr;
+class CachedBufferEntry final : public RasterizerCacheObject {
+public:
+ explicit CachedBufferEntry(VAddr cpu_addr, std::size_t size, u64 offset, std::size_t alignment,
+ u8* host_ptr);
+
+ VAddr GetCpuAddr() const override {
+ return cpu_addr;
}
std::size_t GetSizeInBytes() const override {
return size;
}
+ std::size_t GetSize() const {
+ return size;
+ }
+
+ u64 GetOffset() const {
+ return offset;
+ }
+
+ std::size_t GetAlignment() const {
+ return alignment;
+ }
+
// We do not have to flush this cache as things in it are never modified by us.
void Flush() override {}
- VAddr addr;
- std::size_t size;
- u64 offset;
- std::size_t alignment;
+private:
+ VAddr cpu_addr{};
+ std::size_t size{};
+ u64 offset{};
+ std::size_t alignment{};
};
class VKBufferCache final : public RasterizerCache<std::shared_ptr<CachedBufferEntry>> {
@@ -51,8 +68,7 @@ public:
/// Uploads data from a guest GPU address. Returns host's buffer offset where it's been
/// allocated.
- u64 UploadMemory(Tegra::GPUVAddr gpu_addr, std::size_t size, u64 alignment = 4,
- bool cache = true);
+ u64 UploadMemory(GPUVAddr gpu_addr, std::size_t size, u64 alignment = 4, bool cache = true);
/// Uploads from a host memory. Returns host's buffer offset where it's been allocated.
u64 UploadHostMemory(const u8* raw_pointer, std::size_t size, u64 alignment = 4);
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.cpp b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
new file mode 100644
index 000000000..ed3178f09
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.cpp
@@ -0,0 +1,81 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <cstring>
+#include <optional>
+#include <unordered_map>
+
+#include "common/assert.h"
+#include "common/cityhash.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/renderer_vulkan/maxwell_to_vk.h"
+#include "video_core/renderer_vulkan/vk_sampler_cache.h"
+#include "video_core/textures/texture.h"
+
+namespace Vulkan {
+
+static std::optional<vk::BorderColor> TryConvertBorderColor(std::array<float, 4> color) {
+ // TODO(Rodrigo): Manage integer border colors
+ if (color == std::array<float, 4>{0, 0, 0, 0}) {
+ return vk::BorderColor::eFloatTransparentBlack;
+ } else if (color == std::array<float, 4>{0, 0, 0, 1}) {
+ return vk::BorderColor::eFloatOpaqueBlack;
+ } else if (color == std::array<float, 4>{1, 1, 1, 1}) {
+ return vk::BorderColor::eFloatOpaqueWhite;
+ } else {
+ return {};
+ }
+}
+
+std::size_t SamplerCacheKey::Hash() const {
+ static_assert(sizeof(raw) % sizeof(u64) == 0);
+ return static_cast<std::size_t>(
+ Common::CityHash64(reinterpret_cast<const char*>(raw.data()), sizeof(raw) / sizeof(u64)));
+}
+
+bool SamplerCacheKey::operator==(const SamplerCacheKey& rhs) const {
+ return raw == rhs.raw;
+}
+
+VKSamplerCache::VKSamplerCache(const VKDevice& device) : device{device} {}
+
+VKSamplerCache::~VKSamplerCache() = default;
+
+vk::Sampler VKSamplerCache::GetSampler(const Tegra::Texture::TSCEntry& tsc) {
+ const auto [entry, is_cache_miss] = cache.try_emplace(SamplerCacheKey{tsc});
+ auto& sampler = entry->second;
+ if (is_cache_miss) {
+ sampler = CreateSampler(tsc);
+ }
+ return *sampler;
+}
+
+UniqueSampler VKSamplerCache::CreateSampler(const Tegra::Texture::TSCEntry& tsc) {
+ const float max_anisotropy = tsc.GetMaxAnisotropy();
+ const bool has_anisotropy = max_anisotropy > 1.0f;
+
+ const auto border_color = tsc.GetBorderColor();
+ const auto vk_border_color = TryConvertBorderColor(border_color);
+ UNIMPLEMENTED_IF_MSG(!vk_border_color, "Unimplemented border color {} {} {} {}",
+ border_color[0], border_color[1], border_color[2], border_color[3]);
+
+ constexpr bool unnormalized_coords = false;
+
+ const vk::SamplerCreateInfo sampler_ci(
+ {}, MaxwellToVK::Sampler::Filter(tsc.mag_filter),
+ MaxwellToVK::Sampler::Filter(tsc.min_filter),
+ MaxwellToVK::Sampler::MipmapMode(tsc.mipmap_filter),
+ MaxwellToVK::Sampler::WrapMode(tsc.wrap_u), MaxwellToVK::Sampler::WrapMode(tsc.wrap_v),
+ MaxwellToVK::Sampler::WrapMode(tsc.wrap_p), tsc.GetLodBias(), has_anisotropy,
+ max_anisotropy, tsc.depth_compare_enabled,
+ MaxwellToVK::Sampler::DepthCompareFunction(tsc.depth_compare_func), tsc.GetMinLod(),
+ tsc.GetMaxLod(), vk_border_color.value_or(vk::BorderColor::eFloatTransparentBlack),
+ unnormalized_coords);
+
+ const auto& dld = device.GetDispatchLoader();
+ const auto dev = device.GetLogical();
+ return dev.createSamplerUnique(sampler_ci, nullptr, dld);
+}
+
+} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_sampler_cache.h b/src/video_core/renderer_vulkan/vk_sampler_cache.h
new file mode 100644
index 000000000..c6394dc87
--- /dev/null
+++ b/src/video_core/renderer_vulkan/vk_sampler_cache.h
@@ -0,0 +1,56 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <unordered_map>
+
+#include "common/common_types.h"
+#include "video_core/renderer_vulkan/declarations.h"
+#include "video_core/textures/texture.h"
+
+namespace Vulkan {
+
+class VKDevice;
+
+struct SamplerCacheKey final : public Tegra::Texture::TSCEntry {
+ std::size_t Hash() const;
+
+ bool operator==(const SamplerCacheKey& rhs) const;
+
+ bool operator!=(const SamplerCacheKey& rhs) const {
+ return !operator==(rhs);
+ }
+};
+
+} // namespace Vulkan
+
+namespace std {
+
+template <>
+struct hash<Vulkan::SamplerCacheKey> {
+ std::size_t operator()(const Vulkan::SamplerCacheKey& k) const noexcept {
+ return k.Hash();
+ }
+};
+
+} // namespace std
+
+namespace Vulkan {
+
+class VKSamplerCache {
+public:
+ explicit VKSamplerCache(const VKDevice& device);
+ ~VKSamplerCache();
+
+ vk::Sampler GetSampler(const Tegra::Texture::TSCEntry& tsc);
+
+private:
+ UniqueSampler CreateSampler(const Tegra::Texture::TSCEntry& tsc);
+
+ const VKDevice& device;
+ std::unordered_map<SamplerCacheKey, UniqueSampler> cache;
+};
+
+} // namespace Vulkan
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp
index cad7340f5..995d0e068 100644
--- a/src/video_core/textures/decoders.cpp
+++ b/src/video_core/textures/decoders.cpp
@@ -6,7 +6,6 @@
#include <cstring>
#include "common/alignment.h"
#include "common/assert.h"
-#include "core/memory.h"
#include "video_core/gpu.h"
#include "video_core/textures/decoders.h"
#include "video_core/textures/texture.h"
@@ -230,18 +229,18 @@ u32 BytesPerPixel(TextureFormat format) {
}
}
-void UnswizzleTexture(u8* const unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
+void UnswizzleTexture(u8* const unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
u32 bytes_per_pixel, u32 width, u32 height, u32 depth, u32 block_height,
u32 block_depth, u32 width_spacing) {
CopySwizzledData((width + tile_size_x - 1) / tile_size_x,
(height + tile_size_y - 1) / tile_size_y, depth, bytes_per_pixel,
- bytes_per_pixel, Memory::GetPointer(address), unswizzled_data, true,
- block_height, block_depth, width_spacing);
+ bytes_per_pixel, address, unswizzled_data, true, block_height, block_depth,
+ width_spacing);
}
-std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
- u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
- u32 block_height, u32 block_depth, u32 width_spacing) {
+std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
+ u32 width, u32 height, u32 depth, u32 block_height,
+ u32 block_depth, u32 width_spacing) {
std::vector<u8> unswizzled_data(width * height * depth * bytes_per_pixel);
UnswizzleTexture(unswizzled_data.data(), address, tile_size_x, tile_size_y, bytes_per_pixel,
width, height, depth, block_height, block_depth, width_spacing);
@@ -249,8 +248,7 @@ std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y
}
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height) {
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height) {
const u32 image_width_in_gobs{(swizzled_width * bytes_per_pixel + (gob_size_x - 1)) /
gob_size_x};
for (u32 line = 0; line < subrect_height; ++line) {
@@ -262,17 +260,17 @@ void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32
const u32 gob_address =
gob_address_y + (x * bytes_per_pixel / gob_size_x) * gob_size * block_height;
const u32 swizzled_offset = gob_address + table[(x * bytes_per_pixel) % gob_size_x];
- const VAddr source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
- const VAddr dest_addr = swizzled_data + swizzled_offset;
+ u8* source_line = unswizzled_data + line * source_pitch + x * bytes_per_pixel;
+ u8* dest_addr = swizzled_data + swizzled_offset;
- Memory::CopyBlock(dest_addr, source_line, bytes_per_pixel);
+ std::memcpy(dest_addr, source_line, bytes_per_pixel);
}
}
}
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height, u32 offset_x, u32 offset_y) {
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
+ u32 offset_x, u32 offset_y) {
for (u32 line = 0; line < subrect_height; ++line) {
const u32 y2 = line + offset_y;
const u32 gob_address_y = (y2 / (gob_size_y * block_height)) * gob_size * block_height +
@@ -282,10 +280,10 @@ void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32
const u32 x2 = (x + offset_x) * bytes_per_pixel;
const u32 gob_address = gob_address_y + (x2 / gob_size_x) * gob_size * block_height;
const u32 swizzled_offset = gob_address + table[x2 % gob_size_x];
- const VAddr dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
- const VAddr source_addr = swizzled_data + swizzled_offset;
+ u8* dest_line = unswizzled_data + line * dest_pitch + x * bytes_per_pixel;
+ u8* source_addr = swizzled_data + swizzled_offset;
- Memory::CopyBlock(dest_line, source_addr, bytes_per_pixel);
+ std::memcpy(dest_line, source_addr, bytes_per_pixel);
}
}
}
diff --git a/src/video_core/textures/decoders.h b/src/video_core/textures/decoders.h
index 65df86890..e078fa274 100644
--- a/src/video_core/textures/decoders.h
+++ b/src/video_core/textures/decoders.h
@@ -17,14 +17,14 @@ inline std::size_t GetGOBSize() {
}
/// Unswizzles a swizzled texture without changing its format.
-void UnswizzleTexture(u8* unswizzled_data, VAddr address, u32 tile_size_x, u32 tile_size_y,
+void UnswizzleTexture(u8* unswizzled_data, u8* address, u32 tile_size_x, u32 tile_size_y,
u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
u32 block_height = TICEntry::DefaultBlockHeight,
u32 block_depth = TICEntry::DefaultBlockHeight, u32 width_spacing = 0);
/// Unswizzles a swizzled texture without changing its format.
-std::vector<u8> UnswizzleTexture(VAddr address, u32 tile_size_x, u32 tile_size_y,
- u32 bytes_per_pixel, u32 width, u32 height, u32 depth,
+std::vector<u8> UnswizzleTexture(u8* address, u32 tile_size_x, u32 tile_size_y, u32 bytes_per_pixel,
+ u32 width, u32 height, u32 depth,
u32 block_height = TICEntry::DefaultBlockHeight,
u32 block_depth = TICEntry::DefaultBlockHeight,
u32 width_spacing = 0);
@@ -44,12 +44,11 @@ std::size_t CalculateSize(bool tiled, u32 bytes_per_pixel, u32 width, u32 height
/// Copies an untiled subrectangle into a tiled surface.
void SwizzleSubrect(u32 subrect_width, u32 subrect_height, u32 source_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height);
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height);
/// Copies a tiled subrectangle into a linear surface.
void UnswizzleSubrect(u32 subrect_width, u32 subrect_height, u32 dest_pitch, u32 swizzled_width,
- u32 bytes_per_pixel, VAddr swizzled_data, VAddr unswizzled_data,
- u32 block_height, u32 offset_x, u32 offset_y);
+ u32 bytes_per_pixel, u8* swizzled_data, u8* unswizzled_data, u32 block_height,
+ u32 offset_x, u32 offset_y);
} // namespace Tegra::Texture
diff --git a/src/video_core/textures/texture.h b/src/video_core/textures/texture.h
index 8c278c0e2..93ecc6e31 100644
--- a/src/video_core/textures/texture.h
+++ b/src/video_core/textures/texture.h
@@ -283,31 +283,36 @@ enum class TextureMipmapFilter : u32 {
struct TSCEntry {
union {
- BitField<0, 3, WrapMode> wrap_u;
- BitField<3, 3, WrapMode> wrap_v;
- BitField<6, 3, WrapMode> wrap_p;
- BitField<9, 1, u32> depth_compare_enabled;
- BitField<10, 3, DepthCompareFunc> depth_compare_func;
- BitField<13, 1, u32> srgb_conversion;
- BitField<20, 3, u32> max_anisotropy;
+ struct {
+ union {
+ BitField<0, 3, WrapMode> wrap_u;
+ BitField<3, 3, WrapMode> wrap_v;
+ BitField<6, 3, WrapMode> wrap_p;
+ BitField<9, 1, u32> depth_compare_enabled;
+ BitField<10, 3, DepthCompareFunc> depth_compare_func;
+ BitField<13, 1, u32> srgb_conversion;
+ BitField<20, 3, u32> max_anisotropy;
+ };
+ union {
+ BitField<0, 2, TextureFilter> mag_filter;
+ BitField<4, 2, TextureFilter> min_filter;
+ BitField<6, 2, TextureMipmapFilter> mipmap_filter;
+ BitField<9, 1, u32> cubemap_interface_filtering;
+ BitField<12, 13, u32> mip_lod_bias;
+ };
+ union {
+ BitField<0, 12, u32> min_lod_clamp;
+ BitField<12, 12, u32> max_lod_clamp;
+ BitField<24, 8, u32> srgb_border_color_r;
+ };
+ union {
+ BitField<12, 8, u32> srgb_border_color_g;
+ BitField<20, 8, u32> srgb_border_color_b;
+ };
+ std::array<f32, 4> border_color;
+ };
+ std::array<u8, 0x20> raw;
};
- union {
- BitField<0, 2, TextureFilter> mag_filter;
- BitField<4, 2, TextureFilter> min_filter;
- BitField<6, 2, TextureMipmapFilter> mipmap_filter;
- BitField<9, 1, u32> cubemap_interface_filtering;
- BitField<12, 13, u32> mip_lod_bias;
- };
- union {
- BitField<0, 12, u32> min_lod_clamp;
- BitField<12, 12, u32> max_lod_clamp;
- BitField<24, 8, u32> srgb_border_color_r;
- };
- union {
- BitField<12, 8, u32> srgb_border_color_g;
- BitField<20, 8, u32> srgb_border_color_b;
- };
- std::array<f32, 4> border_color;
float GetMaxAnisotropy() const {
return static_cast<float>(1U << max_anisotropy);
@@ -324,7 +329,7 @@ struct TSCEntry {
float GetLodBias() const {
// Sign extend the 13-bit value.
constexpr u32 mask = 1U << (13 - 1);
- return static_cast<float>((mip_lod_bias ^ mask) - mask) / 256.0f;
+ return static_cast<s32>((mip_lod_bias ^ mask) - mask) / 256.0f;
}
std::array<float, 4> GetBorderColor() const {
diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp
index 3b070bfbb..05ad19e1d 100644
--- a/src/yuzu/bootmanager.cpp
+++ b/src/yuzu/bootmanager.cpp
@@ -24,8 +24,6 @@ void EmuThread::run() {
MicroProfileOnThreadCreate("EmuThread");
- stop_run = false;
-
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
@@ -40,7 +38,7 @@ void EmuThread::run() {
render_window->DoneCurrent();
}
- // holds whether the cpu was running during the last iteration,
+ // Holds whether the cpu was running during the last iteration,
// so that the DebugModeLeft signal can be emitted before the
// next execution step
bool was_active = false;
@@ -123,7 +121,6 @@ GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
setAttribute(Qt::WA_AcceptTouchEvents);
InputCommon::Init();
- InputCommon::StartJoystickEventHandler();
connect(this, &GRenderWindow::FirstFrameDisplayed, static_cast<GMainWindow*>(parent),
&GMainWindow::OnLoadComplete);
}
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 74dc6bb28..4650f96a3 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -209,7 +209,7 @@ void Config::ReadPlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
auto& player = Settings::values.players[p];
- player.connected = qt_config->value(QString("player_%1_connected").arg(p), false).toBool();
+ player.connected = ReadSetting(QString("player_%1_connected").arg(p), false).toBool();
player.type = static_cast<Settings::ControllerType>(
qt_config
@@ -269,7 +269,7 @@ void Config::ReadPlayerValues() {
}
void Config::ReadDebugValues() {
- Settings::values.debug_pad_enabled = qt_config->value("debug_pad_enabled", false).toBool();
+ Settings::values.debug_pad_enabled = ReadSetting("debug_pad_enabled", false).toBool();
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
Settings::values.debug_pad_buttons[i] =
@@ -298,7 +298,7 @@ void Config::ReadDebugValues() {
}
void Config::ReadKeyboardValues() {
- Settings::values.keyboard_enabled = qt_config->value("keyboard_enabled", false).toBool();
+ Settings::values.keyboard_enabled = ReadSetting("keyboard_enabled", false).toBool();
std::transform(default_keyboard_keys.begin(), default_keyboard_keys.end(),
Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam);
@@ -311,7 +311,7 @@ void Config::ReadKeyboardValues() {
}
void Config::ReadMouseValues() {
- Settings::values.mouse_enabled = qt_config->value("mouse_enabled", false).toBool();
+ Settings::values.mouse_enabled = ReadSetting("mouse_enabled", false).toBool();
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
@@ -327,16 +327,14 @@ void Config::ReadMouseValues() {
}
void Config::ReadTouchscreenValues() {
- Settings::values.touchscreen.enabled = qt_config->value("touchscreen_enabled", true).toBool();
+ Settings::values.touchscreen.enabled = ReadSetting("touchscreen_enabled", true).toBool();
Settings::values.touchscreen.device =
- qt_config->value("touchscreen_device", "engine:emu_window").toString().toStdString();
-
- Settings::values.touchscreen.finger = qt_config->value("touchscreen_finger", 0).toUInt();
- Settings::values.touchscreen.rotation_angle = qt_config->value("touchscreen_angle", 0).toUInt();
- Settings::values.touchscreen.diameter_x =
- qt_config->value("touchscreen_diameter_x", 15).toUInt();
- Settings::values.touchscreen.diameter_y =
- qt_config->value("touchscreen_diameter_y", 15).toUInt();
+ ReadSetting("touchscreen_device", "engine:emu_window").toString().toStdString();
+
+ Settings::values.touchscreen.finger = ReadSetting("touchscreen_finger", 0).toUInt();
+ Settings::values.touchscreen.rotation_angle = ReadSetting("touchscreen_angle", 0).toUInt();
+ Settings::values.touchscreen.diameter_x = ReadSetting("touchscreen_diameter_x", 15).toUInt();
+ Settings::values.touchscreen.diameter_y = ReadSetting("touchscreen_diameter_y", 15).toUInt();
qt_config->endGroup();
}
@@ -357,42 +355,41 @@ void Config::ReadValues() {
ReadTouchscreenValues();
Settings::values.motion_device =
- qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
+ ReadSetting("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
.toString()
.toStdString();
qt_config->beginGroup("Core");
- Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
- Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
+ Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
+ Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("Renderer");
- Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
- Settings::values.use_frame_limit = qt_config->value("use_frame_limit", true).toBool();
- Settings::values.frame_limit = qt_config->value("frame_limit", 100).toInt();
- Settings::values.use_disk_shader_cache =
- qt_config->value("use_disk_shader_cache", false).toBool();
+ Settings::values.resolution_factor = ReadSetting("resolution_factor", 1.0).toFloat();
+ Settings::values.use_frame_limit = ReadSetting("use_frame_limit", true).toBool();
+ Settings::values.frame_limit = ReadSetting("frame_limit", 100).toInt();
+ Settings::values.use_disk_shader_cache = ReadSetting("use_disk_shader_cache", true).toBool();
Settings::values.use_accurate_gpu_emulation =
- qt_config->value("use_accurate_gpu_emulation", false).toBool();
+ ReadSetting("use_accurate_gpu_emulation", false).toBool();
Settings::values.use_asynchronous_gpu_emulation =
- qt_config->value("use_asynchronous_gpu_emulation", false).toBool();
+ ReadSetting("use_asynchronous_gpu_emulation", false).toBool();
- Settings::values.bg_red = qt_config->value("bg_red", 0.0).toFloat();
- Settings::values.bg_green = qt_config->value("bg_green", 0.0).toFloat();
- Settings::values.bg_blue = qt_config->value("bg_blue", 0.0).toFloat();
+ Settings::values.bg_red = ReadSetting("bg_red", 0.0).toFloat();
+ Settings::values.bg_green = ReadSetting("bg_green", 0.0).toFloat();
+ Settings::values.bg_blue = ReadSetting("bg_blue", 0.0).toFloat();
qt_config->endGroup();
qt_config->beginGroup("Audio");
- Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
+ Settings::values.sink_id = ReadSetting("output_engine", "auto").toString().toStdString();
Settings::values.enable_audio_stretching =
- qt_config->value("enable_audio_stretching", true).toBool();
+ ReadSetting("enable_audio_stretching", true).toBool();
Settings::values.audio_device_id =
- qt_config->value("output_device", "auto").toString().toStdString();
- Settings::values.volume = qt_config->value("volume", 1).toFloat();
+ ReadSetting("output_device", "auto").toString().toStdString();
+ Settings::values.volume = ReadSetting("volume", 1).toFloat();
qt_config->endGroup();
qt_config->beginGroup("Data Storage");
- Settings::values.use_virtual_sd = qt_config->value("use_virtual_sd", true).toBool();
+ Settings::values.use_virtual_sd = ReadSetting("use_virtual_sd", true).toBool();
FileUtil::GetUserPath(
FileUtil::UserPath::NANDDir,
qt_config
@@ -410,30 +407,30 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("Core");
- Settings::values.use_cpu_jit = qt_config->value("use_cpu_jit", true).toBool();
- Settings::values.use_multi_core = qt_config->value("use_multi_core", false).toBool();
+ Settings::values.use_cpu_jit = ReadSetting("use_cpu_jit", true).toBool();
+ Settings::values.use_multi_core = ReadSetting("use_multi_core", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("System");
- Settings::values.use_docked_mode = qt_config->value("use_docked_mode", false).toBool();
- Settings::values.enable_nfc = qt_config->value("enable_nfc", true).toBool();
+ Settings::values.use_docked_mode = ReadSetting("use_docked_mode", false).toBool();
+ Settings::values.enable_nfc = ReadSetting("enable_nfc", true).toBool();
- Settings::values.current_user = std::clamp<int>(qt_config->value("current_user", 0).toInt(), 0,
- Service::Account::MAX_USERS - 1);
+ Settings::values.current_user =
+ std::clamp<int>(ReadSetting("current_user", 0).toInt(), 0, Service::Account::MAX_USERS - 1);
- Settings::values.language_index = qt_config->value("language_index", 1).toInt();
+ Settings::values.language_index = ReadSetting("language_index", 1).toInt();
- const auto rng_seed_enabled = qt_config->value("rng_seed_enabled", false).toBool();
+ const auto rng_seed_enabled = ReadSetting("rng_seed_enabled", false).toBool();
if (rng_seed_enabled) {
- Settings::values.rng_seed = qt_config->value("rng_seed", 0).toULongLong();
+ Settings::values.rng_seed = ReadSetting("rng_seed", 0).toULongLong();
} else {
Settings::values.rng_seed = std::nullopt;
}
- const auto custom_rtc_enabled = qt_config->value("custom_rtc_enabled", false).toBool();
+ const auto custom_rtc_enabled = ReadSetting("custom_rtc_enabled", false).toBool();
if (custom_rtc_enabled) {
Settings::values.custom_rtc =
- std::chrono::seconds(qt_config->value("custom_rtc", 0).toULongLong());
+ std::chrono::seconds(ReadSetting("custom_rtc", 0).toULongLong());
} else {
Settings::values.custom_rtc = std::nullopt;
}
@@ -441,35 +438,35 @@ void Config::ReadValues() {
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
- Settings::values.log_filter = qt_config->value("log_filter", "*:Info").toString().toStdString();
- Settings::values.use_dev_keys = qt_config->value("use_dev_keys", false).toBool();
+ Settings::values.log_filter = ReadSetting("log_filter", "*:Info").toString().toStdString();
+ Settings::values.use_dev_keys = ReadSetting("use_dev_keys", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("Debugging");
- Settings::values.use_gdbstub = qt_config->value("use_gdbstub", false).toBool();
- Settings::values.gdbstub_port = qt_config->value("gdbstub_port", 24689).toInt();
- Settings::values.program_args = qt_config->value("program_args", "").toString().toStdString();
- Settings::values.dump_exefs = qt_config->value("dump_exefs", false).toBool();
- Settings::values.dump_nso = qt_config->value("dump_nso", false).toBool();
+ Settings::values.use_gdbstub = ReadSetting("use_gdbstub", false).toBool();
+ Settings::values.gdbstub_port = ReadSetting("gdbstub_port", 24689).toInt();
+ Settings::values.program_args = ReadSetting("program_args", "").toString().toStdString();
+ Settings::values.dump_exefs = ReadSetting("dump_exefs", false).toBool();
+ Settings::values.dump_nso = ReadSetting("dump_nso", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("WebService");
- Settings::values.enable_telemetry = qt_config->value("enable_telemetry", true).toBool();
+ Settings::values.enable_telemetry = ReadSetting("enable_telemetry", true).toBool();
Settings::values.web_api_url =
- qt_config->value("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
- Settings::values.yuzu_username = qt_config->value("yuzu_username").toString().toStdString();
- Settings::values.yuzu_token = qt_config->value("yuzu_token").toString().toStdString();
+ ReadSetting("web_api_url", "https://api.yuzu-emu.org").toString().toStdString();
+ Settings::values.yuzu_username = ReadSetting("yuzu_username").toString().toStdString();
+ Settings::values.yuzu_token = ReadSetting("yuzu_token").toString().toStdString();
qt_config->endGroup();
const auto size = qt_config->beginReadArray("DisabledAddOns");
for (int i = 0; i < size; ++i) {
qt_config->setArrayIndex(i);
- const auto title_id = qt_config->value("title_id", 0).toULongLong();
+ const auto title_id = ReadSetting("title_id", 0).toULongLong();
std::vector<std::string> out;
const auto d_size = qt_config->beginReadArray("disabled");
for (int j = 0; j < d_size; ++j) {
qt_config->setArrayIndex(j);
- out.push_back(qt_config->value("d", "").toString().toStdString());
+ out.push_back(ReadSetting("d", "").toString().toStdString());
}
qt_config->endArray();
Settings::values.disabled_addons.insert_or_assign(title_id, out);
@@ -477,41 +474,38 @@ void Config::ReadValues() {
qt_config->endArray();
qt_config->beginGroup("UI");
- UISettings::values.theme = qt_config->value("theme", UISettings::themes[0].second).toString();
+ UISettings::values.theme = ReadSetting("theme", UISettings::themes[0].second).toString();
UISettings::values.enable_discord_presence =
- qt_config->value("enable_discord_presence", true).toBool();
+ ReadSetting("enable_discord_presence", true).toBool();
UISettings::values.screenshot_resolution_factor =
- static_cast<u16>(qt_config->value("screenshot_resolution_factor", 0).toUInt());
- UISettings::values.select_user_on_boot =
- qt_config->value("select_user_on_boot", false).toBool();
+ static_cast<u16>(ReadSetting("screenshot_resolution_factor", 0).toUInt());
+ UISettings::values.select_user_on_boot = ReadSetting("select_user_on_boot", false).toBool();
qt_config->beginGroup("UIGameList");
- UISettings::values.show_unknown = qt_config->value("show_unknown", true).toBool();
- UISettings::values.show_add_ons = qt_config->value("show_add_ons", true).toBool();
- UISettings::values.icon_size = qt_config->value("icon_size", 64).toUInt();
- UISettings::values.row_1_text_id = qt_config->value("row_1_text_id", 3).toUInt();
- UISettings::values.row_2_text_id = qt_config->value("row_2_text_id", 2).toUInt();
+ UISettings::values.show_unknown = ReadSetting("show_unknown", true).toBool();
+ UISettings::values.show_add_ons = ReadSetting("show_add_ons", true).toBool();
+ UISettings::values.icon_size = ReadSetting("icon_size", 64).toUInt();
+ UISettings::values.row_1_text_id = ReadSetting("row_1_text_id", 3).toUInt();
+ UISettings::values.row_2_text_id = ReadSetting("row_2_text_id", 2).toUInt();
qt_config->endGroup();
qt_config->beginGroup("UILayout");
- UISettings::values.geometry = qt_config->value("geometry").toByteArray();
- UISettings::values.state = qt_config->value("state").toByteArray();
- UISettings::values.renderwindow_geometry =
- qt_config->value("geometryRenderWindow").toByteArray();
- UISettings::values.gamelist_header_state =
- qt_config->value("gameListHeaderState").toByteArray();
+ UISettings::values.geometry = ReadSetting("geometry").toByteArray();
+ UISettings::values.state = ReadSetting("state").toByteArray();
+ UISettings::values.renderwindow_geometry = ReadSetting("geometryRenderWindow").toByteArray();
+ UISettings::values.gamelist_header_state = ReadSetting("gameListHeaderState").toByteArray();
UISettings::values.microprofile_geometry =
- qt_config->value("microProfileDialogGeometry").toByteArray();
+ ReadSetting("microProfileDialogGeometry").toByteArray();
UISettings::values.microprofile_visible =
- qt_config->value("microProfileDialogVisible", false).toBool();
+ ReadSetting("microProfileDialogVisible", false).toBool();
qt_config->endGroup();
qt_config->beginGroup("Paths");
- UISettings::values.roms_path = qt_config->value("romsPath").toString();
- UISettings::values.symbols_path = qt_config->value("symbolsPath").toString();
- UISettings::values.gamedir = qt_config->value("gameListRootDir", ".").toString();
- UISettings::values.gamedir_deepscan = qt_config->value("gameListDeepScan", false).toBool();
- UISettings::values.recent_files = qt_config->value("recentFiles").toStringList();
+ UISettings::values.roms_path = ReadSetting("romsPath").toString();
+ UISettings::values.symbols_path = ReadSetting("symbolsPath").toString();
+ UISettings::values.gamedir = ReadSetting("gameListRootDir", ".").toString();
+ UISettings::values.gamedir_deepscan = ReadSetting("gameListDeepScan", false).toBool();
+ UISettings::values.recent_files = ReadSetting("recentFiles").toStringList();
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
@@ -524,8 +518,8 @@ void Config::ReadValues() {
qt_config->beginGroup(hotkey);
UISettings::values.shortcuts.emplace_back(UISettings::Shortcut(
group + "/" + hotkey,
- UISettings::ContextualShortcut(qt_config->value("KeySeq").toString(),
- qt_config->value("Context").toInt())));
+ UISettings::ContextualShortcut(ReadSetting("KeySeq").toString(),
+ ReadSetting("Context").toInt())));
qt_config->endGroup();
}
@@ -533,16 +527,16 @@ void Config::ReadValues() {
}
qt_config->endGroup();
- UISettings::values.single_window_mode = qt_config->value("singleWindowMode", true).toBool();
- UISettings::values.fullscreen = qt_config->value("fullscreen", false).toBool();
- UISettings::values.display_titlebar = qt_config->value("displayTitleBars", true).toBool();
- UISettings::values.show_filter_bar = qt_config->value("showFilterBar", true).toBool();
- UISettings::values.show_status_bar = qt_config->value("showStatusBar", true).toBool();
- UISettings::values.confirm_before_closing = qt_config->value("confirmClose", true).toBool();
- UISettings::values.first_start = qt_config->value("firstStart", true).toBool();
- UISettings::values.callout_flags = qt_config->value("calloutFlags", 0).toUInt();
- UISettings::values.show_console = qt_config->value("showConsole", false).toBool();
- UISettings::values.profile_index = qt_config->value("profileIndex", 0).toUInt();
+ UISettings::values.single_window_mode = ReadSetting("singleWindowMode", true).toBool();
+ UISettings::values.fullscreen = ReadSetting("fullscreen", false).toBool();
+ UISettings::values.display_titlebar = ReadSetting("displayTitleBars", true).toBool();
+ UISettings::values.show_filter_bar = ReadSetting("showFilterBar", true).toBool();
+ UISettings::values.show_status_bar = ReadSetting("showStatusBar", true).toBool();
+ UISettings::values.confirm_before_closing = ReadSetting("confirmClose", true).toBool();
+ UISettings::values.first_start = ReadSetting("firstStart", true).toBool();
+ UISettings::values.callout_flags = ReadSetting("calloutFlags", 0).toUInt();
+ UISettings::values.show_console = ReadSetting("showConsole", false).toBool();
+ UISettings::values.profile_index = ReadSetting("profileIndex", 0).toUInt();
ApplyDefaultProfileIfInputInvalid();
@@ -553,62 +547,79 @@ void Config::SavePlayerValues() {
for (std::size_t p = 0; p < Settings::values.players.size(); ++p) {
const auto& player = Settings::values.players[p];
- qt_config->setValue(QString("player_%1_connected").arg(p), player.connected);
- qt_config->setValue(QString("player_%1_type").arg(p), static_cast<u8>(player.type));
+ WriteSetting(QString("player_%1_connected").arg(p), player.connected, false);
+ WriteSetting(QString("player_%1_type").arg(p), static_cast<u8>(player.type),
+ static_cast<u8>(Settings::ControllerType::DualJoycon));
- qt_config->setValue(QString("player_%1_body_color_left").arg(p), player.body_color_left);
- qt_config->setValue(QString("player_%1_body_color_right").arg(p), player.body_color_right);
- qt_config->setValue(QString("player_%1_button_color_left").arg(p),
- player.button_color_left);
- qt_config->setValue(QString("player_%1_button_color_right").arg(p),
- player.button_color_right);
+ WriteSetting(QString("player_%1_body_color_left").arg(p), player.body_color_left,
+ Settings::JOYCON_BODY_NEON_BLUE);
+ WriteSetting(QString("player_%1_body_color_right").arg(p), player.body_color_right,
+ Settings::JOYCON_BODY_NEON_RED);
+ WriteSetting(QString("player_%1_button_color_left").arg(p), player.button_color_left,
+ Settings::JOYCON_BUTTONS_NEON_BLUE);
+ WriteSetting(QString("player_%1_button_color_right").arg(p), player.button_color_right,
+ Settings::JOYCON_BUTTONS_NEON_RED);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- qt_config->setValue(QString("player_%1_").arg(p) +
- QString::fromStdString(Settings::NativeButton::mapping[i]),
- QString::fromStdString(player.buttons[i]));
+ std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(QString("player_%1_").arg(p) +
+ QString::fromStdString(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(player.buttons[i]),
+ QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- qt_config->setValue(QString("player_%1_").arg(p) +
- QString::fromStdString(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(player.analogs[i]));
+ std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_analogs[i][4], 0.5f);
+ WriteSetting(QString("player_%1_").arg(p) +
+ QString::fromStdString(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(player.analogs[i]),
+ QString::fromStdString(default_param));
}
}
}
void Config::SaveDebugValues() {
- qt_config->setValue("debug_pad_enabled", Settings::values.debug_pad_enabled);
+ WriteSetting("debug_pad_enabled", Settings::values.debug_pad_enabled, false);
for (int i = 0; i < Settings::NativeButton::NumButtons; ++i) {
- qt_config->setValue(QString("debug_pad_") +
- QString::fromStdString(Settings::NativeButton::mapping[i]),
- QString::fromStdString(Settings::values.debug_pad_buttons[i]));
+ std::string default_param = InputCommon::GenerateKeyboardParam(default_buttons[i]);
+ WriteSetting(QString("debug_pad_") +
+ QString::fromStdString(Settings::NativeButton::mapping[i]),
+ QString::fromStdString(Settings::values.debug_pad_buttons[i]),
+ QString::fromStdString(default_param));
}
for (int i = 0; i < Settings::NativeAnalog::NumAnalogs; ++i) {
- qt_config->setValue(QString("debug_pad_") +
- QString::fromStdString(Settings::NativeAnalog::mapping[i]),
- QString::fromStdString(Settings::values.debug_pad_analogs[i]));
+ std::string default_param = InputCommon::GenerateAnalogParamFromKeys(
+ default_analogs[i][0], default_analogs[i][1], default_analogs[i][2],
+ default_analogs[i][3], default_analogs[i][4], 0.5f);
+ WriteSetting(QString("debug_pad_") +
+ QString::fromStdString(Settings::NativeAnalog::mapping[i]),
+ QString::fromStdString(Settings::values.debug_pad_analogs[i]),
+ QString::fromStdString(default_param));
}
}
void Config::SaveMouseValues() {
- qt_config->setValue("mouse_enabled", Settings::values.mouse_enabled);
+ WriteSetting("mouse_enabled", Settings::values.mouse_enabled, false);
for (int i = 0; i < Settings::NativeMouseButton::NumMouseButtons; ++i) {
- qt_config->setValue(QString("mouse_") +
- QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
- QString::fromStdString(Settings::values.mouse_buttons[i]));
+ std::string default_param = InputCommon::GenerateKeyboardParam(default_mouse_buttons[i]);
+ WriteSetting(QString("mouse_") +
+ QString::fromStdString(Settings::NativeMouseButton::mapping[i]),
+ QString::fromStdString(Settings::values.mouse_buttons[i]),
+ QString::fromStdString(default_param));
}
}
void Config::SaveTouchscreenValues() {
- qt_config->setValue("touchscreen_enabled", Settings::values.touchscreen.enabled);
- qt_config->setValue("touchscreen_device",
- QString::fromStdString(Settings::values.touchscreen.device));
-
- qt_config->setValue("touchscreen_finger", Settings::values.touchscreen.finger);
- qt_config->setValue("touchscreen_angle", Settings::values.touchscreen.rotation_angle);
- qt_config->setValue("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x);
- qt_config->setValue("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y);
+ WriteSetting("touchscreen_enabled", Settings::values.touchscreen.enabled, true);
+ WriteSetting("touchscreen_device", QString::fromStdString(Settings::values.touchscreen.device),
+ "engine:emu_window");
+
+ WriteSetting("touchscreen_finger", Settings::values.touchscreen.finger, 0);
+ WriteSetting("touchscreen_angle", Settings::values.touchscreen.rotation_angle, 0);
+ WriteSetting("touchscreen_diameter_x", Settings::values.touchscreen.diameter_x, 15);
+ WriteSetting("touchscreen_diameter_y", Settings::values.touchscreen.diameter_y, 15);
}
void Config::SaveValues() {
@@ -619,91 +630,96 @@ void Config::SaveValues() {
SaveMouseValues();
SaveTouchscreenValues();
- qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
- qt_config->setValue("keyboard_enabled", Settings::values.keyboard_enabled);
+ WriteSetting("motion_device", QString::fromStdString(Settings::values.motion_device),
+ "engine:motion_emu,update_period:100,sensitivity:0.01");
+ WriteSetting("keyboard_enabled", Settings::values.keyboard_enabled, false);
qt_config->endGroup();
qt_config->beginGroup("Core");
- qt_config->setValue("use_cpu_jit", Settings::values.use_cpu_jit);
- qt_config->setValue("use_multi_core", Settings::values.use_multi_core);
+ WriteSetting("use_cpu_jit", Settings::values.use_cpu_jit, true);
+ WriteSetting("use_multi_core", Settings::values.use_multi_core, false);
qt_config->endGroup();
qt_config->beginGroup("Renderer");
- qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
- qt_config->setValue("use_frame_limit", Settings::values.use_frame_limit);
- qt_config->setValue("frame_limit", Settings::values.frame_limit);
- qt_config->setValue("use_disk_shader_cache", Settings::values.use_disk_shader_cache);
- qt_config->setValue("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation);
- qt_config->setValue("use_asynchronous_gpu_emulation",
- Settings::values.use_asynchronous_gpu_emulation);
+ WriteSetting("resolution_factor", (double)Settings::values.resolution_factor, 1.0);
+ WriteSetting("use_frame_limit", Settings::values.use_frame_limit, true);
+ WriteSetting("frame_limit", Settings::values.frame_limit, 100);
+ WriteSetting("use_disk_shader_cache", Settings::values.use_disk_shader_cache, true);
+ WriteSetting("use_accurate_gpu_emulation", Settings::values.use_accurate_gpu_emulation, false);
+ WriteSetting("use_asynchronous_gpu_emulation", Settings::values.use_asynchronous_gpu_emulation,
+ false);
// Cast to double because Qt's written float values are not human-readable
- qt_config->setValue("bg_red", (double)Settings::values.bg_red);
- qt_config->setValue("bg_green", (double)Settings::values.bg_green);
- qt_config->setValue("bg_blue", (double)Settings::values.bg_blue);
+ WriteSetting("bg_red", (double)Settings::values.bg_red, 0.0);
+ WriteSetting("bg_green", (double)Settings::values.bg_green, 0.0);
+ WriteSetting("bg_blue", (double)Settings::values.bg_blue, 0.0);
qt_config->endGroup();
qt_config->beginGroup("Audio");
- qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
- qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
- qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id));
- qt_config->setValue("volume", Settings::values.volume);
+ WriteSetting("output_engine", QString::fromStdString(Settings::values.sink_id), "auto");
+ WriteSetting("enable_audio_stretching", Settings::values.enable_audio_stretching, true);
+ WriteSetting("output_device", QString::fromStdString(Settings::values.audio_device_id), "auto");
+ WriteSetting("volume", Settings::values.volume, 1.0f);
qt_config->endGroup();
qt_config->beginGroup("Data Storage");
- qt_config->setValue("use_virtual_sd", Settings::values.use_virtual_sd);
- qt_config->setValue("nand_directory",
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
- qt_config->setValue("sdmc_directory",
- QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
+ WriteSetting("use_virtual_sd", Settings::values.use_virtual_sd, true);
+ WriteSetting("nand_directory",
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir)));
+ WriteSetting("sdmc_directory",
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)),
+ QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir)));
qt_config->endGroup();
qt_config->beginGroup("System");
- qt_config->setValue("use_docked_mode", Settings::values.use_docked_mode);
- qt_config->setValue("enable_nfc", Settings::values.enable_nfc);
- qt_config->setValue("current_user", Settings::values.current_user);
- qt_config->setValue("language_index", Settings::values.language_index);
+ WriteSetting("use_docked_mode", Settings::values.use_docked_mode, false);
+ WriteSetting("enable_nfc", Settings::values.enable_nfc, true);
+ WriteSetting("current_user", Settings::values.current_user, 0);
+ WriteSetting("language_index", Settings::values.language_index, 1);
- qt_config->setValue("rng_seed_enabled", Settings::values.rng_seed.has_value());
- qt_config->setValue("rng_seed", Settings::values.rng_seed.value_or(0));
+ WriteSetting("rng_seed_enabled", Settings::values.rng_seed.has_value(), false);
+ WriteSetting("rng_seed", Settings::values.rng_seed.value_or(0), 0);
- qt_config->setValue("custom_rtc_enabled", Settings::values.custom_rtc.has_value());
- qt_config->setValue("custom_rtc",
- QVariant::fromValue<long long>(
- Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()));
+ WriteSetting("custom_rtc_enabled", Settings::values.custom_rtc.has_value(), false);
+ WriteSetting("custom_rtc",
+ QVariant::fromValue<long long>(
+ Settings::values.custom_rtc.value_or(std::chrono::seconds{}).count()),
+ 0);
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
- qt_config->setValue("log_filter", QString::fromStdString(Settings::values.log_filter));
- qt_config->setValue("use_dev_keys", Settings::values.use_dev_keys);
+ WriteSetting("log_filter", QString::fromStdString(Settings::values.log_filter), "*:Info");
+ WriteSetting("use_dev_keys", Settings::values.use_dev_keys, false);
qt_config->endGroup();
qt_config->beginGroup("Debugging");
- qt_config->setValue("use_gdbstub", Settings::values.use_gdbstub);
- qt_config->setValue("gdbstub_port", Settings::values.gdbstub_port);
- qt_config->setValue("program_args", QString::fromStdString(Settings::values.program_args));
- qt_config->setValue("dump_exefs", Settings::values.dump_exefs);
- qt_config->setValue("dump_nso", Settings::values.dump_nso);
+ WriteSetting("use_gdbstub", Settings::values.use_gdbstub, false);
+ WriteSetting("gdbstub_port", Settings::values.gdbstub_port, 24689);
+ WriteSetting("program_args", QString::fromStdString(Settings::values.program_args), "");
+ WriteSetting("dump_exefs", Settings::values.dump_exefs, false);
+ WriteSetting("dump_nso", Settings::values.dump_nso, false);
qt_config->endGroup();
qt_config->beginGroup("WebService");
- qt_config->setValue("enable_telemetry", Settings::values.enable_telemetry);
- qt_config->setValue("web_api_url", QString::fromStdString(Settings::values.web_api_url));
- qt_config->setValue("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
- qt_config->setValue("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
+ WriteSetting("enable_telemetry", Settings::values.enable_telemetry, true);
+ WriteSetting("web_api_url", QString::fromStdString(Settings::values.web_api_url),
+ "https://api.yuzu-emu.org");
+ WriteSetting("yuzu_username", QString::fromStdString(Settings::values.yuzu_username));
+ WriteSetting("yuzu_token", QString::fromStdString(Settings::values.yuzu_token));
qt_config->endGroup();
qt_config->beginWriteArray("DisabledAddOns");
int i = 0;
for (const auto& elem : Settings::values.disabled_addons) {
qt_config->setArrayIndex(i);
- qt_config->setValue("title_id", QVariant::fromValue<u64>(elem.first));
+ WriteSetting("title_id", QVariant::fromValue<u64>(elem.first), 0);
qt_config->beginWriteArray("disabled");
for (std::size_t j = 0; j < elem.second.size(); ++j) {
qt_config->setArrayIndex(static_cast<int>(j));
- qt_config->setValue("d", QString::fromStdString(elem.second[j]));
+ WriteSetting("d", QString::fromStdString(elem.second[j]), "");
}
qt_config->endArray();
++i;
@@ -711,60 +727,86 @@ void Config::SaveValues() {
qt_config->endArray();
qt_config->beginGroup("UI");
- qt_config->setValue("theme", UISettings::values.theme);
- qt_config->setValue("enable_discord_presence", UISettings::values.enable_discord_presence);
- qt_config->setValue("screenshot_resolution_factor",
- UISettings::values.screenshot_resolution_factor);
- qt_config->setValue("select_user_on_boot", UISettings::values.select_user_on_boot);
+ WriteSetting("theme", UISettings::values.theme, UISettings::themes[0].second);
+ WriteSetting("enable_discord_presence", UISettings::values.enable_discord_presence, true);
+ WriteSetting("screenshot_resolution_factor", UISettings::values.screenshot_resolution_factor,
+ 0);
+ WriteSetting("select_user_on_boot", UISettings::values.select_user_on_boot, false);
qt_config->beginGroup("UIGameList");
- qt_config->setValue("show_unknown", UISettings::values.show_unknown);
- qt_config->setValue("show_add_ons", UISettings::values.show_add_ons);
- qt_config->setValue("icon_size", UISettings::values.icon_size);
- qt_config->setValue("row_1_text_id", UISettings::values.row_1_text_id);
- qt_config->setValue("row_2_text_id", UISettings::values.row_2_text_id);
+ WriteSetting("show_unknown", UISettings::values.show_unknown, true);
+ WriteSetting("show_add_ons", UISettings::values.show_add_ons, true);
+ WriteSetting("icon_size", UISettings::values.icon_size, 64);
+ WriteSetting("row_1_text_id", UISettings::values.row_1_text_id, 3);
+ WriteSetting("row_2_text_id", UISettings::values.row_2_text_id, 2);
qt_config->endGroup();
qt_config->beginGroup("UILayout");
- qt_config->setValue("geometry", UISettings::values.geometry);
- qt_config->setValue("state", UISettings::values.state);
- qt_config->setValue("geometryRenderWindow", UISettings::values.renderwindow_geometry);
- qt_config->setValue("gameListHeaderState", UISettings::values.gamelist_header_state);
- qt_config->setValue("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
- qt_config->setValue("microProfileDialogVisible", UISettings::values.microprofile_visible);
+ WriteSetting("geometry", UISettings::values.geometry);
+ WriteSetting("state", UISettings::values.state);
+ WriteSetting("geometryRenderWindow", UISettings::values.renderwindow_geometry);
+ WriteSetting("gameListHeaderState", UISettings::values.gamelist_header_state);
+ WriteSetting("microProfileDialogGeometry", UISettings::values.microprofile_geometry);
+ WriteSetting("microProfileDialogVisible", UISettings::values.microprofile_visible, false);
qt_config->endGroup();
qt_config->beginGroup("Paths");
- qt_config->setValue("romsPath", UISettings::values.roms_path);
- qt_config->setValue("symbolsPath", UISettings::values.symbols_path);
- qt_config->setValue("screenshotPath", UISettings::values.screenshot_path);
- qt_config->setValue("gameListRootDir", UISettings::values.gamedir);
- qt_config->setValue("gameListDeepScan", UISettings::values.gamedir_deepscan);
- qt_config->setValue("recentFiles", UISettings::values.recent_files);
+ WriteSetting("romsPath", UISettings::values.roms_path);
+ WriteSetting("symbolsPath", UISettings::values.symbols_path);
+ WriteSetting("screenshotPath", UISettings::values.screenshot_path);
+ WriteSetting("gameListRootDir", UISettings::values.gamedir, ".");
+ WriteSetting("gameListDeepScan", UISettings::values.gamedir_deepscan, false);
+ WriteSetting("recentFiles", UISettings::values.recent_files);
qt_config->endGroup();
qt_config->beginGroup("Shortcuts");
for (auto shortcut : UISettings::values.shortcuts) {
- qt_config->setValue(shortcut.first + "/KeySeq", shortcut.second.first);
- qt_config->setValue(shortcut.first + "/Context", shortcut.second.second);
+ WriteSetting(shortcut.first + "/KeySeq", shortcut.second.first);
+ WriteSetting(shortcut.first + "/Context", shortcut.second.second);
}
qt_config->endGroup();
- qt_config->setValue("singleWindowMode", UISettings::values.single_window_mode);
- qt_config->setValue("fullscreen", UISettings::values.fullscreen);
- qt_config->setValue("displayTitleBars", UISettings::values.display_titlebar);
- qt_config->setValue("showFilterBar", UISettings::values.show_filter_bar);
- qt_config->setValue("showStatusBar", UISettings::values.show_status_bar);
- qt_config->setValue("confirmClose", UISettings::values.confirm_before_closing);
- qt_config->setValue("firstStart", UISettings::values.first_start);
- qt_config->setValue("calloutFlags", UISettings::values.callout_flags);
- qt_config->setValue("showConsole", UISettings::values.show_console);
- qt_config->setValue("profileIndex", UISettings::values.profile_index);
+ WriteSetting("singleWindowMode", UISettings::values.single_window_mode, true);
+ WriteSetting("fullscreen", UISettings::values.fullscreen, false);
+ WriteSetting("displayTitleBars", UISettings::values.display_titlebar, true);
+ WriteSetting("showFilterBar", UISettings::values.show_filter_bar, true);
+ WriteSetting("showStatusBar", UISettings::values.show_status_bar, true);
+ WriteSetting("confirmClose", UISettings::values.confirm_before_closing, true);
+ WriteSetting("firstStart", UISettings::values.first_start, true);
+ WriteSetting("calloutFlags", UISettings::values.callout_flags, 0);
+ WriteSetting("showConsole", UISettings::values.show_console, false);
+ WriteSetting("profileIndex", UISettings::values.profile_index, 0);
qt_config->endGroup();
}
+QVariant Config::ReadSetting(const QString& name) const {
+ return qt_config->value(name);
+}
+
+QVariant Config::ReadSetting(const QString& name, const QVariant& default_value) const {
+ QVariant result;
+ if (qt_config->value(name + "/default", false).toBool()) {
+ result = default_value;
+ } else {
+ result = qt_config->value(name, default_value);
+ }
+ return result;
+}
+
+void Config::WriteSetting(const QString& name, const QVariant& value) {
+ qt_config->setValue(name, value);
+}
+
+void Config::WriteSetting(const QString& name, const QVariant& value,
+ const QVariant& default_value) {
+ qt_config->setValue(name + "/default", value == default_value);
+ qt_config->setValue(name, value);
+}
+
void Config::Reload() {
ReadValues();
+ // To apply default value changes
+ SaveValues();
Settings::Apply();
}
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index e73ad19bb..f4185db18 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -42,6 +42,11 @@ private:
void SaveMouseValues();
void SaveTouchscreenValues();
+ QVariant ReadSetting(const QString& name) const;
+ QVariant ReadSetting(const QString& name, const QVariant& default_value) const;
+ void WriteSetting(const QString& name, const QVariant& value);
+ void WriteSetting(const QString& name, const QVariant& value, const QVariant& default_value);
+
std::unique_ptr<QSettings> qt_config;
std::string qt_config_loc;
};
diff --git a/src/yuzu/debugger/graphics/graphics_surface.cpp b/src/yuzu/debugger/graphics/graphics_surface.cpp
index 71683da8e..11023ed63 100644
--- a/src/yuzu/debugger/graphics/graphics_surface.cpp
+++ b/src/yuzu/debugger/graphics/graphics_surface.cpp
@@ -261,7 +261,7 @@ void GraphicsSurfaceWidget::OnSurfaceSourceChanged(int new_value) {
void GraphicsSurfaceWidget::OnSurfaceAddressChanged(qint64 new_value) {
if (surface_address != new_value) {
- surface_address = static_cast<Tegra::GPUVAddr>(new_value);
+ surface_address = static_cast<GPUVAddr>(new_value);
surface_source_list->setCurrentIndex(static_cast<int>(Source::Custom));
emit Update();
@@ -383,13 +383,12 @@ void GraphicsSurfaceWidget::OnUpdate() {
// TODO: Implement a good way to visualize alpha components!
QImage decoded_image(surface_width, surface_height, QImage::Format_ARGB32);
- std::optional<VAddr> address = gpu.MemoryManager().GpuToCpuAddress(surface_address);
// TODO(bunnei): Will not work with BCn formats that swizzle 4x4 tiles.
// Needs to be fixed if we plan to use this feature more, otherwise we may remove it.
auto unswizzled_data = Tegra::Texture::UnswizzleTexture(
- *address, 1, 1, Tegra::Texture::BytesPerPixel(surface_format), surface_width,
- surface_height, 1U);
+ gpu.MemoryManager().GetPointer(surface_address), 1, 1,
+ Tegra::Texture::BytesPerPixel(surface_format), surface_width, surface_height, 1U);
auto texture_data = Tegra::Texture::DecodeTexture(unswizzled_data, surface_format,
surface_width, surface_height);
diff --git a/src/yuzu/debugger/graphics/graphics_surface.h b/src/yuzu/debugger/graphics/graphics_surface.h
index 323e39d94..89445b18f 100644
--- a/src/yuzu/debugger/graphics/graphics_surface.h
+++ b/src/yuzu/debugger/graphics/graphics_surface.h
@@ -87,7 +87,7 @@ private:
QPushButton* save_surface;
Source surface_source;
- Tegra::GPUVAddr surface_address;
+ GPUVAddr surface_address;
unsigned surface_width;
unsigned surface_height;
Tegra::Texture::TextureFormat surface_format;
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
index 7df8eff53..de7a26e14 100644
--- a/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
+++ b/src/yuzu_cmd/emu_window/emu_window_sdl2.cpp
@@ -135,16 +135,16 @@ bool EmuWindow_SDL2::SupportsRequiredGLExtensions() {
}
EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
- InputCommon::Init();
-
- SDL_SetMainReady();
-
// Initialize the window
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) {
LOG_CRITICAL(Frontend, "Failed to initialize SDL2! Exiting...");
exit(1);
}
+ InputCommon::Init();
+
+ SDL_SetMainReady();
+
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
@@ -201,11 +201,9 @@ EmuWindow_SDL2::EmuWindow_SDL2(bool fullscreen) {
}
EmuWindow_SDL2::~EmuWindow_SDL2() {
- InputCommon::SDL::CloseSDLJoysticks();
+ InputCommon::Shutdown();
SDL_GL_DeleteContext(gl_context);
SDL_Quit();
-
- InputCommon::Shutdown();
}
void EmuWindow_SDL2::SwapBuffers() {
@@ -262,7 +260,6 @@ void EmuWindow_SDL2::PollEvents() {
is_open = false;
break;
default:
- InputCommon::SDL::HandleGameControllerEvent(event);
break;
}
}
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index c6c66a787..245f25847 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -114,9 +114,9 @@ int main(int argc, char** argv) {
};
while (optind < argc) {
- char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
+ int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
if (arg != -1) {
- switch (arg) {
+ switch (static_cast<char>(arg)) {
case 'g':
errno = 0;
gdb_port = strtoul(optarg, &endarg, 0);