Compare commits
2 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6612d6d8d | ||
|
|
13bc74e957 |
@@ -104,84 +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()
|
||||
|
||||
# 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.66.0 QUIET)
|
||||
find_package(Boost 1.64.0 QUIET)
|
||||
if (NOT Boost_FOUND)
|
||||
message(STATUS "Boost 1.66.0 or newer not found, falling back to externals")
|
||||
message(STATUS "Boost 1.64.0 or newer not found, falling back to externals")
|
||||
|
||||
set(BOOST_ROOT "${PROJECT_SOURCE_DIR}/externals/boost")
|
||||
set(Boost_NO_SYSTEM_PATHS OFF)
|
||||
@@ -326,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.
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -92,14 +92,10 @@ 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
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/swap.h"
|
||||
|
||||
/*
|
||||
* Abstract bitfield class
|
||||
@@ -109,7 +108,7 @@
|
||||
* symptoms.
|
||||
*/
|
||||
#pragma pack(1)
|
||||
template <std::size_t Position, std::size_t Bits, typename T, typename EndianTag = LETag>
|
||||
template <std::size_t Position, std::size_t Bits, typename T>
|
||||
struct BitField {
|
||||
private:
|
||||
// UnderlyingType is T for non-enum types and the underlying type of T if
|
||||
@@ -122,8 +121,6 @@ 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;
|
||||
@@ -173,7 +170,7 @@ public:
|
||||
}
|
||||
|
||||
constexpr FORCE_INLINE void Assign(const T& value) {
|
||||
storage = (static_cast<StorageType>(storage) & ~mask) | FormatValue(value);
|
||||
storage = (storage & ~mask) | FormatValue(value);
|
||||
}
|
||||
|
||||
constexpr T Value() const {
|
||||
@@ -185,7 +182,7 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
StorageTypeWithEndian storage;
|
||||
StorageType storage;
|
||||
|
||||
static_assert(bits + position <= 8 * sizeof(T), "Bitfield out of range");
|
||||
|
||||
@@ -196,6 +193,3 @@ 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>;
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
// 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);
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
} // namespace Common
|
||||
@@ -1,80 +0,0 @@
|
||||
// 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,
|
||||
};
|
||||
|
||||
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<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;
|
||||
|
||||
const std::size_t page_size_in_bits{};
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
@@ -17,8 +17,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#if defined(_MSC_VER)
|
||||
#include <cstdlib>
|
||||
#elif defined(__linux__)
|
||||
@@ -172,7 +170,7 @@ struct swap_struct_t {
|
||||
using swapped_t = swap_struct_t;
|
||||
|
||||
protected:
|
||||
T value;
|
||||
T value = T();
|
||||
|
||||
static T swap(T v) {
|
||||
return F::swap(v);
|
||||
@@ -607,154 +605,52 @@ struct swap_double_t {
|
||||
}
|
||||
};
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
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)));
|
||||
}
|
||||
};
|
||||
|
||||
struct SwapTag {}; // Use the different endianness from the system
|
||||
struct KeepTag {}; // Use the same endianness as the system
|
||||
|
||||
template <typename T, typename Tag>
|
||||
struct AddEndian;
|
||||
|
||||
// KeepTag specializations
|
||||
|
||||
template <typename T>
|
||||
struct AddEndian<T, KeepTag> {
|
||||
using type = T;
|
||||
};
|
||||
|
||||
// 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>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<u64, SwapTag> {
|
||||
using type = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s8, SwapTag> {
|
||||
using type = s8;
|
||||
};
|
||||
|
||||
template <>
|
||||
struct AddEndian<s16, SwapTag> {
|
||||
using type = 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>;
|
||||
};
|
||||
|
||||
// Alias LETag/BETag as KeepTag/SwapTag depending on the system
|
||||
#if COMMON_LITTLE_ENDIAN
|
||||
using u16_le = u16;
|
||||
using u32_le = u32;
|
||||
using u64_le = u64;
|
||||
|
||||
using LETag = KeepTag;
|
||||
using BETag = SwapTag;
|
||||
using s16_le = s16;
|
||||
using s32_le = s32;
|
||||
using s64_le = s64;
|
||||
|
||||
using float_le = float;
|
||||
using double_le = double;
|
||||
|
||||
using u64_be = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_be = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_be = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_be = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_be = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_be = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_be = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_be = swap_struct_t<double, swap_double_t<double>>;
|
||||
#else
|
||||
|
||||
using BETag = KeepTag;
|
||||
using LETag = SwapTag;
|
||||
using u64_le = swap_struct_t<u64, swap_64_t<u64>>;
|
||||
using s64_le = swap_struct_t<s64, swap_64_t<s64>>;
|
||||
|
||||
using u32_le = swap_struct_t<u32, swap_32_t<u32>>;
|
||||
using s32_le = swap_struct_t<s32, swap_32_t<s32>>;
|
||||
|
||||
using u16_le = swap_struct_t<u16, swap_16_t<u16>>;
|
||||
using s16_le = swap_struct_t<s16, swap_16_t<s16>>;
|
||||
|
||||
using float_le = swap_struct_t<float, swap_float_t<float>>;
|
||||
using double_le = swap_struct_t<double, swap_double_t<double>>;
|
||||
|
||||
using u16_be = u16;
|
||||
using u32_be = u32;
|
||||
using u64_be = u64;
|
||||
|
||||
using s16_be = s16;
|
||||
using s32_be = s32;
|
||||
using s64_be = s64;
|
||||
|
||||
using float_be = float;
|
||||
using double_be = double;
|
||||
|
||||
#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;
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <deque>
|
||||
#include <boost/range/algorithm_ext/erase.hpp>
|
||||
|
||||
namespace Common {
|
||||
|
||||
@@ -110,9 +111,8 @@ struct ThreadQueueList {
|
||||
}
|
||||
|
||||
void remove(Priority priority, const T& 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());
|
||||
Queue* cur = &queues[priority];
|
||||
boost::remove_erase(cur->data, thread_id);
|
||||
}
|
||||
|
||||
void rotate(Priority priority) {
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
// 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>
|
||||
|
||||
|
||||
@@ -1,8 +1,3 @@
|
||||
// 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"
|
||||
|
||||
@@ -107,8 +107,6 @@ 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,6 +419,8 @@ 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
|
||||
@@ -437,6 +437,8 @@ 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
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
#include "core/arm/exclusive_monitor.h"
|
||||
#include "core/arm/unicorn/arm_unicorn.h"
|
||||
|
||||
namespace Common {
|
||||
namespace Memory {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ private:
|
||||
Timing::CoreTiming& core_timing;
|
||||
DynarmicExclusiveMonitor& exclusive_monitor;
|
||||
|
||||
Common::PageTable* current_page_table = nullptr;
|
||||
Memory::PageTable* current_page_table = nullptr;
|
||||
};
|
||||
|
||||
class DynarmicExclusiveMonitor final : public ExclusiveMonitor {
|
||||
|
||||
@@ -24,26 +24,13 @@ 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 that can be accessed by applications.
|
||||
PublicData = 5,
|
||||
Data_Unknown5 = 5, ///< Seems to be used on some system archives
|
||||
};
|
||||
|
||||
enum class NCASectionCryptoType : u8 {
|
||||
|
||||
@@ -94,7 +94,7 @@ static ContentRecordType GetCRTypeFromNCAType(NCAContentType type) {
|
||||
case NCAContentType::Control:
|
||||
return ContentRecordType::Control;
|
||||
case NCAContentType::Data:
|
||||
case NCAContentType::PublicData:
|
||||
case NCAContentType::Data_Unknown5:
|
||||
return ContentRecordType::Data;
|
||||
case NCAContentType::Manual:
|
||||
// TODO(DarkLordZach): Peek at NCA contents to differentiate Manual and Legal.
|
||||
|
||||
@@ -39,10 +39,10 @@ struct CommandHeader {
|
||||
union {
|
||||
u32_le raw_low;
|
||||
BitField<0, 16, CommandType> type;
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
enum class BufferDescriptorCFlag : u32 {
|
||||
@@ -53,28 +53,28 @@ struct CommandHeader {
|
||||
|
||||
union {
|
||||
u32_le raw_high;
|
||||
BitField<0, 10, u32> data_size;
|
||||
BitField<0, 10, u32_le> data_size;
|
||||
BitField<10, 4, BufferDescriptorCFlag> buf_c_descriptor_flags;
|
||||
BitField<31, 1, u32> enable_handle_descriptor;
|
||||
BitField<31, 1, u32_le> enable_handle_descriptor;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(CommandHeader) == 8, "CommandHeader size is incorrect");
|
||||
|
||||
union HandleDescriptorHeader {
|
||||
u32_le raw_high;
|
||||
BitField<0, 1, u32> send_current_pid;
|
||||
BitField<1, 4, u32> num_handles_to_copy;
|
||||
BitField<5, 4, u32> num_handles_to_move;
|
||||
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;
|
||||
};
|
||||
static_assert(sizeof(HandleDescriptorHeader) == 4, "HandleDescriptorHeader size is incorrect");
|
||||
|
||||
struct BufferDescriptorX {
|
||||
union {
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
u32_le address_bits_0_31;
|
||||
@@ -103,10 +103,10 @@ struct BufferDescriptorABW {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -128,8 +128,8 @@ struct BufferDescriptorC {
|
||||
u32_le address_bits_0_31;
|
||||
|
||||
union {
|
||||
BitField<0, 16, u32> address_bits_32_47;
|
||||
BitField<16, 16, u32> size;
|
||||
BitField<0, 16, u32_le> address_bits_32_47;
|
||||
BitField<16, 16, u32_le> size;
|
||||
};
|
||||
|
||||
VAddr Address() const {
|
||||
@@ -167,8 +167,8 @@ struct DomainMessageHeader {
|
||||
struct {
|
||||
union {
|
||||
BitField<0, 8, CommandType> command;
|
||||
BitField<8, 8, u32> input_object_count;
|
||||
BitField<16, 16, u32> size;
|
||||
BitField<8, 8, u32_le> input_object_count;
|
||||
BitField<16, 16, u32_le> size;
|
||||
};
|
||||
u32_le object_id;
|
||||
INSERT_PADDING_WORDS(2);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
// 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
|
||||
@@ -1,90 +0,0 @@
|
||||
// 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
|
||||
@@ -9,7 +9,6 @@
|
||||
#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"
|
||||
@@ -32,7 +31,7 @@ namespace {
|
||||
*/
|
||||
void SetupMainThread(Process& owner_process, KernelCore& kernel, VAddr entry_point, u32 priority) {
|
||||
// Setup page table so we can write to memory
|
||||
Memory::SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
SetCurrentPageTable(&owner_process.VMManager().page_table);
|
||||
|
||||
// Initialize new "main" thread
|
||||
const VAddr stack_top = owner_process.VMManager().GetTLSIORegionEndAddress();
|
||||
@@ -51,6 +50,9 @@ 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();
|
||||
|
||||
@@ -210,7 +212,7 @@ void Process::FreeTLSSlot(VAddr tls_address) {
|
||||
}
|
||||
|
||||
void Process::LoadModule(CodeSet module_, VAddr base_addr) {
|
||||
const auto MapSegment = [&](const CodeSet::Segment& segment, VMAPermission permissions,
|
||||
const auto MapSegment = [&](CodeSet::Segment& segment, VMAPermission permissions,
|
||||
MemoryState memory_state) {
|
||||
const auto vma = vm_manager
|
||||
.MapMemoryBlock(segment.addr + base_addr, module_.memory,
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
@@ -32,8 +33,6 @@ class KernelCore;
|
||||
class ResourceLimit;
|
||||
class Thread;
|
||||
|
||||
struct CodeSet;
|
||||
|
||||
struct AddressMapping {
|
||||
// Address and size must be page-aligned
|
||||
VAddr address;
|
||||
@@ -66,6 +65,46 @@ 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 {
|
||||
|
||||
@@ -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);
|
||||
Memory::SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
SetCurrentPageTable(&thread_owner_process->VMManager().page_table);
|
||||
}
|
||||
|
||||
cpu_core.LoadContext(new_thread->GetContext());
|
||||
|
||||
@@ -7,6 +7,8 @@
|
||||
#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"
|
||||
@@ -256,8 +258,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.
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter != wait_mutex_threads.end());
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -265,16 +267,11 @@ void Thread::AddMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == nullptr);
|
||||
|
||||
// Ensure that the thread is not already in the list of mutex waiters
|
||||
const auto iter = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(iter == wait_mutex_threads.end());
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr == 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();
|
||||
}
|
||||
|
||||
@@ -282,44 +279,32 @@ void Thread::RemoveMutexWaiter(SharedPtr<Thread> thread) {
|
||||
ASSERT(thread->lock_owner == this);
|
||||
|
||||
// Ensure that the thread is in the list of mutex waiters
|
||||
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);
|
||||
auto itr = std::find(wait_mutex_threads.begin(), wait_mutex_threads.end(), thread);
|
||||
ASSERT(itr != wait_mutex_threads.end());
|
||||
|
||||
boost::remove_erase(wait_mutex_threads, thread);
|
||||
thread->lock_owner = nullptr;
|
||||
UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::UpdatePriority() {
|
||||
// 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.
|
||||
// Find the highest priority among all the threads that are waiting for this thread's lock
|
||||
u32 new_priority = nominal_priority;
|
||||
if (!wait_mutex_threads.empty()) {
|
||||
if (wait_mutex_threads.front()->current_priority < new_priority) {
|
||||
new_priority = wait_mutex_threads.front()->current_priority;
|
||||
}
|
||||
for (const auto& thread : wait_mutex_threads) {
|
||||
if (thread->nominal_priority < new_priority)
|
||||
new_priority = thread->nominal_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.
|
||||
lock_owner->UpdatePriority();
|
||||
if (lock_owner)
|
||||
lock_owner->UpdatePriority();
|
||||
}
|
||||
|
||||
void Thread::ChangeCore(u32 core, u64 mask) {
|
||||
|
||||
@@ -401,14 +401,8 @@ private:
|
||||
VAddr entry_point = 0;
|
||||
VAddr stack_top = 0;
|
||||
|
||||
/// 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;
|
||||
u32 nominal_priority = 0; ///< Nominal thread priority, as set by the emulated application
|
||||
u32 current_priority = 0; ///< Current thread priority, can be temporarily changed
|
||||
|
||||
u64 total_cpu_time_ticks = 0; ///< Total CPU running ticks.
|
||||
u64 last_running_ticks = 0; ///< CPU tick when thread was last running
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
#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 {
|
||||
@@ -177,7 +177,7 @@ ResultVal<VAddr> VMManager::FindFreeRegion(u64 size) const {
|
||||
|
||||
ResultVal<VMManager::VMAHandle> VMManager::MapMMIO(VAddr target, PAddr paddr, u64 size,
|
||||
MemoryState state,
|
||||
Common::MemoryHookPointer mmio_handler) {
|
||||
Memory::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(),
|
||||
Common::PageType::Unmapped);
|
||||
Memory::PageType::Unmapped);
|
||||
}
|
||||
|
||||
VMManager::CheckResults VMManager::CheckRangeState(VAddr address, u64 size, MemoryState state_mask,
|
||||
|
||||
@@ -9,10 +9,9 @@
|
||||
#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;
|
||||
@@ -291,7 +290,7 @@ struct VirtualMemoryArea {
|
||||
// Settings for type = MMIO
|
||||
/// Physical address of the register area this VMA maps to.
|
||||
PAddr paddr = 0;
|
||||
Common::MemoryHookPointer mmio_handler = nullptr;
|
||||
Memory::MemoryHookPointer mmio_handler = nullptr;
|
||||
|
||||
/// Tests if this area can be merged to the right with `next`.
|
||||
bool CanBeMergedWith(const VirtualMemoryArea& next) const;
|
||||
@@ -369,7 +368,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,
|
||||
Common::MemoryHookPointer mmio_handler);
|
||||
Memory::MemoryHookPointer mmio_handler);
|
||||
|
||||
/// Unmaps a range of addresses, splitting VMAs as necessary.
|
||||
ResultCode UnmapRange(VAddr target, u64 size);
|
||||
@@ -510,7 +509,7 @@ public:
|
||||
|
||||
/// Each VMManager has its own page table, which is set as the main one when the owning process
|
||||
/// is scheduled.
|
||||
Common::PageTable page_table{Memory::PAGE_BITS};
|
||||
Memory::PageTable page_table;
|
||||
|
||||
private:
|
||||
using VMAIter = VMAMap::iterator;
|
||||
|
||||
@@ -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,84 +93,38 @@ 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, &IAudioController::ChangeMainAppletMasterVolume, "ChangeMainAppletMasterVolume"},
|
||||
{4, &IAudioController::SetTransparentAudioRate, "SetTransparentVolumeRate"},
|
||||
{1, &IAudioController::GetMainAppletExpectedMasterVolume,
|
||||
"GetMainAppletExpectedMasterVolume"},
|
||||
{2, &IAudioController::GetLibraryAppletExpectedMasterVolume,
|
||||
"GetLibraryAppletExpectedMasterVolume"},
|
||||
{3, nullptr, "ChangeMainAppletMasterVolume"},
|
||||
{4, nullptr, "SetTransparentVolumeRate"},
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
IAudioController::~IAudioController() = default;
|
||||
|
||||
void IAudioController::SetExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
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);
|
||||
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void IAudioController::GetMainAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called. main_applet_volume={}", main_applet_volume);
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push(main_applet_volume);
|
||||
rb.Push(volume);
|
||||
}
|
||||
|
||||
void IAudioController::GetLibraryAppletExpectedMasterVolume(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Service_AM, "called. library_applet_volume={}", library_applet_volume);
|
||||
LOG_WARNING(Service_AM, "(STUBBED) called");
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
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);
|
||||
rb.Push(volume);
|
||||
}
|
||||
|
||||
IDisplayController::IDisplayController() : ServiceFramework("IDisplayController") {
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <chrono>
|
||||
#include <memory>
|
||||
#include <queue>
|
||||
#include "core/hle/kernel/writable_event.h"
|
||||
@@ -82,21 +81,8 @@ 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);
|
||||
|
||||
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};
|
||||
u32 volume{100};
|
||||
};
|
||||
|
||||
class IDisplayController final : public ServiceFramework<IDisplayController> {
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include <vector>
|
||||
|
||||
#include <opus.h>
|
||||
#include <opus_multistream.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@@ -19,12 +18,12 @@
|
||||
namespace Service::Audio {
|
||||
namespace {
|
||||
struct OpusDeleter {
|
||||
void operator()(OpusMSDecoder* ptr) const {
|
||||
opus_multistream_decoder_destroy(ptr);
|
||||
void operator()(void* ptr) const {
|
||||
operator delete(ptr);
|
||||
}
|
||||
};
|
||||
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusMSDecoder, OpusDeleter>;
|
||||
using OpusDecoderPtr = std::unique_ptr<OpusDecoder, OpusDeleter>;
|
||||
|
||||
struct OpusPacketHeader {
|
||||
// Packet size in bytes.
|
||||
@@ -34,7 +33,7 @@ struct OpusPacketHeader {
|
||||
};
|
||||
static_assert(sizeof(OpusPacketHeader) == 0x8, "OpusHeader is an invalid size");
|
||||
|
||||
class OpusDecoderState {
|
||||
class OpusDecoderStateBase {
|
||||
public:
|
||||
/// Describes extra behavior that may be asked of the decoding context.
|
||||
enum class ExtraBehavior {
|
||||
@@ -50,13 +49,22 @@ public:
|
||||
Enabled,
|
||||
};
|
||||
|
||||
explicit OpusDecoderState(OpusDecoderPtr decoder, u32 sample_rate, u32 channel_count)
|
||||
: decoder{std::move(decoder)}, sample_rate{sample_rate}, channel_count{channel_count} {}
|
||||
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} {}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx, PerfTime perf_time,
|
||||
ExtraBehavior extra_behavior) {
|
||||
ExtraBehavior extra_behavior) override {
|
||||
if (perf_time == PerfTime::Disabled) {
|
||||
DecodeInterleavedHelper(ctx, nullptr, extra_behavior);
|
||||
} else {
|
||||
@@ -127,7 +135,7 @@ private:
|
||||
|
||||
const int frame_size = (static_cast<int>(raw_output_sz / sizeof(s16) / channel_count));
|
||||
const auto out_sample_count =
|
||||
opus_multistream_decode(decoder.get(), frame, hdr.size, output.data(), frame_size, 0);
|
||||
opus_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, "
|
||||
@@ -150,7 +158,7 @@ private:
|
||||
void ResetDecoderContext() {
|
||||
ASSERT(decoder != nullptr);
|
||||
|
||||
opus_multistream_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
opus_decoder_ctl(decoder.get(), OPUS_RESET_STATE);
|
||||
}
|
||||
|
||||
OpusDecoderPtr decoder;
|
||||
@@ -160,7 +168,7 @@ private:
|
||||
|
||||
class IHardwareOpusDecoderManager final : public ServiceFramework<IHardwareOpusDecoderManager> {
|
||||
public:
|
||||
explicit IHardwareOpusDecoderManager(OpusDecoderState decoder_state)
|
||||
explicit IHardwareOpusDecoderManager(std::unique_ptr<OpusDecoderStateBase> decoder_state)
|
||||
: ServiceFramework("IHardwareOpusDecoderManager"), decoder_state{std::move(decoder_state)} {
|
||||
// clang-format off
|
||||
static const FunctionInfo functions[] = {
|
||||
@@ -182,51 +190,35 @@ private:
|
||||
void DecodeInterleavedOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Disabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Disabled,
|
||||
OpusDecoderStateBase::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleavedWithPerfOld(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled,
|
||||
OpusDecoderState::ExtraBehavior::None);
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
OpusDecoderStateBase::ExtraBehavior::None);
|
||||
}
|
||||
|
||||
void DecodeInterleaved(Kernel::HLERequestContext& ctx) {
|
||||
LOG_DEBUG(Audio, "called");
|
||||
|
||||
IPC::RequestParser rp{ctx};
|
||||
const auto extra_behavior = rp.Pop<bool>() ? OpusDecoderState::ExtraBehavior::ResetContext
|
||||
: OpusDecoderState::ExtraBehavior::None;
|
||||
const auto extra_behavior = rp.Pop<bool>()
|
||||
? OpusDecoderStateBase::ExtraBehavior::ResetContext
|
||||
: OpusDecoderStateBase::ExtraBehavior::None;
|
||||
|
||||
decoder_state.DecodeInterleaved(ctx, OpusDecoderState::PerfTime::Enabled, extra_behavior);
|
||||
decoder_state->DecodeInterleaved(ctx, OpusDecoderStateBase::PerfTime::Enabled,
|
||||
extra_behavior);
|
||||
}
|
||||
|
||||
OpusDecoderState decoder_state;
|
||||
std::unique_ptr<OpusDecoderStateBase> decoder_state;
|
||||
};
|
||||
|
||||
std::size_t WorkerBufferSize(u32 channel_count) {
|
||||
ASSERT_MSG(channel_count == 1 || channel_count == 2, "Invalid 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}};
|
||||
return opus_decoder_get_size(static_cast<int>(channel_count));
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
@@ -267,15 +259,9 @@ 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");
|
||||
|
||||
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);
|
||||
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);
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
// TODO(ogniK): Use correct error code
|
||||
rb.Push(ResultCode(-1));
|
||||
@@ -285,7 +271,7 @@ void HwOpus::OpenOpusDecoder(Kernel::HLERequestContext& ctx) {
|
||||
IPC::ResponseBuilder rb{ctx, 2, 0, 1};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.PushIpcInterface<IHardwareOpusDecoderManager>(
|
||||
OpusDecoderState{std::move(decoder), sample_rate, channel_count});
|
||||
std::make_unique<OpusDecoderState>(std::move(decoder), sample_rate, channel_count));
|
||||
}
|
||||
|
||||
HwOpus::HwOpus() : ServiceFramework("hwopus") {
|
||||
|
||||
@@ -733,10 +733,7 @@ FSP_SRV::FSP_SRV() : ServiceFramework("fsp-srv") {
|
||||
FSP_SRV::~FSP_SRV() = default;
|
||||
|
||||
void FSP_SRV::SetCurrentProcess(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
current_process_id = rp.Pop<u64>();
|
||||
|
||||
LOG_DEBUG(Service_FS, "called. current_process_id=0x{:016X}", current_process_id);
|
||||
LOG_WARNING(Service_FS, "(STUBBED) called");
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 2};
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
|
||||
@@ -32,7 +32,6 @@ private:
|
||||
void OpenPatchDataStorageByCurrentProcess(Kernel::HLERequestContext& ctx);
|
||||
|
||||
FileSys::VirtualFile romfs;
|
||||
u64 current_process_id = 0;
|
||||
};
|
||||
|
||||
} // namespace Service::FileSystem
|
||||
|
||||
@@ -41,20 +41,20 @@ private:
|
||||
struct PadState {
|
||||
union {
|
||||
u32_le raw{};
|
||||
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;
|
||||
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;
|
||||
};
|
||||
};
|
||||
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> connected;
|
||||
BitField<0, 1, u32_le> connected;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
|
||||
|
||||
@@ -39,13 +39,13 @@ public:
|
||||
union {
|
||||
u32_le raw{};
|
||||
|
||||
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<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<6, 1, u32> pokeball; // TODO(ogniK): Confirm when possible
|
||||
BitField<6, 1, u32_le> 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> 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;
|
||||
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;
|
||||
|
||||
// D-Pad
|
||||
BitField<12, 1, u64> d_left;
|
||||
BitField<13, 1, u64> d_up;
|
||||
BitField<14, 1, u64> d_right;
|
||||
BitField<15, 1, u64> d_down;
|
||||
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;
|
||||
|
||||
// Left JoyStick
|
||||
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;
|
||||
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;
|
||||
|
||||
// Right JoyStick
|
||||
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;
|
||||
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;
|
||||
|
||||
// Not always active?
|
||||
BitField<24, 1, u64> left_sl;
|
||||
BitField<25, 1, u64> left_sr;
|
||||
BitField<24, 1, u64_le> left_sl;
|
||||
BitField<25, 1, u64_le> left_sr;
|
||||
|
||||
BitField<26, 1, u64> right_sl;
|
||||
BitField<27, 1, u64> right_sr;
|
||||
BitField<26, 1, u64_le> right_sl;
|
||||
BitField<27, 1, u64_le> 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> 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;
|
||||
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;
|
||||
};
|
||||
};
|
||||
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> is_vertical;
|
||||
BitField<12, 1, s64> is_horizontal;
|
||||
BitField<13, 1, s64> use_plus;
|
||||
BitField<14, 1, s64> use_minus;
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
struct NPadDevice {
|
||||
union {
|
||||
u32_le raw{};
|
||||
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;
|
||||
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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -33,8 +33,8 @@ private:
|
||||
struct Attributes {
|
||||
union {
|
||||
u32 raw{};
|
||||
BitField<0, 1, u32> start_touch;
|
||||
BitField<1, 1, u32> end_touch;
|
||||
BitField<0, 1, u32_le> start_touch;
|
||||
BitField<1, 1, u32_le> end_touch;
|
||||
};
|
||||
};
|
||||
static_assert(sizeof(Attributes) == 0x4, "Attributes is an invalid size");
|
||||
|
||||
@@ -42,7 +42,7 @@ private:
|
||||
union {
|
||||
BitField<0, 16, Flags> flags;
|
||||
BitField<16, 8, Severity> severity;
|
||||
BitField<24, 8, u32> verbosity;
|
||||
BitField<24, 8, u32_le> verbosity;
|
||||
};
|
||||
u32_le payload_size;
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ public:
|
||||
virtual ~nvdevice() = default;
|
||||
union Ioctl {
|
||||
u32_le raw;
|
||||
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;
|
||||
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;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#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"
|
||||
|
||||
147
src/core/loader/linker.cpp
Normal file
147
src/core/loader/linker.cpp
Normal file
@@ -0,0 +1,147 @@
|
||||
// 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
|
||||
36
src/core/loader/linker.h
Normal file
36
src/core/loader/linker.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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
|
||||
@@ -14,7 +14,6 @@
|
||||
#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"
|
||||
|
||||
@@ -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 {
|
||||
class AppLoader_NRO final : public AppLoader, Linker {
|
||||
public:
|
||||
explicit AppLoader_NRO(FileSys::VirtualFile file);
|
||||
~AppLoader_NRO() override;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include "common/swap.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"
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
#include <optional>
|
||||
#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 {
|
||||
@@ -26,7 +26,7 @@ struct NSOArgumentHeader {
|
||||
static_assert(sizeof(NSOArgumentHeader) == 0x20, "NSOArgumentHeader has incorrect size.");
|
||||
|
||||
/// Loads an NSO file
|
||||
class AppLoader_NSO final : public AppLoader {
|
||||
class AppLoader_NSO final : public AppLoader, Linker {
|
||||
public:
|
||||
explicit AppLoader_NSO(FileSys::VirtualFile file);
|
||||
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#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"
|
||||
@@ -24,9 +23,9 @@
|
||||
|
||||
namespace Memory {
|
||||
|
||||
static Common::PageTable* current_page_table = nullptr;
|
||||
static PageTable* current_page_table = nullptr;
|
||||
|
||||
void SetCurrentPageTable(Common::PageTable* page_table) {
|
||||
void SetCurrentPageTable(PageTable* page_table) {
|
||||
current_page_table = page_table;
|
||||
|
||||
auto& system = Core::System::GetInstance();
|
||||
@@ -38,12 +37,34 @@ void SetCurrentPageTable(Common::PageTable* page_table) {
|
||||
}
|
||||
}
|
||||
|
||||
Common::PageTable* GetCurrentPageTable() {
|
||||
PageTable* GetCurrentPageTable() {
|
||||
return current_page_table;
|
||||
}
|
||||
|
||||
static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* memory,
|
||||
Common::PageType type) {
|
||||
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) {
|
||||
LOG_DEBUG(HW_Memory, "Mapping {} onto {:016X}-{:016X}", fmt::ptr(memory), base * PAGE_SIZE,
|
||||
(base + size) * PAGE_SIZE);
|
||||
|
||||
@@ -71,47 +92,41 @@ static void MapPages(Common::PageTable& page_table, VAddr base, u64 size, u8* me
|
||||
}
|
||||
}
|
||||
|
||||
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* target) {
|
||||
void MapMemoryRegion(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, Common::PageType::Memory);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory);
|
||||
}
|
||||
|
||||
void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer mmio_handler) {
|
||||
void MapIoRegion(PageTable& page_table, VAddr base, u64 size, 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, Common::PageType::Special);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special);
|
||||
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
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}));
|
||||
SpecialRegion region{SpecialRegion::Type::IODevice, std::move(mmio_handler)};
|
||||
page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
|
||||
void UnmapRegion(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, Common::PageType::Unmapped);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped);
|
||||
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
page_table.special_regions.erase(interval);
|
||||
}
|
||||
|
||||
void AddDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook) {
|
||||
void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.add(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.add(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
void RemoveDebugHook(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer hook) {
|
||||
void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook) {
|
||||
auto interval = boost::icl::discrete_interval<VAddr>::closed(base, base + size - 1);
|
||||
Common::SpecialRegion region{Common::SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.subtract(
|
||||
std::make_pair(interval, std::set<Common::SpecialRegion>{region}));
|
||||
SpecialRegion region{SpecialRegion::Type::DebugHook, std::move(hook)};
|
||||
page_table.special_regions.subtract(std::make_pair(interval, std::set<SpecialRegion>{region}));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -160,15 +175,15 @@ T Read(const VAddr vaddr) {
|
||||
return value;
|
||||
}
|
||||
|
||||
Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped:
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Read{} @ 0x{:08X}", sizeof(T) * 8, vaddr);
|
||||
return 0;
|
||||
case Common::PageType::Memory:
|
||||
case PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
auto host_ptr{GetPointerFromVMA(vaddr)};
|
||||
Core::System::GetInstance().GPU().FlushRegion(ToCacheAddr(host_ptr), sizeof(T));
|
||||
T value;
|
||||
@@ -190,16 +205,16 @@ void Write(const VAddr vaddr, const T data) {
|
||||
return;
|
||||
}
|
||||
|
||||
Common::PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
case Common::PageType::Unmapped:
|
||||
case PageType::Unmapped:
|
||||
LOG_ERROR(HW_Memory, "Unmapped Write{} 0x{:08X} @ 0x{:016X}", sizeof(data) * 8,
|
||||
static_cast<u32>(data), vaddr);
|
||||
return;
|
||||
case Common::PageType::Memory:
|
||||
case PageType::Memory:
|
||||
ASSERT_MSG(false, "Mapped memory page without a pointer @ {:016X}", vaddr);
|
||||
break;
|
||||
case Common::PageType::RasterizerCachedMemory: {
|
||||
case PageType::RasterizerCachedMemory: {
|
||||
auto host_ptr{GetPointerFromVMA(vaddr)};
|
||||
Core::System::GetInstance().GPU().InvalidateRegion(ToCacheAddr(host_ptr), sizeof(T));
|
||||
std::memcpy(host_ptr, &data, sizeof(T));
|
||||
@@ -217,10 +232,10 @@ bool IsValidVirtualAddress(const Kernel::Process& process, const VAddr vaddr) {
|
||||
if (page_pointer)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] == Common::PageType::RasterizerCachedMemory)
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory)
|
||||
return true;
|
||||
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] != Common::PageType::Special)
|
||||
if (page_table.attributes[vaddr >> PAGE_BITS] != PageType::Special)
|
||||
return false;
|
||||
|
||||
return false;
|
||||
@@ -240,8 +255,7 @@ u8* GetPointer(const VAddr vaddr) {
|
||||
return page_pointer + (vaddr & PAGE_MASK);
|
||||
}
|
||||
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] ==
|
||||
Common::PageType::RasterizerCachedMemory) {
|
||||
if (current_page_table->attributes[vaddr >> PAGE_BITS] == PageType::RasterizerCachedMemory) {
|
||||
return GetPointerFromVMA(vaddr);
|
||||
}
|
||||
|
||||
@@ -275,20 +289,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) {
|
||||
Common::PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
PageType& page_type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
|
||||
if (cached) {
|
||||
// Switch page type to cached if now cached
|
||||
switch (page_type) {
|
||||
case Common::PageType::Unmapped:
|
||||
case 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 Common::PageType::Memory:
|
||||
page_type = Common::PageType::RasterizerCachedMemory;
|
||||
case PageType::Memory:
|
||||
page_type = PageType::RasterizerCachedMemory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = nullptr;
|
||||
break;
|
||||
case Common::PageType::RasterizerCachedMemory:
|
||||
case 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;
|
||||
@@ -298,23 +312,23 @@ void RasterizerMarkRegionCached(VAddr vaddr, u64 size, bool cached) {
|
||||
} else {
|
||||
// Switch page type to uncached if now uncached
|
||||
switch (page_type) {
|
||||
case Common::PageType::Unmapped:
|
||||
case 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 Common::PageType::Memory:
|
||||
case 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 Common::PageType::RasterizerCachedMemory: {
|
||||
case 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 = Common::PageType::Unmapped;
|
||||
page_type = PageType::Unmapped;
|
||||
} else {
|
||||
page_type = Common::PageType::Memory;
|
||||
page_type = PageType::Memory;
|
||||
current_page_table->pointers[vaddr >> PAGE_BITS] = pointer;
|
||||
}
|
||||
break;
|
||||
@@ -356,21 +370,21 @@ 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 Common::PageType::Unmapped: {
|
||||
case 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 Common::PageType::Memory: {
|
||||
case 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 Common::PageType::RasterizerCachedMemory: {
|
||||
case 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);
|
||||
@@ -420,20 +434,20 @@ 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 Common::PageType::Unmapped: {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped WriteBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
case 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 Common::PageType::RasterizerCachedMemory: {
|
||||
case 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);
|
||||
@@ -466,20 +480,20 @@ 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 Common::PageType::Unmapped: {
|
||||
case PageType::Unmapped: {
|
||||
LOG_ERROR(HW_Memory,
|
||||
"Unmapped ZeroBlock @ 0x{:016X} (start address = 0x{:016X}, size = {})",
|
||||
current_vaddr, dest_addr, size);
|
||||
break;
|
||||
}
|
||||
case Common::PageType::Memory: {
|
||||
case 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 Common::PageType::RasterizerCachedMemory: {
|
||||
case 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);
|
||||
@@ -508,20 +522,20 @@ 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 Common::PageType::Unmapped: {
|
||||
case 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 Common::PageType::Memory: {
|
||||
case 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 Common::PageType::RasterizerCachedMemory: {
|
||||
case 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);
|
||||
|
||||
@@ -10,10 +10,7 @@
|
||||
#include <vector>
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace Kernel {
|
||||
class Process;
|
||||
@@ -29,6 +26,71 @@ 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.
|
||||
@@ -54,8 +116,8 @@ enum : VAddr {
|
||||
};
|
||||
|
||||
/// Currently active page table
|
||||
void SetCurrentPageTable(Common::PageTable* page_table);
|
||||
Common::PageTable* GetCurrentPageTable();
|
||||
void SetCurrentPageTable(PageTable* page_table);
|
||||
PageTable* GetCurrentPageTable();
|
||||
|
||||
/// Determines if the given VAddr is valid for the specified process.
|
||||
bool IsValidVirtualAddress(const Kernel::Process& process, VAddr vaddr);
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/memory_hook.h"
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
namespace Memory {
|
||||
|
||||
MemoryHook::~MemoryHook() = default;
|
||||
|
||||
} // namespace Common
|
||||
} // namespace Memory
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace Common {
|
||||
namespace Memory {
|
||||
|
||||
/**
|
||||
* Memory hooks have two purposes:
|
||||
@@ -44,4 +44,4 @@ public:
|
||||
};
|
||||
|
||||
using MemoryHookPointer = std::shared_ptr<MemoryHook>;
|
||||
} // namespace Common
|
||||
} // namespace Memory
|
||||
@@ -5,11 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/memory_hook.h"
|
||||
|
||||
namespace Common {
|
||||
struct PageTable;
|
||||
}
|
||||
#include "core/memory_hook.h"
|
||||
|
||||
namespace Memory {
|
||||
|
||||
@@ -21,7 +17,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(Common::PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
void MapMemoryRegion(PageTable& page_table, VAddr base, u64 size, u8* target);
|
||||
|
||||
/**
|
||||
* Maps a region of the emulated process address space as a IO region.
|
||||
@@ -30,14 +26,11 @@ void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, u8* ta
|
||||
* @param size The amount of bytes to map. Must be page-aligned.
|
||||
* @param mmio_handler The handler that backs the mapping.
|
||||
*/
|
||||
void MapIoRegion(Common::PageTable& page_table, VAddr base, u64 size,
|
||||
Common::MemoryHookPointer mmio_handler);
|
||||
void MapIoRegion(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer mmio_handler);
|
||||
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size);
|
||||
void UnmapRegion(PageTable& page_table, VAddr base, u64 size);
|
||||
|
||||
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);
|
||||
void AddDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
|
||||
void RemoveDebugHook(PageTable& page_table, VAddr base, u64 size, MemoryHookPointer hook);
|
||||
|
||||
} // namespace Memory
|
||||
|
||||
@@ -24,19 +24,17 @@ namespace InputCommon::SDL {
|
||||
|
||||
class State {
|
||||
public:
|
||||
using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>;
|
||||
|
||||
/// Unregisters SDL device factories and shut them down.
|
||||
/// Unresisters SDL device factories and shut them down.
|
||||
virtual ~State() = default;
|
||||
|
||||
virtual Pollers GetPollers(Polling::DeviceType type) = 0;
|
||||
virtual std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) = 0;
|
||||
};
|
||||
|
||||
class NullState : public State {
|
||||
public:
|
||||
Pollers GetPollers(Polling::DeviceType type) override {
|
||||
return {};
|
||||
}
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) override {}
|
||||
};
|
||||
|
||||
std::unique_ptr<State> Init();
|
||||
|
||||
@@ -475,11 +475,12 @@ SDLState::SDLState() {
|
||||
|
||||
initialized = true;
|
||||
if (start_thread) {
|
||||
poll_thread = std::thread([this] {
|
||||
poll_thread = std::thread([&] {
|
||||
using namespace std::chrono_literals;
|
||||
SDL_Event event;
|
||||
while (initialized) {
|
||||
SDL_PumpEvents();
|
||||
std::this_thread::sleep_for(10ms);
|
||||
std::this_thread::sleep_for(std::chrono::duration(10ms));
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -650,9 +651,9 @@ private:
|
||||
};
|
||||
} // namespace Polling
|
||||
|
||||
SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
|
||||
Pollers pollers;
|
||||
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> SDLState::GetPollers(
|
||||
InputCommon::Polling::DeviceType type) {
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> pollers;
|
||||
switch (type) {
|
||||
case InputCommon::Polling::DeviceType::Analog:
|
||||
pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this));
|
||||
@@ -660,9 +661,8 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) {
|
||||
case InputCommon::Polling::DeviceType::Button:
|
||||
pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this));
|
||||
break;
|
||||
return pollers;
|
||||
}
|
||||
|
||||
return pollers;
|
||||
}
|
||||
|
||||
} // namespace SDL
|
||||
|
||||
@@ -25,7 +25,7 @@ public:
|
||||
/// Initializes and registers SDL device factories
|
||||
SDLState();
|
||||
|
||||
/// Unregisters SDL device factories and shut them down.
|
||||
/// Unresisters SDL device factories and shut them down.
|
||||
~SDLState() override;
|
||||
|
||||
/// Handle SDL_Events for joysticks from SDL_PollEvent
|
||||
@@ -35,7 +35,8 @@ public:
|
||||
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;
|
||||
std::vector<std::unique_ptr<InputCommon::Polling::DevicePoller>> GetPollers(
|
||||
InputCommon::Polling::DeviceType type) override;
|
||||
|
||||
/// Used by the Pollers during config
|
||||
std::atomic<bool> polling = false;
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
add_executable(tests
|
||||
common/bit_field.cpp
|
||||
common/param_package.cpp
|
||||
common/ring_buffer.cpp
|
||||
core/arm/arm_test_common.cpp
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
// 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,
|
||||
}});
|
||||
}
|
||||
@@ -4,7 +4,6 @@
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/page_table.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
#include "core/memory.h"
|
||||
@@ -23,7 +22,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(),
|
||||
Common::PageType::Unmapped);
|
||||
Memory::PageType::Unmapped);
|
||||
|
||||
Memory::MapIoRegion(*page_table, 0x00000000, 0x80000000, test_memory);
|
||||
Memory::MapIoRegion(*page_table, 0x80000000, 0x80000000, test_memory);
|
||||
|
||||
@@ -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 Common {
|
||||
namespace Memory {
|
||||
struct PageTable;
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ public:
|
||||
|
||||
private:
|
||||
friend struct TestMemory;
|
||||
struct TestMemory final : Common::MemoryHook {
|
||||
struct TestMemory final : Memory::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;
|
||||
Common::PageTable* page_table = nullptr;
|
||||
Memory::PageTable* page_table = nullptr;
|
||||
Kernel::KernelCore kernel;
|
||||
};
|
||||
|
||||
|
||||
@@ -106,8 +106,6 @@ add_library(video_core STATIC
|
||||
textures/decoders.cpp
|
||||
textures/decoders.h
|
||||
textures/texture.h
|
||||
texture_cache.cpp
|
||||
texture_cache.h
|
||||
video_core.cpp
|
||||
video_core.h
|
||||
)
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/alignment.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/cityhash.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/surface.h"
|
||||
#include "video_core/texture_cache.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
#include "video_core/textures/texture.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
using VideoCore::Surface::SurfaceTarget;
|
||||
|
||||
using VideoCore::Surface::ComponentTypeFromDepthFormat;
|
||||
using VideoCore::Surface::ComponentTypeFromRenderTarget;
|
||||
using VideoCore::Surface::ComponentTypeFromTexture;
|
||||
using VideoCore::Surface::PixelFormatFromDepthFormat;
|
||||
using VideoCore::Surface::PixelFormatFromRenderTargetFormat;
|
||||
using VideoCore::Surface::PixelFormatFromTextureFormat;
|
||||
using VideoCore::Surface::SurfaceTargetFromTextureType;
|
||||
|
||||
constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
|
||||
return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
|
||||
const Tegra::Texture::FullTextureInfo& config) {
|
||||
SurfaceParams params;
|
||||
params.is_tiled = config.tic.IsTiled();
|
||||
params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0,
|
||||
params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0,
|
||||
params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0,
|
||||
params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1;
|
||||
params.pixel_format =
|
||||
PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), false);
|
||||
params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value());
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.target = SurfaceTargetFromTextureType(config.tic.texture_type);
|
||||
params.width = Common::AlignUp(config.tic.Width(), GetCompressionFactor(params.pixel_format));
|
||||
params.height = Common::AlignUp(config.tic.Height(), GetCompressionFactor(params.pixel_format));
|
||||
params.depth = config.tic.Depth();
|
||||
if (params.target == SurfaceTarget::TextureCubemap ||
|
||||
params.target == SurfaceTarget::TextureCubeArray) {
|
||||
params.depth *= 6;
|
||||
}
|
||||
params.pitch = params.is_tiled ? 0 : config.tic.Pitch();
|
||||
params.unaligned_height = config.tic.Height();
|
||||
params.num_levels = config.tic.max_mip_level + 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForDepthBuffer(
|
||||
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
|
||||
u32 block_width, u32 block_height, u32 block_depth,
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type) {
|
||||
SurfaceParams params;
|
||||
params.is_tiled = type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
|
||||
params.block_width = 1 << std::min(block_width, 5U);
|
||||
params.block_height = 1 << std::min(block_height, 5U);
|
||||
params.block_depth = 1 << std::min(block_depth, 5U);
|
||||
params.tile_width_spacing = 1;
|
||||
params.pixel_format = PixelFormatFromDepthFormat(format);
|
||||
params.component_type = ComponentTypeFromDepthFormat(format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = zeta_width;
|
||||
params.height = zeta_height;
|
||||
params.unaligned_height = zeta_height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.depth = 1;
|
||||
params.num_levels = 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForFramebuffer(Core::System& system, std::size_t index) {
|
||||
const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
|
||||
SurfaceParams params;
|
||||
params.is_tiled =
|
||||
config.memory_layout.type == Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout::BlockLinear;
|
||||
params.block_width = 1 << config.memory_layout.block_width;
|
||||
params.block_height = 1 << config.memory_layout.block_height;
|
||||
params.block_depth = 1 << config.memory_layout.block_depth;
|
||||
params.tile_width_spacing = 1;
|
||||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
|
||||
params.component_type = ComponentTypeFromRenderTarget(config.format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
if (params.is_tiled) {
|
||||
params.width = config.width;
|
||||
} else {
|
||||
const u32 bpp = GetFormatBpp(params.pixel_format) / CHAR_BIT;
|
||||
params.pitch = config.width;
|
||||
params.width = params.pitch / bpp;
|
||||
}
|
||||
params.height = config.height;
|
||||
params.depth = 1;
|
||||
params.unaligned_height = config.height;
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.num_levels = 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
SurfaceParams SurfaceParams::CreateForFermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
|
||||
SurfaceParams params{};
|
||||
params.is_tiled = !config.linear;
|
||||
params.block_width = params.is_tiled ? std::min(config.BlockWidth(), 32U) : 0,
|
||||
params.block_height = params.is_tiled ? std::min(config.BlockHeight(), 32U) : 0,
|
||||
params.block_depth = params.is_tiled ? std::min(config.BlockDepth(), 32U) : 0,
|
||||
params.tile_width_spacing = 1;
|
||||
params.pixel_format = PixelFormatFromRenderTargetFormat(config.format);
|
||||
params.component_type = ComponentTypeFromRenderTarget(config.format);
|
||||
params.type = GetFormatType(params.pixel_format);
|
||||
params.width = config.width;
|
||||
params.height = config.height;
|
||||
params.unaligned_height = config.height;
|
||||
// TODO(Rodrigo): Try to guess the surface target from depth and layer parameters
|
||||
params.target = SurfaceTarget::Texture2D;
|
||||
params.depth = 1;
|
||||
params.num_levels = 1;
|
||||
|
||||
params.CalculateCachedValues();
|
||||
return params;
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipWidth(u32 level) const {
|
||||
return std::max(1U, width >> level);
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipHeight(u32 level) const {
|
||||
return std::max(1U, height >> level);
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipDepth(u32 level) const {
|
||||
return IsLayered() ? depth : std::max(1U, depth >> level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsLayered() const {
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipBlockHeight(u32 level) const {
|
||||
// Auto block resizing algorithm from:
|
||||
// https://cgit.freedesktop.org/mesa/mesa/tree/src/gallium/drivers/nouveau/nv50/nv50_miptree.c
|
||||
if (level == 0) {
|
||||
return block_height;
|
||||
}
|
||||
const u32 height{GetMipHeight(level)};
|
||||
const u32 default_block_height{GetDefaultBlockHeight(pixel_format)};
|
||||
const u32 blocks_in_y{(height + default_block_height - 1) / default_block_height};
|
||||
u32 block_height = 16;
|
||||
while (block_height > 1 && blocks_in_y <= block_height * 4) {
|
||||
block_height >>= 1;
|
||||
}
|
||||
return block_height;
|
||||
}
|
||||
|
||||
u32 SurfaceParams::GetMipBlockDepth(u32 level) const {
|
||||
if (level == 0)
|
||||
return block_depth;
|
||||
if (target != SurfaceTarget::Texture3D)
|
||||
return 1;
|
||||
|
||||
const u32 depth{GetMipDepth(level)};
|
||||
u32 block_depth = 32;
|
||||
while (block_depth > 1 && depth * 2 <= block_depth) {
|
||||
block_depth >>= 1;
|
||||
}
|
||||
if (block_depth == 32 && GetMipBlockHeight(level) >= 4) {
|
||||
return 16;
|
||||
}
|
||||
return block_depth;
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetGuestMipmapLevelOffset(u32 level) const {
|
||||
std::size_t offset = 0;
|
||||
for (u32 i = 0; i < level; i++) {
|
||||
offset += GetInnerMipmapMemorySize(i, false, IsLayered(), false);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetHostMipmapLevelOffset(u32 level) const {
|
||||
std::size_t offset = 0;
|
||||
for (u32 i = 0; i < level; i++) {
|
||||
offset += GetInnerMipmapMemorySize(i, true, false, false);
|
||||
}
|
||||
return offset;
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetGuestLayerSize() const {
|
||||
return GetInnerMemorySize(false, true, false);
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetHostLayerSize(u32 level) const {
|
||||
return GetInnerMipmapMemorySize(level, true, IsLayered(), false);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsFamiliar(const SurfaceParams& view_params) const {
|
||||
if (std::tie(is_tiled, tile_width_spacing, pixel_format, component_type, type) !=
|
||||
std::tie(view_params.is_tiled, view_params.tile_width_spacing, view_params.pixel_format,
|
||||
view_params.component_type, view_params.type)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const SurfaceTarget view_target{view_params.target};
|
||||
if (view_target == target) {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
case SurfaceTarget::Texture2D:
|
||||
case SurfaceTarget::Texture3D:
|
||||
return false;
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
return view_target == SurfaceTarget::Texture1D;
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
return view_target == SurfaceTarget::Texture2D;
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
return view_target == SurfaceTarget::Texture2D ||
|
||||
view_target == SurfaceTarget::Texture2DArray;
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
return view_target == SurfaceTarget::Texture2D ||
|
||||
view_target == SurfaceTarget::Texture2DArray ||
|
||||
view_target == SurfaceTarget::TextureCubemap;
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented texture family={}", static_cast<u32>(target));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsPixelFormatZeta() const {
|
||||
return pixel_format >= VideoCore::Surface::PixelFormat::MaxColorFormat &&
|
||||
pixel_format < VideoCore::Surface::PixelFormat::MaxDepthStencilFormat;
|
||||
}
|
||||
|
||||
void SurfaceParams::CalculateCachedValues() {
|
||||
guest_size_in_bytes = GetInnerMemorySize(false, false, false);
|
||||
|
||||
// ASTC is uncompressed in software, in emulated as RGBA8
|
||||
if (IsPixelFormatASTC(pixel_format)) {
|
||||
host_size_in_bytes = width * height * depth * 4;
|
||||
} else {
|
||||
host_size_in_bytes = GetInnerMemorySize(true, false, false);
|
||||
}
|
||||
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
case SurfaceTarget::Texture2D:
|
||||
case SurfaceTarget::Texture3D:
|
||||
num_layers = 1;
|
||||
break;
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
case SurfaceTarget::TextureCubeArray:
|
||||
num_layers = depth;
|
||||
break;
|
||||
default:
|
||||
UNREACHABLE();
|
||||
}
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
|
||||
bool uncompressed) const {
|
||||
const bool tiled{as_host_size ? false : is_tiled};
|
||||
const u32 tile_x{GetDefaultBlockWidth(pixel_format)};
|
||||
const u32 tile_y{GetDefaultBlockHeight(pixel_format)};
|
||||
const u32 width{GetMipmapSize(uncompressed, GetMipWidth(level), tile_x)};
|
||||
const u32 height{GetMipmapSize(uncompressed, GetMipHeight(level), tile_y)};
|
||||
const u32 depth{layer_only ? 1U : GetMipDepth(level)};
|
||||
return Tegra::Texture::CalculateSize(tiled, GetBytesPerPixel(pixel_format), width, height,
|
||||
depth, GetMipBlockHeight(level), GetMipBlockDepth(level));
|
||||
}
|
||||
|
||||
std::size_t SurfaceParams::GetInnerMemorySize(bool as_host_size, bool layer_only,
|
||||
bool uncompressed) const {
|
||||
std::size_t size = 0;
|
||||
for (u32 level = 0; level < num_levels; ++level) {
|
||||
size += GetInnerMipmapMemorySize(level, as_host_size, layer_only, uncompressed);
|
||||
}
|
||||
if (!as_host_size && is_tiled) {
|
||||
size = Common::AlignUp(size, Tegra::Texture::GetGOBSize() * block_height * block_depth);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
std::map<u64, std::pair<u32, u32>> SurfaceParams::CreateViewOffsetMap() const {
|
||||
std::map<u64, std::pair<u32, u32>> view_offset_map;
|
||||
switch (target) {
|
||||
case SurfaceTarget::Texture1D:
|
||||
case SurfaceTarget::Texture2D:
|
||||
case SurfaceTarget::Texture3D: {
|
||||
constexpr u32 layer = 0;
|
||||
for (u32 level = 0; level < num_levels; ++level) {
|
||||
const std::size_t offset{GetGuestMipmapLevelOffset(level)};
|
||||
view_offset_map.insert({offset, {layer, level}});
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SurfaceTarget::Texture1DArray:
|
||||
case SurfaceTarget::Texture2DArray:
|
||||
case SurfaceTarget::TextureCubemap:
|
||||
case SurfaceTarget::TextureCubeArray: {
|
||||
const std::size_t layer_size{GetGuestLayerSize()};
|
||||
for (u32 level = 0; level < num_levels; ++level) {
|
||||
const std::size_t level_offset{GetGuestMipmapLevelOffset(level)};
|
||||
for (u32 layer = 0; layer < num_layers; ++layer) {
|
||||
const auto layer_offset{static_cast<std::size_t>(layer_size * layer)};
|
||||
const std::size_t offset{level_offset + layer_offset};
|
||||
view_offset_map.insert({offset, {layer, level}});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unimplemented surface target {}", static_cast<u32>(target));
|
||||
}
|
||||
return view_offset_map;
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const {
|
||||
return IsDimensionValid(view_params, level) && IsDepthValid(view_params, level) &&
|
||||
IsInBounds(view_params, layer, level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsDimensionValid(const SurfaceParams& view_params, u32 level) const {
|
||||
return view_params.width == GetMipWidth(level) && view_params.height == GetMipHeight(level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsDepthValid(const SurfaceParams& view_params, u32 level) const {
|
||||
if (view_params.target != SurfaceTarget::Texture3D) {
|
||||
return true;
|
||||
}
|
||||
return view_params.depth == GetMipDepth(level);
|
||||
}
|
||||
|
||||
bool SurfaceParams::IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const {
|
||||
return layer + view_params.num_layers <= num_layers &&
|
||||
level + view_params.num_levels <= num_levels;
|
||||
}
|
||||
|
||||
std::size_t HasheableSurfaceParams::Hash() const {
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
|
||||
}
|
||||
|
||||
bool HasheableSurfaceParams::operator==(const HasheableSurfaceParams& rhs) const {
|
||||
return std::tie(is_tiled, block_width, block_height, block_depth, tile_width_spacing, width,
|
||||
height, depth, pitch, unaligned_height, num_levels, pixel_format,
|
||||
component_type, type, target) ==
|
||||
std::tie(rhs.is_tiled, rhs.block_width, rhs.block_height, rhs.block_depth,
|
||||
rhs.tile_width_spacing, rhs.width, rhs.height, rhs.depth, rhs.pitch,
|
||||
rhs.unaligned_height, rhs.num_levels, rhs.pixel_format, rhs.component_type,
|
||||
rhs.type, rhs.target);
|
||||
}
|
||||
|
||||
std::size_t ViewKey::Hash() const {
|
||||
return static_cast<std::size_t>(
|
||||
Common::CityHash64(reinterpret_cast<const char*>(this), sizeof(*this)));
|
||||
}
|
||||
|
||||
bool ViewKey::operator==(const ViewKey& rhs) const {
|
||||
return std::tie(base_layer, num_layers, base_level, num_levels) ==
|
||||
std::tie(rhs.base_layer, rhs.num_layers, rhs.base_level, rhs.num_levels);
|
||||
}
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -1,586 +0,0 @@
|
||||
// Copyright 2019 yuzu Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/icl/interval_map.hpp>
|
||||
#include <boost/range/iterator_range.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/memory.h"
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/engines/maxwell_3d.h"
|
||||
#include "video_core/gpu.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
#include "video_core/surface.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Tegra::Texture {
|
||||
struct FullTextureInfo;
|
||||
}
|
||||
|
||||
namespace VideoCore {
|
||||
class RasterizerInterface;
|
||||
}
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
class HasheableSurfaceParams {
|
||||
public:
|
||||
std::size_t Hash() const;
|
||||
|
||||
bool operator==(const HasheableSurfaceParams& rhs) const;
|
||||
|
||||
protected:
|
||||
// Avoid creation outside of a managed environment.
|
||||
HasheableSurfaceParams() = default;
|
||||
|
||||
bool is_tiled;
|
||||
u32 block_width;
|
||||
u32 block_height;
|
||||
u32 block_depth;
|
||||
u32 tile_width_spacing;
|
||||
u32 width;
|
||||
u32 height;
|
||||
u32 depth;
|
||||
u32 pitch;
|
||||
u32 unaligned_height;
|
||||
u32 num_levels;
|
||||
VideoCore::Surface::PixelFormat pixel_format;
|
||||
VideoCore::Surface::ComponentType component_type;
|
||||
VideoCore::Surface::SurfaceType type;
|
||||
VideoCore::Surface::SurfaceTarget target;
|
||||
};
|
||||
|
||||
class SurfaceParams final : public HasheableSurfaceParams {
|
||||
public:
|
||||
/// Creates SurfaceCachedParams from a texture configuration.
|
||||
static SurfaceParams CreateForTexture(Core::System& system,
|
||||
const Tegra::Texture::FullTextureInfo& config);
|
||||
|
||||
/// Creates SurfaceCachedParams for a depth buffer configuration.
|
||||
static SurfaceParams CreateForDepthBuffer(
|
||||
Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
|
||||
u32 block_width, u32 block_height, u32 block_depth,
|
||||
Tegra::Engines::Maxwell3D::Regs::InvMemoryLayout type);
|
||||
|
||||
/// Creates SurfaceCachedParams from a framebuffer configuration.
|
||||
static SurfaceParams CreateForFramebuffer(Core::System& system, std::size_t index);
|
||||
|
||||
/// Creates SurfaceCachedParams from a Fermi2D surface configuration.
|
||||
static SurfaceParams CreateForFermiCopySurface(
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config);
|
||||
|
||||
bool IsTiled() const {
|
||||
return is_tiled;
|
||||
}
|
||||
|
||||
u32 GetBlockWidth() const {
|
||||
return block_width;
|
||||
}
|
||||
|
||||
u32 GetTileWidthSpacing() const {
|
||||
return tile_width_spacing;
|
||||
}
|
||||
|
||||
u32 GetWidth() const {
|
||||
return width;
|
||||
}
|
||||
|
||||
u32 GetHeight() const {
|
||||
return height;
|
||||
}
|
||||
|
||||
u32 GetDepth() const {
|
||||
return depth;
|
||||
}
|
||||
|
||||
u32 GetPitch() const {
|
||||
return pitch;
|
||||
}
|
||||
|
||||
u32 GetNumLevels() const {
|
||||
return num_levels;
|
||||
}
|
||||
|
||||
VideoCore::Surface::PixelFormat GetPixelFormat() const {
|
||||
return pixel_format;
|
||||
}
|
||||
|
||||
VideoCore::Surface::ComponentType GetComponentType() const {
|
||||
return component_type;
|
||||
}
|
||||
|
||||
VideoCore::Surface::SurfaceTarget GetTarget() const {
|
||||
return target;
|
||||
}
|
||||
|
||||
VideoCore::Surface::SurfaceType GetType() const {
|
||||
return type;
|
||||
}
|
||||
|
||||
std::size_t GetGuestSizeInBytes() const {
|
||||
return guest_size_in_bytes;
|
||||
}
|
||||
|
||||
std::size_t GetHostSizeInBytes() const {
|
||||
return host_size_in_bytes;
|
||||
}
|
||||
|
||||
u32 GetNumLayers() const {
|
||||
return num_layers;
|
||||
}
|
||||
|
||||
/// Returns the width of a given mipmap level.
|
||||
u32 GetMipWidth(u32 level) const;
|
||||
|
||||
/// Returns the height of a given mipmap level.
|
||||
u32 GetMipHeight(u32 level) const;
|
||||
|
||||
/// Returns the depth of a given mipmap level.
|
||||
u32 GetMipDepth(u32 level) const;
|
||||
|
||||
/// Returns true if these parameters are from a layered surface.
|
||||
bool IsLayered() const;
|
||||
|
||||
/// Returns the block height of a given mipmap level.
|
||||
u32 GetMipBlockHeight(u32 level) const;
|
||||
|
||||
/// Returns the block depth of a given mipmap level.
|
||||
u32 GetMipBlockDepth(u32 level) const;
|
||||
|
||||
/// Returns the offset in bytes in guest memory of a given mipmap level.
|
||||
std::size_t GetGuestMipmapLevelOffset(u32 level) const;
|
||||
|
||||
/// Returns the offset in bytes in host memory (linear) of a given mipmap level.
|
||||
std::size_t GetHostMipmapLevelOffset(u32 level) const;
|
||||
|
||||
/// Returns the size of a layer in bytes in guest memory.
|
||||
std::size_t GetGuestLayerSize() const;
|
||||
|
||||
/// Returns the size of a layer in bytes in host memory for a given mipmap level.
|
||||
std::size_t GetHostLayerSize(u32 level) const;
|
||||
|
||||
/// Returns true if another surface can be familiar with this. This is a loosely defined term
|
||||
/// that reflects the possibility of these two surface parameters potentially being part of a
|
||||
/// bigger superset.
|
||||
bool IsFamiliar(const SurfaceParams& view_params) const;
|
||||
|
||||
/// Returns true if the pixel format is a depth and/or stencil format.
|
||||
bool IsPixelFormatZeta() const;
|
||||
|
||||
/// Creates a map that redirects an address difference to a layer and mipmap level.
|
||||
std::map<u64, std::pair<u32, u32>> CreateViewOffsetMap() const;
|
||||
|
||||
/// Returns true if the passed surface view parameters is equal or a valid subset of this.
|
||||
bool IsViewValid(const SurfaceParams& view_params, u32 layer, u32 level) const;
|
||||
|
||||
private:
|
||||
/// Calculates values that can be deduced from HasheableSurfaceParams.
|
||||
void CalculateCachedValues();
|
||||
|
||||
/// Returns the size of a given mipmap level.
|
||||
std::size_t GetInnerMipmapMemorySize(u32 level, bool as_host_size, bool layer_only,
|
||||
bool uncompressed) const;
|
||||
|
||||
/// Returns the size of all mipmap levels and aligns as needed.
|
||||
std::size_t GetInnerMemorySize(bool as_host_size, bool layer_only, bool uncompressed) const;
|
||||
|
||||
/// Returns true if the passed view width and height match the size of this params in a given
|
||||
/// mipmap level.
|
||||
bool IsDimensionValid(const SurfaceParams& view_params, u32 level) const;
|
||||
|
||||
/// Returns true if the passed view depth match the size of this params in a given mipmap level.
|
||||
bool IsDepthValid(const SurfaceParams& view_params, u32 level) const;
|
||||
|
||||
/// Returns true if the passed view layers and mipmap levels are in bounds.
|
||||
bool IsInBounds(const SurfaceParams& view_params, u32 layer, u32 level) const;
|
||||
|
||||
std::size_t guest_size_in_bytes;
|
||||
std::size_t host_size_in_bytes;
|
||||
u32 num_layers;
|
||||
};
|
||||
|
||||
struct ViewKey {
|
||||
std::size_t Hash() const;
|
||||
|
||||
bool operator==(const ViewKey& rhs) const;
|
||||
|
||||
u32 base_layer{};
|
||||
u32 num_layers{};
|
||||
u32 base_level{};
|
||||
u32 num_levels{};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<VideoCommon::SurfaceParams> {
|
||||
std::size_t operator()(const VideoCommon::SurfaceParams& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct hash<VideoCommon::ViewKey> {
|
||||
std::size_t operator()(const VideoCommon::ViewKey& k) const noexcept {
|
||||
return k.Hash();
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace std
|
||||
|
||||
namespace VideoCommon {
|
||||
|
||||
template <typename TView, typename TExecutionContext>
|
||||
class SurfaceBase {
|
||||
static_assert(std::is_trivially_copyable_v<TExecutionContext>);
|
||||
|
||||
public:
|
||||
virtual void LoadBuffer() = 0;
|
||||
|
||||
virtual TExecutionContext FlushBuffer(TExecutionContext exctx) = 0;
|
||||
|
||||
virtual TExecutionContext UploadTexture(TExecutionContext exctx) = 0;
|
||||
|
||||
TView* TryGetView(VAddr view_addr, const SurfaceParams& view_params) {
|
||||
if (view_addr < cpu_addr || !params.IsFamiliar(view_params)) {
|
||||
// It can't be a view if it's in a prior address.
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto relative_offset{static_cast<u64>(view_addr - cpu_addr)};
|
||||
const auto it{view_offset_map.find(relative_offset)};
|
||||
if (it == view_offset_map.end()) {
|
||||
// Couldn't find an aligned view.
|
||||
return {};
|
||||
}
|
||||
const auto [layer, level] = it->second;
|
||||
|
||||
if (!params.IsViewValid(view_params, layer, level)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return GetView(layer, view_params.GetNumLayers(), level, view_params.GetNumLevels());
|
||||
}
|
||||
|
||||
VAddr GetCpuAddr() const {
|
||||
ASSERT(is_registered);
|
||||
return cpu_addr;
|
||||
}
|
||||
|
||||
u8* GetHostPtr() const {
|
||||
ASSERT(is_registered);
|
||||
return host_ptr;
|
||||
}
|
||||
|
||||
CacheAddr GetCacheAddr() const {
|
||||
ASSERT(is_registered);
|
||||
return cache_addr;
|
||||
}
|
||||
|
||||
std::size_t GetSizeInBytes() const {
|
||||
return params.GetGuestSizeInBytes();
|
||||
}
|
||||
|
||||
void MarkAsModified(bool is_modified_) {
|
||||
is_modified = is_modified_;
|
||||
}
|
||||
|
||||
const SurfaceParams& GetSurfaceParams() const {
|
||||
return params;
|
||||
}
|
||||
|
||||
TView* GetView(VAddr view_addr, const SurfaceParams& view_params) {
|
||||
TView* view{TryGetView(view_addr, view_params)};
|
||||
ASSERT(view != nullptr);
|
||||
return view;
|
||||
}
|
||||
|
||||
void Register(VAddr cpu_addr_, u8* host_ptr_) {
|
||||
ASSERT(!is_registered);
|
||||
is_registered = true;
|
||||
cpu_addr = cpu_addr_;
|
||||
host_ptr = host_ptr_;
|
||||
cache_addr = ToCacheAddr(host_ptr_);
|
||||
}
|
||||
|
||||
void Register(VAddr cpu_addr_) {
|
||||
Register(cpu_addr_, Memory::GetPointer(cpu_addr_));
|
||||
}
|
||||
|
||||
void Unregister() {
|
||||
ASSERT(is_registered);
|
||||
is_registered = false;
|
||||
}
|
||||
|
||||
bool IsRegistered() const {
|
||||
return is_registered;
|
||||
}
|
||||
|
||||
protected:
|
||||
explicit SurfaceBase(const SurfaceParams& params)
|
||||
: params{params}, view_offset_map{params.CreateViewOffsetMap()} {}
|
||||
|
||||
~SurfaceBase() = default;
|
||||
|
||||
virtual std::unique_ptr<TView> CreateView(const ViewKey& view_key) = 0;
|
||||
|
||||
bool IsModified() const {
|
||||
return is_modified;
|
||||
}
|
||||
|
||||
const SurfaceParams params;
|
||||
|
||||
private:
|
||||
TView* GetView(u32 base_layer, u32 num_layers, u32 base_level, u32 num_levels) {
|
||||
const ViewKey key{base_layer, num_layers, base_level, num_levels};
|
||||
const auto [entry, is_cache_miss] = views.try_emplace(key);
|
||||
auto& view{entry->second};
|
||||
if (is_cache_miss) {
|
||||
view = CreateView(key);
|
||||
}
|
||||
return view.get();
|
||||
}
|
||||
|
||||
const std::map<u64, std::pair<u32, u32>> view_offset_map;
|
||||
|
||||
VAddr cpu_addr{};
|
||||
u8* host_ptr{};
|
||||
CacheAddr cache_addr{};
|
||||
bool is_modified{};
|
||||
bool is_registered{};
|
||||
std::unordered_map<ViewKey, std::unique_ptr<TView>> views;
|
||||
};
|
||||
|
||||
template <typename TSurface, typename TView, typename TExecutionContext>
|
||||
class TextureCache {
|
||||
static_assert(std::is_trivially_copyable_v<TExecutionContext>);
|
||||
using ResultType = std::tuple<TView*, TExecutionContext>;
|
||||
using IntervalMap = boost::icl::interval_map<CacheAddr, std::set<TSurface*>>;
|
||||
using IntervalType = typename IntervalMap::interval_type;
|
||||
|
||||
public:
|
||||
void InvalidateRegion(CacheAddr addr, std::size_t size) {
|
||||
for (TSurface* surface : GetSurfacesInRegion(addr, size)) {
|
||||
if (!surface->IsRegistered()) {
|
||||
// Skip duplicates
|
||||
continue;
|
||||
}
|
||||
Unregister(surface);
|
||||
}
|
||||
}
|
||||
|
||||
ResultType GetTextureSurface(TExecutionContext exctx,
|
||||
const Tegra::Texture::FullTextureInfo& config) {
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(config.tic.Address())};
|
||||
if (!cpu_addr) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
const auto params{SurfaceParams::CreateForTexture(system, config)};
|
||||
return GetSurfaceView(exctx, *cpu_addr, params, true);
|
||||
}
|
||||
|
||||
ResultType GetDepthBufferSurface(TExecutionContext exctx, bool preserve_contents) {
|
||||
const auto& regs{system.GPU().Maxwell3D().regs};
|
||||
if (!regs.zeta.Address() || !regs.zeta_enable) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(regs.zeta.Address())};
|
||||
if (!cpu_addr) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
const auto depth_params{SurfaceParams::CreateForDepthBuffer(
|
||||
system, regs.zeta_width, regs.zeta_height, regs.zeta.format,
|
||||
regs.zeta.memory_layout.block_width, regs.zeta.memory_layout.block_height,
|
||||
regs.zeta.memory_layout.block_depth, regs.zeta.memory_layout.type)};
|
||||
return GetSurfaceView(exctx, *cpu_addr, depth_params, preserve_contents);
|
||||
}
|
||||
|
||||
ResultType GetColorBufferSurface(TExecutionContext exctx, std::size_t index,
|
||||
bool preserve_contents) {
|
||||
ASSERT(index < Tegra::Engines::Maxwell3D::Regs::NumRenderTargets);
|
||||
|
||||
const auto& regs{system.GPU().Maxwell3D().regs};
|
||||
if (index >= regs.rt_control.count || regs.rt[index].Address() == 0 ||
|
||||
regs.rt[index].format == Tegra::RenderTargetFormat::NONE) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
auto& memory_manager{system.GPU().MemoryManager()};
|
||||
const auto& config{system.GPU().Maxwell3D().regs.rt[index]};
|
||||
const auto cpu_addr{memory_manager.GpuToCpuAddress(
|
||||
config.Address() + config.base_layer * config.layer_stride * sizeof(u32))};
|
||||
if (!cpu_addr) {
|
||||
return {{}, exctx};
|
||||
}
|
||||
|
||||
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFramebuffer(system, index),
|
||||
preserve_contents);
|
||||
}
|
||||
|
||||
ResultType GetFermiSurface(TExecutionContext exctx,
|
||||
const Tegra::Engines::Fermi2D::Regs::Surface& config) {
|
||||
const auto cpu_addr{system.GPU().MemoryManager().GpuToCpuAddress(config.Address())};
|
||||
ASSERT(cpu_addr);
|
||||
return GetSurfaceView(exctx, *cpu_addr, SurfaceParams::CreateForFermiCopySurface(config),
|
||||
true);
|
||||
}
|
||||
|
||||
TSurface* TryFindFramebufferSurface(const u8* host_ptr) const {
|
||||
const auto it{registered_surfaces.find(ToCacheAddr(host_ptr))};
|
||||
return it != registered_surfaces.end() ? *it->second.begin() : nullptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
TextureCache(Core::System& system, VideoCore::RasterizerInterface& rasterizer)
|
||||
: system{system}, rasterizer{rasterizer} {}
|
||||
|
||||
~TextureCache() = default;
|
||||
|
||||
virtual ResultType TryFastGetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
|
||||
const SurfaceParams& params, bool preserve_contents,
|
||||
const std::vector<TSurface*>& overlaps) = 0;
|
||||
|
||||
virtual std::unique_ptr<TSurface> CreateSurface(const SurfaceParams& params) = 0;
|
||||
|
||||
void Register(TSurface* surface, VAddr cpu_addr, u8* host_ptr) {
|
||||
surface->Register(cpu_addr, host_ptr);
|
||||
registered_surfaces.add({GetSurfaceInterval(surface), {surface}});
|
||||
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), 1);
|
||||
}
|
||||
|
||||
void Unregister(TSurface* surface) {
|
||||
registered_surfaces.subtract({GetSurfaceInterval(surface), {surface}});
|
||||
rasterizer.UpdatePagesCachedCount(surface->GetCpuAddr(), surface->GetSizeInBytes(), -1);
|
||||
surface->Unregister();
|
||||
}
|
||||
|
||||
TSurface* GetUncachedSurface(const SurfaceParams& params) {
|
||||
if (TSurface* surface = TryGetReservedSurface(params); surface)
|
||||
return surface;
|
||||
// No reserved surface available, create a new one and reserve it
|
||||
auto new_surface{CreateSurface(params)};
|
||||
TSurface* surface{new_surface.get()};
|
||||
ReserveSurface(params, std::move(new_surface));
|
||||
return surface;
|
||||
}
|
||||
|
||||
Core::System& system;
|
||||
|
||||
private:
|
||||
ResultType GetSurfaceView(TExecutionContext exctx, VAddr cpu_addr, const SurfaceParams& params,
|
||||
bool preserve_contents) {
|
||||
const auto host_ptr{Memory::GetPointer(cpu_addr)};
|
||||
const auto cache_addr{ToCacheAddr(host_ptr)};
|
||||
const auto overlaps{GetSurfacesInRegion(cache_addr, params.GetGuestSizeInBytes())};
|
||||
if (overlaps.empty()) {
|
||||
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
|
||||
}
|
||||
|
||||
if (overlaps.size() == 1) {
|
||||
if (TView* view = overlaps[0]->TryGetView(cpu_addr, params); view)
|
||||
return {view, exctx};
|
||||
}
|
||||
|
||||
TView* fast_view;
|
||||
std::tie(fast_view, exctx) =
|
||||
TryFastGetSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents, overlaps);
|
||||
|
||||
for (TSurface* surface : overlaps) {
|
||||
if (!fast_view) {
|
||||
// Flush even when we don't care about the contents, to preserve memory not written
|
||||
// by the new surface.
|
||||
exctx = surface->FlushBuffer(exctx);
|
||||
}
|
||||
Unregister(surface);
|
||||
}
|
||||
|
||||
if (fast_view) {
|
||||
return {fast_view, exctx};
|
||||
}
|
||||
|
||||
return LoadSurfaceView(exctx, cpu_addr, host_ptr, params, preserve_contents);
|
||||
}
|
||||
|
||||
ResultType LoadSurfaceView(TExecutionContext exctx, VAddr cpu_addr, u8* host_ptr,
|
||||
const SurfaceParams& params, bool preserve_contents) {
|
||||
TSurface* new_surface{GetUncachedSurface(params)};
|
||||
Register(new_surface, cpu_addr, host_ptr);
|
||||
if (preserve_contents) {
|
||||
exctx = LoadSurface(exctx, new_surface);
|
||||
}
|
||||
return {new_surface->GetView(cpu_addr, params), exctx};
|
||||
}
|
||||
|
||||
TExecutionContext LoadSurface(TExecutionContext exctx, TSurface* surface) {
|
||||
surface->LoadBuffer();
|
||||
exctx = surface->UploadTexture(exctx);
|
||||
surface->MarkAsModified(false);
|
||||
return exctx;
|
||||
}
|
||||
|
||||
std::vector<TSurface*> GetSurfacesInRegion(CacheAddr cache_addr, std::size_t size) const {
|
||||
if (size == 0) {
|
||||
return {};
|
||||
}
|
||||
const IntervalType interval{cache_addr, cache_addr + size};
|
||||
|
||||
std::vector<TSurface*> surfaces;
|
||||
for (auto& pair : boost::make_iterator_range(registered_surfaces.equal_range(interval))) {
|
||||
surfaces.push_back(*pair.second.begin());
|
||||
}
|
||||
return surfaces;
|
||||
}
|
||||
|
||||
void ReserveSurface(const SurfaceParams& params, std::unique_ptr<TSurface> surface) {
|
||||
surface_reserve[params].push_back(std::move(surface));
|
||||
}
|
||||
|
||||
TSurface* TryGetReservedSurface(const SurfaceParams& params) {
|
||||
auto search{surface_reserve.find(params)};
|
||||
if (search == surface_reserve.end()) {
|
||||
return {};
|
||||
}
|
||||
for (auto& surface : search->second) {
|
||||
if (!surface->IsRegistered()) {
|
||||
return surface.get();
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
IntervalType GetSurfaceInterval(TSurface* surface) const {
|
||||
return IntervalType::right_open(surface->GetCacheAddr(),
|
||||
surface->GetCacheAddr() + surface->GetSizeInBytes());
|
||||
}
|
||||
|
||||
VideoCore::RasterizerInterface& rasterizer;
|
||||
|
||||
IntervalMap registered_surfaces;
|
||||
|
||||
/// The surface reserve is a "backup" cache, this is where we put unique surfaces that have
|
||||
/// previously been used. This is to prevent surfaces from being constantly created and
|
||||
/// destroyed when used with different surface parameters.
|
||||
std::unordered_map<SurfaceParams, std::list<std::unique_ptr<TSurface>>> surface_reserve;
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
@@ -24,6 +24,8 @@ void EmuThread::run() {
|
||||
|
||||
MicroProfileOnThreadCreate("EmuThread");
|
||||
|
||||
stop_run = false;
|
||||
|
||||
emit LoadProgress(VideoCore::LoadCallbackStage::Prepare, 0, 0);
|
||||
|
||||
Core::System::GetInstance().Renderer().Rasterizer().LoadDiskResources(
|
||||
@@ -38,7 +40,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;
|
||||
|
||||
@@ -114,9 +114,9 @@ int main(int argc, char** argv) {
|
||||
};
|
||||
|
||||
while (optind < argc) {
|
||||
int arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
|
||||
char arg = getopt_long(argc, argv, "g:fhvp::", long_options, &option_index);
|
||||
if (arg != -1) {
|
||||
switch (static_cast<char>(arg)) {
|
||||
switch (arg) {
|
||||
case 'g':
|
||||
errno = 0;
|
||||
gdb_port = strtoul(optarg, &endarg, 0);
|
||||
|
||||
Reference in New Issue
Block a user