Compare commits
1 Commits
enforce-se
...
new-vmm
Author | SHA1 | Date | |
---|---|---|---|
c1876c322f |
@ -1,10 +1,5 @@
|
|||||||
#!/bin/bash -ex
|
#!/bin/bash -ex
|
||||||
|
|
||||||
# TODO: Work around pip install issues with Python 3.12 in the GitHub runner image.
|
|
||||||
# See: https://github.com/actions/runner-images/issues/8709
|
|
||||||
PYTHON_PATH=$(brew --prefix python@3.11)
|
|
||||||
export PATH=$PYTHON_PATH/bin:$PYTHON_PATH/libexec/bin:$PATH
|
|
||||||
|
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake .. -GNinja \
|
cmake .. -GNinja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
@ -1,10 +1,5 @@
|
|||||||
#!/bin/bash -ex
|
#!/bin/bash -ex
|
||||||
|
|
||||||
# TODO: Work around pip install issues with Python 3.12 in the GitHub runner image.
|
|
||||||
# See: https://github.com/actions/runner-images/issues/8709
|
|
||||||
PYTHON_PATH=$(brew --prefix python@3.11)
|
|
||||||
export PATH=$PYTHON_PATH/bin:$PYTHON_PATH/libexec/bin:$PATH
|
|
||||||
|
|
||||||
mkdir build && cd build
|
mkdir build && cd build
|
||||||
cmake .. -GNinja \
|
cmake .. -GNinja \
|
||||||
-DCMAKE_BUILD_TYPE=Release \
|
-DCMAKE_BUILD_TYPE=Release \
|
||||||
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -59,7 +59,7 @@ jobs:
|
|||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: artifacts/
|
path: artifacts/
|
||||||
macos:
|
macos:
|
||||||
runs-on: macos-13
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
target: ["x86_64", "arm64"]
|
target: ["x86_64", "arm64"]
|
||||||
@ -92,7 +92,7 @@ jobs:
|
|||||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||||
macos-universal:
|
macos-universal:
|
||||||
runs-on: macos-13
|
runs-on: macos-latest
|
||||||
needs: macos
|
needs: macos
|
||||||
env:
|
env:
|
||||||
OS: macos
|
OS: macos
|
||||||
@ -234,7 +234,7 @@ jobs:
|
|||||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||||
path: src/android/app/artifacts/
|
path: src/android/app/artifacts/
|
||||||
ios:
|
ios:
|
||||||
runs-on: macos-13
|
runs-on: macos-latest
|
||||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||||
env:
|
env:
|
||||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||||
|
@ -236,7 +236,7 @@ find_package(Threads REQUIRED)
|
|||||||
|
|
||||||
if (ENABLE_QT)
|
if (ENABLE_QT)
|
||||||
if (NOT USE_SYSTEM_QT)
|
if (NOT USE_SYSTEM_QT)
|
||||||
download_qt(6.6.0)
|
download_qt(6.5.1)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||||
|
@ -2,13 +2,7 @@
|
|||||||
Name=colorful_dark
|
Name=colorful_dark
|
||||||
Comment=Colorful theme (Dark style)
|
Comment=Colorful theme (Dark style)
|
||||||
Inherits=default
|
Inherits=default
|
||||||
Directories=16x16,48x48,256x256
|
Directories=16x16
|
||||||
|
|
||||||
[16x16]
|
[16x16]
|
||||||
Size=16
|
Size=16
|
||||||
|
|
||||||
[48x48]
|
|
||||||
Size=48
|
|
||||||
|
|
||||||
[256x256]
|
|
||||||
Size=256
|
|
||||||
|
@ -2,13 +2,7 @@
|
|||||||
Name=colorful_midnight_blue
|
Name=colorful_midnight_blue
|
||||||
Comment=Colorful theme (Midnight Blue style)
|
Comment=Colorful theme (Midnight Blue style)
|
||||||
Inherits=default
|
Inherits=default
|
||||||
Directories=16x16,48x48,256x256
|
Directories=16x16
|
||||||
|
|
||||||
[16x16]
|
[16x16]
|
||||||
Size=16
|
Size=16
|
||||||
|
|
||||||
[48x48]
|
|
||||||
Size=48
|
|
||||||
|
|
||||||
[256x256]
|
|
||||||
Size=256
|
|
||||||
|
35
externals/CMakeLists.txt
vendored
35
externals/CMakeLists.txt
vendored
@ -46,17 +46,11 @@ set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
|||||||
add_subdirectory(catch2)
|
add_subdirectory(catch2)
|
||||||
|
|
||||||
# Crypto++
|
# Crypto++
|
||||||
if(USE_SYSTEM_CRYPTOPP)
|
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
||||||
find_package(cryptopp REQUIRED)
|
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
||||||
add_library(cryptopp INTERFACE)
|
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
||||||
else()
|
add_subdirectory(cryptopp-cmake)
|
||||||
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
|
||||||
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
|
||||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
|
||||||
set(CRYPTOPP_SOURCES "${CMAKE_SOURCE_DIR}/externals/cryptopp" CACHE STRING "")
|
|
||||||
add_subdirectory(cryptopp-cmake)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# dds-ktx
|
# dds-ktx
|
||||||
add_library(dds-ktx INTERFACE)
|
add_library(dds-ktx INTERFACE)
|
||||||
@ -234,30 +228,15 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
# ENet
|
# ENet
|
||||||
if(USE_SYSTEM_ENET)
|
add_subdirectory(enet)
|
||||||
find_package(libenet REQUIRED)
|
target_include_directories(enet INTERFACE ./enet/include)
|
||||||
add_library(enet INTERFACE)
|
|
||||||
target_link_libraries(enet INTERFACE libenet::libenet)
|
|
||||||
else()
|
|
||||||
add_subdirectory(enet)
|
|
||||||
target_include_directories(enet INTERFACE ./enet/include)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Cubeb
|
# Cubeb
|
||||||
if (ENABLE_CUBEB)
|
if (ENABLE_CUBEB)
|
||||||
if(USE_SYSTEM_CUBEB)
|
|
||||||
find_package(cubeb REQUIRED)
|
|
||||||
add_library(cubeb INTERFACE)
|
|
||||||
target_link_libraries(cubeb INTERFACE cubeb::cubeb)
|
|
||||||
if(TARGET cubeb::cubeb)
|
|
||||||
message(STATUS "Found system cubeb")
|
|
||||||
endif()
|
|
||||||
else()
|
|
||||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||||
set(BUILD_TOOLS OFF CACHE BOOL "")
|
set(BUILD_TOOLS OFF CACHE BOOL "")
|
||||||
set(BUNDLE_SPEEX ON CACHE BOOL "")
|
set(BUNDLE_SPEEX ON CACHE BOOL "")
|
||||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||||
endif()
|
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# DiscordRPC
|
# DiscordRPC
|
||||||
|
@ -19,9 +19,6 @@ option(USE_SYSTEM_FDK_AAC_HEADERS "Use the system fdk-aac headers (instead of th
|
|||||||
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_FFMPEG_HEADERS "Use the system FFmpeg headers (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
|
option(USE_SYSTEM_GLSLANG "Use the system glslang and SPIR-V libraries (instead of the bundled ones)" OFF)
|
||||||
option(USE_SYSTEM_ZSTD "Use the system Zstandard library (instead of the bundled one)" OFF)
|
option(USE_SYSTEM_ZSTD "Use the system Zstandard library (instead of the bundled one)" OFF)
|
||||||
option(USE_SYSTEM_ENET "Use the system libenet (instead of the bundled one)" OFF)
|
|
||||||
option(USE_SYSTEM_CRYPTOPP "Use the system cryptopp (instead of the bundled one)" OFF)
|
|
||||||
option(USE_SYSTEM_CUBEB "Use the system cubeb (instead of the bundled one)" OFF)
|
|
||||||
|
|
||||||
# Qt and MoltenVK are handled separately
|
# Qt and MoltenVK are handled separately
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_SDL2 "Disable system SDL2" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_SDL2 "Disable system SDL2" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
@ -40,9 +37,6 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FDK_AAC_HEADERS "Disable system fdk_aac" O
|
|||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FFMPEG_HEADERS "Disable system ffmpeg" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_GLSLANG "Disable system glslang" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_ZSTD "Disable system Zstandard" OFF "USE_SYSTEM_LIBS" OFF)
|
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_ZSTD "Disable system Zstandard" OFF "USE_SYSTEM_LIBS" OFF)
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_ENET "Disable system libenet" OFF "USE_SYSTEM_LIBS" OFF)
|
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CRYPTOPP "Disable system cryptopp" OFF "USE_SYSTEM_LIBS" OFF)
|
|
||||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_CUBEB "Disable system cubeb" OFF "USE_SYSTEM_LIBS" OFF)
|
|
||||||
|
|
||||||
set(LIB_VAR_LIST
|
set(LIB_VAR_LIST
|
||||||
SDL2
|
SDL2
|
||||||
@ -61,9 +55,6 @@ set(LIB_VAR_LIST
|
|||||||
FFMPEG_HEADERS
|
FFMPEG_HEADERS
|
||||||
GLSLANG
|
GLSLANG
|
||||||
ZSTD
|
ZSTD
|
||||||
ENET
|
|
||||||
CRYPTOPP
|
|
||||||
CUBEB
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# First, check that USE_SYSTEM_XXX is not used with USE_SYSTEM_LIBS
|
# First, check that USE_SYSTEM_XXX is not used with USE_SYSTEM_LIBS
|
||||||
|
36
externals/cmake-modules/Findcryptopp.cmake
vendored
36
externals/cmake-modules/Findcryptopp.cmake
vendored
@ -1,36 +0,0 @@
|
|||||||
if(NOT CRYPTOPP_FOUND)
|
|
||||||
pkg_check_modules(CRYPTOPP_TMP libcrypto++)
|
|
||||||
|
|
||||||
find_path(CRYPTOPP_INCLUDE_DIRS NAMES cryptlib.h
|
|
||||||
PATHS
|
|
||||||
${CRYPTOPP_TMP_INCLUDE_DIRS}
|
|
||||||
/usr/include
|
|
||||||
/usr/include/crypto++
|
|
||||||
/usr/local/include
|
|
||||||
/usr/local/include/crypto++
|
|
||||||
)
|
|
||||||
|
|
||||||
find_library(CRYPTOPP_LIBRARY_DIRS NAMES crypto++
|
|
||||||
PATHS
|
|
||||||
${CRYPTOPP_TMP_LIBRARY_DIRS}
|
|
||||||
/usr/lib
|
|
||||||
/usr/locallib
|
|
||||||
)
|
|
||||||
|
|
||||||
if(CRYPTOPP_INCLUDE_DIRS AND CRYPTOPP_LIBRARY_DIRS)
|
|
||||||
set(CRYPTOPP_FOUND TRUE CACHE INTERNAL "Found cryptopp")
|
|
||||||
message(STATUS "Found cryptopp: ${CRYPTOPP_LIBRARY_DIRS}, ${CRYPTOPP_INCLUDE_DIRS}")
|
|
||||||
else()
|
|
||||||
set(CRYPTOPP_FOUND FALSE CACHE INTERNAL "Found cryptopp")
|
|
||||||
message(STATUS "Cryptopp not found.")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(CRYPTOPP_FOUND AND NOT TARGET cryptopp::cryptopp)
|
|
||||||
add_library(cryptopp::cryptopp UNKNOWN IMPORTED)
|
|
||||||
set_target_properties(cryptopp::cryptopp PROPERTIES
|
|
||||||
INCLUDE_DIRECTORIES ${CRYPTOPP_INCLUDE_DIRS}
|
|
||||||
INTERFACE_LINK_LIBRARIES ${CRYPTOPP_LIBRARY_DIRS}
|
|
||||||
IMPORTED_LOCATION ${CRYPTOPP_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
endif()
|
|
34
externals/cmake-modules/Findlibenet.cmake
vendored
34
externals/cmake-modules/Findlibenet.cmake
vendored
@ -1,34 +0,0 @@
|
|||||||
if(NOT libenet_FOUND)
|
|
||||||
pkg_check_modules(ENET_TMP libenet)
|
|
||||||
|
|
||||||
find_path(libenet_INCLUDE_DIRS NAMES enet.h PATH_SUFFIXES enet
|
|
||||||
PATHS
|
|
||||||
${ENET_TMP_INCLUDE_DIRS}
|
|
||||||
/usr/include
|
|
||||||
/usr/local/include
|
|
||||||
)
|
|
||||||
|
|
||||||
find_library(libenet_LIBRARY_DIRS NAMES enet
|
|
||||||
PATHS
|
|
||||||
${ENET_TMP_LIBRARY_DIRS}
|
|
||||||
/usr/lib
|
|
||||||
/usr/local/lib
|
|
||||||
)
|
|
||||||
|
|
||||||
if(libenet_INCLUDE_DIRS AND libenet_LIBRARY_DIRS)
|
|
||||||
set(libenet_FOUND TRUE CACHE INTERNAL "Found libenet")
|
|
||||||
message(STATUS "Found libenet ${libenet_LIBRARY_DIRS}, ${libenet_INCLUDE_DIRS}")
|
|
||||||
else()
|
|
||||||
set(libenet_FOUND FALSE CACHE INTERNAL "Found libenet")
|
|
||||||
message(STATUS "Libenet not found.")
|
|
||||||
endif()
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(libenet_FOUND AND NOT TARGET libenet::libenet)
|
|
||||||
add_library(libenet::libenet UNKNOWN IMPORTED)
|
|
||||||
set_target_properties(libenet::libenet PROPERTIES
|
|
||||||
INCLUDE_DIRECTORIES ${libenet_INCLUDE_DIRS}
|
|
||||||
INTERFACE_LINK_LIBRARIES ${libenet_LIBRARY_DIRS}
|
|
||||||
IMPORTED_LOCATION ${libenet_LIBRARY_DIRS}
|
|
||||||
)
|
|
||||||
endif()
|
|
@ -258,15 +258,13 @@ void GameList::OnUpdateThemedIcons() {
|
|||||||
for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
|
for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
|
||||||
QStandardItem* child = item_model->invisibleRootItem()->child(i);
|
QStandardItem* child = item_model->invisibleRootItem()->child(i);
|
||||||
|
|
||||||
const int icon_size = IconSizes.at(UISettings::values.game_list_icon_size.GetValue());
|
|
||||||
switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) {
|
switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) {
|
||||||
case GameListItemType::InstalledDir:
|
case GameListItemType::InstalledDir:
|
||||||
child->setData(QIcon::fromTheme(QStringLiteral("sd_card")).pixmap(icon_size),
|
child->setData(QIcon::fromTheme(QStringLiteral("sd_card")).pixmap(48),
|
||||||
Qt::DecorationRole);
|
Qt::DecorationRole);
|
||||||
break;
|
break;
|
||||||
case GameListItemType::SystemDir:
|
case GameListItemType::SystemDir:
|
||||||
child->setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(icon_size),
|
child->setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(48), Qt::DecorationRole);
|
||||||
Qt::DecorationRole);
|
|
||||||
break;
|
break;
|
||||||
case GameListItemType::CustomDir: {
|
case GameListItemType::CustomDir: {
|
||||||
const UISettings::GameDir& game_dir =
|
const UISettings::GameDir& game_dir =
|
||||||
@ -274,12 +272,11 @@ void GameList::OnUpdateThemedIcons() {
|
|||||||
const QString icon_name = QFileInfo::exists(game_dir.path)
|
const QString icon_name = QFileInfo::exists(game_dir.path)
|
||||||
? QStringLiteral("folder")
|
? QStringLiteral("folder")
|
||||||
: QStringLiteral("bad_folder");
|
: QStringLiteral("bad_folder");
|
||||||
child->setData(QIcon::fromTheme(icon_name).pixmap(icon_size), Qt::DecorationRole);
|
child->setData(QIcon::fromTheme(icon_name).pixmap(48), Qt::DecorationRole);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case GameListItemType::AddDir:
|
case GameListItemType::AddDir:
|
||||||
child->setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(icon_size),
|
child->setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(48), Qt::DecorationRole);
|
||||||
Qt::DecorationRole);
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -2741,10 +2741,7 @@ void GMainWindow::filterBarSetChecked(bool state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GMainWindow::UpdateUITheme() {
|
void GMainWindow::UpdateUITheme() {
|
||||||
const QString icons_base_path = QStringLiteral(":/icons/");
|
const QString default_icons = QStringLiteral(":/icons/default");
|
||||||
const QString default_theme = QStringLiteral("default");
|
|
||||||
const QString default_theme_path = icons_base_path + default_theme;
|
|
||||||
|
|
||||||
const QString& current_theme = UISettings::values.theme;
|
const QString& current_theme = UISettings::values.theme;
|
||||||
const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
|
const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
|
||||||
QStringList theme_paths(default_theme_paths);
|
QStringList theme_paths(default_theme_paths);
|
||||||
@ -2762,8 +2759,8 @@ void GMainWindow::UpdateUITheme() {
|
|||||||
qApp->setStyleSheet({});
|
qApp->setStyleSheet({});
|
||||||
setStyleSheet({});
|
setStyleSheet({});
|
||||||
}
|
}
|
||||||
theme_paths.append(default_theme_path);
|
theme_paths.append(default_icons);
|
||||||
QIcon::setThemeName(default_theme);
|
QIcon::setThemeName(default_icons);
|
||||||
} else {
|
} else {
|
||||||
const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
|
const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
|
||||||
QFile f(theme_uri);
|
QFile f(theme_uri);
|
||||||
@ -2775,9 +2772,9 @@ void GMainWindow::UpdateUITheme() {
|
|||||||
LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
|
LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString current_theme_path = icons_base_path + current_theme;
|
const QString theme_name = QStringLiteral(":/icons/") + current_theme;
|
||||||
theme_paths.append({default_theme_path, current_theme_path});
|
theme_paths.append({default_icons, theme_name});
|
||||||
QIcon::setThemeName(current_theme);
|
QIcon::setThemeName(theme_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
QIcon::setThemeSearchPaths(theme_paths);
|
QIcon::setThemeSearchPaths(theme_paths);
|
||||||
|
@ -17,7 +17,7 @@
|
|||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/logging/log.h"
|
#include "common/logging/log.h"
|
||||||
|
|
||||||
#ifdef Q_OS_MACOS
|
#ifdef Q_OS_OSX
|
||||||
#define DEFAULT_TOOL_PATH QStringLiteral("../../../../maintenancetool")
|
#define DEFAULT_TOOL_PATH QStringLiteral("../../../../maintenancetool")
|
||||||
#else
|
#else
|
||||||
#define DEFAULT_TOOL_PATH QStringLiteral("../maintenancetool")
|
#define DEFAULT_TOOL_PATH QStringLiteral("../maintenancetool")
|
||||||
@ -102,7 +102,7 @@ QString UpdaterPrivate::ToSystemExe(QString base_path) {
|
|||||||
return base_path + QStringLiteral(".exe");
|
return base_path + QStringLiteral(".exe");
|
||||||
else
|
else
|
||||||
return base_path;
|
return base_path;
|
||||||
#elif defined(Q_OS_MACOS)
|
#elif defined(Q_OS_OSX)
|
||||||
if (base_path.endsWith(QStringLiteral(".app")))
|
if (base_path.endsWith(QStringLiteral(".app")))
|
||||||
base_path.truncate(base_path.lastIndexOf(QStringLiteral(".")));
|
base_path.truncate(base_path.lastIndexOf(QStringLiteral(".")));
|
||||||
return base_path + QStringLiteral(".app/Contents/MacOS/") + QFileInfo(base_path).fileName();
|
return base_path + QStringLiteral(".app/Contents/MacOS/") + QFileInfo(base_path).fileName();
|
||||||
@ -112,7 +112,7 @@ QString UpdaterPrivate::ToSystemExe(QString base_path) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
QFileInfo UpdaterPrivate::GetMaintenanceTool() const {
|
QFileInfo UpdaterPrivate::GetMaintenanceTool() const {
|
||||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||||
const auto appimage_path = QProcessEnvironment::systemEnvironment()
|
const auto appimage_path = QProcessEnvironment::systemEnvironment()
|
||||||
.value(QStringLiteral("APPIMAGE"), {})
|
.value(QStringLiteral("APPIMAGE"), {})
|
||||||
.toStdString();
|
.toStdString();
|
||||||
|
@ -86,6 +86,7 @@ add_library(citra_common STATIC
|
|||||||
file_util.cpp
|
file_util.cpp
|
||||||
file_util.h
|
file_util.h
|
||||||
hash.h
|
hash.h
|
||||||
|
intrusive_list.h
|
||||||
linear_disk_cache.h
|
linear_disk_cache.h
|
||||||
literals.h
|
literals.h
|
||||||
logging/backend.cpp
|
logging/backend.cpp
|
||||||
@ -107,8 +108,11 @@ add_library(citra_common STATIC
|
|||||||
microprofile.h
|
microprofile.h
|
||||||
microprofileui.h
|
microprofileui.h
|
||||||
misc.cpp
|
misc.cpp
|
||||||
|
page_table.cpp
|
||||||
|
page_table.h
|
||||||
param_package.cpp
|
param_package.cpp
|
||||||
param_package.h
|
param_package.h
|
||||||
|
parent_of_member.h
|
||||||
polyfill_thread.h
|
polyfill_thread.h
|
||||||
precompiled_headers.h
|
precompiled_headers.h
|
||||||
quaternion.h
|
quaternion.h
|
||||||
@ -124,7 +128,6 @@ add_library(citra_common STATIC
|
|||||||
serialization/boost_flat_set.h
|
serialization/boost_flat_set.h
|
||||||
serialization/boost_small_vector.hpp
|
serialization/boost_small_vector.hpp
|
||||||
serialization/boost_vector.hpp
|
serialization/boost_vector.hpp
|
||||||
static_lru_cache.h
|
|
||||||
string_literal.h
|
string_literal.h
|
||||||
string_util.cpp
|
string_util.cpp
|
||||||
string_util.h
|
string_util.h
|
||||||
|
@ -110,6 +110,14 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
|||||||
return static_cast<T>(key) == 0; \
|
return static_cast<T>(key) == 0; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#define CITRA_NON_COPYABLE(cls) \
|
||||||
|
cls(const cls&) = delete; \
|
||||||
|
cls& operator=(const cls&) = delete
|
||||||
|
|
||||||
|
#define CITRA_NON_MOVEABLE(cls) \
|
||||||
|
cls(cls&&) = delete; \
|
||||||
|
cls& operator=(cls&&) = delete
|
||||||
|
|
||||||
// Generic function to get last error message.
|
// Generic function to get last error message.
|
||||||
// Call directly after the command or use the error num.
|
// Call directly after the command or use the error num.
|
||||||
// This function might change the error code.
|
// This function might change the error code.
|
||||||
|
@ -1155,43 +1155,6 @@ std::size_t IOFile::ReadImpl(void* data, std::size_t length, std::size_t data_si
|
|||||||
return std::fread(data, data_size, length, m_file);
|
return std::fread(data, data_size, length, m_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
static std::size_t pread(int fd, void* buf, size_t count, uint64_t offset) {
|
|
||||||
long unsigned int read_bytes = 0;
|
|
||||||
OVERLAPPED overlapped = {0};
|
|
||||||
HANDLE file = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
|
|
||||||
|
|
||||||
overlapped.OffsetHigh = static_cast<uint32_t>(offset >> 32);
|
|
||||||
overlapped.Offset = static_cast<uint32_t>(offset & 0xFFFF'FFFFLL);
|
|
||||||
SetLastError(0);
|
|
||||||
bool ret = ReadFile(file, buf, static_cast<uint32_t>(count), &read_bytes, &overlapped);
|
|
||||||
|
|
||||||
if (!ret && GetLastError() != ERROR_HANDLE_EOF) {
|
|
||||||
errno = GetLastError();
|
|
||||||
return std::numeric_limits<std::size_t>::max();
|
|
||||||
}
|
|
||||||
return read_bytes;
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#define pread ::pread
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::size_t IOFile::ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
|
|
||||||
std::size_t offset) {
|
|
||||||
if (!IsOpen()) {
|
|
||||||
m_good = false;
|
|
||||||
return std::numeric_limits<std::size_t>::max();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (length == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
DEBUG_ASSERT(data != nullptr);
|
|
||||||
|
|
||||||
return pread(fileno(m_file), data, data_size * length, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
|
std::size_t IOFile::WriteImpl(const void* data, std::size_t length, std::size_t data_size) {
|
||||||
if (!IsOpen()) {
|
if (!IsOpen()) {
|
||||||
m_good = false;
|
m_good = false;
|
||||||
|
@ -294,18 +294,6 @@ public:
|
|||||||
return items_read;
|
return items_read;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
std::size_t ReadAtArray(T* data, std::size_t length, std::size_t offset) {
|
|
||||||
static_assert(std::is_trivially_copyable_v<T>,
|
|
||||||
"Given array does not consist of trivially copyable objects");
|
|
||||||
|
|
||||||
std::size_t items_read = ReadAtImpl(data, length, sizeof(T), offset);
|
|
||||||
if (items_read != length)
|
|
||||||
m_good = false;
|
|
||||||
|
|
||||||
return items_read;
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::size_t WriteArray(const T* data, std::size_t length) {
|
std::size_t WriteArray(const T* data, std::size_t length) {
|
||||||
static_assert(std::is_trivially_copyable_v<T>,
|
static_assert(std::is_trivially_copyable_v<T>,
|
||||||
@ -324,12 +312,6 @@ public:
|
|||||||
return ReadArray(reinterpret_cast<char*>(data), length);
|
return ReadArray(reinterpret_cast<char*>(data), length);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
|
||||||
std::size_t ReadAtBytes(T* data, std::size_t length, std::size_t offset) {
|
|
||||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
|
||||||
return ReadAtArray(reinterpret_cast<char*>(data), length, offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::size_t WriteBytes(const T* data, std::size_t length) {
|
std::size_t WriteBytes(const T* data, std::size_t length) {
|
||||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||||
@ -381,8 +363,6 @@ public:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size);
|
std::size_t ReadImpl(void* data, std::size_t length, std::size_t data_size);
|
||||||
std::size_t ReadAtImpl(void* data, std::size_t length, std::size_t data_size,
|
|
||||||
std::size_t offset);
|
|
||||||
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
|
std::size_t WriteImpl(const void* data, std::size_t length, std::size_t data_size);
|
||||||
|
|
||||||
bool Open();
|
bool Open();
|
||||||
|
631
src/common/intrusive_list.h
Normal file
631
src/common/intrusive_list.h
Normal file
@ -0,0 +1,631 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/parent_of_member.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
|
||||||
|
// Forward declare implementation class for Node.
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
class IntrusiveListImpl;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class IntrusiveListNode {
|
||||||
|
CITRA_NON_COPYABLE(IntrusiveListNode);
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class impl::IntrusiveListImpl;
|
||||||
|
|
||||||
|
IntrusiveListNode* m_prev;
|
||||||
|
IntrusiveListNode* m_next;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr IntrusiveListNode() : m_prev(this), m_next(this) {}
|
||||||
|
|
||||||
|
constexpr bool IsLinked() const {
|
||||||
|
return m_next != this;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
constexpr void LinkPrev(IntrusiveListNode* node) {
|
||||||
|
// We can't link an already linked node.
|
||||||
|
ASSERT(!node->IsLinked());
|
||||||
|
this->SplicePrev(node, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void SplicePrev(IntrusiveListNode* first, IntrusiveListNode* last) {
|
||||||
|
// Splice a range into the list.
|
||||||
|
auto last_prev = last->m_prev;
|
||||||
|
first->m_prev = m_prev;
|
||||||
|
last_prev->m_next = this;
|
||||||
|
m_prev->m_next = first;
|
||||||
|
m_prev = last_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void LinkNext(IntrusiveListNode* node) {
|
||||||
|
// We can't link an already linked node.
|
||||||
|
ASSERT(!node->IsLinked());
|
||||||
|
return this->SpliceNext(node, node);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void SpliceNext(IntrusiveListNode* first, IntrusiveListNode* last) {
|
||||||
|
// Splice a range into the list.
|
||||||
|
auto last_prev = last->m_prev;
|
||||||
|
first->m_prev = this;
|
||||||
|
last_prev->m_next = m_next;
|
||||||
|
m_next->m_prev = last_prev;
|
||||||
|
m_next = first;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void Unlink() {
|
||||||
|
this->Unlink(m_next);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void Unlink(IntrusiveListNode* last) {
|
||||||
|
// Unlink a node from a next node.
|
||||||
|
auto last_prev = last->m_prev;
|
||||||
|
m_prev->m_next = last;
|
||||||
|
last->m_prev = m_prev;
|
||||||
|
last_prev->m_next = this;
|
||||||
|
m_prev = last_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr IntrusiveListNode* GetPrev() {
|
||||||
|
return m_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const IntrusiveListNode* GetPrev() const {
|
||||||
|
return m_prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr IntrusiveListNode* GetNext() {
|
||||||
|
return m_next;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const IntrusiveListNode* GetNext() const {
|
||||||
|
return m_next;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// DEPRECATED: static_assert(std::is_literal_type<IntrusiveListNode>::value);
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
class IntrusiveListImpl {
|
||||||
|
CITRA_NON_COPYABLE(IntrusiveListImpl);
|
||||||
|
|
||||||
|
private:
|
||||||
|
IntrusiveListNode m_root_node;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <bool Const>
|
||||||
|
class Iterator;
|
||||||
|
|
||||||
|
using value_type = IntrusiveListNode;
|
||||||
|
using size_type = size_t;
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using const_pointer = const value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
using const_reference = const value_type&;
|
||||||
|
using iterator = Iterator<false>;
|
||||||
|
using const_iterator = Iterator<true>;
|
||||||
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||||
|
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||||
|
|
||||||
|
template <bool Const>
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
using iterator_category = std::bidirectional_iterator_tag;
|
||||||
|
using value_type = typename IntrusiveListImpl::value_type;
|
||||||
|
using difference_type = typename IntrusiveListImpl::difference_type;
|
||||||
|
using pointer =
|
||||||
|
std::conditional_t<Const, IntrusiveListImpl::const_pointer, IntrusiveListImpl::pointer>;
|
||||||
|
using reference = std::conditional_t<Const, IntrusiveListImpl::const_reference,
|
||||||
|
IntrusiveListImpl::reference>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
pointer m_node;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr explicit Iterator(pointer n) : m_node(n) {}
|
||||||
|
|
||||||
|
constexpr bool operator==(const Iterator& rhs) const {
|
||||||
|
return m_node == rhs.m_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr pointer operator->() const {
|
||||||
|
return m_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reference operator*() const {
|
||||||
|
return *m_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator& operator++() {
|
||||||
|
m_node = m_node->m_next;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator& operator--() {
|
||||||
|
m_node = m_node->m_prev;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator operator++(int) {
|
||||||
|
const Iterator it{*this};
|
||||||
|
++(*this);
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator operator--(int) {
|
||||||
|
const Iterator it{*this};
|
||||||
|
--(*this);
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr operator Iterator<true>() const {
|
||||||
|
return Iterator<true>(m_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator<false> GetNonConstIterator() const {
|
||||||
|
return Iterator<false>(const_cast<IntrusiveListImpl::pointer>(m_node));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr IntrusiveListImpl() : m_root_node() {}
|
||||||
|
|
||||||
|
// Iterator accessors.
|
||||||
|
constexpr iterator begin() {
|
||||||
|
return iterator(m_root_node.GetNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator begin() const {
|
||||||
|
return const_iterator(m_root_node.GetNext());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator end() {
|
||||||
|
return iterator(std::addressof(m_root_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator end() const {
|
||||||
|
return const_iterator(std::addressof(m_root_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator iterator_to(reference v) {
|
||||||
|
// Only allow iterator_to for values in lists.
|
||||||
|
ASSERT(v.IsLinked());
|
||||||
|
return iterator(std::addressof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator iterator_to(const_reference v) const {
|
||||||
|
// Only allow iterator_to for values in lists.
|
||||||
|
ASSERT(v.IsLinked());
|
||||||
|
return const_iterator(std::addressof(v));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content management.
|
||||||
|
constexpr bool empty() const {
|
||||||
|
return !m_root_node.IsLinked();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_type size() const {
|
||||||
|
return static_cast<size_type>(std::distance(this->begin(), this->end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reference back() {
|
||||||
|
return *m_root_node.GetPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reference back() const {
|
||||||
|
return *m_root_node.GetPrev();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reference front() {
|
||||||
|
return *m_root_node.GetNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reference front() const {
|
||||||
|
return *m_root_node.GetNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void push_back(reference node) {
|
||||||
|
m_root_node.LinkPrev(std::addressof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void push_front(reference node) {
|
||||||
|
m_root_node.LinkNext(std::addressof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void pop_back() {
|
||||||
|
m_root_node.GetPrev()->Unlink();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void pop_front() {
|
||||||
|
m_root_node.GetNext()->Unlink();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator insert(const_iterator pos, reference node) {
|
||||||
|
pos.GetNonConstIterator()->LinkPrev(std::addressof(node));
|
||||||
|
return iterator(std::addressof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void splice(const_iterator pos, IntrusiveListImpl& o) {
|
||||||
|
splice_impl(pos, o.begin(), o.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void splice(const_iterator pos, IntrusiveListImpl&, const_iterator first) {
|
||||||
|
const_iterator last(first);
|
||||||
|
std::advance(last, 1);
|
||||||
|
splice_impl(pos, first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void splice(const_iterator pos, IntrusiveListImpl&, const_iterator first,
|
||||||
|
const_iterator last) {
|
||||||
|
splice_impl(pos, first, last);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator erase(const_iterator pos) {
|
||||||
|
if (pos == this->end()) {
|
||||||
|
return this->end();
|
||||||
|
}
|
||||||
|
iterator it(pos.GetNonConstIterator());
|
||||||
|
(it++)->Unlink();
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void clear() {
|
||||||
|
while (!this->empty()) {
|
||||||
|
this->pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
constexpr void splice_impl(const_iterator _pos, const_iterator _first, const_iterator _last) {
|
||||||
|
if (_first == _last) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
iterator pos(_pos.GetNonConstIterator());
|
||||||
|
iterator first(_first.GetNonConstIterator());
|
||||||
|
iterator last(_last.GetNonConstIterator());
|
||||||
|
first->Unlink(std::addressof(*last));
|
||||||
|
pos->SplicePrev(std::addressof(*first), std::addressof(*first));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
template <class T, class Traits>
|
||||||
|
class IntrusiveList {
|
||||||
|
CITRA_NON_COPYABLE(IntrusiveList);
|
||||||
|
|
||||||
|
private:
|
||||||
|
impl::IntrusiveListImpl m_impl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <bool Const>
|
||||||
|
class Iterator;
|
||||||
|
|
||||||
|
using value_type = T;
|
||||||
|
using size_type = size_t;
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using const_pointer = const value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
using const_reference = const value_type&;
|
||||||
|
using iterator = Iterator<false>;
|
||||||
|
using const_iterator = Iterator<true>;
|
||||||
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||||
|
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||||
|
|
||||||
|
template <bool Const>
|
||||||
|
class Iterator {
|
||||||
|
public:
|
||||||
|
friend class Common::IntrusiveList<T, Traits>;
|
||||||
|
|
||||||
|
using ImplIterator =
|
||||||
|
std::conditional_t<Const, Common::impl::IntrusiveListImpl::const_iterator,
|
||||||
|
Common::impl::IntrusiveListImpl::iterator>;
|
||||||
|
|
||||||
|
using iterator_category = std::bidirectional_iterator_tag;
|
||||||
|
using value_type = typename IntrusiveList::value_type;
|
||||||
|
using difference_type = typename IntrusiveList::difference_type;
|
||||||
|
using pointer =
|
||||||
|
std::conditional_t<Const, IntrusiveList::const_pointer, IntrusiveList::pointer>;
|
||||||
|
using reference =
|
||||||
|
std::conditional_t<Const, IntrusiveList::const_reference, IntrusiveList::reference>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ImplIterator m_iterator;
|
||||||
|
|
||||||
|
private:
|
||||||
|
constexpr explicit Iterator(ImplIterator it) : m_iterator(it) {}
|
||||||
|
|
||||||
|
constexpr ImplIterator GetImplIterator() const {
|
||||||
|
return m_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr bool operator==(const Iterator& rhs) const {
|
||||||
|
return m_iterator == rhs.m_iterator;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr pointer operator->() const {
|
||||||
|
return std::addressof(Traits::GetParent(*m_iterator));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reference operator*() const {
|
||||||
|
return Traits::GetParent(*m_iterator);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator& operator++() {
|
||||||
|
++m_iterator;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator& operator--() {
|
||||||
|
--m_iterator;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator operator++(int) {
|
||||||
|
const Iterator it{*this};
|
||||||
|
++m_iterator;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr Iterator operator--(int) {
|
||||||
|
const Iterator it{*this};
|
||||||
|
--m_iterator;
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr operator Iterator<true>() const {
|
||||||
|
return Iterator<true>(m_iterator);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr IntrusiveListNode& GetNode(reference ref) {
|
||||||
|
return Traits::GetNode(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr IntrusiveListNode const& GetNode(const_reference ref) {
|
||||||
|
return Traits::GetNode(ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr reference GetParent(IntrusiveListNode& node) {
|
||||||
|
return Traits::GetParent(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr const_reference GetParent(IntrusiveListNode const& node) {
|
||||||
|
return Traits::GetParent(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr IntrusiveList() : m_impl() {}
|
||||||
|
|
||||||
|
// Iterator accessors.
|
||||||
|
constexpr iterator begin() {
|
||||||
|
return iterator(m_impl.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator begin() const {
|
||||||
|
return const_iterator(m_impl.begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator end() {
|
||||||
|
return iterator(m_impl.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator end() const {
|
||||||
|
return const_iterator(m_impl.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator cbegin() const {
|
||||||
|
return this->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator cend() const {
|
||||||
|
return this->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reverse_iterator rbegin() {
|
||||||
|
return reverse_iterator(this->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reverse_iterator rbegin() const {
|
||||||
|
return const_reverse_iterator(this->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reverse_iterator rend() {
|
||||||
|
return reverse_iterator(this->begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reverse_iterator rend() const {
|
||||||
|
return const_reverse_iterator(this->begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reverse_iterator crbegin() const {
|
||||||
|
return this->rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reverse_iterator crend() const {
|
||||||
|
return this->rend();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator iterator_to(reference v) {
|
||||||
|
return iterator(m_impl.iterator_to(GetNode(v)));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_iterator iterator_to(const_reference v) const {
|
||||||
|
return const_iterator(m_impl.iterator_to(GetNode(v)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content management.
|
||||||
|
constexpr bool empty() const {
|
||||||
|
return m_impl.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr size_type size() const {
|
||||||
|
return m_impl.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reference back() {
|
||||||
|
return GetParent(m_impl.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reference back() const {
|
||||||
|
return GetParent(m_impl.back());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr reference front() {
|
||||||
|
return GetParent(m_impl.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr const_reference front() const {
|
||||||
|
return GetParent(m_impl.front());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void push_back(reference ref) {
|
||||||
|
m_impl.push_back(GetNode(ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void push_front(reference ref) {
|
||||||
|
m_impl.push_front(GetNode(ref));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void pop_back() {
|
||||||
|
m_impl.pop_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void pop_front() {
|
||||||
|
m_impl.pop_front();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator insert(const_iterator pos, reference ref) {
|
||||||
|
return iterator(m_impl.insert(pos.GetImplIterator(), GetNode(ref)));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void splice(const_iterator pos, IntrusiveList& o) {
|
||||||
|
m_impl.splice(pos.GetImplIterator(), o.m_impl);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first) {
|
||||||
|
m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void splice(const_iterator pos, IntrusiveList& o, const_iterator first,
|
||||||
|
const_iterator last) {
|
||||||
|
m_impl.splice(pos.GetImplIterator(), o.m_impl, first.GetImplIterator(),
|
||||||
|
last.GetImplIterator());
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr iterator erase(const_iterator pos) {
|
||||||
|
return iterator(m_impl.erase(pos.GetImplIterator()));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void clear() {
|
||||||
|
m_impl.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <auto T, class Derived = Common::impl::GetParentType<T>>
|
||||||
|
class IntrusiveListMemberTraits;
|
||||||
|
|
||||||
|
template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
|
||||||
|
class IntrusiveListMemberTraits<Member, Derived> {
|
||||||
|
public:
|
||||||
|
using ListType = IntrusiveList<Derived, IntrusiveListMemberTraits>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class IntrusiveList<Derived, IntrusiveListMemberTraits>;
|
||||||
|
|
||||||
|
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
|
||||||
|
return parent.*Member;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
|
||||||
|
return parent.*Member;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Derived& GetParent(IntrusiveListNode& node) {
|
||||||
|
return Common::GetParentReference<Member, Derived>(std::addressof(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
static Derived const& GetParent(IntrusiveListNode const& node) {
|
||||||
|
return Common::GetParentReference<Member, Derived>(std::addressof(node));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <auto T, class Derived = Common::impl::GetParentType<T>>
|
||||||
|
class IntrusiveListMemberTraitsByNonConstexprOffsetOf;
|
||||||
|
|
||||||
|
template <class Parent, IntrusiveListNode Parent::*Member, class Derived>
|
||||||
|
class IntrusiveListMemberTraitsByNonConstexprOffsetOf<Member, Derived> {
|
||||||
|
public:
|
||||||
|
using ListType = IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class IntrusiveList<Derived, IntrusiveListMemberTraitsByNonConstexprOffsetOf>;
|
||||||
|
|
||||||
|
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
|
||||||
|
return parent.*Member;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
|
||||||
|
return parent.*Member;
|
||||||
|
}
|
||||||
|
|
||||||
|
static Derived& GetParent(IntrusiveListNode& node) {
|
||||||
|
return *reinterpret_cast<Derived*>(reinterpret_cast<char*>(std::addressof(node)) -
|
||||||
|
GetOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
static Derived const& GetParent(IntrusiveListNode const& node) {
|
||||||
|
return *reinterpret_cast<const Derived*>(
|
||||||
|
reinterpret_cast<const char*>(std::addressof(node)) - GetOffset());
|
||||||
|
}
|
||||||
|
|
||||||
|
static uintptr_t GetOffset() {
|
||||||
|
return reinterpret_cast<uintptr_t>(std::addressof(reinterpret_cast<Derived*>(0)->*Member));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
class IntrusiveListBaseNode : public IntrusiveListNode {};
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
class IntrusiveListBaseTraits {
|
||||||
|
public:
|
||||||
|
using ListType = IntrusiveList<Derived, IntrusiveListBaseTraits>;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class IntrusiveList<Derived, IntrusiveListBaseTraits>;
|
||||||
|
|
||||||
|
static constexpr IntrusiveListNode& GetNode(Derived& parent) {
|
||||||
|
return static_cast<IntrusiveListNode&>(
|
||||||
|
static_cast<IntrusiveListBaseNode<Derived>&>(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr IntrusiveListNode const& GetNode(Derived const& parent) {
|
||||||
|
return static_cast<const IntrusiveListNode&>(
|
||||||
|
static_cast<const IntrusiveListBaseNode<Derived>&>(parent));
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Derived& GetParent(IntrusiveListNode& node) {
|
||||||
|
return static_cast<Derived&>(static_cast<IntrusiveListBaseNode<Derived>&>(node));
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr Derived const& GetParent(IntrusiveListNode const& node) {
|
||||||
|
return static_cast<const Derived&>(
|
||||||
|
static_cast<const IntrusiveListBaseNode<Derived>&>(node));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
64
src/common/page_table.cpp
Normal file
64
src/common/page_table.cpp
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2023 Citra 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() = default;
|
||||||
|
|
||||||
|
PageTable::~PageTable() noexcept = default;
|
||||||
|
|
||||||
|
bool PageTable::BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context, VAddr address) const {
|
||||||
|
// Setup invalid defaults.
|
||||||
|
out_entry->phys_addr = 0;
|
||||||
|
out_entry->block_size = page_size;
|
||||||
|
out_context->next_page = 0;
|
||||||
|
|
||||||
|
// Validate that we can read the actual entry.
|
||||||
|
const auto page = address / page_size;
|
||||||
|
if (page >= backing_addr.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the entry is mapped.
|
||||||
|
const auto phys_addr = backing_addr[page];
|
||||||
|
if (phys_addr == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the results.
|
||||||
|
out_entry->phys_addr = phys_addr + address;
|
||||||
|
out_context->next_page = page + 1;
|
||||||
|
out_context->next_offset = address + page_size;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PageTable::ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const {
|
||||||
|
// Setup invalid defaults.
|
||||||
|
out_entry->phys_addr = 0;
|
||||||
|
out_entry->block_size = page_size;
|
||||||
|
|
||||||
|
// Validate that we can read the actual entry.
|
||||||
|
const auto page = context->next_page;
|
||||||
|
if (page >= backing_addr.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate that the entry is mapped.
|
||||||
|
const auto phys_addr = backing_addr[page];
|
||||||
|
if (phys_addr == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Populate the results.
|
||||||
|
out_entry->phys_addr = phys_addr + context->next_offset;
|
||||||
|
context->next_page = page + 1;
|
||||||
|
context->next_offset += page_size;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
116
src/common/page_table.h
Normal file
116
src/common/page_table.h
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.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,
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 {
|
||||||
|
struct TraversalEntry {
|
||||||
|
u64 phys_addr{};
|
||||||
|
std::size_t block_size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TraversalContext {
|
||||||
|
u64 next_page{};
|
||||||
|
u64 next_offset{};
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Number of bits reserved for attribute tagging.
|
||||||
|
/// This can be at most the guaranteed alignment of the pointers in the page table.
|
||||||
|
static constexpr int ATTRIBUTE_BITS = 2;
|
||||||
|
static constexpr size_t PAGE_BITS = 12;
|
||||||
|
static constexpr size_t NUM_ENTRIES = 1 << (32 - PAGE_BITS);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pair of host pointer and page type attribute.
|
||||||
|
* This uses the lower bits of a given pointer to store the attribute tag.
|
||||||
|
* Writing and reading the pointer attribute pair is guaranteed to be atomic for the same method
|
||||||
|
* call. In other words, they are guaranteed to be synchronized at all times.
|
||||||
|
*/
|
||||||
|
class PageInfo {
|
||||||
|
public:
|
||||||
|
/// Returns the page pointer
|
||||||
|
[[nodiscard]] uintptr_t Pointer() const noexcept {
|
||||||
|
return ExtractPointer(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the page type attribute
|
||||||
|
[[nodiscard]] PageType Type() const noexcept {
|
||||||
|
return ExtractType(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the page pointer and attribute pair, extracted from the same atomic read
|
||||||
|
[[nodiscard]] std::pair<uintptr_t, PageType> PointerType() const noexcept {
|
||||||
|
return {ExtractPointer(raw), ExtractType(raw)};
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the raw representation of the page information.
|
||||||
|
/// Use ExtractPointer and ExtractType to unpack the value.
|
||||||
|
[[nodiscard]] uintptr_t Raw() const noexcept {
|
||||||
|
return raw;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Write a page pointer and type pair atomically
|
||||||
|
void Store(uintptr_t pointer, PageType type) noexcept {
|
||||||
|
raw = pointer | static_cast<uintptr_t>(type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unpack a pointer from a page info raw representation
|
||||||
|
[[nodiscard]] static uintptr_t ExtractPointer(uintptr_t raw) noexcept {
|
||||||
|
return raw & (~uintptr_t{0} << ATTRIBUTE_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Unpack a page type from a page info raw representation
|
||||||
|
[[nodiscard]] static PageType ExtractType(uintptr_t raw) noexcept {
|
||||||
|
return static_cast<PageType>(raw & ((uintptr_t{1} << ATTRIBUTE_BITS) - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
uintptr_t raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
PageTable();
|
||||||
|
~PageTable() noexcept;
|
||||||
|
|
||||||
|
PageTable(const PageTable&) = delete;
|
||||||
|
PageTable& operator=(const PageTable&) = delete;
|
||||||
|
|
||||||
|
PageTable(PageTable&&) noexcept = default;
|
||||||
|
PageTable& operator=(PageTable&&) noexcept = default;
|
||||||
|
|
||||||
|
bool BeginTraversal(TraversalEntry* out_entry, TraversalContext* out_context, VAddr address) const;
|
||||||
|
bool ContinueTraversal(TraversalEntry* out_entry, TraversalContext* context) const;
|
||||||
|
|
||||||
|
PAddr GetPhysicalAddress(VAddr virt_addr) const {
|
||||||
|
return backing_addr[virt_addr / page_size] + virt_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vector of memory pointers backing each page. An entry can only be non-null if the
|
||||||
|
* corresponding attribute element is of type `Memory`.
|
||||||
|
*/
|
||||||
|
std::array<PageInfo, NUM_ENTRIES> pointers;
|
||||||
|
std::array<u64, NUM_ENTRIES> blocks;
|
||||||
|
std::array<u64, NUM_ENTRIES> backing_addr;
|
||||||
|
std::size_t page_size{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Common
|
190
src/common/parent_of_member.h
Normal file
190
src/common/parent_of_member.h
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <type_traits>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
namespace detail {
|
||||||
|
template <typename T, size_t Size, size_t Align>
|
||||||
|
struct TypedStorageImpl {
|
||||||
|
alignas(Align) u8 storage_[Size];
|
||||||
|
};
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
using TypedStorage = detail::TypedStorageImpl<T, sizeof(T), alignof(T)>;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static constexpr T* GetPointer(TypedStorage<T>& ts) {
|
||||||
|
return static_cast<T*>(static_cast<void*>(std::addressof(ts.storage_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static constexpr const T* GetPointer(const TypedStorage<T>& ts) {
|
||||||
|
return static_cast<const T*>(static_cast<const void*>(std::addressof(ts.storage_)));
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
template <size_t MaxDepth>
|
||||||
|
struct OffsetOfUnionHolder {
|
||||||
|
template <typename ParentType, typename MemberType, size_t Offset>
|
||||||
|
union UnionImpl {
|
||||||
|
using PaddingMember = char;
|
||||||
|
static constexpr size_t GetOffset() {
|
||||||
|
return Offset;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pragma pack(push, 1)
|
||||||
|
struct {
|
||||||
|
PaddingMember padding[Offset];
|
||||||
|
MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1];
|
||||||
|
} data;
|
||||||
|
#pragma pack(pop)
|
||||||
|
UnionImpl<ParentType, MemberType, Offset + 1> next_union;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ParentType, typename MemberType>
|
||||||
|
union UnionImpl<ParentType, MemberType, 0> {
|
||||||
|
static constexpr size_t GetOffset() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct {
|
||||||
|
MemberType members[(sizeof(ParentType) / sizeof(MemberType)) + 1];
|
||||||
|
} data;
|
||||||
|
UnionImpl<ParentType, MemberType, 1> next_union;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ParentType, typename MemberType>
|
||||||
|
union UnionImpl<ParentType, MemberType, MaxDepth> {};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename ParentType, typename MemberType>
|
||||||
|
struct OffsetOfCalculator {
|
||||||
|
using UnionHolder =
|
||||||
|
typename OffsetOfUnionHolder<sizeof(MemberType)>::template UnionImpl<ParentType, MemberType,
|
||||||
|
0>;
|
||||||
|
union Union {
|
||||||
|
char c{};
|
||||||
|
UnionHolder first_union;
|
||||||
|
TypedStorage<ParentType> parent;
|
||||||
|
|
||||||
|
constexpr Union() : c() {}
|
||||||
|
};
|
||||||
|
static constexpr Union U = {};
|
||||||
|
|
||||||
|
static constexpr const MemberType* GetNextAddress(const MemberType* start,
|
||||||
|
const MemberType* target) {
|
||||||
|
while (start < target) {
|
||||||
|
start++;
|
||||||
|
}
|
||||||
|
return start;
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::ptrdiff_t GetDifference(const MemberType* start,
|
||||||
|
const MemberType* target) {
|
||||||
|
return (target - start) * sizeof(MemberType);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename CurUnion>
|
||||||
|
static constexpr std::ptrdiff_t OffsetOfImpl(MemberType ParentType::*member,
|
||||||
|
CurUnion& cur_union) {
|
||||||
|
constexpr size_t Offset = CurUnion::GetOffset();
|
||||||
|
const auto target = std::addressof(GetPointer(U.parent)->*member);
|
||||||
|
const auto start = std::addressof(cur_union.data.members[0]);
|
||||||
|
const auto next = GetNextAddress(start, target);
|
||||||
|
|
||||||
|
if (next != target) {
|
||||||
|
if constexpr (Offset < sizeof(MemberType) - 1) {
|
||||||
|
return OffsetOfImpl(member, cur_union.next_union);
|
||||||
|
} else {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<ptrdiff_t>(static_cast<size_t>(next - start) * sizeof(MemberType) +
|
||||||
|
Offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
static constexpr std::ptrdiff_t OffsetOf(MemberType ParentType::*member) {
|
||||||
|
return OffsetOfImpl(member, U.first_union);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct GetMemberPointerTraits;
|
||||||
|
|
||||||
|
template <typename P, typename M>
|
||||||
|
struct GetMemberPointerTraits<M P::*> {
|
||||||
|
using Parent = P;
|
||||||
|
using Member = M;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <auto MemberPtr>
|
||||||
|
using GetParentType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Parent;
|
||||||
|
|
||||||
|
template <auto MemberPtr>
|
||||||
|
using GetMemberType = typename GetMemberPointerTraits<decltype(MemberPtr)>::Member;
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = GetParentType<MemberPtr>>
|
||||||
|
constexpr std::ptrdiff_t OffsetOf() {
|
||||||
|
using DeducedParentType = GetParentType<MemberPtr>;
|
||||||
|
using MemberType = GetMemberType<MemberPtr>;
|
||||||
|
static_assert(std::is_base_of<DeducedParentType, RealParentType>::value ||
|
||||||
|
std::is_same<RealParentType, DeducedParentType>::value);
|
||||||
|
|
||||||
|
return OffsetOfCalculator<RealParentType, MemberType>::OffsetOf(MemberPtr);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>* member) {
|
||||||
|
std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>();
|
||||||
|
return *static_cast<RealParentType*>(
|
||||||
|
static_cast<void*>(static_cast<uint8_t*>(static_cast<void*>(member)) - Offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const* member) {
|
||||||
|
std::ptrdiff_t Offset = impl::OffsetOf<MemberPtr, RealParentType>();
|
||||||
|
return *static_cast<const RealParentType*>(static_cast<const void*>(
|
||||||
|
static_cast<const uint8_t*>(static_cast<const void*>(member)) - Offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>* member) {
|
||||||
|
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const* member) {
|
||||||
|
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType& GetParentReference(impl::GetMemberType<MemberPtr>& member) {
|
||||||
|
return GetParentReference<MemberPtr, RealParentType>(std::addressof(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType const& GetParentReference(impl::GetMemberType<MemberPtr> const& member) {
|
||||||
|
return GetParentReference<MemberPtr, RealParentType>(std::addressof(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType* GetParentPointer(impl::GetMemberType<MemberPtr>& member) {
|
||||||
|
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
template <auto MemberPtr, typename RealParentType = impl::GetParentType<MemberPtr>>
|
||||||
|
constexpr RealParentType const* GetParentPointer(impl::GetMemberType<MemberPtr> const& member) {
|
||||||
|
return std::addressof(GetParentReference<MemberPtr, RealParentType>(member));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Common
|
@ -1,113 +0,0 @@
|
|||||||
// Modified version of: https://www.boost.org/doc/libs/1_79_0/boost/compute/detail/lru_cache.hpp
|
|
||||||
// Most important change is the use of an array instead of a map, so that elements are
|
|
||||||
// statically allocated. The insert and get methods have been merged into the request method.
|
|
||||||
// Original license:
|
|
||||||
//
|
|
||||||
//---------------------------------------------------------------------------//
|
|
||||||
// Copyright (c) 2013 Kyle Lutz <kyle.r.lutz@gmail.com>
|
|
||||||
//
|
|
||||||
// Distributed under the Boost Software License, Version 1.0
|
|
||||||
// See accompanying file LICENSE_1_0.txt or copy at
|
|
||||||
// http://www.boost.org/LICENSE_1_0.txt
|
|
||||||
//
|
|
||||||
// See http://boostorg.github.com/compute for more information.
|
|
||||||
//---------------------------------------------------------------------------//
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <list>
|
|
||||||
#include <tuple>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
namespace Common {
|
|
||||||
|
|
||||||
// a cache which evicts the least recently used item when it is full
|
|
||||||
// the cache elements are statically allocated.
|
|
||||||
template <class Key, class Value, size_t Size>
|
|
||||||
class StaticLRUCache {
|
|
||||||
public:
|
|
||||||
using key_type = Key;
|
|
||||||
using value_type = Value;
|
|
||||||
using list_type = std::list<std::pair<Key, size_t>>;
|
|
||||||
using array_type = std::array<Value, Size>;
|
|
||||||
|
|
||||||
StaticLRUCache() = default;
|
|
||||||
|
|
||||||
~StaticLRUCache() = default;
|
|
||||||
|
|
||||||
size_t size() const {
|
|
||||||
return m_list.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr size_t capacity() const {
|
|
||||||
return m_array.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool empty() const {
|
|
||||||
return m_list.empty();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool contains(const key_type& key) const {
|
|
||||||
return find(key) != m_list.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Requests an element from the cache. If it is not found,
|
|
||||||
// the element is inserted using its key.
|
|
||||||
// Returns whether the element was present in the cache
|
|
||||||
// and a reference to the element itself.
|
|
||||||
std::pair<bool, value_type&> request(const key_type& key) {
|
|
||||||
// lookup value in the cache
|
|
||||||
auto i = find(key);
|
|
||||||
if (i == m_list.cend()) {
|
|
||||||
size_t next_index = size();
|
|
||||||
// insert item into the cache, but first check if it is full
|
|
||||||
if (next_index >= capacity()) {
|
|
||||||
// cache is full, evict the least recently used item
|
|
||||||
next_index = evict();
|
|
||||||
}
|
|
||||||
|
|
||||||
// insert the new item
|
|
||||||
m_list.push_front(std::make_pair(key, next_index));
|
|
||||||
return std::pair<bool, value_type&>(false, m_array[next_index]);
|
|
||||||
}
|
|
||||||
// return the value, but first update its place in the most
|
|
||||||
// recently used list
|
|
||||||
if (i != m_list.cbegin()) {
|
|
||||||
// move item to the front of the most recently used list
|
|
||||||
auto backup = *i;
|
|
||||||
m_list.erase(i);
|
|
||||||
m_list.push_front(backup);
|
|
||||||
|
|
||||||
// return the value
|
|
||||||
return std::pair<bool, value_type&>(true, m_array[backup.second]);
|
|
||||||
} else {
|
|
||||||
// the item is already at the front of the most recently
|
|
||||||
// used list so just return it
|
|
||||||
return std::pair<bool, value_type&>(true, m_array[i->second]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void clear() {
|
|
||||||
m_list.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
typename list_type::const_iterator find(const key_type& key) const {
|
|
||||||
return std::find_if(m_list.cbegin(), m_list.cend(),
|
|
||||||
[&key](const auto& el) { return el.first == key; });
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t evict() {
|
|
||||||
// evict item from the end of most recently used list
|
|
||||||
typename list_type::iterator i = --m_list.end();
|
|
||||||
size_t evicted_index = i->second;
|
|
||||||
m_list.erase(i);
|
|
||||||
return evicted_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
array_type m_array;
|
|
||||||
list_type m_list;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace Common
|
|
@ -149,6 +149,27 @@ add_library(citra_core STATIC
|
|||||||
hle/kernel/ipc_debugger/recorder.h
|
hle/kernel/ipc_debugger/recorder.h
|
||||||
hle/kernel/kernel.cpp
|
hle/kernel/kernel.cpp
|
||||||
hle/kernel/kernel.h
|
hle/kernel/kernel.h
|
||||||
|
hle/kernel/k_auto_object.cpp
|
||||||
|
hle/kernel/k_auto_object.h
|
||||||
|
hle/kernel/k_class_token.cpp
|
||||||
|
hle/kernel/k_class_token.h
|
||||||
|
hle/kernel/k_linked_list.cpp
|
||||||
|
hle/kernel/k_linked_list.h
|
||||||
|
hle/kernel/k_memory_block.cpp
|
||||||
|
hle/kernel/k_memory_block.h
|
||||||
|
hle/kernel/k_memory_block_manager.cpp
|
||||||
|
hle/kernel/k_memory_block_manager.h
|
||||||
|
hle/kernel/k_memory_manager.cpp
|
||||||
|
hle/kernel/k_memory_manager.h
|
||||||
|
hle/kernel/k_page_group.cpp
|
||||||
|
hle/kernel/k_page_group.h
|
||||||
|
hle/kernel/k_page_heap.cpp
|
||||||
|
hle/kernel/k_page_heap.h
|
||||||
|
hle/kernel/k_page_manager.cpp
|
||||||
|
hle/kernel/k_page_manager.h
|
||||||
|
hle/kernel/k_page_table.cpp
|
||||||
|
hle/kernel/k_page_table.h
|
||||||
|
hle/kernel/k_slab_heap.h
|
||||||
hle/kernel/memory.cpp
|
hle/kernel/memory.cpp
|
||||||
hle/kernel/memory.h
|
hle/kernel/memory.h
|
||||||
hle/kernel/mutex.cpp
|
hle/kernel/mutex.cpp
|
||||||
@ -473,6 +494,7 @@ add_library(citra_core STATIC
|
|||||||
tracer/citrace.h
|
tracer/citrace.h
|
||||||
tracer/recorder.cpp
|
tracer/recorder.cpp
|
||||||
tracer/recorder.h
|
tracer/recorder.h
|
||||||
|
hle/kernel/slab_helpers.h
|
||||||
)
|
)
|
||||||
|
|
||||||
create_target_directory_groups(citra_core)
|
create_target_directory_groups(citra_core)
|
||||||
|
@ -86,20 +86,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
virtual void Flush() const = 0;
|
virtual void Flush() const = 0;
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the backend supports cached reads.
|
|
||||||
*/
|
|
||||||
virtual bool AllowsCachedReads() const {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Whether the cache is ready for a specified offset and length.
|
|
||||||
*/
|
|
||||||
virtual bool CacheReady(std::size_t file_offset, std::size_t length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
std::unique_ptr<DelayGenerator> delay_generator;
|
std::unique_ptr<DelayGenerator> delay_generator;
|
||||||
|
|
||||||
|
@ -131,14 +131,6 @@ public:
|
|||||||
}
|
}
|
||||||
void Flush() const override {}
|
void Flush() const override {}
|
||||||
|
|
||||||
bool AllowsCachedReads() const override {
|
|
||||||
return romfs_file->AllowsCachedReads();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
|
||||||
return romfs_file->CacheReady(file_offset, length);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::shared_ptr<RomFSReader> romfs_file;
|
std::shared_ptr<RomFSReader> romfs_file;
|
||||||
|
|
||||||
|
@ -53,14 +53,6 @@ public:
|
|||||||
|
|
||||||
bool DumpRomFS(const std::string& target_path);
|
bool DumpRomFS(const std::string& target_path);
|
||||||
|
|
||||||
bool AllowsCachedReads() const override {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CacheReady(std::size_t file_offset, std::size_t length) override {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct File;
|
struct File;
|
||||||
struct Directory {
|
struct Directory {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <vector>
|
|
||||||
#include <cryptopp/aes.h>
|
#include <cryptopp/aes.h>
|
||||||
#include <cryptopp/modes.h>
|
#include <cryptopp/modes.h>
|
||||||
#include "common/archives.h"
|
#include "common/archives.h"
|
||||||
@ -10,102 +9,17 @@ SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader)
|
|||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
|
std::size_t DirectRomFSReader::ReadFile(std::size_t offset, std::size_t length, u8* buffer) {
|
||||||
length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
|
||||||
if (length == 0)
|
if (length == 0)
|
||||||
return 0; // Crypto++ does not like zero size buffer
|
return 0; // Crypto++ does not like zero size buffer
|
||||||
|
file.Seek(file_offset + offset, SEEK_SET);
|
||||||
const auto segments = BreakupRead(offset, length);
|
std::size_t read_length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
||||||
size_t read_progress = 0;
|
read_length = file.ReadBytes(buffer, read_length);
|
||||||
|
|
||||||
// Skip cache if the read is too big
|
|
||||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
|
||||||
length = file.ReadAtBytes(buffer, length, file_offset + offset);
|
|
||||||
if (is_encrypted) {
|
if (is_encrypted) {
|
||||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||||
d.Seek(crypto_offset + offset);
|
d.Seek(crypto_offset + offset);
|
||||||
d.ProcessData(buffer, buffer, length);
|
d.ProcessData(buffer, buffer, read_length);
|
||||||
}
|
}
|
||||||
// LOG_INFO(Service_FS, "Cache SKIP: offset={}, length={}", offset, length);
|
return read_length;
|
||||||
return length;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
|
||||||
// std::unique_lock<std::shared_mutex> read_guard(cache_mutex);
|
|
||||||
for (const auto& seg : segments) {
|
|
||||||
size_t read_size = cache_line_size;
|
|
||||||
size_t page = OffsetToPage(seg.first);
|
|
||||||
// Check if segment is in cache
|
|
||||||
auto cache_entry = cache.request(page);
|
|
||||||
if (!cache_entry.first) {
|
|
||||||
// If not found, read from disk and cache the data
|
|
||||||
read_size = file.ReadAtBytes(cache_entry.second.data(), read_size, file_offset + page);
|
|
||||||
if (is_encrypted && read_size) {
|
|
||||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
|
||||||
d.Seek(crypto_offset + page);
|
|
||||||
d.ProcessData(cache_entry.second.data(), cache_entry.second.data(), read_size);
|
|
||||||
}
|
|
||||||
// LOG_INFO(Service_FS, "Cache MISS: page={}, length={}, into={}", page, seg.second,
|
|
||||||
// (seg.first - page));
|
|
||||||
} else {
|
|
||||||
// LOG_INFO(Service_FS, "Cache HIT: page={}, length={}, into={}", page, seg.second,
|
|
||||||
// (seg.first - page));
|
|
||||||
}
|
|
||||||
size_t copy_amount =
|
|
||||||
(read_size > (seg.first - page))
|
|
||||||
? std::min((seg.first - page) + seg.second, read_size) - (seg.first - page)
|
|
||||||
: 0;
|
|
||||||
std::memcpy(buffer + read_progress, cache_entry.second.data() + (seg.first - page),
|
|
||||||
copy_amount);
|
|
||||||
read_progress += copy_amount;
|
|
||||||
}
|
|
||||||
return read_progress;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DirectRomFSReader::AllowsCachedReads() const {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool DirectRomFSReader::CacheReady(std::size_t file_offset, std::size_t length) {
|
|
||||||
auto segments = BreakupRead(file_offset, length);
|
|
||||||
if (segments.size() == 1 && segments[0].second > cache_line_size) {
|
|
||||||
return false;
|
|
||||||
} else {
|
|
||||||
// TODO(PabloMK7): Since the LRU cache is not thread safe, a lock must be used.
|
|
||||||
// However, this completely breaks the point of using a cache, because
|
|
||||||
// smaller reads may be blocked by bigger reads. For now, always return
|
|
||||||
// data being in cache to prevent the need of a lock, and only read data
|
|
||||||
// asynchronously if it is too big to use the cache.
|
|
||||||
/*
|
|
||||||
std::shared_lock<std::shared_mutex> read_guard(cache_mutex);
|
|
||||||
for (auto it = segments.begin(); it != segments.end(); it++) {
|
|
||||||
if (!cache.contains(OffsetToPage(it->first)))
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<std::size_t, std::size_t>> DirectRomFSReader::BreakupRead(
|
|
||||||
std::size_t offset, std::size_t length) {
|
|
||||||
|
|
||||||
std::vector<std::pair<std::size_t, std::size_t>> ret;
|
|
||||||
|
|
||||||
// Reads bigger than the cache line size will probably never hit again
|
|
||||||
if (length > cache_line_size) {
|
|
||||||
ret.push_back(std::make_pair(offset, length));
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t curr_offset = offset;
|
|
||||||
while (length) {
|
|
||||||
size_t next_page = OffsetToPage(curr_offset + cache_line_size);
|
|
||||||
size_t curr_page_len = std::min(length, next_page - curr_offset);
|
|
||||||
ret.push_back(std::make_pair(curr_offset, curr_page_len));
|
|
||||||
curr_offset = next_page;
|
|
||||||
length -= curr_page_len;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace FileSys
|
} // namespace FileSys
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <shared_mutex>
|
|
||||||
#include <boost/serialization/array.hpp>
|
#include <boost/serialization/array.hpp>
|
||||||
#include <boost/serialization/base_object.hpp>
|
#include <boost/serialization/base_object.hpp>
|
||||||
#include <boost/serialization/export.hpp>
|
#include <boost/serialization/export.hpp>
|
||||||
#include "common/alignment.h"
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "common/static_lru_cache.h"
|
|
||||||
|
|
||||||
namespace FileSys {
|
namespace FileSys {
|
||||||
|
|
||||||
@ -21,8 +18,6 @@ public:
|
|||||||
|
|
||||||
virtual std::size_t GetSize() const = 0;
|
virtual std::size_t GetSize() const = 0;
|
||||||
virtual std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) = 0;
|
virtual std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) = 0;
|
||||||
virtual bool AllowsCachedReads() const = 0;
|
|
||||||
virtual bool CacheReady(std::size_t file_offset, std::size_t length) = 0;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
@ -53,10 +48,6 @@ public:
|
|||||||
|
|
||||||
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
|
std::size_t ReadFile(std::size_t offset, std::size_t length, u8* buffer) override;
|
||||||
|
|
||||||
bool AllowsCachedReads() const override;
|
|
||||||
|
|
||||||
bool CacheReady(std::size_t file_offset, std::size_t length) override;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool is_encrypted;
|
bool is_encrypted;
|
||||||
FileUtil::IOFile file;
|
FileUtil::IOFile file;
|
||||||
@ -66,23 +57,8 @@ private:
|
|||||||
u64 crypto_offset;
|
u64 crypto_offset;
|
||||||
u64 data_size;
|
u64 data_size;
|
||||||
|
|
||||||
// Total cache size: 128KB
|
|
||||||
static constexpr size_t cache_line_size = (1 << 13); // About 8KB
|
|
||||||
static constexpr size_t cache_line_count = 16;
|
|
||||||
|
|
||||||
Common::StaticLRUCache<std::size_t, std::array<u8, cache_line_size>, cache_line_count> cache;
|
|
||||||
// TODO(PabloMK7): Make cache thread safe, read the comment in CacheReady function.
|
|
||||||
// std::shared_mutex cache_mutex;
|
|
||||||
|
|
||||||
DirectRomFSReader() = default;
|
DirectRomFSReader() = default;
|
||||||
|
|
||||||
std::size_t OffsetToPage(std::size_t offset) {
|
|
||||||
return Common::AlignDown<std::size_t>(offset, cache_line_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<std::pair<std::size_t, std::size_t>> BreakupRead(std::size_t offset,
|
|
||||||
std::size_t length);
|
|
||||||
|
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int) {
|
void serialize(Archive& ar, const unsigned int) {
|
||||||
ar& boost::serialization::base_object<RomFSReader>(*this);
|
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||||
|
@ -221,13 +221,6 @@ public:
|
|||||||
return session;
|
return session;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the client thread that made the service request.
|
|
||||||
*/
|
|
||||||
std::shared_ptr<Thread> ClientThread() const {
|
|
||||||
return thread;
|
|
||||||
}
|
|
||||||
|
|
||||||
class WakeupCallback {
|
class WakeupCallback {
|
||||||
public:
|
public:
|
||||||
virtual ~WakeupCallback() = default;
|
virtual ~WakeupCallback() = default;
|
||||||
|
22
src/core/hle/kernel/k_auto_object.cpp
Normal file
22
src/core/hle/kernel/k_auto_object.cpp
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_auto_object.h"
|
||||||
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
KAutoObject* KAutoObject::Create(KAutoObject* obj) {
|
||||||
|
obj->m_ref_count = 1;
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KAutoObject::RegisterWithKernel() {
|
||||||
|
m_kernel.RegisterKernelObject(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KAutoObject::UnregisterWithKernel(KernelCore& kernel, KAutoObject* self) {
|
||||||
|
kernel.UnregisterKernelObject(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
268
src/core/hle/kernel/k_auto_object.h
Normal file
268
src/core/hle/kernel/k_auto_object.h
Normal file
@ -0,0 +1,268 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/kernel/k_class_token.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class KernelSystem;
|
||||||
|
class KProcess;
|
||||||
|
|
||||||
|
#define KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, ATTRIBUTE) \
|
||||||
|
private: \
|
||||||
|
friend class ::Kernel::KClassTokenGenerator; \
|
||||||
|
static constexpr inline auto ObjectType = ::Kernel::KClassTokenGenerator::ObjectType::CLASS; \
|
||||||
|
static constexpr inline const char* const TypeName = #CLASS; \
|
||||||
|
static constexpr inline ClassTokenType ClassToken() { return ::Kernel::ClassToken<CLASS>; } \
|
||||||
|
\
|
||||||
|
public: \
|
||||||
|
CITRA_NON_COPYABLE(CLASS); \
|
||||||
|
CITRA_NON_MOVEABLE(CLASS); \
|
||||||
|
\
|
||||||
|
using BaseClass = BASE_CLASS; \
|
||||||
|
static constexpr TypeObj GetStaticTypeObj() { \
|
||||||
|
constexpr ClassTokenType Token = ClassToken(); \
|
||||||
|
return TypeObj(TypeName, Token); \
|
||||||
|
} \
|
||||||
|
static constexpr const char* GetStaticTypeName() { return TypeName; } \
|
||||||
|
virtual TypeObj GetTypeObj() ATTRIBUTE { return GetStaticTypeObj(); } \
|
||||||
|
virtual const char* GetTypeName() ATTRIBUTE { return GetStaticTypeName(); } \
|
||||||
|
\
|
||||||
|
private: \
|
||||||
|
constexpr bool operator!=(const TypeObj& rhs)
|
||||||
|
|
||||||
|
#define KERNEL_AUTOOBJECT_TRAITS(CLASS, BASE_CLASS) \
|
||||||
|
KERNEL_AUTOOBJECT_TRAITS_IMPL(CLASS, BASE_CLASS, const override)
|
||||||
|
|
||||||
|
class KAutoObject {
|
||||||
|
protected:
|
||||||
|
class TypeObj {
|
||||||
|
public:
|
||||||
|
constexpr explicit TypeObj(const char* n, ClassTokenType tok)
|
||||||
|
: m_name(n), m_class_token(tok) {}
|
||||||
|
|
||||||
|
constexpr const char* GetName() const {
|
||||||
|
return m_name;
|
||||||
|
}
|
||||||
|
constexpr ClassTokenType GetClassToken() const {
|
||||||
|
return m_class_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(const TypeObj& rhs) const {
|
||||||
|
return this->GetClassToken() == rhs.GetClassToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!=(const TypeObj& rhs) const {
|
||||||
|
return this->GetClassToken() != rhs.GetClassToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool IsDerivedFrom(const TypeObj& rhs) const {
|
||||||
|
return (this->GetClassToken() | rhs.GetClassToken()) == this->GetClassToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
const char* m_name;
|
||||||
|
ClassTokenType m_class_token;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
KERNEL_AUTOOBJECT_TRAITS_IMPL(KAutoObject, KAutoObject, const);
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KAutoObject(KernelSystem& kernel) : m_kernel(kernel) {
|
||||||
|
RegisterWithKernel();
|
||||||
|
}
|
||||||
|
virtual ~KAutoObject() = default;
|
||||||
|
|
||||||
|
static KAutoObject* Create(KAutoObject* ptr);
|
||||||
|
|
||||||
|
// Destroy is responsible for destroying the auto object's resources when ref_count hits zero.
|
||||||
|
virtual void Destroy() {
|
||||||
|
UNIMPLEMENTED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finalize is responsible for cleaning up resource, but does not destroy the object.
|
||||||
|
virtual void Finalize() {}
|
||||||
|
|
||||||
|
virtual KProcess* GetOwner() const {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 GetReferenceCount() const {
|
||||||
|
return m_ref_count.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDerivedFrom(const TypeObj& rhs) const {
|
||||||
|
return this->GetTypeObj().IsDerivedFrom(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsDerivedFrom(const KAutoObject& rhs) const {
|
||||||
|
return this->IsDerivedFrom(rhs.GetTypeObj());
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Derived>
|
||||||
|
Derived DynamicCast() {
|
||||||
|
static_assert(std::is_pointer_v<Derived>);
|
||||||
|
using DerivedType = std::remove_pointer_t<Derived>;
|
||||||
|
|
||||||
|
if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) {
|
||||||
|
return static_cast<Derived>(this);
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename Derived>
|
||||||
|
const Derived DynamicCast() const {
|
||||||
|
static_assert(std::is_pointer_v<Derived>);
|
||||||
|
using DerivedType = std::remove_pointer_t<Derived>;
|
||||||
|
|
||||||
|
if (this->IsDerivedFrom(DerivedType::GetStaticTypeObj())) {
|
||||||
|
return static_cast<Derived>(this);
|
||||||
|
} else {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Open() {
|
||||||
|
// Atomically increment the reference count, only if it's positive.
|
||||||
|
u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
if (cur_ref_count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ASSERT(cur_ref_count < cur_ref_count + 1);
|
||||||
|
} while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count + 1,
|
||||||
|
std::memory_order_relaxed));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Close() {
|
||||||
|
// Atomically decrement the reference count, not allowing it to become negative.
|
||||||
|
u32 cur_ref_count = m_ref_count.load(std::memory_order_acquire);
|
||||||
|
do {
|
||||||
|
ASSERT(cur_ref_count > 0);
|
||||||
|
} while (!m_ref_count.compare_exchange_weak(cur_ref_count, cur_ref_count - 1,
|
||||||
|
std::memory_order_acq_rel));
|
||||||
|
|
||||||
|
// If ref count hits zero, destroy the object.
|
||||||
|
if (cur_ref_count - 1 == 0) {
|
||||||
|
KernelSystem& kernel = m_kernel;
|
||||||
|
this->Destroy();
|
||||||
|
KAutoObject::UnregisterWithKernel(kernel, this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void RegisterWithKernel();
|
||||||
|
static void UnregisterWithKernel(KernelSystem& kernel, KAutoObject* self);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
KernelSystem& m_kernel;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<u32> m_ref_count{};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class KScopedAutoObject {
|
||||||
|
public:
|
||||||
|
CITRA_NON_COPYABLE(KScopedAutoObject);
|
||||||
|
|
||||||
|
constexpr KScopedAutoObject() = default;
|
||||||
|
|
||||||
|
constexpr KScopedAutoObject(T* o) : m_obj(o) {
|
||||||
|
if (m_obj != nullptr) {
|
||||||
|
m_obj->Open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
~KScopedAutoObject() {
|
||||||
|
if (m_obj != nullptr) {
|
||||||
|
m_obj->Close();
|
||||||
|
}
|
||||||
|
m_obj = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename U>
|
||||||
|
requires(std::derived_from<T, U> || std::derived_from<U, T>)
|
||||||
|
constexpr KScopedAutoObject(KScopedAutoObject<U>&& rhs) {
|
||||||
|
if constexpr (std::derived_from<U, T>) {
|
||||||
|
// Upcast.
|
||||||
|
m_obj = rhs.m_obj;
|
||||||
|
rhs.m_obj = nullptr;
|
||||||
|
} else {
|
||||||
|
// Downcast.
|
||||||
|
T* derived = nullptr;
|
||||||
|
if (rhs.m_obj != nullptr) {
|
||||||
|
derived = rhs.m_obj->template DynamicCast<T*>();
|
||||||
|
if (derived == nullptr) {
|
||||||
|
rhs.m_obj->Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_obj = derived;
|
||||||
|
rhs.m_obj = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr KScopedAutoObject<T>& operator=(KScopedAutoObject<T>&& rhs) {
|
||||||
|
rhs.Swap(*this);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T* operator->() {
|
||||||
|
return m_obj;
|
||||||
|
}
|
||||||
|
constexpr T& operator*() {
|
||||||
|
return *m_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr void Reset(T* o) {
|
||||||
|
KScopedAutoObject(o).Swap(*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T* GetPointerUnsafe() {
|
||||||
|
return m_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T* GetPointerUnsafe() const {
|
||||||
|
return m_obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr T* ReleasePointerUnsafe() {
|
||||||
|
T* ret = m_obj;
|
||||||
|
m_obj = nullptr;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool IsNull() const {
|
||||||
|
return m_obj == nullptr;
|
||||||
|
}
|
||||||
|
constexpr bool IsNotNull() const {
|
||||||
|
return m_obj != nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <typename U>
|
||||||
|
friend class KScopedAutoObject;
|
||||||
|
|
||||||
|
private:
|
||||||
|
T* m_obj{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
constexpr void Swap(KScopedAutoObject& rhs) noexcept {
|
||||||
|
std::swap(m_obj, rhs.m_obj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
125
src/core/hle/kernel/k_class_token.cpp
Normal file
125
src/core/hle/kernel/k_class_token.cpp
Normal file
@ -0,0 +1,125 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_auto_object.h"
|
||||||
|
#include "core/hle/kernel/k_class_token.h"
|
||||||
|
#include "core/hle/kernel/k_client_port.h"
|
||||||
|
#include "core/hle/kernel/k_client_session.h"
|
||||||
|
#include "core/hle/kernel/k_code_memory.h"
|
||||||
|
#include "core/hle/kernel/k_event.h"
|
||||||
|
#include "core/hle/kernel/k_port.h"
|
||||||
|
#include "core/hle/kernel/k_process.h"
|
||||||
|
#include "core/hle/kernel/k_readable_event.h"
|
||||||
|
#include "core/hle/kernel/k_resource_limit.h"
|
||||||
|
#include "core/hle/kernel/k_server_port.h"
|
||||||
|
#include "core/hle/kernel/k_server_session.h"
|
||||||
|
#include "core/hle/kernel/k_session.h"
|
||||||
|
#include "core/hle/kernel/k_shared_memory.h"
|
||||||
|
#include "core/hle/kernel/k_synchronization_object.h"
|
||||||
|
#include "core/hle/kernel/k_system_resource.h"
|
||||||
|
#include "core/hle/kernel/k_thread.h"
|
||||||
|
#include "core/hle/kernel/k_transfer_memory.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
// Ensure that we generate correct class tokens for all types.
|
||||||
|
|
||||||
|
// Ensure that the absolute token values are correct.
|
||||||
|
static_assert(ClassToken<KAutoObject> == 0b00000000'00000000);
|
||||||
|
static_assert(ClassToken<KSynchronizationObject> == 0b00000000'00000001);
|
||||||
|
static_assert(ClassToken<KReadableEvent> == 0b00000000'00000011);
|
||||||
|
// static_assert(ClassToken<KInterruptEvent> == 0b00000111'00000011);
|
||||||
|
// static_assert(ClassToken<KDebug> == 0b00001011'00000001);
|
||||||
|
static_assert(ClassToken<KThread> == 0b00010011'00000001);
|
||||||
|
static_assert(ClassToken<KServerPort> == 0b00100011'00000001);
|
||||||
|
static_assert(ClassToken<KServerSession> == 0b01000011'00000001);
|
||||||
|
static_assert(ClassToken<KClientPort> == 0b10000011'00000001);
|
||||||
|
static_assert(ClassToken<KClientSession> == 0b00001101'00000000);
|
||||||
|
static_assert(ClassToken<KProcess> == 0b00010101'00000001);
|
||||||
|
static_assert(ClassToken<KResourceLimit> == 0b00100101'00000000);
|
||||||
|
// static_assert(ClassToken<KLightSession> == 0b01000101'00000000);
|
||||||
|
static_assert(ClassToken<KPort> == 0b10000101'00000000);
|
||||||
|
static_assert(ClassToken<KSession> == 0b00011001'00000000);
|
||||||
|
static_assert(ClassToken<KSharedMemory> == 0b00101001'00000000);
|
||||||
|
static_assert(ClassToken<KEvent> == 0b01001001'00000000);
|
||||||
|
// static_assert(ClassToken<KLightClientSession> == 0b00110001'00000000);
|
||||||
|
// static_assert(ClassToken<KLightServerSession> == 0b01010001'00000000);
|
||||||
|
static_assert(ClassToken<KTransferMemory> == 0b01010001'00000000);
|
||||||
|
// static_assert(ClassToken<KDeviceAddressSpace> == 0b01100001'00000000);
|
||||||
|
// static_assert(ClassToken<KSessionRequest> == 0b10100001'00000000);
|
||||||
|
static_assert(ClassToken<KCodeMemory> == 0b10100001'00000000);
|
||||||
|
|
||||||
|
// Ensure that the token hierarchy is correct.
|
||||||
|
|
||||||
|
// Base classes
|
||||||
|
static_assert(ClassToken<KAutoObject> == (0b00000000));
|
||||||
|
static_assert(ClassToken<KSynchronizationObject> == (0b00000001 | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KReadableEvent> == (0b00000010 | ClassToken<KSynchronizationObject>));
|
||||||
|
|
||||||
|
// Final classes
|
||||||
|
// static_assert(ClassToken<KInterruptEvent> == ((0b00000111 << 8) | ClassToken<KReadableEvent>));
|
||||||
|
// static_assert(ClassToken<KDebug> == ((0b00001011 << 8) | ClassToken<KSynchronizationObject>));
|
||||||
|
static_assert(ClassToken<KThread> == ((0b00010011 << 8) | ClassToken<KSynchronizationObject>));
|
||||||
|
static_assert(ClassToken<KServerPort> == ((0b00100011 << 8) | ClassToken<KSynchronizationObject>));
|
||||||
|
static_assert(ClassToken<KServerSession> ==
|
||||||
|
((0b01000011 << 8) | ClassToken<KSynchronizationObject>));
|
||||||
|
static_assert(ClassToken<KClientPort> == ((0b10000011 << 8) | ClassToken<KSynchronizationObject>));
|
||||||
|
static_assert(ClassToken<KClientSession> == ((0b00001101 << 8) | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KProcess> == ((0b00010101 << 8) | ClassToken<KSynchronizationObject>));
|
||||||
|
static_assert(ClassToken<KResourceLimit> == ((0b00100101 << 8) | ClassToken<KAutoObject>));
|
||||||
|
// static_assert(ClassToken<KLightSession> == ((0b01000101 << 8) | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KPort> == ((0b10000101 << 8) | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KSession> == ((0b00011001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KSharedMemory> == ((0b00101001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KEvent> == ((0b01001001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
// static_assert(ClassToken<KLightClientSession> == ((0b00110001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
// static_assert(ClassToken<KLightServerSession> == ((0b01010001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KTransferMemory> == ((0b01010001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
// static_assert(ClassToken<KDeviceAddressSpace> == ((0b01100001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
// static_assert(ClassToken<KSessionRequest> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
static_assert(ClassToken<KCodeMemory> == ((0b10100001 << 8) | ClassToken<KAutoObject>));
|
||||||
|
|
||||||
|
// Ensure that the token hierarchy reflects the class hierarchy.
|
||||||
|
|
||||||
|
// Base classes.
|
||||||
|
static_assert(!std::is_final_v<KSynchronizationObject> &&
|
||||||
|
std::is_base_of_v<KAutoObject, KSynchronizationObject>);
|
||||||
|
static_assert(!std::is_final_v<KReadableEvent> &&
|
||||||
|
std::is_base_of_v<KSynchronizationObject, KReadableEvent>);
|
||||||
|
|
||||||
|
// Final classes
|
||||||
|
// static_assert(std::is_final_v<KInterruptEvent> &&
|
||||||
|
// std::is_base_of_v<KReadableEvent, KInterruptEvent>);
|
||||||
|
// static_assert(std::is_final_v<KDebug> &&
|
||||||
|
// std::is_base_of_v<KSynchronizationObject, KDebug>);
|
||||||
|
static_assert(std::is_final_v<KThread> && std::is_base_of_v<KSynchronizationObject, KThread>);
|
||||||
|
static_assert(std::is_final_v<KServerPort> &&
|
||||||
|
std::is_base_of_v<KSynchronizationObject, KServerPort>);
|
||||||
|
static_assert(std::is_final_v<KServerSession> &&
|
||||||
|
std::is_base_of_v<KSynchronizationObject, KServerSession>);
|
||||||
|
static_assert(std::is_final_v<KClientPort> &&
|
||||||
|
std::is_base_of_v<KSynchronizationObject, KClientPort>);
|
||||||
|
static_assert(std::is_final_v<KClientSession> && std::is_base_of_v<KAutoObject, KClientSession>);
|
||||||
|
static_assert(std::is_final_v<KProcess> && std::is_base_of_v<KSynchronizationObject, KProcess>);
|
||||||
|
static_assert(std::is_final_v<KResourceLimit> && std::is_base_of_v<KAutoObject, KResourceLimit>);
|
||||||
|
// static_assert(std::is_final_v<KLightSession> &&
|
||||||
|
// std::is_base_of_v<KAutoObject, KLightSession>);
|
||||||
|
static_assert(std::is_final_v<KPort> && std::is_base_of_v<KAutoObject, KPort>);
|
||||||
|
static_assert(std::is_final_v<KSession> && std::is_base_of_v<KAutoObject, KSession>);
|
||||||
|
static_assert(std::is_final_v<KSharedMemory> && std::is_base_of_v<KAutoObject, KSharedMemory>);
|
||||||
|
static_assert(std::is_final_v<KEvent> && std::is_base_of_v<KAutoObject, KEvent>);
|
||||||
|
// static_assert(std::is_final_v<KLightClientSession> &&
|
||||||
|
// std::is_base_of_v<KAutoObject, KLightClientSession>);
|
||||||
|
// static_assert(std::is_final_v<KLightServerSession> &&
|
||||||
|
// std::is_base_of_v<KAutoObject, KLightServerSession>);
|
||||||
|
static_assert(std::is_final_v<KTransferMemory> && std::is_base_of_v<KAutoObject, KTransferMemory>);
|
||||||
|
// static_assert(std::is_final_v<KDeviceAddressSpace> &&
|
||||||
|
// std::is_base_of_v<KAutoObject, KDeviceAddressSpace>);
|
||||||
|
// static_assert(std::is_final_v<KSessionRequest> &&
|
||||||
|
// std::is_base_of_v<KAutoObject, KSessionRequest>);
|
||||||
|
// static_assert(std::is_final_v<KCodeMemory> &&
|
||||||
|
// std::is_base_of_v<KAutoObject, KCodeMemory>);
|
||||||
|
|
||||||
|
static_assert(std::is_base_of_v<KAutoObject, KSystemResource>);
|
||||||
|
|
||||||
|
} // namespace Kernel
|
111
src/core/hle/kernel/k_class_token.h
Normal file
111
src/core/hle/kernel/k_class_token.h
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class KAutoObject;
|
||||||
|
class KSynchronizationObject;
|
||||||
|
|
||||||
|
class KClassTokenGenerator {
|
||||||
|
public:
|
||||||
|
using TokenBaseType = u8;
|
||||||
|
|
||||||
|
public:
|
||||||
|
static constexpr size_t BaseClassBits = 1;
|
||||||
|
static constexpr size_t FinalClassBits = (sizeof(TokenBaseType) * CHAR_BIT) - BaseClassBits - 1;
|
||||||
|
// One bit per base class.
|
||||||
|
static constexpr size_t NumBaseClasses = BaseClassBits;
|
||||||
|
// Final classes are permutations of three bits.
|
||||||
|
static constexpr size_t NumFinalClasses = [] {
|
||||||
|
TokenBaseType index = 0;
|
||||||
|
for (size_t i = 0; i < FinalClassBits; i++) {
|
||||||
|
for (size_t j = i + 1; j < FinalClassBits; j++) {
|
||||||
|
for (size_t k = j + 1; k < FinalClassBits; k++) {
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return index;
|
||||||
|
}();
|
||||||
|
|
||||||
|
private:
|
||||||
|
template <TokenBaseType Index>
|
||||||
|
static constexpr inline TokenBaseType BaseClassToken = 1U << Index;
|
||||||
|
|
||||||
|
template <TokenBaseType Index>
|
||||||
|
static constexpr inline TokenBaseType FinalClassToken = [] {
|
||||||
|
TokenBaseType index = 0;
|
||||||
|
for (size_t i = 0; i < FinalClassBits; i++) {
|
||||||
|
for (size_t j = i + 1; j < FinalClassBits; j++) {
|
||||||
|
for (size_t k = j + 1; k < FinalClassBits; k++) {
|
||||||
|
if ((index++) == Index) {
|
||||||
|
return static_cast<TokenBaseType>(((1ULL << i) | (1ULL << j) | (1ULL << k))
|
||||||
|
<< BaseClassBits);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
UNREACHABLE();
|
||||||
|
}();
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static constexpr inline TokenBaseType GetClassToken() {
|
||||||
|
static_assert(std::is_base_of<KAutoObject, T>::value);
|
||||||
|
if constexpr (std::is_same<T, KAutoObject>::value) {
|
||||||
|
static_assert(T::ObjectType == ObjectType::KAutoObject);
|
||||||
|
return 0;
|
||||||
|
} else if constexpr (std::is_same<T, KSynchronizationObject>::value) {
|
||||||
|
static_assert(T::ObjectType == ObjectType::KSynchronizationObject);
|
||||||
|
return 1;
|
||||||
|
} else if constexpr (ObjectType::FinalClassesStart <= T::ObjectType &&
|
||||||
|
T::ObjectType < ObjectType::FinalClassesEnd) {
|
||||||
|
constexpr auto ClassIndex = static_cast<TokenBaseType>(T::ObjectType) -
|
||||||
|
static_cast<TokenBaseType>(ObjectType::FinalClassesStart);
|
||||||
|
return FinalClassToken<ClassIndex> | GetClassToken<typename T::BaseClass>();
|
||||||
|
} else {
|
||||||
|
static_assert(!std::is_same<T, T>::value, "GetClassToken: Invalid Type");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
enum class ObjectType {
|
||||||
|
KAutoObject,
|
||||||
|
KSynchronizationObject,
|
||||||
|
|
||||||
|
FinalClassesStart,
|
||||||
|
KSemaphore,
|
||||||
|
KEvent,
|
||||||
|
KTimer,
|
||||||
|
KMutex,
|
||||||
|
KDebug,
|
||||||
|
KServerPort,
|
||||||
|
KDmaObject,
|
||||||
|
KClientPort,
|
||||||
|
KCodeSet,
|
||||||
|
KSession,
|
||||||
|
KThread,
|
||||||
|
KServerSession,
|
||||||
|
KAddressArbiter,
|
||||||
|
KClientSession,
|
||||||
|
KPort,
|
||||||
|
KSharedMemory,
|
||||||
|
KProcess,
|
||||||
|
KResourceLimit,
|
||||||
|
|
||||||
|
FinalClassesEnd = FinalClassesStart + NumFinalClasses,
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static constexpr inline TokenBaseType ClassToken = GetClassToken<T>();
|
||||||
|
};
|
||||||
|
|
||||||
|
using ClassTokenType = KClassTokenGenerator::TokenBaseType;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
static constexpr inline ClassTokenType ClassToken = KClassTokenGenerator::ClassToken<T>;
|
||||||
|
|
||||||
|
} // namespace Kernel
|
0
src/core/hle/kernel/k_linked_list.cpp
Normal file
0
src/core/hle/kernel/k_linked_list.cpp
Normal file
237
src/core/hle/kernel/k_linked_list.h
Normal file
237
src/core/hle/kernel/k_linked_list.h
Normal file
@ -0,0 +1,237 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/intrusive_list.h"
|
||||||
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class KernelSystem;
|
||||||
|
|
||||||
|
class KLinkedListNode : public Common::IntrusiveListBaseNode<KLinkedListNode>,
|
||||||
|
public KSlabAllocated<KLinkedListNode> {
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KLinkedListNode(KernelSystem&) {}
|
||||||
|
KLinkedListNode() = default;
|
||||||
|
|
||||||
|
void Initialize(void* it) {
|
||||||
|
m_item = it;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GetItem() const {
|
||||||
|
return m_item;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void* m_item = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class KLinkedList : private Common::IntrusiveListBaseTraits<KLinkedListNode>::ListType {
|
||||||
|
private:
|
||||||
|
using BaseList = Common::IntrusiveListBaseTraits<KLinkedListNode>::ListType;
|
||||||
|
|
||||||
|
public:
|
||||||
|
template <bool Const>
|
||||||
|
class Iterator;
|
||||||
|
|
||||||
|
using value_type = T;
|
||||||
|
using size_type = size_t;
|
||||||
|
using difference_type = ptrdiff_t;
|
||||||
|
using pointer = value_type*;
|
||||||
|
using const_pointer = const value_type*;
|
||||||
|
using reference = value_type&;
|
||||||
|
using const_reference = const value_type&;
|
||||||
|
using iterator = Iterator<false>;
|
||||||
|
using const_iterator = Iterator<true>;
|
||||||
|
using reverse_iterator = std::reverse_iterator<iterator>;
|
||||||
|
using const_reverse_iterator = std::reverse_iterator<const_iterator>;
|
||||||
|
|
||||||
|
template <bool Const>
|
||||||
|
class Iterator {
|
||||||
|
private:
|
||||||
|
using BaseIterator = BaseList::iterator;
|
||||||
|
friend class KLinkedList;
|
||||||
|
|
||||||
|
public:
|
||||||
|
using iterator_category = std::bidirectional_iterator_tag;
|
||||||
|
using value_type = typename KLinkedList::value_type;
|
||||||
|
using difference_type = typename KLinkedList::difference_type;
|
||||||
|
using pointer = std::conditional_t<Const, KLinkedList::const_pointer, KLinkedList::pointer>;
|
||||||
|
using reference =
|
||||||
|
std::conditional_t<Const, KLinkedList::const_reference, KLinkedList::reference>;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit Iterator(BaseIterator it) : m_base_it(it) {}
|
||||||
|
|
||||||
|
pointer GetItem() const {
|
||||||
|
return static_cast<pointer>(m_base_it->GetItem());
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(const Iterator& rhs) const {
|
||||||
|
return m_base_it == rhs.m_base_it;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const Iterator& rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pointer operator->() const {
|
||||||
|
return this->GetItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
reference operator*() const {
|
||||||
|
return *this->GetItem();
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator& operator++() {
|
||||||
|
++m_base_it;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator& operator--() {
|
||||||
|
--m_base_it;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator operator++(int) {
|
||||||
|
const Iterator it{*this};
|
||||||
|
++(*this);
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
Iterator operator--(int) {
|
||||||
|
const Iterator it{*this};
|
||||||
|
--(*this);
|
||||||
|
return it;
|
||||||
|
}
|
||||||
|
|
||||||
|
operator Iterator<true>() const {
|
||||||
|
return Iterator<true>(m_base_it);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
BaseIterator m_base_it;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr KLinkedList(KernelSystem& kernel_) : BaseList(), kernel{kernel_} {}
|
||||||
|
|
||||||
|
~KLinkedList() {
|
||||||
|
// Erase all elements.
|
||||||
|
for (auto it = begin(); it != end(); it = erase(it)) {
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we succeeded.
|
||||||
|
ASSERT(this->empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterator accessors.
|
||||||
|
iterator begin() {
|
||||||
|
return iterator(BaseList::begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator begin() const {
|
||||||
|
return const_iterator(BaseList::begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator end() {
|
||||||
|
return iterator(BaseList::end());
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator end() const {
|
||||||
|
return const_iterator(BaseList::end());
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cbegin() const {
|
||||||
|
return this->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_iterator cend() const {
|
||||||
|
return this->end();
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_iterator rbegin() {
|
||||||
|
return reverse_iterator(this->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
const_reverse_iterator rbegin() const {
|
||||||
|
return const_reverse_iterator(this->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
reverse_iterator rend() {
|
||||||
|
return reverse_iterator(this->begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
const_reverse_iterator rend() const {
|
||||||
|
return const_reverse_iterator(this->begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
const_reverse_iterator crbegin() const {
|
||||||
|
return this->rbegin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_reverse_iterator crend() const {
|
||||||
|
return this->rend();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Content management.
|
||||||
|
using BaseList::empty;
|
||||||
|
using BaseList::size;
|
||||||
|
|
||||||
|
reference back() {
|
||||||
|
return *(--this->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
const_reference back() const {
|
||||||
|
return *(--this->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
reference front() {
|
||||||
|
return *this->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
const_reference front() const {
|
||||||
|
return *this->begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator insert(const_iterator pos, reference ref) {
|
||||||
|
KLinkedListNode* new_node = KLinkedListNode::Allocate(kernel);
|
||||||
|
ASSERT(new_node != nullptr);
|
||||||
|
new_node->Initialize(std::addressof(ref));
|
||||||
|
return iterator(BaseList::insert(pos.m_base_it, *new_node));
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_back(reference ref) {
|
||||||
|
this->insert(this->end(), ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void push_front(reference ref) {
|
||||||
|
this->insert(this->begin(), ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_back() {
|
||||||
|
this->erase(--this->end());
|
||||||
|
}
|
||||||
|
|
||||||
|
void pop_front() {
|
||||||
|
this->erase(this->begin());
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator erase(const iterator pos) {
|
||||||
|
KLinkedListNode* freed_node = std::addressof(*pos.m_base_it);
|
||||||
|
iterator ret = iterator(BaseList::erase(pos.m_base_it));
|
||||||
|
KLinkedListNode::Free(kernel, freed_node);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KernelSystem& kernel;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
41
src/core/hle/kernel/k_memory_block.cpp
Normal file
41
src/core/hle/kernel/k_memory_block.cpp
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_memory_block.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
void KMemoryBlock::ShrinkBlock(VAddr addr, u32 num_pages) {
|
||||||
|
const VAddr end_addr = addr + (num_pages << Memory::CITRA_PAGE_BITS) - 1;
|
||||||
|
const VAddr last_addr = this->GetLastAddress();
|
||||||
|
if (m_base_addr < end_addr && end_addr < last_addr) {
|
||||||
|
m_base_addr = end_addr + 1;
|
||||||
|
m_num_pages = (last_addr - end_addr) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (m_base_addr < addr && addr < last_addr) {
|
||||||
|
m_num_pages = (addr - m_base_addr) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KMemoryBlock::GrowBlock(VAddr addr, u32 num_pages) {
|
||||||
|
const u32 end_addr = addr + (num_pages << Memory::CITRA_PAGE_BITS) - 1;
|
||||||
|
const u32 last_addr = this->GetLastAddress();
|
||||||
|
if (addr < m_base_addr) {
|
||||||
|
m_base_addr = addr;
|
||||||
|
m_num_pages = (last_addr - addr + 1) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
if (last_addr < end_addr) {
|
||||||
|
m_num_pages = (end_addr - m_base_addr + 1) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KMemoryBlock::IncludesRange(VAddr addr, u32 num_pages) {
|
||||||
|
const u32 end_addr = addr + (num_pages << Memory::CITRA_PAGE_BITS) - 1;
|
||||||
|
const u32 last_addr = this->GetLastAddress();
|
||||||
|
return m_base_addr >= addr && last_addr <= end_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
187
src/core/hle/kernel/k_memory_block.h
Normal file
187
src/core/hle/kernel/k_memory_block.h
Normal file
@ -0,0 +1,187 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
enum class KMemoryPermission : u32 {
|
||||||
|
None = 0x0,
|
||||||
|
UserRead = 0x1,
|
||||||
|
UserWrite = 0x2,
|
||||||
|
UserReadWrite = UserRead | UserWrite,
|
||||||
|
UserExecute = 0x4,
|
||||||
|
UserReadExecute = UserRead | UserExecute,
|
||||||
|
KernelRead = 0x8,
|
||||||
|
KernelWrite = 0x10,
|
||||||
|
KernelExecute = 0x20,
|
||||||
|
KernelReadWrite = KernelRead | KernelWrite,
|
||||||
|
DontCare = 0x10000000,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(KMemoryPermission)
|
||||||
|
|
||||||
|
enum class KMemoryState : u32 {
|
||||||
|
Free = 0x0,
|
||||||
|
Reserved = 0x1,
|
||||||
|
Io = 0x2,
|
||||||
|
Static = 0x3,
|
||||||
|
Code = 0x4,
|
||||||
|
Private = 0x5,
|
||||||
|
Shared = 0x6,
|
||||||
|
Continuous = 0x7,
|
||||||
|
Aliased = 0x8,
|
||||||
|
Alias = 0x9,
|
||||||
|
Aliascode = 0xA,
|
||||||
|
Locked = 0xB,
|
||||||
|
KernelMask = 0xFF,
|
||||||
|
|
||||||
|
FlagDeallocatable = 0x100,
|
||||||
|
FlagProtectible = 0x200,
|
||||||
|
FlagDebuggable = 0x400,
|
||||||
|
FlagIpcAllowed = 0x800,
|
||||||
|
FlagMapped = 0x1000,
|
||||||
|
FlagPrivate = 0x2000,
|
||||||
|
FlagShared = 0x4000,
|
||||||
|
FlagsPrivateOrShared = 0x6000,
|
||||||
|
FlagCodeAllowed = 0x8000,
|
||||||
|
FlagsIpc = 0x1800,
|
||||||
|
FlagsPrivateData = 0x3800,
|
||||||
|
FlagsPrivateCodeAllowed = 0xB800,
|
||||||
|
FlagsPrivateCode = 0xBC00,
|
||||||
|
FlagsCode = 0x9C00,
|
||||||
|
|
||||||
|
KernelIo = 0x1002,
|
||||||
|
KernelStatic = 0x1003,
|
||||||
|
KernelShared = 0x5806,
|
||||||
|
KernelLinear = 0x3907,
|
||||||
|
KernelAliased = 0x3A08,
|
||||||
|
KernelAlias = 0x1A09,
|
||||||
|
KernelAliasCode = 0x9C0A,
|
||||||
|
PrivateAliasCode = 0xBC0A,
|
||||||
|
PrivateCode = 0xBC04,
|
||||||
|
PrivateData = 0xBB05,
|
||||||
|
KernelLocked = 0x380B,
|
||||||
|
FlagsAny = 0xFFFFFFFF,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(KMemoryState)
|
||||||
|
|
||||||
|
struct KMemoryInfo {
|
||||||
|
VAddr m_base_address;
|
||||||
|
u32 m_size;
|
||||||
|
KMemoryPermission m_perms;
|
||||||
|
KMemoryState m_state;
|
||||||
|
|
||||||
|
constexpr VAddr GetAddress() const {
|
||||||
|
return m_base_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetSize() const {
|
||||||
|
return m_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetNumPages() const {
|
||||||
|
return this->GetSize() >> Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr VAddr GetEndAddress() const {
|
||||||
|
return this->GetAddress() + this->GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr VAddr GetLastAddress() const {
|
||||||
|
return this->GetEndAddress() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr KMemoryPermission GetPerms() const {
|
||||||
|
return m_perms;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr KMemoryState GetState() const {
|
||||||
|
return m_state;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KMemoryBlock : public KSlabAllocated<KMemoryBlock> {
|
||||||
|
public:
|
||||||
|
explicit KMemoryBlock() = default;
|
||||||
|
|
||||||
|
constexpr void Initialize(VAddr base_addr, u32 num_pages, u32 tag, KMemoryState state,
|
||||||
|
KMemoryPermission perms) {
|
||||||
|
m_base_addr = base_addr;
|
||||||
|
m_num_pages = num_pages;
|
||||||
|
m_permission = perms;
|
||||||
|
m_memory_state = state;
|
||||||
|
m_tag = tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool Contains(VAddr addr) const {
|
||||||
|
return this->GetAddress() <= addr && addr <= this->GetLastAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr KMemoryInfo GetInfo() const {
|
||||||
|
return {
|
||||||
|
.m_base_address = m_base_addr,
|
||||||
|
.m_size = this->GetSize(),
|
||||||
|
.m_perms = m_permission,
|
||||||
|
.m_state = m_memory_state,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool HasProperties(KMemoryState s, KMemoryPermission p, u32 t) const {
|
||||||
|
return m_memory_state == s && m_permission == p && m_tag == t;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool HasSameProperties(const KMemoryBlock& rhs) const {
|
||||||
|
return m_memory_state == rhs.m_memory_state && m_permission == rhs.m_permission &&
|
||||||
|
m_tag == rhs.m_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetSize() const {
|
||||||
|
return m_num_pages << Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetEndAddress() const {
|
||||||
|
return this->GetAddress() + this->GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetLastAddress() const {
|
||||||
|
return this->GetEndAddress() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetAddress() const {
|
||||||
|
return m_base_addr;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetNumPages() const {
|
||||||
|
return m_num_pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr KMemoryPermission GetPermission() const {
|
||||||
|
return m_permission;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr KMemoryState GetState() const {
|
||||||
|
return m_memory_state;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetTag() const {
|
||||||
|
return m_tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShrinkBlock(VAddr addr, u32 num_pages);
|
||||||
|
void GrowBlock(VAddr addr, u32 num_pages);
|
||||||
|
bool IncludesRange(VAddr addr, u32 num_pages);
|
||||||
|
|
||||||
|
private:
|
||||||
|
u32 m_base_addr{};
|
||||||
|
u32 m_num_pages{};
|
||||||
|
KMemoryPermission m_permission{};
|
||||||
|
KMemoryState m_memory_state{};
|
||||||
|
u32 m_tag{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
199
src/core/hle/kernel/k_memory_block_manager.cpp
Normal file
199
src/core/hle/kernel/k_memory_block_manager.cpp
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/scope_exit.h"
|
||||||
|
#include "core/hle/kernel/k_memory_block_manager.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
void KMemoryBlockManager::Initialize(u32 addr_space_start, u32 addr_space_end) {
|
||||||
|
const u32 num_pages = (addr_space_end - addr_space_start) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
KMemoryBlock* block = KMemoryBlock::Allocate(m_kernel);
|
||||||
|
block->Initialize(addr_space_start, num_pages, 0, KMemoryState::Free, KMemoryPermission::None);
|
||||||
|
m_blocks.push_back(*block);
|
||||||
|
}
|
||||||
|
|
||||||
|
s64 KMemoryBlockManager::GetTotalCommittedMemory() {
|
||||||
|
u32 total_commited_memory{};
|
||||||
|
for (const auto& block : m_blocks) {
|
||||||
|
const KMemoryInfo info = block.GetInfo();
|
||||||
|
if (info.GetAddress() - 0x1C000000 >= 0x4000000 &&
|
||||||
|
True(info.GetState() & KMemoryState::Private)) {
|
||||||
|
total_commited_memory += info.GetSize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total_commited_memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
KMemoryBlock* KMemoryBlockManager::FindFreeBlockInRegion(VAddr start, u32 num_pages,
|
||||||
|
u32 block_num_pages) {
|
||||||
|
const VAddr end = start + (num_pages << Memory::CITRA_PAGE_BITS);
|
||||||
|
const u32 block_size = block_num_pages << Memory::CITRA_PAGE_BITS;
|
||||||
|
for (auto& block : m_blocks) {
|
||||||
|
const KMemoryInfo info = block.GetInfo();
|
||||||
|
if (info.GetState() != KMemoryState::Free) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
const VAddr block_start = std::max(info.GetAddress(), start);
|
||||||
|
const VAddr block_end = block_start + block_size;
|
||||||
|
if (block_end <= end && block_end <= info.GetEndAddress()) {
|
||||||
|
return std::addressof(block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KMemoryBlockManager::CoalesceBlocks() {
|
||||||
|
auto it = m_blocks.begin();
|
||||||
|
while (true) {
|
||||||
|
iterator prev = it++;
|
||||||
|
if (it == m_blocks.end()) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge adjacent blocks with the same properties.
|
||||||
|
if (prev->HasSameProperties(*it)) {
|
||||||
|
KMemoryBlock* block = std::addressof(*it);
|
||||||
|
const KMemoryInfo info = block->GetInfo();
|
||||||
|
prev->GrowBlock(info.GetAddress(), info.GetNumPages());
|
||||||
|
KMemoryBlock::Free(m_kernel, block);
|
||||||
|
m_blocks.erase(it);
|
||||||
|
it = prev;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResultCode KMemoryBlockManager::MutateRange(VAddr addr, u32 num_pages, KMemoryState state,
|
||||||
|
KMemoryPermission perms, u32 tag) {
|
||||||
|
// Initialize iterators.
|
||||||
|
const VAddr last_addr = addr + (num_pages << Memory::CITRA_PAGE_BITS) - 1;
|
||||||
|
iterator begin = FindIterator(addr);
|
||||||
|
iterator end = FindIterator(last_addr);
|
||||||
|
|
||||||
|
// Before returning we have to coalesce.
|
||||||
|
SCOPE_EXIT({ this->CoalesceBlocks(); });
|
||||||
|
|
||||||
|
// Begin and end addresses are in different blocks. We need to shrink/remove
|
||||||
|
// any blocks in that range and insert a new one with the new attributes.
|
||||||
|
if (begin != end) {
|
||||||
|
// Any blocks in-between begin and end can be completely erased.
|
||||||
|
for (auto it = std::next(begin); it != end;) {
|
||||||
|
KMemoryBlock::Free(m_kernel, std::addressof(*it));
|
||||||
|
it = m_blocks.erase(it);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If begin block has same properties, grow it to accomodate the range.
|
||||||
|
if (begin->HasProperties(state, perms, tag)) {
|
||||||
|
begin->GrowBlock(addr, num_pages);
|
||||||
|
// If the end block is fully overwritten, remove it.
|
||||||
|
if (end->GetLastAddress() == last_addr) {
|
||||||
|
KMemoryBlock::Free(m_kernel, std::addressof(*end));
|
||||||
|
m_blocks.erase(end);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
} else if (end->HasProperties(state, perms, tag)) {
|
||||||
|
// If end block has same properties, grow it to accomodate the range.
|
||||||
|
end->GrowBlock(addr, num_pages);
|
||||||
|
|
||||||
|
// Remove start block if fully overwritten
|
||||||
|
if (begin->GetAddress() == addr) {
|
||||||
|
KMemoryBlock::Free(m_kernel, std::addressof(*begin));
|
||||||
|
m_blocks.erase(begin);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Neither begin and end blocks have required properties.
|
||||||
|
// Shrink them both and create a new block in-between.
|
||||||
|
if (begin->IncludesRange(addr, num_pages)) {
|
||||||
|
KMemoryBlock::Free(m_kernel, std::addressof(*begin));
|
||||||
|
begin = m_blocks.erase(begin);
|
||||||
|
} else {
|
||||||
|
// Otherwise cut off the part that inside our range
|
||||||
|
begin->ShrinkBlock(addr, num_pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the end block is fully inside the range, remove it
|
||||||
|
if (end->IncludesRange(addr, num_pages)) {
|
||||||
|
KMemoryBlock::Free(m_kernel, std::addressof(*end));
|
||||||
|
end = m_blocks.erase(end);
|
||||||
|
} else {
|
||||||
|
// Otherwise cut off the part that inside our range
|
||||||
|
end->ShrinkBlock(addr, num_pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
// The range [va, endVa] is now void, create new block in its place.
|
||||||
|
KMemoryBlock* block = KMemoryBlock::Allocate(m_kernel);
|
||||||
|
block->Initialize(addr, num_pages, 0, state, perms);
|
||||||
|
|
||||||
|
// Insert it to the block list
|
||||||
|
m_blocks.insert(end, *block);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shrink the block containing the start va
|
||||||
|
begin->ShrinkBlock(addr, num_pages);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start and end address are in same block, we have to split that.
|
||||||
|
if (!begin->HasProperties(state, perms, tag)) {
|
||||||
|
const KMemoryInfo info = begin->GetInfo();
|
||||||
|
const u32 pages_in_block = (addr - info.GetAddress()) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
|
||||||
|
// Block has same starting address, we can just adjust the size.
|
||||||
|
if (info.GetAddress() == addr) {
|
||||||
|
// Block size matches, simply change attributes.
|
||||||
|
if (info.GetSize() == num_pages << Memory::CITRA_PAGE_BITS) {
|
||||||
|
begin->Initialize(addr, num_pages, tag, state, perms);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
// Block size is bigger, split, insert new block after and update
|
||||||
|
begin->ShrinkBlock(addr, num_pages);
|
||||||
|
KMemoryBlock* block = KMemoryBlock::Allocate(m_kernel);
|
||||||
|
block->Initialize(addr, num_pages, tag, state, perms);
|
||||||
|
|
||||||
|
// Insert it to the block list.
|
||||||
|
m_blocks.insert(begin, *block);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Same end address, but different base addr.
|
||||||
|
if (info.GetLastAddress() == last_addr) {
|
||||||
|
begin->ShrinkBlock(addr, num_pages);
|
||||||
|
KMemoryBlock* block = KMemoryBlock::Allocate(m_kernel);
|
||||||
|
block->Initialize(addr, num_pages, tag, state, perms);
|
||||||
|
|
||||||
|
// Insert it to the block list
|
||||||
|
m_blocks.insert(++begin, *block);
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Block fully contains start and end addresses. Shrink it to [last_addr, block_end] range.
|
||||||
|
begin->ShrinkBlock(0, num_pages + (addr >> Memory::CITRA_PAGE_BITS));
|
||||||
|
|
||||||
|
// Create a new block for [addr, last_addr] with the provided attributes.
|
||||||
|
KMemoryBlock* middle_block = KMemoryBlock::Allocate(m_kernel);
|
||||||
|
middle_block->Initialize(addr, num_pages, tag, state, perms);
|
||||||
|
begin = m_blocks.insert(begin, *middle_block);
|
||||||
|
|
||||||
|
// Create another block for the third range [block_addr, addr].
|
||||||
|
KMemoryBlock* start_block = KMemoryBlock::Allocate(m_kernel);
|
||||||
|
start_block->Initialize(info.GetAddress(), pages_in_block, 0, info.GetState(),
|
||||||
|
info.GetPerms());
|
||||||
|
m_blocks.insert(begin, *start_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// We are done :)
|
||||||
|
R_SUCCEED();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KMemoryBlockManager::Finalize() {
|
||||||
|
auto it = m_blocks.begin();
|
||||||
|
while (it != m_blocks.end()) {
|
||||||
|
KMemoryBlock::Free(m_kernel, std::addressof(*it));
|
||||||
|
it = m_blocks.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
41
src/core/hle/kernel/k_memory_block_manager.h
Normal file
41
src/core/hle/kernel/k_memory_block_manager.h
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_linked_list.h"
|
||||||
|
#include "core/hle/kernel/k_memory_block.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class KMemoryBlockManager final {
|
||||||
|
using BlockList = KLinkedList<KMemoryBlock>;
|
||||||
|
using iterator = BlockList::iterator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KMemoryBlockManager(KernelSystem& kernel) : m_kernel{kernel}, m_blocks{kernel} {}
|
||||||
|
~KMemoryBlockManager() = default;
|
||||||
|
|
||||||
|
void Initialize(u32 addr_space_start, u32 addr_sce_end);
|
||||||
|
void Finalize();
|
||||||
|
|
||||||
|
void CoalesceBlocks();
|
||||||
|
s64 GetTotalCommittedMemory();
|
||||||
|
ResultCode MutateRange(VAddr addr, u32 num_pages, KMemoryState state, KMemoryPermission perms,
|
||||||
|
u32 tag);
|
||||||
|
|
||||||
|
KMemoryBlock* GetMemoryBlockContainingAddr(u32 addr);
|
||||||
|
KMemoryBlock* FindFreeBlockInRegion(VAddr start, u32 num_pages, u32 block_num_pages);
|
||||||
|
|
||||||
|
iterator FindIterator(VAddr address) {
|
||||||
|
return std::find_if(m_blocks.begin(), m_blocks.end(),
|
||||||
|
[address](auto& block) { return block.Contains(address); });
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KernelSystem& m_kernel;
|
||||||
|
BlockList m_blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
73
src/core/hle/kernel/k_memory_manager.cpp
Normal file
73
src/core/hle/kernel/k_memory_manager.cpp
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_memory_manager.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
void KMemoryManager::Initialize(FcramLayout* layout, u32 fcram_addr, u32 fcram_size) {
|
||||||
|
m_application_heap.Initialize(layout->application_addr, layout->application_size);
|
||||||
|
m_system_heap.Initialize(layout->system_addr, layout->system_size);
|
||||||
|
m_base_heap.Initialize(layout->base_addr, layout->base_size);
|
||||||
|
m_page_manager.Initialize(fcram_addr, fcram_size >> Memory::CITRA_PAGE_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KMemoryManager::ConvertSharedMemPaLinearWithAppMemType(PAddr addr) {
|
||||||
|
int v2; // r1
|
||||||
|
|
||||||
|
const u32 fcram_offset = addr - Memory::FCRAM_PADDR;
|
||||||
|
if ((unsigned __int8)g_kernelSharedConfigPagePtr->appMemType == 7) {
|
||||||
|
v2 = 0x30000000;
|
||||||
|
} else {
|
||||||
|
v2 = 0x14000000;
|
||||||
|
}
|
||||||
|
return fcram_offset + v2;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAddr KMemoryManager::AllocateContiguous(u32 num_pages, u32 page_alignment, MemoryOperation op) {
|
||||||
|
// KLightScopedMutex m{m_page_manager.GetMutex()};
|
||||||
|
|
||||||
|
if (True(op & MemoryOperation::Kernel)) {
|
||||||
|
m_page_manager.GetKernelMemoryUsage() += num_pages << Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op & MemoryOperation::RegionMask) {
|
||||||
|
case MemoryOperation::RegionApplication:
|
||||||
|
return m_application_heap.AllocateContiguous(num_pages, page_alignment);
|
||||||
|
case MemoryOperation::RegionSystem:
|
||||||
|
return m_system_heap.AllocateContiguous(num_pages, page_alignment);
|
||||||
|
case MemoryOperation::RegionBase:
|
||||||
|
return m_base_heap.AllocateContiguous(num_pages, page_alignment);
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
VAddr KMemoryManager::AllocateContiguousBackwards(u32 num_pages, MemoryOperation op) {
|
||||||
|
// KLightScopedMutex m{m_page_manager.GetMutex()};
|
||||||
|
|
||||||
|
if (True(op & MemoryOperation::Kernel)) {
|
||||||
|
m_page_manager.GetKernelMemoryUsage() += num_pages << Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (op & MemoryOperation::RegionMask) {
|
||||||
|
case MemoryOperation::RegionApplication:
|
||||||
|
return m_application_heap.AllocateBackwards(num_pages);
|
||||||
|
case MemoryOperation::RegionSystem:
|
||||||
|
return m_system_heap.AllocateBackwards(num_pages);
|
||||||
|
case MemoryOperation::RegionBase:
|
||||||
|
return m_base_heap.AllocateBackwards(num_pages);
|
||||||
|
default:
|
||||||
|
UNREACHABLE();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KMemoryManager::FreeContiguousLocked(u32 addr, u32 num_pages) {
|
||||||
|
// KLightScopedMutex m{m_page_manager.GetMutex()};
|
||||||
|
m_page_manager.FreeContiguous(addr, num_pages, MemoryOperation::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
60
src/core/hle/kernel/k_memory_manager.h
Normal file
60
src/core/hle/kernel/k_memory_manager.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_page_heap.h"
|
||||||
|
#include "core/hle/kernel/k_page_manager.h"
|
||||||
|
|
||||||
|
namespace Memory {
|
||||||
|
class MemorySystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
struct FcramLayout {
|
||||||
|
u32 application_addr;
|
||||||
|
u32 application_size;
|
||||||
|
u32 system_addr;
|
||||||
|
u32 system_size;
|
||||||
|
u32 base_addr;
|
||||||
|
u32 base_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
class KMemoryManager {
|
||||||
|
public:
|
||||||
|
explicit KMemoryManager(Memory::MemorySystem& memory)
|
||||||
|
: m_application_heap{memory}, m_system_heap{memory}, m_base_heap{memory}, m_page_manager{
|
||||||
|
memory,
|
||||||
|
this} {}
|
||||||
|
~KMemoryManager() = default;
|
||||||
|
|
||||||
|
void Initialize(FcramLayout* layout, u32 fcram_addr, u32 fcram_size);
|
||||||
|
|
||||||
|
u32 ConvertSharedMemPaLinearWithAppMemType(PAddr addr);
|
||||||
|
|
||||||
|
KPageHeap& GetApplicationHeap() noexcept {
|
||||||
|
return m_application_heap;
|
||||||
|
}
|
||||||
|
|
||||||
|
KPageHeap& GetSystemHeap() noexcept {
|
||||||
|
return m_system_heap;
|
||||||
|
}
|
||||||
|
|
||||||
|
KPageHeap& GetBaseHeap() noexcept {
|
||||||
|
return m_base_heap;
|
||||||
|
}
|
||||||
|
|
||||||
|
VAddr AllocateContiguous(u32 num_pages, u32 page_alignment, MemoryOperation op);
|
||||||
|
VAddr AllocateContiguousBackwards(u32 num_pages, MemoryOperation op);
|
||||||
|
void FreeContiguousLocked(u32 addr, u32 num_pages);
|
||||||
|
|
||||||
|
private:
|
||||||
|
KPageHeap m_application_heap;
|
||||||
|
KPageHeap m_system_heap;
|
||||||
|
KPageHeap m_base_heap;
|
||||||
|
KPageManager m_page_manager;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
82
src/core/hle/kernel/k_page_group.cpp
Normal file
82
src/core/hle/kernel/k_page_group.cpp
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_page_group.h"
|
||||||
|
#include "core/hle/kernel/k_page_manager.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
KPageGroup::~KPageGroup() {
|
||||||
|
EraseAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
void KPageGroup::AddRange(u32 addr, u32 num_pages) {
|
||||||
|
// If the provided range is empty there is nothing to do.
|
||||||
|
if (num_pages == 0 || addr + (num_pages << Memory::CITRA_PAGE_BITS) == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// KScopedSchedulerLock lk{m_kernel};
|
||||||
|
|
||||||
|
// Attempt to coaelse with last block if possible.
|
||||||
|
if (!m_blocks.empty()) {
|
||||||
|
KBlockInfo& last = m_blocks.back();
|
||||||
|
if (addr != 0 && addr == last.GetEndAddress()) {
|
||||||
|
last.m_num_pages += num_pages;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocate and initialize the new block.
|
||||||
|
KBlockInfo* new_block = KBlockInfo::Allocate(m_kernel);
|
||||||
|
new_block->Initialize(addr, num_pages);
|
||||||
|
|
||||||
|
// Push the block to the list.
|
||||||
|
m_blocks.push_back(*new_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KPageGroup::IncrefPages() {
|
||||||
|
// Iterate over block list and increment page reference counts.
|
||||||
|
for (const auto& block : m_blocks) {
|
||||||
|
m_page_manager->IncrefPages(block.GetAddress(), block.GetNumPages());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KPageGroup::GetTotalNumPages() {
|
||||||
|
// Iterate over block list and count number of pages.
|
||||||
|
u32 total_num_pages{};
|
||||||
|
for (const auto& block : m_blocks) {
|
||||||
|
total_num_pages = block.GetNumPages();
|
||||||
|
}
|
||||||
|
return total_num_pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KPageGroup::EraseAll() {
|
||||||
|
// Free all blocks referenced in the linked list.
|
||||||
|
auto it = m_blocks.begin();
|
||||||
|
while (it != m_blocks.end()) {
|
||||||
|
KBlockInfo::Free(m_kernel, std::addressof(*it));
|
||||||
|
it = m_blocks.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KPageGroup::IsEquivalentTo(const KPageGroup& rhs) const {
|
||||||
|
auto lit = m_blocks.begin();
|
||||||
|
auto rit = rhs.m_blocks.begin();
|
||||||
|
auto lend = m_blocks.end();
|
||||||
|
auto rend = rhs.m_blocks.end();
|
||||||
|
|
||||||
|
while (lit != lend && rit != rend) {
|
||||||
|
if (*lit != *rit) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
++lit;
|
||||||
|
++rit;
|
||||||
|
}
|
||||||
|
|
||||||
|
return lit == lend && rit == rend;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
102
src/core/hle/kernel/k_page_group.h
Normal file
102
src/core/hle/kernel/k_page_group.h
Normal file
@ -0,0 +1,102 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_linked_list.h"
|
||||||
|
#include "core/hle/kernel/slab_helpers.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
struct KBlockInfo final : public KSlabAllocated<KBlockInfo> {
|
||||||
|
public:
|
||||||
|
explicit KBlockInfo() = default;
|
||||||
|
~KBlockInfo() = default;
|
||||||
|
|
||||||
|
void Initialize(u32 address, u32 num_pages) {
|
||||||
|
m_base_address = address;
|
||||||
|
m_num_pages = num_pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetAddress() const {
|
||||||
|
return m_base_address;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetEndAddress() const {
|
||||||
|
return this->GetAddress() + this->GetSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetSize() const {
|
||||||
|
return m_num_pages << Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetNumPages() const {
|
||||||
|
return m_num_pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool IsEquivalentTo(const KBlockInfo& rhs) const {
|
||||||
|
return m_base_address == rhs.m_base_address && m_num_pages == rhs.m_num_pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator==(const KBlockInfo& rhs) const {
|
||||||
|
return this->IsEquivalentTo(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool operator!=(const KBlockInfo& rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
u32 m_base_address;
|
||||||
|
u32 m_num_pages;
|
||||||
|
};
|
||||||
|
|
||||||
|
class KPageManager;
|
||||||
|
class KernelSystem;
|
||||||
|
|
||||||
|
class KPageGroup {
|
||||||
|
using BlockInfoList = KLinkedList<KBlockInfo>;
|
||||||
|
using iterator = BlockInfoList::const_iterator;
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KPageGroup(KernelSystem& kernel, KPageManager* page_manager)
|
||||||
|
: m_kernel{kernel}, m_page_manager{page_manager}, m_blocks{kernel} {}
|
||||||
|
~KPageGroup();
|
||||||
|
|
||||||
|
iterator begin() const {
|
||||||
|
return this->m_blocks.begin();
|
||||||
|
}
|
||||||
|
iterator end() const {
|
||||||
|
return this->m_blocks.end();
|
||||||
|
}
|
||||||
|
bool empty() const {
|
||||||
|
return this->m_blocks.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AddRange(u32 addr, u32 num_pages);
|
||||||
|
void IncrefPages();
|
||||||
|
|
||||||
|
void EraseAll();
|
||||||
|
void FreeMemory();
|
||||||
|
|
||||||
|
u32 GetTotalNumPages();
|
||||||
|
|
||||||
|
bool IsEquivalentTo(const KPageGroup& rhs) const;
|
||||||
|
|
||||||
|
bool operator==(const KPageGroup& rhs) const {
|
||||||
|
return this->IsEquivalentTo(rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(const KPageGroup& rhs) const {
|
||||||
|
return !(*this == rhs);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
KernelSystem& m_kernel;
|
||||||
|
KPageManager* m_page_manager{};
|
||||||
|
BlockInfoList m_blocks;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
408
src/core/hle/kernel/k_page_heap.cpp
Normal file
408
src/core/hle/kernel/k_page_heap.cpp
Normal file
@ -0,0 +1,408 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_page_heap.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
void KPageHeap::Initialize(VAddr region_start, u32 region_size) {
|
||||||
|
m_region_start = region_start;
|
||||||
|
m_region_size = region_size;
|
||||||
|
|
||||||
|
// Retrieve the first block in the provided region.
|
||||||
|
Block* first_block = m_memory.GetPointer<Block>(m_region_start);
|
||||||
|
ASSERT(first_block);
|
||||||
|
|
||||||
|
// Initialize the block.
|
||||||
|
first_block->num_pages = this->GetNumPages();
|
||||||
|
first_block->current = first_block;
|
||||||
|
|
||||||
|
// Insert the block to our block list.
|
||||||
|
m_blocks.push_front(*first_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 KPageHeap::GetTotalNumPages() {
|
||||||
|
// Iterate over the blocks.
|
||||||
|
u32 total_num_pages{};
|
||||||
|
for (const auto& block : m_blocks) {
|
||||||
|
total_num_pages = block.num_pages;
|
||||||
|
}
|
||||||
|
return total_num_pages;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KPageHeap::FreeBlock(u32 addr, u32 num_pages) {
|
||||||
|
// Return if there are no pages to free.
|
||||||
|
if (num_pages == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return if unable to insert block at the beginning.
|
||||||
|
auto start_block = std::addressof(m_blocks.front());
|
||||||
|
if (this->TryInsert(addr, num_pages, nullptr, start_block)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over the blocks.
|
||||||
|
for (auto it = m_blocks.begin(); it != m_blocks.end();) {
|
||||||
|
// Attempt to insert.
|
||||||
|
Block* block = std::addressof(*it++);
|
||||||
|
Block* next_block = std::addressof(*it++);
|
||||||
|
if (this->TryInsert(addr, num_pages, block, next_block)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void* KPageHeap::AllocateBackwards(u32 size) {
|
||||||
|
// Ensure allocation is possible.
|
||||||
|
if (size == 0) [[unlikely]] {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iterate over block list backwards.
|
||||||
|
u32 remaining = size;
|
||||||
|
for (auto it = m_blocks.rbegin(); it != m_blocks.rend(); it++) {
|
||||||
|
// If block does not cover remaining pages continue.
|
||||||
|
auto block = std::addressof(*it);
|
||||||
|
const u32 num_pages = block->num_pages;
|
||||||
|
if (remaining > num_pages) {
|
||||||
|
remaining -= num_pages;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Split last block at our boundary.
|
||||||
|
const u32 new_block_pages = num_pages - remaining;
|
||||||
|
auto new_block = this->SplitBlock(block, new_block_pages);
|
||||||
|
ASSERT(new_block && new_block->num_pages == new_block_pages);
|
||||||
|
|
||||||
|
// new_block.prev = 0;
|
||||||
|
this->SetLastBlock(block);
|
||||||
|
|
||||||
|
// Return final block which points to our allocated memory.
|
||||||
|
return new_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* KPageHeap::AllocateContiguous(u32 size, u32 page_alignment) {
|
||||||
|
KPageHeapBlock* next; // r6
|
||||||
|
KPageHeapBlock* v13; // r1
|
||||||
|
KPageHeapBlock* prev; // [sp+0h] [bp-30h]
|
||||||
|
KPageHeapBlock* block; // [sp+4h] [bp-2Ch]
|
||||||
|
|
||||||
|
// Ensure allocation is possible.
|
||||||
|
if (m_blocks.empty() || size == 0) [[unlikely]] {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto it = m_blocks.begin(); it != m_blocks.end(); it++) {
|
||||||
|
// Ensure block is valid.
|
||||||
|
auto block = std::addressof(*it);
|
||||||
|
this->ValidateBlock(block);
|
||||||
|
}
|
||||||
|
|
||||||
|
KPageHeapBlock* current_node = m_link.next;
|
||||||
|
while (current_node) {
|
||||||
|
u32 misalignment = 0;
|
||||||
|
KPageHeap::ValidateBlock(current_node);
|
||||||
|
const u32 num_pages = current_node->num_pages;
|
||||||
|
// if (current_node->num_pages > this->GetNumPages() || this->GetRegionEnd() < (unsigned
|
||||||
|
// int)current_node + 4096 * num_pages) {
|
||||||
|
// UNREACHABLE();
|
||||||
|
// }
|
||||||
|
if (page_alignment > 1) {
|
||||||
|
const u32 v11 = ((unsigned int)current_node >> 12) % page_alignment;
|
||||||
|
if (v11) {
|
||||||
|
misalignment = page_alignment - v11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size + misalignment <= num_pages) {
|
||||||
|
block = current_node;
|
||||||
|
if (misalignment) {
|
||||||
|
block = KPageHeap::SplitBlock(current_node, misalignment);
|
||||||
|
}
|
||||||
|
|
||||||
|
KPageHeap::SplitBlock(block, size);
|
||||||
|
KPageHeap::ValidateBlock(block);
|
||||||
|
prev = block->link.prev;
|
||||||
|
next = block->link.next;
|
||||||
|
KPageHeap::ValidateBlock(prev);
|
||||||
|
KPageHeap::ValidateBlock(next);
|
||||||
|
|
||||||
|
if (prev) {
|
||||||
|
prev->link.next = next;
|
||||||
|
v13 = prev;
|
||||||
|
} else {
|
||||||
|
m_link.next = next;
|
||||||
|
if (!next) {
|
||||||
|
m_link.prev = 0;
|
||||||
|
goto LABEL_28;
|
||||||
|
}
|
||||||
|
m_link.next->link.prev = 0;
|
||||||
|
v13 = m_link.next;
|
||||||
|
}
|
||||||
|
KPageHeap::UpdateBlockMac(v13);
|
||||||
|
if (next) {
|
||||||
|
next->link.prev = prev;
|
||||||
|
KPageHeap::UpdateBlockMac(next);
|
||||||
|
LABEL_29:
|
||||||
|
if (block->num_pages != size) {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
LABEL_28:
|
||||||
|
KPageHeap::SetLastBlock(prev);
|
||||||
|
goto LABEL_29;
|
||||||
|
}
|
||||||
|
current_node = current_node->link.next;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (KPageHeapBlock* j = m_link.next; j; j = j->link.next) {
|
||||||
|
KPageHeap::ValidateBlock(j);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void KPageHeap::SetLastBlock(KPageHeapBlock* block) {
|
||||||
|
m_link.prev = block;
|
||||||
|
if (!block) [[unlikely]] {
|
||||||
|
m_link.next = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*u32 v2 = m_key[0];
|
||||||
|
u32 v3 = m_key[1];
|
||||||
|
u32 v4 = m_key[2];
|
||||||
|
u32 v5 = m_key[3];
|
||||||
|
for (int i = 0; i < 2; i++) {
|
||||||
|
int v7 = 0;
|
||||||
|
do {
|
||||||
|
v2 -= *(u32 *)((char *)&block->num_pages + v7) - __ROR4__(v3, 3);
|
||||||
|
v7 += 4;
|
||||||
|
v3 -= __ROR4__(v4, (v5 & 0xF) + 3) ^ __ROR4__(v5, (v2 & 0xF) + 13);
|
||||||
|
v4 -= __ROR4__(v5, v2) * v3;
|
||||||
|
v5 -= __ROR4__(v2, v3) * v4;
|
||||||
|
} while ( v7 < 20 );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((v2 ^ v3) != block->mac) {
|
||||||
|
UNREACHABLE();
|
||||||
|
}*/
|
||||||
|
|
||||||
|
m_link.prev->link.next = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
KPageHeap::Block* KPageHeap::SplitBlock(Block* block, u32 new_block_size) {
|
||||||
|
const u32 num_pages = block->num_pages;
|
||||||
|
ASSERT(block->num_pages <= this->GetNumPages());
|
||||||
|
// if (block->num_pages > this->GetNumPages() || this->GetRegionEnd() < (unsigned int)block +
|
||||||
|
// 4096 * num_pages) {
|
||||||
|
// UNREACHABLE();
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!new_block_size || num_pages == new_block_size) [[unlikely]] {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Block* new_block = (Block*)((char*)block + Memory::CITRA_PAGE_SIZE * new_block_size);
|
||||||
|
Block* next = block->link.next;
|
||||||
|
const u32 v12 = num_pages - new_block_size;
|
||||||
|
new_block->nonce = 0;
|
||||||
|
new_block->num_pages = v12;
|
||||||
|
new_block->mac = 0;
|
||||||
|
new_block->link.next = next;
|
||||||
|
new_block->link.prev = block;
|
||||||
|
new_block->current = new_block;
|
||||||
|
|
||||||
|
if (new_block->num_pages != v12) {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
block->link.next = new_block;
|
||||||
|
block->num_pages = new_block_size;
|
||||||
|
|
||||||
|
if (block->num_pages != new_block_size) {
|
||||||
|
UNREACHABLE();
|
||||||
|
}
|
||||||
|
|
||||||
|
KPageHeapBlock* v13 = new_block->link.next;
|
||||||
|
KPageHeap::ValidateBlock(v13);
|
||||||
|
if (v13) {
|
||||||
|
v13->link.prev = new_block;
|
||||||
|
KPageHeap::UpdateBlockMac(v13);
|
||||||
|
} else {
|
||||||
|
KPageHeap::SetLastBlock(new_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new_block;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool KPageHeap::TryInsert(u32 freedAddr, u32 freedNumPages, Block* prev, Block* next) {
|
||||||
|
KPageHeapBlock* v6; // r5
|
||||||
|
u32 numPages; // r8
|
||||||
|
u32 freedAddrEnd; // r11
|
||||||
|
u32 regionSize; // r0
|
||||||
|
u32 prevRightNeighour; // r10
|
||||||
|
KPageHeapBlock* nxt; // r9
|
||||||
|
u32 regionStart; // r0
|
||||||
|
bool v14; // cc
|
||||||
|
BOOL result; // r0
|
||||||
|
bool v16; // zf
|
||||||
|
u32 v17; // r8
|
||||||
|
u32 v18; // r1
|
||||||
|
u32 v19; // r9
|
||||||
|
KPageHeapBlock* next; // r5
|
||||||
|
u32 v21; // r3
|
||||||
|
u32 v22; // r0
|
||||||
|
u32 v23; // r1
|
||||||
|
u32 v24; // r2
|
||||||
|
int i; // r11
|
||||||
|
int v26; // r12
|
||||||
|
int v27; // r10
|
||||||
|
bool v28; // zf
|
||||||
|
u32 v29; // r4
|
||||||
|
u32 v30; // r1
|
||||||
|
u32 v31; // r8
|
||||||
|
KPageHeapBlock* v32; // r4
|
||||||
|
u32 v33; // [sp+4h] [bp-3Ch]
|
||||||
|
|
||||||
|
v6 = (KPageHeapBlock*)freedAddr;
|
||||||
|
numPages = 0;
|
||||||
|
freedAddrEnd = freedAddr + (freedNumPages << 12);
|
||||||
|
v33 = 0;
|
||||||
|
regionSize = this->regionSize;
|
||||||
|
if (freedNumPages > regionSize >> 12 || regionSize + this->regionStart < freedAddrEnd)
|
||||||
|
kernelpanic();
|
||||||
|
if (prev) {
|
||||||
|
KPageHeap::ValidateBlock(this, prev);
|
||||||
|
numPages = prev->numPages;
|
||||||
|
}
|
||||||
|
if (next) {
|
||||||
|
KPageHeap::ValidateBlock(this, next);
|
||||||
|
v33 = next->numPages;
|
||||||
|
}
|
||||||
|
if (prev) {
|
||||||
|
if ((KPageHeapBlock*)((char*)prev + 4096 * prev->numPages - 1) < prev)
|
||||||
|
kernelpanic();
|
||||||
|
prevRightNeighour = (u32)prev + 4096 * prev->numPages;
|
||||||
|
} else {
|
||||||
|
prevRightNeighour = this->regionStart;
|
||||||
|
}
|
||||||
|
if (next)
|
||||||
|
nxt = next;
|
||||||
|
else
|
||||||
|
nxt = (KPageHeapBlock*)(this->regionStart + this->regionSize);
|
||||||
|
regionStart = this->regionStart;
|
||||||
|
if (regionStart > prevRightNeighour || regionStart + this->regionSize < (unsigned int)nxt)
|
||||||
|
kernelpanic();
|
||||||
|
v14 = prevRightNeighour > (unsigned int)v6;
|
||||||
|
if (prevRightNeighour <= (unsigned int)v6)
|
||||||
|
v14 = freedAddrEnd > (unsigned int)nxt;
|
||||||
|
result = 0;
|
||||||
|
if (!v14) {
|
||||||
|
v6->nonce = 0;
|
||||||
|
v6->link.prev = prev;
|
||||||
|
v6->mac = 0;
|
||||||
|
v6->numPages = freedNumPages;
|
||||||
|
v6->link.next = next;
|
||||||
|
v6->current = v6;
|
||||||
|
KPageHeap::UpdateBlockMac(this, v6);
|
||||||
|
if (v6->numPages != freedNumPages)
|
||||||
|
kernelpanic();
|
||||||
|
if (prev) {
|
||||||
|
prev->link.next = v6;
|
||||||
|
KPageHeap::UpdateBlockMac(this, prev);
|
||||||
|
if (prev->numPages != numPages)
|
||||||
|
kernelpanic();
|
||||||
|
} else {
|
||||||
|
this->link.next = v6;
|
||||||
|
if (v6) {
|
||||||
|
v21 = this->key[2];
|
||||||
|
v22 = this->key[0];
|
||||||
|
v23 = this->key[1];
|
||||||
|
v24 = this->key[3];
|
||||||
|
for (i = 0; i < 2; ++i) {
|
||||||
|
v26 = 0;
|
||||||
|
do {
|
||||||
|
v27 = *(u32*)((char*)&v6->numPages + v26);
|
||||||
|
v26 += 4;
|
||||||
|
v22 -= v27 - __ROR4__(v23, 3);
|
||||||
|
v23 -= __ROR4__(v21, (v24 & 0xF) + 3) ^ __ROR4__(v24, (v22 & 0xF) + 13);
|
||||||
|
v21 -= __ROR4__(v24, v22) * v23;
|
||||||
|
v24 -= __ROR4__(v22, v23) * v21;
|
||||||
|
} while (v26 < 20);
|
||||||
|
}
|
||||||
|
if ((v22 ^ v23) != v6->mac)
|
||||||
|
kernelpanic();
|
||||||
|
this->link.next->link.prev = 0;
|
||||||
|
KPageHeap::UpdateBlockMac(this, this->link.next);
|
||||||
|
} else {
|
||||||
|
this->link.prev = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (next) {
|
||||||
|
next->link.prev = v6;
|
||||||
|
KPageHeap::UpdateBlockMac(this, next);
|
||||||
|
if (next->numPages != v33)
|
||||||
|
kernelpanic();
|
||||||
|
} else {
|
||||||
|
KPageHeap::SetLastBlock(this, v6);
|
||||||
|
}
|
||||||
|
v16 = prev == 0;
|
||||||
|
if (prev)
|
||||||
|
v16 = v6 == 0;
|
||||||
|
if (!v16 && (KPageHeapBlock*)((char*)prev + 4096 * prev->numPages) == v6) {
|
||||||
|
KPageHeap::ValidateBlock(this, prev);
|
||||||
|
v17 = prev->numPages;
|
||||||
|
KPageHeap::ValidateBlock(this, v6);
|
||||||
|
v18 = prev->numPages;
|
||||||
|
v19 = v6->numPages;
|
||||||
|
prev->link.next = v6->link.next;
|
||||||
|
prev->numPages = v18 + v19;
|
||||||
|
KPageHeap::UpdateBlockMac(this, prev);
|
||||||
|
if (prev->numPages != v17 + v19)
|
||||||
|
kernelpanic();
|
||||||
|
next = v6->link.next;
|
||||||
|
KPageHeap::ValidateBlock(this, next);
|
||||||
|
if (next) {
|
||||||
|
next->link.prev = prev;
|
||||||
|
KPageHeap::UpdateBlockMac(this, next);
|
||||||
|
} else {
|
||||||
|
KPageHeap::SetLastBlock(this, prev);
|
||||||
|
}
|
||||||
|
v6 = prev;
|
||||||
|
}
|
||||||
|
v28 = v6 == 0;
|
||||||
|
if (v6)
|
||||||
|
v28 = next == 0;
|
||||||
|
if (!v28 && (KPageHeapBlock*)((char*)v6 + 4096 * v6->numPages) == next) {
|
||||||
|
KPageHeap::ValidateBlock(this, v6);
|
||||||
|
v29 = v6->numPages;
|
||||||
|
KPageHeap::ValidateBlock(this, next);
|
||||||
|
v30 = v6->numPages;
|
||||||
|
v31 = next->numPages;
|
||||||
|
v6->link.next = next->link.next;
|
||||||
|
v6->numPages = v30 + v31;
|
||||||
|
KPageHeap::UpdateBlockMac(this, v6);
|
||||||
|
if (v6->numPages != v29 + v31)
|
||||||
|
kernelpanic();
|
||||||
|
v32 = next->link.next;
|
||||||
|
KPageHeap::ValidateBlock(this, v32);
|
||||||
|
if (v32) {
|
||||||
|
v32->link.prev = v6;
|
||||||
|
KPageHeap::UpdateBlockMac(this, v32);
|
||||||
|
} else {
|
||||||
|
KPageHeap::SetLastBlock(this, v6);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
64
src/core/hle/kernel/k_page_heap.h
Normal file
64
src/core/hle/kernel/k_page_heap.h
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/intrusive_list.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class KPageHeap final {
|
||||||
|
public:
|
||||||
|
explicit KPageHeap(Memory::MemorySystem& memory) : m_memory{memory} {}
|
||||||
|
~KPageHeap() = default;
|
||||||
|
|
||||||
|
constexpr u32 GetNumPages() const {
|
||||||
|
return m_region_size >> Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetRegionStart() const {
|
||||||
|
return m_region_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 GetRegionEnd() const {
|
||||||
|
return m_region_start + m_region_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool Contains(u32 addr) const {
|
||||||
|
return this->GetRegionStart() <= addr && addr < this->GetRegionEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
void Initialize(VAddr region_start, u32 region_size);
|
||||||
|
u32 GetTotalNumPages();
|
||||||
|
|
||||||
|
VAddr AllocateBackwards(u32 size);
|
||||||
|
VAddr AllocateContiguous(u32 size, u32 page_alignment);
|
||||||
|
void FreeBlock(u32 addr, u32 num_pages);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Block final : public Common::IntrusiveListBaseNode<Block> {
|
||||||
|
u32 num_pages;
|
||||||
|
Block* current;
|
||||||
|
u32 nonce;
|
||||||
|
u32 mac;
|
||||||
|
};
|
||||||
|
|
||||||
|
using BlockList = Common::IntrusiveListBaseTraits<Block>::ListType;
|
||||||
|
using iterator = BlockList::iterator;
|
||||||
|
|
||||||
|
Block* SplitBlock(Block* block, u32 new_block_size);
|
||||||
|
bool TryInsert(u32 freed_addr, u32 num_freed_pages, Block* prev_block, Block* next_block);
|
||||||
|
void SetLastBlock(Block* block);
|
||||||
|
|
||||||
|
private:
|
||||||
|
BlockList m_blocks{};
|
||||||
|
Memory::MemorySystem& m_memory;
|
||||||
|
u32 m_region_start{};
|
||||||
|
u32 m_region_size{};
|
||||||
|
std::array<u32, 4> m_key{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
107
src/core/hle/kernel/k_page_manager.cpp
Normal file
107
src/core/hle/kernel/k_page_manager.cpp
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_memory_manager.h"
|
||||||
|
#include "core/hle/kernel/k_page_manager.h"
|
||||||
|
#include "core/memory.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
void KPageManager::Initialize(u32 start_addr, u32 num_pages) {
|
||||||
|
// Initialize page manager address range.
|
||||||
|
m_start_addr = start_addr;
|
||||||
|
m_num_pages = num_pages;
|
||||||
|
|
||||||
|
// Compute the number of pages to allocate from the base heap.
|
||||||
|
const u32 num_ref_counts_pages = ((sizeof(u32) * num_pages - 1) >> Memory::CITRA_PAGE_BITS) + 1;
|
||||||
|
auto& base_heap = m_memory_manager->GetBaseHeap();
|
||||||
|
|
||||||
|
// Allocate page refcounting memory.
|
||||||
|
u32 ref_counts_addr{};
|
||||||
|
{
|
||||||
|
// KLightScopedMutex m{m_mutex};
|
||||||
|
m_kernel_memory_usage += num_ref_counts_pages << Memory::CITRA_PAGE_BITS;
|
||||||
|
ref_counts_addr = base_heap.AllocateContiguous(num_ref_counts_pages, 0);
|
||||||
|
m_page_ref_counts = m_memory.GetPointer<u32>(ref_counts_addr);
|
||||||
|
ASSERT(m_page_ref_counts);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zero-initialize reference counts.
|
||||||
|
if (num_pages) {
|
||||||
|
std::memset(m_page_ref_counts, 0, num_ref_counts_pages << Memory::CITRA_PAGE_BITS);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Track allocated pages.
|
||||||
|
this->IncrefPages(ref_counts_addr, num_ref_counts_pages);
|
||||||
|
}
|
||||||
|
|
||||||
|
void KPageManager::IncrefPages(u32 addr, u32 num_pages) {
|
||||||
|
// KLightScopedMutex m{m_mutex};
|
||||||
|
|
||||||
|
// Increment page reference counts.
|
||||||
|
const u32 page_start = (addr - m_start_addr) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
const u32 page_end = num_pages + page_start;
|
||||||
|
for (u32 page = page_start; page < page_end; page++) {
|
||||||
|
m_page_ref_counts[page_start]++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void KPageManager::FreeContiguous(u32 addr, u32 num_pages, MemoryOperation op) {
|
||||||
|
// Ensure the provided address is in range.
|
||||||
|
const u32 page_start = (addr - m_start_addr) >> Memory::CITRA_PAGE_BITS;
|
||||||
|
const u32 page_end = page_start + num_pages;
|
||||||
|
if (page_start >= page_end) [[unlikely]] {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Retrieve page heaps from the memory manager.
|
||||||
|
auto& application_heap = m_memory_manager->GetApplicationHeap();
|
||||||
|
auto& base_heap = m_memory_manager->GetBaseHeap();
|
||||||
|
auto& system_heap = m_memory_manager->GetSystemHeap();
|
||||||
|
|
||||||
|
// Frees the range of pages provided from the appropriate heap.
|
||||||
|
const auto FreePages = [&](u32 start_page, u32 num_pages) {
|
||||||
|
const u32 current_addr = m_start_addr + (start_page << Memory::CITRA_PAGE_BITS);
|
||||||
|
if (base_heap.Contains(current_addr)) {
|
||||||
|
base_heap.FreeBlock(current_addr, num_pages);
|
||||||
|
} else if (system_heap.Contains(current_addr)) {
|
||||||
|
system_heap.FreeBlock(current_addr, num_pages);
|
||||||
|
} else {
|
||||||
|
application_heap.FreeBlock(current_addr, num_pages);
|
||||||
|
}
|
||||||
|
// Update kernel memory usage if requested.
|
||||||
|
if (True(op & MemoryOperation::Kernel)) {
|
||||||
|
m_kernel_memory_usage -= num_pages << Memory::CITRA_PAGE_BITS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Iterate over the range of pages to free.
|
||||||
|
u32 start_free_page = 0;
|
||||||
|
u32 num_pages_to_free = 0;
|
||||||
|
for (u32 page = page_start; page < page_end; page++) {
|
||||||
|
const u32 new_count = --m_page_ref_counts[page];
|
||||||
|
if (new_count) {
|
||||||
|
// Nothing to free, continue to next page.
|
||||||
|
if (num_pages_to_free <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
// Free accumulated pages and reset.
|
||||||
|
FreePages(start_free_page, num_pages_to_free);
|
||||||
|
num_pages_to_free = 0;
|
||||||
|
} else if (num_pages_to_free <= 0) {
|
||||||
|
start_free_page = page;
|
||||||
|
num_pages_to_free = 1;
|
||||||
|
} else {
|
||||||
|
// Advance number of pages to free.
|
||||||
|
num_pages_to_free++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Free any remaining pages.
|
||||||
|
if (num_pages_to_free > 0) {
|
||||||
|
FreePages(start_free_page, num_pages_to_free);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Kernel
|
63
src/core/hle/kernel/k_page_manager.h
Normal file
63
src/core/hle/kernel/k_page_manager.h
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Memory {
|
||||||
|
class MemorySystem;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
enum class MemoryOperation : u32 {
|
||||||
|
None = 0x0,
|
||||||
|
RegionApplication = 0x100,
|
||||||
|
RegionSystem = 0x200,
|
||||||
|
RegionBase = 0x300,
|
||||||
|
Kernel = 0x80000000,
|
||||||
|
RegionBaseKernel = Kernel | RegionBase,
|
||||||
|
Free = 0x1,
|
||||||
|
Reserve = 0x2,
|
||||||
|
Alloc = 0x3,
|
||||||
|
Map = 0x4,
|
||||||
|
Unmap = 0x5,
|
||||||
|
Prot = 0x6,
|
||||||
|
OpMask = 0xFF,
|
||||||
|
RegionMask = 0xF00,
|
||||||
|
LinearFlag = 0x10000,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(MemoryOperation)
|
||||||
|
|
||||||
|
class KMemoryManager;
|
||||||
|
|
||||||
|
class KPageManager {
|
||||||
|
public:
|
||||||
|
explicit KPageManager(Memory::MemorySystem& memory, KMemoryManager* memory_manager)
|
||||||
|
: m_memory{memory}, m_memory_manager{memory_manager} {}
|
||||||
|
~KPageManager() = default;
|
||||||
|
|
||||||
|
std::atomic<u32>& GetKernelMemoryUsage() noexcept {
|
||||||
|
return m_kernel_memory_usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize(u32 start_addr, u32 num_pages);
|
||||||
|
void IncrefPages(u32 addr, u32 num_pages);
|
||||||
|
void FreeContiguous(u32 data, u32 num_pages, MemoryOperation op);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Memory::MemorySystem& m_memory;
|
||||||
|
KMemoryManager* m_memory_manager{};
|
||||||
|
u32 m_start_addr{};
|
||||||
|
u32 m_num_pages{};
|
||||||
|
u32* m_page_ref_counts{};
|
||||||
|
std::atomic<u32> m_kernel_memory_usage{};
|
||||||
|
// KLightMutex m_mutex;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
1511
src/core/hle/kernel/k_page_table.cpp
Normal file
1511
src/core/hle/kernel/k_page_table.cpp
Normal file
File diff suppressed because it is too large
Load Diff
134
src/core/hle/kernel/k_page_table.h
Normal file
134
src/core/hle/kernel/k_page_table.h
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "core/hle/kernel/k_memory_block_manager.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
|
namespace Common {
|
||||||
|
class PageTable;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
enum class KMemoryUpdateFlags {
|
||||||
|
None = 0x0,
|
||||||
|
State = 0x1,
|
||||||
|
Perms = 0x100,
|
||||||
|
StateAndPerms = State | Perms,
|
||||||
|
};
|
||||||
|
DECLARE_ENUM_FLAG_OPERATORS(KMemoryUpdateFlags)
|
||||||
|
|
||||||
|
enum class MemoryOperation : u32;
|
||||||
|
|
||||||
|
class KPageGroup;
|
||||||
|
class KPageManager;
|
||||||
|
|
||||||
|
class KPageTable {
|
||||||
|
public:
|
||||||
|
explicit KPageTable(KernelSystem& kernel, KPageManager* page_manager)
|
||||||
|
: m_kernel{kernel}, m_page_manager{page_manager}, m_memory_block_manager{kernel} {}
|
||||||
|
~KPageTable() = default;
|
||||||
|
|
||||||
|
Common::PageTable& GetImpl() {
|
||||||
|
return *m_impl;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InitizalizeL1Table(u32** outL1TablePtr, u32* L1Table);
|
||||||
|
|
||||||
|
ResultCode CheckAndUpdateAddrRangeMaskedStateAndPerms(
|
||||||
|
u32 addr, u32 num_pages, KMemoryState state_mask, KMemoryState expected_state,
|
||||||
|
KMemoryPermission min_perms, KMemoryState new_state, KMemoryPermission new_perms);
|
||||||
|
ResultCode CheckAddressRangeSizeAndState(u32 addr, u32 size, KMemoryState state);
|
||||||
|
ResultCode CheckAddressRangeSizeAndStateFlags(u32 addr, u32 size, KMemoryState stateMask,
|
||||||
|
KMemoryState expectedStateFlags);
|
||||||
|
ResultCode CheckMemoryBlockAttributes(u32 addr, u32 size, KMemoryState state,
|
||||||
|
KMemoryPermission perms);
|
||||||
|
ResultCode CheckAddrRangeMaskedStateAndPerms(u32 addr, u32 size, KMemoryState stateMask,
|
||||||
|
KMemoryState expectedState,
|
||||||
|
KMemoryPermission minPerms);
|
||||||
|
ResultCode CheckAndChangeGroupStateAndPerms(u32 addr, KPageGroup* pgGroup,
|
||||||
|
KMemoryState stateMask, KMemoryState expectedState,
|
||||||
|
KMemoryPermission minPerms, KMemoryState newState,
|
||||||
|
KMemoryPermission newPerms);
|
||||||
|
|
||||||
|
ResultCode MapL2Entries(u32 va, u32 pa, u32 numPages_reused, u32* attribsPtr, bool isLarge);
|
||||||
|
ResultCode MapL1Entries(u32 va, u32 pa, u32 numPages, u32* attribsPtr, bool isLarge);
|
||||||
|
ResultCode MapContiguousPhysicalAddressRange(u32 va, u32 pa, u32 numPages, u32* mmuAttribs);
|
||||||
|
ResultCode MergeContiguousEntries(u32 va);
|
||||||
|
ResultCode MapNewlyAllocatedPhysicalAddressRange(u32 va, u32 pa, u32 numPages, u32* mmuAttrbis);
|
||||||
|
|
||||||
|
ResultCode RemapMemoryInterprocess(KPageTable* dstPgTbl, KPageTable* srcPgTbl, u32 dstAddr,
|
||||||
|
u32 srcAddr, u32 numPages, KMemoryState dstMemState,
|
||||||
|
KMemoryPermission dstMemPerms);
|
||||||
|
|
||||||
|
ResultCode ChangePageAttributes(u32 addr, u32 size, u32* mmuAttribs);
|
||||||
|
ResultCode CheckAndUnmapPageGroup(u32 addr, KPageGroup* pgGroup);
|
||||||
|
|
||||||
|
ResultCode CreateAlias(u32 srcAddr, u32 dstAddr, u32 numPages, KMemoryState expectedStateSrc,
|
||||||
|
KMemoryPermission expectedMinPermsSrc, KMemoryState newStateSrc,
|
||||||
|
KMemoryPermission newPermsSrc, KMemoryState newStateDst,
|
||||||
|
KMemoryPermission newPermsDst);
|
||||||
|
ResultCode DestroyAlias(u32 srcAddr, u32 dstAddr, u32 numPages, KMemoryState expectedStateSrc,
|
||||||
|
KMemoryPermission expectedMinPermsSrc, KMemoryState expectedStateDst,
|
||||||
|
KMemoryPermission expectedMinPermsDst, KMemoryState newStateSrc,
|
||||||
|
KMemoryPermission newPermsSrc);
|
||||||
|
|
||||||
|
void Unmap(u32 addr, u32 numPages);
|
||||||
|
void UnmapEntries(u32 currentVa, u32 numPages, KPageGroup* outPgGroupUnmapped);
|
||||||
|
|
||||||
|
ResultCode OperateOnGroup(u32 addr, KPageGroup* pgGroup, KMemoryState state,
|
||||||
|
KMemoryPermission perms, KMemoryUpdateFlags updateFlags);
|
||||||
|
ResultCode OperateOnAnyFreeBlockInRegionWithGuardPage(u32* outAddr, u32 blockNumPages,
|
||||||
|
u32 regionStart, u32 regionNumPages,
|
||||||
|
u32 pa, KMemoryState state,
|
||||||
|
KMemoryPermission perms,
|
||||||
|
KMemoryUpdateFlags updateFlags,
|
||||||
|
MemoryOperation region);
|
||||||
|
ResultCode Operate(u32 va, u32 numPages, u32 pa, KMemoryState state, KMemoryPermission perms,
|
||||||
|
KMemoryUpdateFlags updateFlags, MemoryOperation region);
|
||||||
|
|
||||||
|
ResultCode MakePageGroup(KPageGroup& pg, VAddr addr, u32 num_pages);
|
||||||
|
ResultCode QueryInfo(KMemoryInfo* outMemoryInfo, u32* pageInfo, u32 addr);
|
||||||
|
ResultCode CopyMemoryInterprocessForIpc(u32 dstAddr, KPageTable* srcPgTbl, u32 srcAddr,
|
||||||
|
u32 size);
|
||||||
|
ResultCode SplitContiguousEntries(u32 va, u32 size);
|
||||||
|
|
||||||
|
u32 ConvertVaToPa(u32** L1TablePtr, u32 va);
|
||||||
|
|
||||||
|
void InvalidateAllTlbEntries();
|
||||||
|
void InvalidateEntireInstructionCache();
|
||||||
|
void InvalidateEntireInstructionCacheLocal();
|
||||||
|
void InvalidateTlbEntryByMva(u32 addr);
|
||||||
|
void InvalidateDataCacheRange(u32 addr, u32 size);
|
||||||
|
void InvalidateDataCacheRangeLocal(u32 addr, u32 size);
|
||||||
|
|
||||||
|
void CleanInvalidateEntireDataCacheLocal();
|
||||||
|
void CleanInvalidateDataCacheRangeLocal(u32 addr, u32 size);
|
||||||
|
void CleanInvalidateDataCacheRange(u32 addr, u32 size);
|
||||||
|
void CleanInvalidateInstructionCacheRange(u32 addr, u32 size);
|
||||||
|
void CleanInvalidateEntireDataCache();
|
||||||
|
void CleanDataCacheRange(u32 addr, u32 size);
|
||||||
|
|
||||||
|
private:
|
||||||
|
KernelSystem& m_kernel;
|
||||||
|
KPageManager* m_page_manager;
|
||||||
|
// KLightMutex mutex;
|
||||||
|
std::unique_ptr<Common::PageTable> m_impl{};
|
||||||
|
std::array<bool, 4> m_tlb_needs_invalidating{};
|
||||||
|
KMemoryBlockManager m_memory_block_manager;
|
||||||
|
u32 m_translation_table_base{};
|
||||||
|
u8 m_asid{};
|
||||||
|
bool m_is_kernel{};
|
||||||
|
bool m_use_small_pages{};
|
||||||
|
u32 m_address_space_start{};
|
||||||
|
u32 m_address_space_end{};
|
||||||
|
u32 m_linear_address_range_start{};
|
||||||
|
u32 m_translation_table_size{};
|
||||||
|
u32* m_l1_table{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
191
src/core/hle/kernel/k_slab_heap.h
Normal file
191
src/core/hle/kernel/k_slab_heap.h
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/atomic_ops.h"
|
||||||
|
#include "common/common_funcs.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
class KernelSystem;
|
||||||
|
|
||||||
|
namespace impl {
|
||||||
|
|
||||||
|
class KSlabHeapImpl {
|
||||||
|
CITRA_NON_COPYABLE(KSlabHeapImpl);
|
||||||
|
CITRA_NON_MOVEABLE(KSlabHeapImpl);
|
||||||
|
|
||||||
|
public:
|
||||||
|
struct Node {
|
||||||
|
Node* next{};
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr KSlabHeapImpl() = default;
|
||||||
|
|
||||||
|
void Initialize() {
|
||||||
|
ASSERT(m_head == nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
Node* GetHead() const {
|
||||||
|
return m_head;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Allocate() {
|
||||||
|
Node* ret = m_head;
|
||||||
|
if (ret != nullptr) [[likely]] {
|
||||||
|
m_head = ret->next;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Free(void* obj) {
|
||||||
|
Node* node = static_cast<Node*>(obj);
|
||||||
|
node->next = m_head;
|
||||||
|
m_head = node;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::atomic<Node*> m_head{};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace impl
|
||||||
|
|
||||||
|
class KSlabHeapBase : protected impl::KSlabHeapImpl {
|
||||||
|
CITRA_NON_COPYABLE(KSlabHeapBase);
|
||||||
|
CITRA_NON_MOVEABLE(KSlabHeapBase);
|
||||||
|
|
||||||
|
private:
|
||||||
|
size_t m_obj_size{};
|
||||||
|
uintptr_t m_peak{};
|
||||||
|
uintptr_t m_start{};
|
||||||
|
uintptr_t m_end{};
|
||||||
|
|
||||||
|
private:
|
||||||
|
void UpdatePeakImpl(uintptr_t obj) {
|
||||||
|
const uintptr_t alloc_peak = obj + this->GetObjectSize();
|
||||||
|
uintptr_t cur_peak = m_peak;
|
||||||
|
do {
|
||||||
|
if (alloc_peak <= cur_peak) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} while (
|
||||||
|
!Common::AtomicCompareAndSwap(std::addressof(m_peak), alloc_peak, cur_peak, cur_peak));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr KSlabHeapBase() = default;
|
||||||
|
|
||||||
|
bool Contains(uintptr_t address) const {
|
||||||
|
return m_start <= address && address < m_end;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Initialize(size_t obj_size, void* memory, size_t memory_size) {
|
||||||
|
// Ensure we don't initialize a slab using null memory.
|
||||||
|
ASSERT(memory != nullptr);
|
||||||
|
|
||||||
|
// Set our object size.
|
||||||
|
m_obj_size = obj_size;
|
||||||
|
|
||||||
|
// Initialize the base allocator.
|
||||||
|
KSlabHeapImpl::Initialize();
|
||||||
|
|
||||||
|
// Set our tracking variables.
|
||||||
|
const size_t num_obj = (memory_size / obj_size);
|
||||||
|
m_start = reinterpret_cast<uintptr_t>(memory);
|
||||||
|
m_end = m_start + num_obj * obj_size;
|
||||||
|
m_peak = m_start;
|
||||||
|
|
||||||
|
// Free the objects.
|
||||||
|
u8* cur = reinterpret_cast<u8*>(m_end);
|
||||||
|
|
||||||
|
for (size_t i = 0; i < num_obj; i++) {
|
||||||
|
cur -= obj_size;
|
||||||
|
KSlabHeapImpl::Free(cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetSlabHeapSize() const {
|
||||||
|
return (m_end - m_start) / this->GetObjectSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetObjectSize() const {
|
||||||
|
return m_obj_size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* Allocate() {
|
||||||
|
void* obj = KSlabHeapImpl::Allocate();
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Free(void* obj) {
|
||||||
|
// Don't allow freeing an object that wasn't allocated from this heap.
|
||||||
|
const bool contained = this->Contains(reinterpret_cast<uintptr_t>(obj));
|
||||||
|
ASSERT(contained);
|
||||||
|
KSlabHeapImpl::Free(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetObjectIndex(const void* obj) const {
|
||||||
|
return (reinterpret_cast<uintptr_t>(obj) - m_start) / this->GetObjectSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetPeakIndex() const {
|
||||||
|
return this->GetObjectIndex(reinterpret_cast<const void*>(m_peak));
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t GetSlabHeapAddress() const {
|
||||||
|
return m_start;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetNumRemaining() const {
|
||||||
|
// Only calculate the number of remaining objects under debug configuration.
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class KSlabHeap final : public KSlabHeapBase {
|
||||||
|
private:
|
||||||
|
using BaseHeap = KSlabHeapBase;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr KSlabHeap() = default;
|
||||||
|
|
||||||
|
void Initialize(void* memory, size_t memory_size) {
|
||||||
|
BaseHeap::Initialize(sizeof(T), memory, memory_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
T* Allocate() {
|
||||||
|
T* obj = static_cast<T*>(BaseHeap::Allocate());
|
||||||
|
|
||||||
|
if (obj != nullptr) [[likely]] {
|
||||||
|
std::construct_at(obj);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
T* Allocate(KernelSystem& kernel) {
|
||||||
|
T* obj = static_cast<T*>(BaseHeap::Allocate());
|
||||||
|
|
||||||
|
if (obj != nullptr) [[likely]] {
|
||||||
|
std::construct_at(obj, kernel);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Free(T* obj) {
|
||||||
|
BaseHeap::Free(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetObjectIndex(const T* obj) const {
|
||||||
|
return BaseHeap::GetObjectIndex(obj);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
@ -8,9 +8,11 @@
|
|||||||
#include "common/archives.h"
|
#include "common/archives.h"
|
||||||
#include "common/serialization/atomic.h"
|
#include "common/serialization/atomic.h"
|
||||||
#include "core/hle/kernel/client_port.h"
|
#include "core/hle/kernel/client_port.h"
|
||||||
#include "core/hle/kernel/config_mem.h"
|
|
||||||
#include "core/hle/kernel/handle_table.h"
|
|
||||||
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
#include "core/hle/kernel/ipc_debugger/recorder.h"
|
||||||
|
#include "core/hle/kernel/k_linked_list.h"
|
||||||
|
#include "core/hle/kernel/k_memory_block.h"
|
||||||
|
#include "core/hle/kernel/k_page_group.h"
|
||||||
|
#include "core/hle/kernel/k_slab_heap.h"
|
||||||
#include "core/hle/kernel/kernel.h"
|
#include "core/hle/kernel/kernel.h"
|
||||||
#include "core/hle/kernel/memory.h"
|
#include "core/hle/kernel/memory.h"
|
||||||
#include "core/hle/kernel/process.h"
|
#include "core/hle/kernel/process.h"
|
||||||
@ -29,6 +31,7 @@ KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
|||||||
: memory(memory), timing(timing),
|
: memory(memory), timing(timing),
|
||||||
prepare_reschedule_callback(std::move(prepare_reschedule_callback)), memory_mode(memory_mode),
|
prepare_reschedule_callback(std::move(prepare_reschedule_callback)), memory_mode(memory_mode),
|
||||||
n3ds_hw_caps(n3ds_hw_caps) {
|
n3ds_hw_caps(n3ds_hw_caps) {
|
||||||
|
slab_heap_container = std::make_unique<SlabHeapContainer>();
|
||||||
std::generate(memory_regions.begin(), memory_regions.end(),
|
std::generate(memory_regions.begin(), memory_regions.end(),
|
||||||
[] { return std::make_shared<MemoryRegionInfo>(); });
|
[] { return std::make_shared<MemoryRegionInfo>(); });
|
||||||
MemoryInit(memory_mode, n3ds_hw_caps.memory_mode, override_init_time);
|
MemoryInit(memory_mode, n3ds_hw_caps.memory_mode, override_init_time);
|
||||||
@ -192,6 +195,27 @@ void KernelSystem::serialize(Archive& ar, const unsigned int file_version) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct KernelSystem::SlabHeapContainer {
|
||||||
|
KSlabHeap<KLinkedListNode> linked_list_node;
|
||||||
|
KSlabHeap<KBlockInfo> block_info;
|
||||||
|
KSlabHeap<KMemoryBlock> memory_block;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
KSlabHeap<T>& KernelSystem::SlabHeap() {
|
||||||
|
if constexpr (std::is_same_v<T, KLinkedListNode>) {
|
||||||
|
return slab_heap_container->linked_list_node;
|
||||||
|
} else if constexpr (std::is_same_v<T, KBlockInfo>) {
|
||||||
|
return slab_heap_container->block_info;
|
||||||
|
} else if constexpr (std::is_same_v<T, KMemoryBlock>) {
|
||||||
|
return slab_heap_container->memory_block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template KSlabHeap<KLinkedListNode>& KernelSystem::SlabHeap();
|
||||||
|
template KSlabHeap<KBlockInfo>& KernelSystem::SlabHeap();
|
||||||
|
template KSlabHeap<KMemoryBlock>& KernelSystem::SlabHeap();
|
||||||
|
|
||||||
SERIALIZE_IMPL(KernelSystem)
|
SERIALIZE_IMPL(KernelSystem)
|
||||||
|
|
||||||
} // namespace Kernel
|
} // namespace Kernel
|
||||||
|
@ -130,6 +130,9 @@ private:
|
|||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class KSlabHeap;
|
||||||
|
|
||||||
class KernelSystem {
|
class KernelSystem {
|
||||||
public:
|
public:
|
||||||
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
||||||
@ -260,6 +263,10 @@ public:
|
|||||||
MemoryPermission other_permissions,
|
MemoryPermission other_permissions,
|
||||||
std::string name = "Unknown Applet");
|
std::string name = "Unknown Applet");
|
||||||
|
|
||||||
|
/// Gets the slab heap for the specified kernel object type.
|
||||||
|
template <typename T>
|
||||||
|
KSlabHeap<T>& SlabHeap();
|
||||||
|
|
||||||
u32 GenerateObjectID();
|
u32 GenerateObjectID();
|
||||||
|
|
||||||
/// Retrieves a process from the current list of processes.
|
/// Retrieves a process from the current list of processes.
|
||||||
@ -369,6 +376,10 @@ private:
|
|||||||
MemoryMode memory_mode;
|
MemoryMode memory_mode;
|
||||||
New3dsHwCapabilities n3ds_hw_caps;
|
New3dsHwCapabilities n3ds_hw_caps;
|
||||||
|
|
||||||
|
/// Helper to encapsulate all slab heaps in a single heap allocated container
|
||||||
|
struct SlabHeapContainer;
|
||||||
|
std::unique_ptr<SlabHeapContainer> slab_heap_container;
|
||||||
|
|
||||||
friend class boost::serialization::access;
|
friend class boost::serialization::access;
|
||||||
template <class Archive>
|
template <class Archive>
|
||||||
void serialize(Archive& ar, const unsigned int file_version);
|
void serialize(Archive& ar, const unsigned int file_version);
|
||||||
|
130
src/core/hle/kernel/slab_helpers.h
Normal file
130
src/core/hle/kernel/slab_helpers.h
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
// Copyright 2023 Citra Emulator Project
|
||||||
|
// Licensed under GPLv2 or any later version
|
||||||
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "core/hle/kernel/k_auto_object.h"
|
||||||
|
#include "core/hle/kernel/kernel.h"
|
||||||
|
|
||||||
|
namespace Kernel {
|
||||||
|
|
||||||
|
template <class Derived>
|
||||||
|
class KSlabAllocated {
|
||||||
|
public:
|
||||||
|
constexpr KSlabAllocated() = default;
|
||||||
|
|
||||||
|
size_t GetSlabIndex(KernelSystem& kernel) const {
|
||||||
|
return kernel.SlabHeap<Derived>().GetIndex(static_cast<const Derived*>(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void InitializeSlabHeap(KernelSystem& kernel, void* memory, size_t memory_size) {
|
||||||
|
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Derived* Allocate(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().Allocate(kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Free(KernelSystem& kernel, Derived* obj) {
|
||||||
|
kernel.SlabHeap<Derived>().Free(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetObjectSize(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetObjectSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetSlabHeapSize(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetSlabHeapSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetPeakIndex(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetPeakIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uintptr_t GetSlabHeapAddress(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetSlabHeapAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetNumRemaining(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetNumRemaining();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Derived, typename Base>
|
||||||
|
class KAutoObjectWithSlabHeap : public Base {
|
||||||
|
static_assert(std::is_base_of<KAutoObject, Base>::value);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static Derived* Allocate(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().Allocate(kernel);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Free(KernelSystem& kernel, Derived* obj) {
|
||||||
|
kernel.SlabHeap<Derived>().Free(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit KAutoObjectWithSlabHeap(KernelSystem& kernel) : Base(kernel) {}
|
||||||
|
virtual ~KAutoObjectWithSlabHeap() = default;
|
||||||
|
|
||||||
|
virtual void Destroy() override {
|
||||||
|
const bool is_initialized = this->IsInitialized();
|
||||||
|
uintptr_t arg = 0;
|
||||||
|
if (is_initialized) {
|
||||||
|
arg = this->GetPostDestroyArgument();
|
||||||
|
this->Finalize();
|
||||||
|
}
|
||||||
|
Free(Base::m_kernel, static_cast<Derived*>(this));
|
||||||
|
if (is_initialized) {
|
||||||
|
Derived::PostDestroy(arg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
virtual bool IsInitialized() const {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
virtual uintptr_t GetPostDestroyArgument() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t GetSlabIndex() const {
|
||||||
|
return SlabHeap<Derived>(Base::m_kernel).GetObjectIndex(static_cast<const Derived*>(this));
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
static void InitializeSlabHeap(KernelSystem& kernel, void* memory, size_t memory_size) {
|
||||||
|
kernel.SlabHeap<Derived>().Initialize(memory, memory_size);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Derived* Create(KernelSystem& kernel) {
|
||||||
|
Derived* obj = Allocate(kernel);
|
||||||
|
if (obj != nullptr) {
|
||||||
|
KAutoObject::Create(obj);
|
||||||
|
}
|
||||||
|
return obj;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetObjectSize(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetObjectSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetSlabHeapSize(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetSlabHeapSize();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetPeakIndex(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetPeakIndex();
|
||||||
|
}
|
||||||
|
|
||||||
|
static uintptr_t GetSlabHeapAddress(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetSlabHeapAddress();
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t GetNumRemaining(KernelSystem& kernel) {
|
||||||
|
return kernel.SlabHeap<Derived>().GetNumRemaining();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Kernel
|
@ -408,3 +408,130 @@ private:
|
|||||||
auto CONCAT2(check_result_L, __LINE__) = source; \
|
auto CONCAT2(check_result_L, __LINE__) = source; \
|
||||||
if (CONCAT2(check_result_L, __LINE__).IsError()) \
|
if (CONCAT2(check_result_L, __LINE__).IsError()) \
|
||||||
return CONCAT2(check_result_L, __LINE__);
|
return CONCAT2(check_result_L, __LINE__);
|
||||||
|
|
||||||
|
#define R_SUCCEEDED(res) (static_cast<ResultCode>(res).IsSuccess())
|
||||||
|
#define R_FAILED(res) (!static_cast<ResultCode>(res).IsSuccess())
|
||||||
|
|
||||||
|
namespace ResultImpl {
|
||||||
|
template <auto EvaluateResult, class F>
|
||||||
|
class ScopedResultGuard {
|
||||||
|
private:
|
||||||
|
ResultCode& m_ref;
|
||||||
|
F m_f;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr ScopedResultGuard(ResultCode& ref, F f) : m_ref(ref), m_f(std::move(f)) {}
|
||||||
|
constexpr ~ScopedResultGuard() {
|
||||||
|
if (EvaluateResult(m_ref)) {
|
||||||
|
m_f();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <auto EvaluateResult>
|
||||||
|
class ResultReferenceForScopedResultGuard {
|
||||||
|
private:
|
||||||
|
ResultCode& m_ref;
|
||||||
|
|
||||||
|
public:
|
||||||
|
constexpr ResultReferenceForScopedResultGuard(ResultCode& r) : m_ref(r) {}
|
||||||
|
constexpr operator ResultCode&() const {
|
||||||
|
return m_ref;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
template <auto EvaluateResult, typename F>
|
||||||
|
constexpr ScopedResultGuard<EvaluateResult, F> operator+(
|
||||||
|
ResultReferenceForScopedResultGuard<EvaluateResult> ref, F&& f) {
|
||||||
|
return ScopedResultGuard<EvaluateResult, F>(static_cast<ResultCode&>(ref), std::forward<F>(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr bool EvaluateResultSuccess(const ResultCode& r) {
|
||||||
|
return R_SUCCEEDED(r);
|
||||||
|
}
|
||||||
|
constexpr bool EvaluateResultFailure(const ResultCode& r) {
|
||||||
|
return R_FAILED(r);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
constexpr void UpdateCurrentResultReference(T result_reference, ResultCode result) = delete;
|
||||||
|
// Intentionally not defined
|
||||||
|
|
||||||
|
template <>
|
||||||
|
constexpr void UpdateCurrentResultReference<ResultCode&>(ResultCode& result_reference,
|
||||||
|
ResultCode result) {
|
||||||
|
result_reference = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
constexpr void UpdateCurrentResultReference<const ResultCode>(ResultCode result_reference,
|
||||||
|
ResultCode result) {}
|
||||||
|
} // namespace ResultImpl
|
||||||
|
|
||||||
|
#define DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(COUNTER_VALUE) \
|
||||||
|
[[maybe_unused]] constexpr bool CONCAT2(HasPrevRef_, COUNTER_VALUE) = \
|
||||||
|
std::same_as<decltype(__TmpCurrentResultReference), ResultCode&>; \
|
||||||
|
[[maybe_unused]] Result CONCAT2(PrevRef_, COUNTER_VALUE) = __TmpCurrentResultReference; \
|
||||||
|
[[maybe_unused]] Result CONCAT2(__tmp_result_, COUNTER_VALUE) = ResultSuccess; \
|
||||||
|
Result& __TmpCurrentResultReference = CONCAT2(HasPrevRef_, COUNTER_VALUE) \
|
||||||
|
? CONCAT2(PrevRef_, COUNTER_VALUE) \
|
||||||
|
: CONCAT2(__tmp_result_, COUNTER_VALUE)
|
||||||
|
|
||||||
|
#define ON_RESULT_RETURN_IMPL(...) \
|
||||||
|
static_assert(std::same_as<decltype(__TmpCurrentResultReference), ResultCode&>); \
|
||||||
|
auto CONCAT2(RESULT_GUARD_STATE_, __COUNTER__) = \
|
||||||
|
ResultImpl::ResultReferenceForScopedResultGuard<__VA_ARGS__>( \
|
||||||
|
__TmpCurrentResultReference) + \
|
||||||
|
[&]()
|
||||||
|
|
||||||
|
#define ON_RESULT_FAILURE_2 ON_RESULT_RETURN_IMPL(ResultImpl::EvaluateResultFailure)
|
||||||
|
|
||||||
|
#define ON_RESULT_FAILURE \
|
||||||
|
DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \
|
||||||
|
ON_RESULT_FAILURE_2
|
||||||
|
|
||||||
|
#define ON_RESULT_SUCCESS_2 ON_RESULT_RETURN_IMPL(ResultImpl::EvaluateResultSuccess)
|
||||||
|
|
||||||
|
#define ON_RESULT_SUCCESS \
|
||||||
|
DECLARE_CURRENT_RESULT_REFERENCE_AND_STORAGE(__COUNTER__); \
|
||||||
|
ON_RESULT_SUCCESS_2
|
||||||
|
|
||||||
|
constexpr inline ResultCode __TmpCurrentResultReference = RESULT_SUCCESS;
|
||||||
|
|
||||||
|
/// Returns a result.
|
||||||
|
#define R_RETURN(res_expr) \
|
||||||
|
{ \
|
||||||
|
const ResultCode _tmp_r_throw_rc = (res_expr); \
|
||||||
|
ResultImpl::UpdateCurrentResultReference<decltype(__TmpCurrentResultReference)>( \
|
||||||
|
__TmpCurrentResultReference, _tmp_r_throw_rc); \
|
||||||
|
return _tmp_r_throw_rc; \
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns ResultSuccess()
|
||||||
|
#define R_SUCCEED() R_RETURN(RESULT_SUCCESS)
|
||||||
|
|
||||||
|
/// Throws a result.
|
||||||
|
#define R_THROW(res_expr) R_RETURN(res_expr)
|
||||||
|
|
||||||
|
/// Evaluates a boolean expression, and returns a result unless that expression is true.
|
||||||
|
#define R_UNLESS(expr, res) \
|
||||||
|
{ \
|
||||||
|
if (!(expr)) { \
|
||||||
|
R_THROW(res); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates an expression that returns a result, and returns the result if it would fail.
|
||||||
|
#define R_TRY(res_expr) \
|
||||||
|
{ \
|
||||||
|
const auto _tmp_r_try_rc = (res_expr); \
|
||||||
|
if (R_FAILED(_tmp_r_try_rc)) { \
|
||||||
|
R_THROW(_tmp_r_try_rc); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Evaluates a boolean expression, and succeeds if that expression is true.
|
||||||
|
#define R_SUCCEED_IF(expr) R_UNLESS(!(expr), RESULT_SUCCESS)
|
||||||
|
|
||||||
|
/// Evaluates a boolean expression, and asserts if that expression is false.
|
||||||
|
#define R_ASSERT(expr) ASSERT(R_SUCCEEDED(expr))
|
||||||
|
@ -57,6 +57,7 @@ void File::Read(Kernel::HLERequestContext& ctx) {
|
|||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
u64 offset = rp.Pop<u64>();
|
u64 offset = rp.Pop<u64>();
|
||||||
u32 length = rp.Pop<u32>();
|
u32 length = rp.Pop<u32>();
|
||||||
|
auto& buffer = rp.PopMappedBuffer();
|
||||||
LOG_TRACE(Service_FS, "Read {}: offset=0x{:x} length=0x{:08X}", GetName(), offset, length);
|
LOG_TRACE(Service_FS, "Read {}: offset=0x{:x} length=0x{:08X}", GetName(), offset, length);
|
||||||
|
|
||||||
const FileSessionSlot* file = GetSessionData(ctx.Session());
|
const FileSessionSlot* file = GetSessionData(ctx.Session());
|
||||||
@ -75,17 +76,15 @@ void File::Read(Kernel::HLERequestContext& ctx) {
|
|||||||
offset, length, backend->GetSize());
|
offset, length, backend->GetSize());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Conventional reading if the backend does not support cache.
|
|
||||||
if (!backend->AllowsCachedReads()) {
|
|
||||||
auto& buffer = rp.PopMappedBuffer();
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||||
std::unique_ptr<u8*> data = std::make_unique<u8*>(static_cast<u8*>(operator new(length)));
|
|
||||||
const auto read = backend->Read(offset, length, *data);
|
std::vector<u8> data(length);
|
||||||
|
ResultVal<std::size_t> read = backend->Read(offset, data.size(), data.data());
|
||||||
if (read.Failed()) {
|
if (read.Failed()) {
|
||||||
rb.Push(read.Code());
|
rb.Push(read.Code());
|
||||||
rb.Push<u32>(0);
|
rb.Push<u32>(0);
|
||||||
} else {
|
} else {
|
||||||
buffer.Write(*data, 0, *read);
|
buffer.Write(data.data(), 0, *read);
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.Push<u32>(static_cast<u32>(*read));
|
rb.Push<u32>(static_cast<u32>(*read));
|
||||||
}
|
}
|
||||||
@ -93,76 +92,6 @@ void File::Read(Kernel::HLERequestContext& ctx) {
|
|||||||
|
|
||||||
std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)};
|
std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)};
|
||||||
ctx.SleepClientThread("file::read", read_timeout_ns, nullptr);
|
ctx.SleepClientThread("file::read", read_timeout_ns, nullptr);
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AsyncData {
|
|
||||||
// Input
|
|
||||||
u32 length;
|
|
||||||
u64 offset;
|
|
||||||
std::chrono::steady_clock::time_point pre_timer;
|
|
||||||
bool cache_ready;
|
|
||||||
|
|
||||||
// Output
|
|
||||||
ResultCode ret{0};
|
|
||||||
Kernel::MappedBuffer* buffer;
|
|
||||||
std::unique_ptr<u8*> data;
|
|
||||||
size_t read_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
auto async_data = std::make_shared<AsyncData>();
|
|
||||||
async_data->buffer = &rp.PopMappedBuffer();
|
|
||||||
async_data->length = length;
|
|
||||||
async_data->offset = offset;
|
|
||||||
async_data->cache_ready = backend->CacheReady(offset, length);
|
|
||||||
if (!async_data->cache_ready) {
|
|
||||||
async_data->pre_timer = std::chrono::steady_clock::now();
|
|
||||||
}
|
|
||||||
|
|
||||||
// LOG_DEBUG(Service_FS, "cache={}, offset={}, length={}", cache_ready, offset, length);
|
|
||||||
ctx.RunAsync(
|
|
||||||
[this, async_data](Kernel::HLERequestContext& ctx) {
|
|
||||||
async_data->data =
|
|
||||||
std::make_unique<u8*>(static_cast<u8*>(operator new(async_data->length)));
|
|
||||||
const auto read =
|
|
||||||
backend->Read(async_data->offset, async_data->length, *async_data->data);
|
|
||||||
if (read.Failed()) {
|
|
||||||
async_data->ret = read.Code();
|
|
||||||
async_data->read_size = 0;
|
|
||||||
} else {
|
|
||||||
async_data->ret = RESULT_SUCCESS;
|
|
||||||
async_data->read_size = *read;
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto read_delay = static_cast<s64>(backend->GetReadDelayNs(async_data->length));
|
|
||||||
if (!async_data->cache_ready) {
|
|
||||||
const auto time_took = std::chrono::duration_cast<std::chrono::nanoseconds>(
|
|
||||||
std::chrono::steady_clock::now() - async_data->pre_timer)
|
|
||||||
.count();
|
|
||||||
/*
|
|
||||||
if (time_took > read_delay) {
|
|
||||||
LOG_DEBUG(Service_FS, "Took longer! length={}, time_took={}, read_delay={}",
|
|
||||||
async_data->length, time_took, read_delay);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
return static_cast<s64>((read_delay > time_took) ? (read_delay - time_took) : 0);
|
|
||||||
} else {
|
|
||||||
return static_cast<s64>(read_delay);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
[async_data](Kernel::HLERequestContext& ctx) {
|
|
||||||
IPC::RequestBuilder rb(ctx, 0x0802, 2, 2);
|
|
||||||
if (async_data->ret.IsError()) {
|
|
||||||
rb.Push(async_data->ret);
|
|
||||||
rb.Push<u32>(0);
|
|
||||||
} else {
|
|
||||||
async_data->buffer->Write(*async_data->data, 0, async_data->read_size);
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
|
||||||
rb.Push<u32>(static_cast<u32>(async_data->read_size));
|
|
||||||
}
|
|
||||||
rb.PushMappedBuffer(*async_data->buffer);
|
|
||||||
},
|
|
||||||
!async_data->cache_ready);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void File::Write(Kernel::HLERequestContext& ctx) {
|
void File::Write(Kernel::HLERequestContext& ctx) {
|
||||||
|
@ -670,26 +670,6 @@ void FS_USER::GetFormatInfo(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.Push<bool>(format_info->duplicate_data != 0);
|
rb.Push<bool>(format_info->duplicate_data != 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::GetProductInfo(Kernel::HLERequestContext& ctx) {
|
|
||||||
IPC::RequestParser rp(ctx);
|
|
||||||
|
|
||||||
u32 process_id = rp.Pop<u32>();
|
|
||||||
|
|
||||||
LOG_DEBUG(Service_FS, "called, process_id={}", process_id);
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(6, 0);
|
|
||||||
|
|
||||||
const auto product_info = GetProductInfo(process_id);
|
|
||||||
if (!product_info.has_value()) {
|
|
||||||
rb.Push(ResultCode(FileSys::ErrCodes::ArchiveNotMounted, ErrorModule::FS,
|
|
||||||
ErrorSummary::NotFound, ErrorLevel::Status));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
|
||||||
rb.PushRaw<ProductInfo>(product_info.value());
|
|
||||||
}
|
|
||||||
|
|
||||||
void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) {
|
void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
const auto process_id = rp.Pop<u32>();
|
const auto process_id = rp.Pop<u32>();
|
||||||
@ -707,20 +687,8 @@ void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ProgramInfo program_info = program_info_result.Unwrap();
|
|
||||||
|
|
||||||
// Always report the launched program mediatype is SD if the friends module is requesting this
|
|
||||||
// information and the media type is game card. Otherwise, friends will append a "romid" field
|
|
||||||
// to the NASC request with a cartridge unique identifier. Using a dump of a game card and the
|
|
||||||
// game card itself at the same time online is known to have caused issues in the past.
|
|
||||||
auto process = ctx.ClientThread()->owner_process.lock();
|
|
||||||
if (process && process->codeset->name == "friends" &&
|
|
||||||
program_info.media_type == MediaType::GameCard) {
|
|
||||||
program_info.media_type = MediaType::SDMC;
|
|
||||||
}
|
|
||||||
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
rb.PushRaw<ProgramInfo>(program_info);
|
rb.PushRaw(program_info_result.Unwrap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::ObsoletedCreateExtSaveData(Kernel::HLERequestContext& ctx) {
|
void FS_USER::ObsoletedCreateExtSaveData(Kernel::HLERequestContext& ctx) {
|
||||||
@ -807,12 +775,12 @@ void FS_USER::AddSeed(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
const u64 value = rp.Pop<u64>();
|
u64 value = rp.Pop<u64>();
|
||||||
const u32 secure_value_slot = rp.Pop<u32>();
|
u32 secure_value_slot = rp.Pop<u32>();
|
||||||
const u32 unique_id = rp.Pop<u32>();
|
u32 unique_id = rp.Pop<u32>();
|
||||||
const u8 title_variation = rp.Pop<u8>();
|
u8 title_variation = rp.Pop<u8>();
|
||||||
|
|
||||||
// TODO: Generate and Save the Secure Value
|
// TODO: Generate and Save the Secure Value
|
||||||
|
|
||||||
@ -826,11 +794,12 @@ void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.Push(RESULT_SUCCESS);
|
rb.Push(RESULT_SUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||||
IPC::RequestParser rp(ctx);
|
IPC::RequestParser rp(ctx);
|
||||||
const u32 secure_value_slot = rp.Pop<u32>();
|
|
||||||
const u32 unique_id = rp.Pop<u32>();
|
u32 secure_value_slot = rp.Pop<u32>();
|
||||||
const u8 title_variation = rp.Pop<u8>();
|
u32 unique_id = rp.Pop<u32>();
|
||||||
|
u8 title_variation = rp.Pop<u8>();
|
||||||
|
|
||||||
LOG_WARNING(
|
LOG_WARNING(
|
||||||
Service_FS,
|
Service_FS,
|
||||||
@ -847,77 +816,7 @@ void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
|||||||
rb.Push<u64>(0); // the secure value
|
rb.Push<u64>(0); // the secure value
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
void FS_USER::Register(u32 process_id, u64 program_id, const std::string& filepath) {
|
||||||
IPC::RequestParser rp(ctx);
|
|
||||||
const u32 secure_value_slot = rp.Pop<u32>();
|
|
||||||
const u64 value = rp.Pop<u64>();
|
|
||||||
|
|
||||||
// TODO: Generate and Save the Secure Value
|
|
||||||
|
|
||||||
LOG_WARNING(Service_FS, "(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:08X}", value,
|
|
||||||
secure_value_slot);
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
||||||
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FS_USER::GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
|
||||||
IPC::RequestParser rp(ctx);
|
|
||||||
const u32 secure_value_slot = rp.Pop<u32>();
|
|
||||||
|
|
||||||
LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X}", secure_value_slot);
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(5, 0);
|
|
||||||
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
|
||||||
|
|
||||||
// TODO: Implement Secure Value Lookup & Generation
|
|
||||||
|
|
||||||
rb.Push<bool>(false); // indicates that the secure value doesn't exist
|
|
||||||
rb.Push<bool>(false); // looks like a boolean value, purpose unknown
|
|
||||||
rb.Push<u64>(0); // the secure value
|
|
||||||
}
|
|
||||||
|
|
||||||
void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
|
||||||
IPC::RequestParser rp(ctx);
|
|
||||||
const auto archive_handle = rp.PopRaw<ArchiveHandle>();
|
|
||||||
const u32 secure_value_slot = rp.Pop<u32>();
|
|
||||||
const u64 value = rp.Pop<u64>();
|
|
||||||
const bool flush = rp.Pop<bool>();
|
|
||||||
|
|
||||||
// TODO: Generate and Save the Secure Value
|
|
||||||
|
|
||||||
LOG_WARNING(Service_FS,
|
|
||||||
"(STUBBED) called, value=0x{:016x} secure_value_slot=0x{:04X} "
|
|
||||||
"archive_handle=0x{:08X} flush={}",
|
|
||||||
value, secure_value_slot, archive_handle, flush);
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
|
||||||
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
|
||||||
}
|
|
||||||
|
|
||||||
void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
|
||||||
IPC::RequestParser rp(ctx);
|
|
||||||
const auto archive_handle = rp.PopRaw<ArchiveHandle>();
|
|
||||||
const u32 secure_value_slot = rp.Pop<u32>();
|
|
||||||
|
|
||||||
LOG_WARNING(Service_FS, "(STUBBED) called secure_value_slot=0x{:08X} archive_handle=0x{:08X}",
|
|
||||||
secure_value_slot, archive_handle);
|
|
||||||
|
|
||||||
IPC::RequestBuilder rb = rp.MakeBuilder(5, 0);
|
|
||||||
|
|
||||||
rb.Push(RESULT_SUCCESS);
|
|
||||||
|
|
||||||
// TODO: Implement Secure Value Lookup & Generation
|
|
||||||
|
|
||||||
rb.Push<bool>(false); // indicates that the secure value doesn't exist
|
|
||||||
rb.Push<bool>(false); // looks like a boolean value, purpose unknown
|
|
||||||
rb.Push<u64>(0); // the secure value
|
|
||||||
}
|
|
||||||
|
|
||||||
void FS_USER::RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath) {
|
|
||||||
const MediaType media_type = GetMediaTypeFromPath(filepath);
|
const MediaType media_type = GetMediaTypeFromPath(filepath);
|
||||||
program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type});
|
program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type});
|
||||||
if (media_type == MediaType::GameCard) {
|
if (media_type == MediaType::GameCard) {
|
||||||
@ -929,19 +828,6 @@ std::string FS_USER::GetCurrentGamecardPath() const {
|
|||||||
return current_gamecard_path;
|
return current_gamecard_path;
|
||||||
}
|
}
|
||||||
|
|
||||||
void FS_USER::RegisterProductInfo(u32 process_id, const ProductInfo& product_info) {
|
|
||||||
product_info_map.insert_or_assign(process_id, product_info);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::optional<FS_USER::ProductInfo> FS_USER::GetProductInfo(u32 process_id) {
|
|
||||||
auto it = product_info_map.find(process_id);
|
|
||||||
if (it != product_info_map.end()) {
|
|
||||||
return it->second;
|
|
||||||
} else {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResultVal<u16> FS_USER::GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type) {
|
ResultVal<u16> FS_USER::GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type) {
|
||||||
// TODO(B3N30) check if on real 3DS NCSD is checked if partition exists
|
// TODO(B3N30) check if on real 3DS NCSD is checked if partition exists
|
||||||
|
|
||||||
@ -1043,7 +929,7 @@ FS_USER::FS_USER(Core::System& system)
|
|||||||
{0x082B, nullptr, "CardNorDirectRead_4xIO"},
|
{0x082B, nullptr, "CardNorDirectRead_4xIO"},
|
||||||
{0x082C, nullptr, "CardNorDirectCpuWriteWithoutVerify"},
|
{0x082C, nullptr, "CardNorDirectCpuWriteWithoutVerify"},
|
||||||
{0x082D, nullptr, "CardNorDirectSectorEraseWithoutVerify"},
|
{0x082D, nullptr, "CardNorDirectSectorEraseWithoutVerify"},
|
||||||
{0x082E, &FS_USER::GetProductInfo, "GetProductInfo"},
|
{0x082E, nullptr, "GetProductInfo"},
|
||||||
{0x082F, &FS_USER::GetProgramLaunchInfo, "GetProgramLaunchInfo"},
|
{0x082F, &FS_USER::GetProgramLaunchInfo, "GetProgramLaunchInfo"},
|
||||||
{0x0830, &FS_USER::ObsoletedCreateExtSaveData, "Obsoleted_3_0_CreateExtSaveData"},
|
{0x0830, &FS_USER::ObsoletedCreateExtSaveData, "Obsoleted_3_0_CreateExtSaveData"},
|
||||||
{0x0831, nullptr, "CreateSharedExtSaveData"},
|
{0x0831, nullptr, "CreateSharedExtSaveData"},
|
||||||
@ -1098,16 +984,12 @@ FS_USER::FS_USER(Core::System& system)
|
|||||||
{0x0862, &FS_USER::SetPriority, "SetPriority"},
|
{0x0862, &FS_USER::SetPriority, "SetPriority"},
|
||||||
{0x0863, &FS_USER::GetPriority, "GetPriority"},
|
{0x0863, &FS_USER::GetPriority, "GetPriority"},
|
||||||
{0x0864, nullptr, "GetNandInfo"},
|
{0x0864, nullptr, "GetNandInfo"},
|
||||||
{0x0865, &FS_USER::ObsoletedSetSaveDataSecureValue, "SetSaveDataSecureValue"},
|
{0x0865, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue"},
|
||||||
{0x0866, &FS_USER::ObsoletedGetSaveDataSecureValue, "GetSaveDataSecureValue"},
|
{0x0866, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue"},
|
||||||
{0x0867, nullptr, "ControlSecureSave"},
|
{0x0867, nullptr, "ControlSecureSave"},
|
||||||
{0x0868, nullptr, "GetMediaType"},
|
{0x0868, nullptr, "GetMediaType"},
|
||||||
{0x0869, nullptr, "GetNandEraseCount"},
|
{0x0869, nullptr, "GetNandEraseCount"},
|
||||||
{0x086A, nullptr, "ReadNandReport"},
|
{0x086A, nullptr, "ReadNandReport"},
|
||||||
{0x086E, &FS_USER::SetThisSaveDataSecureValue, "SetThisSaveDataSecureValue" },
|
|
||||||
{0x086F, &FS_USER::GetThisSaveDataSecureValue, "GetThisSaveDataSecureValue" },
|
|
||||||
{0x0875, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue" },
|
|
||||||
{0x0876, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue" },
|
|
||||||
{0x087A, &FS_USER::AddSeed, "AddSeed"},
|
{0x087A, &FS_USER::AddSeed, "AddSeed"},
|
||||||
{0x087D, &FS_USER::GetNumSeeds, "GetNumSeeds"},
|
{0x087D, &FS_USER::GetNumSeeds, "GetNumSeeds"},
|
||||||
{0x0886, nullptr, "CheckUpdatedDat"},
|
{0x0886, nullptr, "CheckUpdatedDat"},
|
||||||
|
@ -4,12 +4,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <optional>
|
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <boost/serialization/base_object.hpp>
|
#include <boost/serialization/base_object.hpp>
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
#include "core/file_sys/errors.h"
|
#include "core/file_sys/errors.h"
|
||||||
#include "core/hle/service/fs/archive.h"
|
|
||||||
#include "core/hle/service/service.h"
|
#include "core/hle/service/service.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
@ -49,23 +47,12 @@ class FS_USER final : public ServiceFramework<FS_USER, ClientSlot> {
|
|||||||
public:
|
public:
|
||||||
explicit FS_USER(Core::System& system);
|
explicit FS_USER(Core::System& system);
|
||||||
|
|
||||||
// On real HW this is part of FSReg (FSReg:Register). But since that module is only used by
|
// On real HW this is part of FS:Reg. But since that module is only used by loader and pm, which
|
||||||
// loader and pm, which we HLEed, we can just directly use it here
|
// we HLEed, we can just directly use it here
|
||||||
void RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath);
|
void Register(u32 process_id, u64 program_id, const std::string& filepath);
|
||||||
|
|
||||||
std::string GetCurrentGamecardPath() const;
|
std::string GetCurrentGamecardPath() const;
|
||||||
|
|
||||||
struct ProductInfo {
|
|
||||||
std::array<u8, 0x10> product_code;
|
|
||||||
u16_le maker_code;
|
|
||||||
u16_le remaster_version;
|
|
||||||
};
|
|
||||||
static_assert(sizeof(ProductInfo) == 0x14);
|
|
||||||
|
|
||||||
void RegisterProductInfo(u32 process_id, const ProductInfo& product_info);
|
|
||||||
|
|
||||||
std::optional<ProductInfo> GetProductInfo(u32 process_id);
|
|
||||||
|
|
||||||
/// Gets the registered program info of a process.
|
/// Gets the registered program info of a process.
|
||||||
ResultVal<ProgramInfo> GetProgramLaunchInfo(u32 process_id) const {
|
ResultVal<ProgramInfo> GetProgramLaunchInfo(u32 process_id) const {
|
||||||
auto info = program_info_map.find(process_id);
|
auto info = program_info_map.find(process_id);
|
||||||
@ -522,17 +509,6 @@ private:
|
|||||||
*/
|
*/
|
||||||
void GetFormatInfo(Kernel::HLERequestContext& ctx);
|
void GetFormatInfo(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
|
||||||
* FS_User::GetProductInfo service function.
|
|
||||||
* Inputs:
|
|
||||||
* 0 : 0x082E0040
|
|
||||||
* 1 : Process ID
|
|
||||||
* Outputs:
|
|
||||||
* 1 : Result of function, 0 on success, otherwise error code
|
|
||||||
* 2-6 : Product info
|
|
||||||
*/
|
|
||||||
void GetProductInfo(Kernel::HLERequestContext& ctx);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FS_User::GetProgramLaunchInfo service function.
|
* FS_User::GetProgramLaunchInfo service function.
|
||||||
* Inputs:
|
* Inputs:
|
||||||
@ -624,7 +600,7 @@ private:
|
|||||||
* 0 : 0x08650140
|
* 0 : 0x08650140
|
||||||
* 1 : Result of function, 0 on success, otherwise error code
|
* 1 : Result of function, 0 on success, otherwise error code
|
||||||
*/
|
*/
|
||||||
void ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
void SetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* FS_User::GetSaveDataSecureValue service function.
|
* FS_User::GetSaveDataSecureValue service function.
|
||||||
@ -639,57 +615,6 @@ private:
|
|||||||
* 2 : If Secure Value doesn't exist, 0, if it exists, 1
|
* 2 : If Secure Value doesn't exist, 0, if it exists, 1
|
||||||
* 3-4 : Secure Value
|
* 3-4 : Secure Value
|
||||||
*/
|
*/
|
||||||
void ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FS_User::SetThisSaveDataSecureValue service function.
|
|
||||||
* Inputs:
|
|
||||||
* 1 : Secure Value Slot
|
|
||||||
* 2-3 : Secure Value
|
|
||||||
* Outputs:
|
|
||||||
* 1 : Result of function, 0 on success, otherwise error code
|
|
||||||
*/
|
|
||||||
void SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FS_User::GetSaveDataSecureValue service function.
|
|
||||||
* Inputs:
|
|
||||||
* 1 : Secure Value Slot
|
|
||||||
* Outputs:
|
|
||||||
* 1 : Result of function, 0 on success, otherwise error code
|
|
||||||
* 2 : If Secure Value doesn't exist, 0, if it exists, 1
|
|
||||||
* 3 : Unknown
|
|
||||||
* 4-5 : Secure Value
|
|
||||||
*/
|
|
||||||
void GetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FS_User::SetSaveDataSecureValue service function.
|
|
||||||
* Inputs:
|
|
||||||
* 0 : 0x08750180
|
|
||||||
* 1-2 : Archive
|
|
||||||
* 3 : Secure Value Slot
|
|
||||||
* 4 : value
|
|
||||||
* 5 : flush
|
|
||||||
* Outputs:
|
|
||||||
* 0 : header
|
|
||||||
* 1 : Result of function, 0 on success, otherwise error code
|
|
||||||
*/
|
|
||||||
void SetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* FS_User::GetSaveDataSecureValue service function.
|
|
||||||
* Inputs:
|
|
||||||
* 0 : 0x087600C0
|
|
||||||
* 1-2 : Archive
|
|
||||||
* 2 : Secure Value slot
|
|
||||||
* Outputs:
|
|
||||||
* 0 : Header
|
|
||||||
* 1 : Result of function, 0 on success, otherwise error code
|
|
||||||
* 2 : If Secure Value doesn't exist, 0, if it exists, 1
|
|
||||||
* 3 : unknown
|
|
||||||
* 4-5 : Secure Value
|
|
||||||
*/
|
|
||||||
void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
void GetSaveDataSecureValue(Kernel::HLERequestContext& ctx);
|
||||||
|
|
||||||
static ResultVal<u16> GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type);
|
static ResultVal<u16> GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type);
|
||||||
@ -699,8 +624,6 @@ private:
|
|||||||
std::unordered_map<u32, ProgramInfo> program_info_map;
|
std::unordered_map<u32, ProgramInfo> program_info_map;
|
||||||
std::string current_gamecard_path;
|
std::string current_gamecard_path;
|
||||||
|
|
||||||
std::unordered_map<u32, ProductInfo> product_info_map;
|
|
||||||
|
|
||||||
u32 priority = -1; ///< For SetPriority and GetPriority service functions
|
u32 priority = -1; ///< For SetPriority and GetPriority service functions
|
||||||
|
|
||||||
Core::System& system;
|
Core::System& system;
|
||||||
|
@ -282,7 +282,7 @@ ResultStatus AppLoader_THREEDSX::Load(std::shared_ptr<Kernel::Process>& process)
|
|||||||
// On real HW this is done with FS:Reg, but we can be lazy
|
// On real HW this is done with FS:Reg, but we can be lazy
|
||||||
auto fs_user =
|
auto fs_user =
|
||||||
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>("fs:USER");
|
||||||
fs_user->RegisterProgramInfo(process->GetObjectId(), process->codeset->program_id, filepath);
|
fs_user->Register(process->GetObjectId(), process->codeset->program_id, filepath);
|
||||||
|
|
||||||
process->Run(48, Kernel::DEFAULT_STACK_SIZE);
|
process->Run(48, Kernel::DEFAULT_STACK_SIZE);
|
||||||
|
|
||||||
|
@ -174,16 +174,7 @@ ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process)
|
|||||||
auto fs_user =
|
auto fs_user =
|
||||||
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
||||||
"fs:USER");
|
"fs:USER");
|
||||||
fs_user->RegisterProgramInfo(process->process_id, process->codeset->program_id, filepath);
|
fs_user->Register(process->process_id, process->codeset->program_id, filepath);
|
||||||
|
|
||||||
Service::FS::FS_USER::ProductInfo product_info{};
|
|
||||||
std::memcpy(product_info.product_code.data(), overlay_ncch->ncch_header.product_code,
|
|
||||||
product_info.product_code.size());
|
|
||||||
std::memcpy(&product_info.remaster_version,
|
|
||||||
overlay_ncch->exheader_header.codeset_info.flags.remaster_version,
|
|
||||||
sizeof(product_info.remaster_version));
|
|
||||||
product_info.maker_code = overlay_ncch->ncch_header.maker_code;
|
|
||||||
fs_user->RegisterProductInfo(process->process_id, product_info);
|
|
||||||
|
|
||||||
process->Run(priority, stack_size);
|
process->Run(priority, stack_size);
|
||||||
return ResultStatus::Success;
|
return ResultStatus::Success;
|
||||||
|
@ -329,6 +329,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
u8* GetPointer(VAddr vaddr);
|
u8* GetPointer(VAddr vaddr);
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
T* GetPointer(VAddr vaddr) {
|
||||||
|
return reinterpret_cast<T*>(GetPointer(vaddr));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a pointer to the given address.
|
* Gets a pointer to the given address.
|
||||||
*
|
*
|
||||||
|
@ -78,11 +78,12 @@ static void WriteUniformFloatReg(ShaderRegs& config, Shader::ShaderSetup& setup,
|
|||||||
(float_regs_counter >= 3 && !uniform_setup.IsFloat32())) {
|
(float_regs_counter >= 3 && !uniform_setup.IsFloat32())) {
|
||||||
float_regs_counter = 0;
|
float_regs_counter = 0;
|
||||||
|
|
||||||
if (uniform_setup.index >= setup.uniforms.f.size()) {
|
auto& uniform = setup.uniforms.f[uniform_setup.index];
|
||||||
|
|
||||||
|
if (uniform_setup.index >= 96) {
|
||||||
LOG_ERROR(HW_GPU, "Invalid {} float uniform index {}", GetShaderSetupTypeName(setup),
|
LOG_ERROR(HW_GPU, "Invalid {} float uniform index {}", GetShaderSetupTypeName(setup),
|
||||||
(int)uniform_setup.index);
|
(int)uniform_setup.index);
|
||||||
} else {
|
} else {
|
||||||
auto& uniform = setup.uniforms.f[uniform_setup.index];
|
|
||||||
|
|
||||||
// NOTE: The destination component order indeed is "backwards"
|
// NOTE: The destination component order indeed is "backwards"
|
||||||
if (uniform_setup.IsFloat32()) {
|
if (uniform_setup.IsFloat32()) {
|
||||||
|
@ -78,8 +78,8 @@ RasterizerOpenGL::RasterizerOpenGL(Memory::MemorySystem& memory,
|
|||||||
VideoCore::CustomTexManager& custom_tex_manager,
|
VideoCore::CustomTexManager& custom_tex_manager,
|
||||||
VideoCore::RendererBase& renderer, Driver& driver_)
|
VideoCore::RendererBase& renderer, Driver& driver_)
|
||||||
: VideoCore::RasterizerAccelerated{memory}, driver{driver_},
|
: VideoCore::RasterizerAccelerated{memory}, driver{driver_},
|
||||||
shader_manager{renderer.GetRenderWindow(), driver}, runtime{driver, renderer},
|
shader_manager{renderer.GetRenderWindow(), driver, !driver.IsOpenGLES()},
|
||||||
res_cache{memory, custom_tex_manager, runtime, regs, renderer},
|
runtime{driver, renderer}, res_cache{memory, custom_tex_manager, runtime, regs, renderer},
|
||||||
texture_buffer_size{TextureBufferSize()}, vertex_buffer{driver, GL_ARRAY_BUFFER,
|
texture_buffer_size{TextureBufferSize()}, vertex_buffer{driver, GL_ARRAY_BUFFER,
|
||||||
VERTEX_BUFFER_SIZE},
|
VERTEX_BUFFER_SIZE},
|
||||||
uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE},
|
uniform_buffer{driver, GL_UNIFORM_BUFFER, UNIFORM_BUFFER_SIZE},
|
||||||
|
@ -2,10 +2,14 @@
|
|||||||
// Licensed under GPLv2 or any later version
|
// Licensed under GPLv2 or any later version
|
||||||
// Refer to the license.txt file included.
|
// Refer to the license.txt file included.
|
||||||
|
|
||||||
|
#include "common/microprofile.h"
|
||||||
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
#include "video_core/renderer_opengl/gl_resource_manager.h"
|
||||||
#include "video_core/renderer_opengl/gl_shader_util.h"
|
#include "video_core/renderer_opengl/gl_shader_util.h"
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
|
|
||||||
|
MICROPROFILE_DEFINE(OpenGL_ResourceCreation, "OpenGL", "Resource Creation", MP_RGB(128, 128, 192));
|
||||||
|
MICROPROFILE_DEFINE(OpenGL_ResourceDeletion, "OpenGL", "Resource Deletion", MP_RGB(128, 128, 192));
|
||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
void OGLRenderbuffer::Create() {
|
void OGLRenderbuffer::Create() {
|
||||||
@ -13,6 +17,7 @@ void OGLRenderbuffer::Create() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
glGenRenderbuffers(1, &handle);
|
glGenRenderbuffers(1, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -21,6 +26,7 @@ void OGLRenderbuffer::Release() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteRenderbuffers(1, &handle);
|
glDeleteRenderbuffers(1, &handle);
|
||||||
OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply();
|
OpenGLState::GetCurState().ResetRenderbuffer(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
@ -31,6 +37,7 @@ void OGLTexture::Create() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
glGenTextures(1, &handle);
|
glGenTextures(1, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,6 +46,7 @@ void OGLTexture::Release() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteTextures(1, &handle);
|
glDeleteTextures(1, &handle);
|
||||||
OpenGLState::GetCurState().ResetTexture(handle).Apply();
|
OpenGLState::GetCurState().ResetTexture(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
@ -80,6 +88,7 @@ void OGLSampler::Create() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
glGenSamplers(1, &handle);
|
glGenSamplers(1, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,41 +97,37 @@ void OGLSampler::Release() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteSamplers(1, &handle);
|
glDeleteSamplers(1, &handle);
|
||||||
OpenGLState::GetCurState().ResetSampler(handle).Apply();
|
OpenGLState::GetCurState().ResetSampler(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLShader::Create(std::string_view source, GLenum type) {
|
void OGLShader::Create(std::string_view source, GLenum type) {
|
||||||
if (handle != 0) {
|
if (handle != 0)
|
||||||
return;
|
return;
|
||||||
}
|
if (source.empty())
|
||||||
if (source.empty()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
handle = LoadShader(source, type);
|
handle = LoadShader(source, type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLShader::Release() {
|
void OGLShader::Release() {
|
||||||
if (handle == 0) {
|
if (handle == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteShader(handle);
|
glDeleteShader(handle);
|
||||||
handle = 0;
|
handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLProgram::Create(std::string_view source, GLenum type) {
|
void OGLProgram::Create(bool separable_program, std::span<const GLuint> shaders) {
|
||||||
if (handle != 0) {
|
if (handle != 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
if (source.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const std::array sources{GetPreamble().data(), source.data()};
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
handle = glCreateShaderProgramv(type, 2, sources.data());
|
handle = LoadProgram(separable_program, shaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLProgram::Create(std::string_view vert_shader, std::string_view frag_shader) {
|
void OGLProgram::Create(std::string_view vert_shader, std::string_view frag_shader) {
|
||||||
@ -130,87 +135,88 @@ void OGLProgram::Create(std::string_view vert_shader, std::string_view frag_shad
|
|||||||
vert.Create(vert_shader, GL_VERTEX_SHADER);
|
vert.Create(vert_shader, GL_VERTEX_SHADER);
|
||||||
frag.Create(frag_shader, GL_FRAGMENT_SHADER);
|
frag.Create(frag_shader, GL_FRAGMENT_SHADER);
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
const std::array shaders{vert.handle, frag.handle};
|
const std::array shaders{vert.handle, frag.handle};
|
||||||
handle = LoadProgram(shaders);
|
Create(false, shaders);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLProgram::Release() {
|
void OGLProgram::Release() {
|
||||||
if (handle == 0) {
|
if (handle == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteProgram(handle);
|
glDeleteProgram(handle);
|
||||||
OpenGLState::GetCurState().ResetProgram(handle).Apply();
|
OpenGLState::GetCurState().ResetProgram(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLPipeline::Create() {
|
void OGLPipeline::Create() {
|
||||||
if (handle != 0) {
|
if (handle != 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
glGenProgramPipelines(1, &handle);
|
glGenProgramPipelines(1, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLPipeline::Release() {
|
void OGLPipeline::Release() {
|
||||||
if (handle == 0) {
|
if (handle == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteProgramPipelines(1, &handle);
|
glDeleteProgramPipelines(1, &handle);
|
||||||
OpenGLState::GetCurState().ResetPipeline(handle).Apply();
|
OpenGLState::GetCurState().ResetPipeline(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLBuffer::Create() {
|
void OGLBuffer::Create() {
|
||||||
if (handle != 0) {
|
if (handle != 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
glGenBuffers(1, &handle);
|
glGenBuffers(1, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLBuffer::Release() {
|
void OGLBuffer::Release() {
|
||||||
if (handle == 0) {
|
if (handle == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteBuffers(1, &handle);
|
glDeleteBuffers(1, &handle);
|
||||||
OpenGLState::GetCurState().ResetBuffer(handle).Apply();
|
OpenGLState::GetCurState().ResetBuffer(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLVertexArray::Create() {
|
void OGLVertexArray::Create() {
|
||||||
if (handle != 0) {
|
if (handle != 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
glGenVertexArrays(1, &handle);
|
glGenVertexArrays(1, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLVertexArray::Release() {
|
void OGLVertexArray::Release() {
|
||||||
if (handle == 0) {
|
if (handle == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteVertexArrays(1, &handle);
|
glDeleteVertexArrays(1, &handle);
|
||||||
OpenGLState::GetCurState().ResetVertexArray(handle).Apply();
|
OpenGLState::GetCurState().ResetVertexArray(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLFramebuffer::Create() {
|
void OGLFramebuffer::Create() {
|
||||||
if (handle != 0) {
|
if (handle != 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceCreation);
|
||||||
glGenFramebuffers(1, &handle);
|
glGenFramebuffers(1, &handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OGLFramebuffer::Release() {
|
void OGLFramebuffer::Release() {
|
||||||
if (handle == 0) {
|
if (handle == 0)
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
|
|
||||||
|
MICROPROFILE_SCOPE(OpenGL_ResourceDeletion);
|
||||||
glDeleteFramebuffers(1, &handle);
|
glDeleteFramebuffers(1, &handle);
|
||||||
OpenGLState::GetCurState().ResetFramebuffer(handle).Apply();
|
OpenGLState::GetCurState().ResetFramebuffer(handle).Apply();
|
||||||
handle = 0;
|
handle = 0;
|
||||||
|
@ -130,7 +130,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new program from given shader objects
|
/// Creates a new program from given shader objects
|
||||||
void Create(std::string_view source, GLenum type);
|
void Create(bool separable_program, std::span<const GLuint> shaders);
|
||||||
|
|
||||||
/// Creates a new program from given shader soruce code
|
/// Creates a new program from given shader soruce code
|
||||||
void Create(std::string_view vert_shader, std::string_view frag_shader);
|
void Create(std::string_view vert_shader, std::string_view frag_shader);
|
||||||
|
@ -103,8 +103,10 @@ bool ShaderDiskCacheRaw::Save(FileUtil::IOFile& file) const {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ShaderDiskCache::ShaderDiskCache()
|
ShaderDiskCache::ShaderDiskCache(bool separable)
|
||||||
: transferable_file(AppendTransferableFile()), precompiled_file(AppendPrecompiledFile()) {}
|
: separable{separable}, transferable_file(AppendTransferableFile()),
|
||||||
|
// seperable shaders use the virtual precompile file, that already has a header.
|
||||||
|
precompiled_file(AppendPrecompiledFile(!separable)) {}
|
||||||
|
|
||||||
std::optional<std::vector<ShaderDiskCacheRaw>> ShaderDiskCache::LoadTransferable() {
|
std::optional<std::vector<ShaderDiskCacheRaw>> ShaderDiskCache::LoadTransferable() {
|
||||||
const bool has_title_id = GetProgramID() != 0;
|
const bool has_title_id = GetProgramID() != 0;
|
||||||
@ -175,7 +177,7 @@ std::optional<std::vector<ShaderDiskCacheRaw>> ShaderDiskCache::LoadTransferable
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
|
std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>
|
||||||
ShaderDiskCache::LoadPrecompiled() {
|
ShaderDiskCache::LoadPrecompiled(bool compressed) {
|
||||||
if (!IsUsable())
|
if (!IsUsable())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
@ -185,7 +187,7 @@ ShaderDiskCache::LoadPrecompiled() {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto result = LoadPrecompiledFile(precompiled_file);
|
const auto result = LoadPrecompiledFile(precompiled_file, compressed);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
LOG_INFO(Render_OpenGL,
|
LOG_INFO(Render_OpenGL,
|
||||||
"Failed to load precompiled cache for game with title id={} - removing",
|
"Failed to load precompiled cache for game with title id={} - removing",
|
||||||
@ -197,16 +199,22 @@ ShaderDiskCache::LoadPrecompiled() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>>
|
std::optional<std::pair<std::unordered_map<u64, ShaderDiskCacheDecompiled>, ShaderDumpsMap>>
|
||||||
ShaderDiskCache::LoadPrecompiledFile(FileUtil::IOFile& file) {
|
ShaderDiskCache::LoadPrecompiledFile(FileUtil::IOFile& file, bool compressed) {
|
||||||
// Read compressed file from disk and decompress to virtual precompiled cache file
|
// Read compressed file from disk and decompress to virtual precompiled cache file
|
||||||
std::vector<u8> precompiled_file(file.GetSize());
|
std::vector<u8> precompiled_file(file.GetSize());
|
||||||
file.ReadBytes(precompiled_file.data(), precompiled_file.size());
|
file.ReadBytes(precompiled_file.data(), precompiled_file.size());
|
||||||
const std::vector<u8> decompressed = Common::Compression::DecompressDataZSTD(precompiled_file);
|
if (compressed) {
|
||||||
|
const std::vector<u8> decompressed =
|
||||||
|
Common::Compression::DecompressDataZSTD(precompiled_file);
|
||||||
if (decompressed.empty()) {
|
if (decompressed.empty()) {
|
||||||
LOG_ERROR(Render_OpenGL, "Could not decompress precompiled shader cache.");
|
LOG_ERROR(Render_OpenGL, "Could not decompress precompiled shader cache.");
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
SaveArrayToPrecompiled(decompressed.data(), decompressed.size());
|
SaveArrayToPrecompiled(decompressed.data(), decompressed.size());
|
||||||
|
} else {
|
||||||
|
SaveArrayToPrecompiled(precompiled_file.data(), precompiled_file.size());
|
||||||
|
}
|
||||||
|
|
||||||
decompressed_precompiled_cache_offset = 0;
|
decompressed_precompiled_cache_offset = 0;
|
||||||
|
|
||||||
ShaderCacheVersionHash file_hash{};
|
ShaderCacheVersionHash file_hash{};
|
||||||
@ -345,7 +353,7 @@ void ShaderDiskCache::InvalidatePrecompiled() {
|
|||||||
if (!FileUtil::Delete(GetPrecompiledPath())) {
|
if (!FileUtil::Delete(GetPrecompiledPath())) {
|
||||||
LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
|
LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
|
||||||
}
|
}
|
||||||
precompiled_file = AppendPrecompiledFile();
|
precompiled_file = AppendPrecompiledFile(!separable);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderDiskCache::SaveRaw(const ShaderDiskCacheRaw& entry) {
|
void ShaderDiskCache::SaveRaw(const ShaderDiskCacheRaw& entry) {
|
||||||
@ -463,11 +471,12 @@ FileUtil::IOFile ShaderDiskCache::AppendTransferableFile() {
|
|||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileUtil::IOFile ShaderDiskCache::AppendPrecompiledFile() {
|
FileUtil::IOFile ShaderDiskCache::AppendPrecompiledFile(bool write_header) {
|
||||||
if (!EnsureDirectories())
|
if (!EnsureDirectories())
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
const auto precompiled_path{GetPrecompiledPath()};
|
const auto precompiled_path{GetPrecompiledPath()};
|
||||||
|
const bool existed = FileUtil::Exists(precompiled_path);
|
||||||
|
|
||||||
FileUtil::IOFile file(precompiled_path, "ab+");
|
FileUtil::IOFile file(precompiled_path, "ab+");
|
||||||
if (!file.IsOpen()) {
|
if (!file.IsOpen()) {
|
||||||
@ -475,6 +484,15 @@ FileUtil::IOFile ShaderDiskCache::AppendPrecompiledFile() {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If the file didn't exist, write its version
|
||||||
|
if (write_header && (!existed || file.GetSize() == 0)) {
|
||||||
|
const auto hash{GetShaderCacheVersionHash()};
|
||||||
|
if (file.WriteArray(hash.data(), hash.size()) != hash.size()) {
|
||||||
|
LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
|
||||||
|
precompiled_path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
}
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -498,7 +516,7 @@ void ShaderDiskCache::SaveVirtualPrecompiledFile() {
|
|||||||
if (!FileUtil::Delete(GetPrecompiledPath())) {
|
if (!FileUtil::Delete(GetPrecompiledPath())) {
|
||||||
LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
|
LOG_ERROR(Render_OpenGL, "Failed to invalidate precompiled file={}", GetPrecompiledPath());
|
||||||
}
|
}
|
||||||
precompiled_file = AppendPrecompiledFile();
|
precompiled_file = AppendPrecompiledFile(!separable);
|
||||||
|
|
||||||
if (precompiled_file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) {
|
if (precompiled_file.WriteBytes(compressed.data(), compressed.size()) != compressed.size()) {
|
||||||
LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
|
LOG_ERROR(Render_OpenGL, "Failed to write precompiled cache version in path={}",
|
||||||
@ -540,7 +558,10 @@ std::string ShaderDiskCache::GetPrecompiledDir() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::string ShaderDiskCache::GetPrecompiledShaderDir() const {
|
std::string ShaderDiskCache::GetPrecompiledShaderDir() const {
|
||||||
|
if (separable) {
|
||||||
return GetPrecompiledDir() + DIR_SEP "separable";
|
return GetPrecompiledDir() + DIR_SEP "separable";
|
||||||
|
}
|
||||||
|
return GetPrecompiledDir() + DIR_SEP "conventional";
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string ShaderDiskCache::GetBaseDir() const {
|
std::string ShaderDiskCache::GetBaseDir() const {
|
||||||
|
@ -16,8 +16,11 @@
|
|||||||
|
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
#include "common/assert.h"
|
||||||
|
#include "common/common_types.h"
|
||||||
#include "common/file_util.h"
|
#include "common/file_util.h"
|
||||||
#include "video_core/shader/generator/shader_gen.h"
|
#include "video_core/regs.h"
|
||||||
|
#include "video_core/shader/generator/glsl_shader_gen.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@ -87,14 +90,14 @@ struct ShaderDiskCacheDump {
|
|||||||
|
|
||||||
class ShaderDiskCache {
|
class ShaderDiskCache {
|
||||||
public:
|
public:
|
||||||
explicit ShaderDiskCache();
|
explicit ShaderDiskCache(bool separable);
|
||||||
~ShaderDiskCache() = default;
|
~ShaderDiskCache() = default;
|
||||||
|
|
||||||
/// Loads transferable cache. If file has a old version or on failure, it deletes the file.
|
/// Loads transferable cache. If file has a old version or on failure, it deletes the file.
|
||||||
std::optional<std::vector<ShaderDiskCacheRaw>> LoadTransferable();
|
std::optional<std::vector<ShaderDiskCacheRaw>> LoadTransferable();
|
||||||
|
|
||||||
/// Loads current game's precompiled cache. Invalidates on failure.
|
/// Loads current game's precompiled cache. Invalidates on failure.
|
||||||
std::pair<ShaderDecompiledMap, ShaderDumpsMap> LoadPrecompiled();
|
std::pair<ShaderDecompiledMap, ShaderDumpsMap> LoadPrecompiled(bool compressed);
|
||||||
|
|
||||||
/// Removes the transferable (and precompiled) cache file.
|
/// Removes the transferable (and precompiled) cache file.
|
||||||
void InvalidateAll();
|
void InvalidateAll();
|
||||||
@ -120,7 +123,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
/// Loads the transferable cache. Returns empty on failure.
|
/// Loads the transferable cache. Returns empty on failure.
|
||||||
std::optional<std::pair<ShaderDecompiledMap, ShaderDumpsMap>> LoadPrecompiledFile(
|
std::optional<std::pair<ShaderDecompiledMap, ShaderDumpsMap>> LoadPrecompiledFile(
|
||||||
FileUtil::IOFile& file);
|
FileUtil::IOFile& file, bool compressed);
|
||||||
|
|
||||||
/// Loads a decompiled cache entry from m_precompiled_cache_virtual_file. Returns empty on
|
/// Loads a decompiled cache entry from m_precompiled_cache_virtual_file. Returns empty on
|
||||||
/// failure.
|
/// failure.
|
||||||
@ -140,7 +143,7 @@ private:
|
|||||||
FileUtil::IOFile AppendTransferableFile();
|
FileUtil::IOFile AppendTransferableFile();
|
||||||
|
|
||||||
/// Opens current game's precompiled file and write it's header if it doesn't exist
|
/// Opens current game's precompiled file and write it's header if it doesn't exist
|
||||||
FileUtil::IOFile AppendPrecompiledFile();
|
FileUtil::IOFile AppendPrecompiledFile(bool write_header);
|
||||||
|
|
||||||
/// Save precompiled header to precompiled_cache_in_memory
|
/// Save precompiled header to precompiled_cache_in_memory
|
||||||
void SavePrecompiledHeaderToVirtualPrecompiledCache();
|
void SavePrecompiledHeaderToVirtualPrecompiledCache();
|
||||||
@ -216,6 +219,8 @@ private:
|
|||||||
// The cache has been loaded at boot
|
// The cache has been loaded at boot
|
||||||
bool tried_to_load{};
|
bool tried_to_load{};
|
||||||
|
|
||||||
|
bool separable{};
|
||||||
|
|
||||||
u64 program_id{};
|
u64 program_id{};
|
||||||
std::string title_id;
|
std::string title_id;
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
|
#include "video_core/renderer_opengl/gl_shader_disk_cache.h"
|
||||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||||
#include "video_core/renderer_opengl/gl_state.h"
|
#include "video_core/renderer_opengl/gl_state.h"
|
||||||
#include "video_core/shader/generator/glsl_shader_gen.h"
|
#include "video_core/shader/generator/shader_uniforms.h"
|
||||||
#include "video_core/video_core.h"
|
#include "video_core/video_core.h"
|
||||||
|
|
||||||
using namespace Pica::Shader::Generator;
|
using namespace Pica::Shader::Generator;
|
||||||
@ -35,16 +35,19 @@ static u64 GetUniqueIdentifier(const Pica::Regs& regs, const ProgramCode& code)
|
|||||||
}
|
}
|
||||||
|
|
||||||
static OGLProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump,
|
static OGLProgram GeneratePrecompiledProgram(const ShaderDiskCacheDump& dump,
|
||||||
const std::set<GLenum>& supported_formats) {
|
const std::set<GLenum>& supported_formats,
|
||||||
|
bool separable) {
|
||||||
|
|
||||||
if (supported_formats.find(dump.binary_format) == supported_formats.end()) {
|
if (supported_formats.find(dump.binary_format) == supported_formats.end()) {
|
||||||
LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing");
|
LOG_INFO(Render_OpenGL, "Precompiled cache entry with unsupported format - removing");
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
OGLProgram shader{};
|
auto shader = OGLProgram();
|
||||||
shader.handle = glCreateProgram();
|
shader.handle = glCreateProgram();
|
||||||
|
if (separable) {
|
||||||
glProgramParameteri(shader.handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
|
glProgramParameteri(shader.handle, GL_PROGRAM_SEPARABLE, GL_TRUE);
|
||||||
|
}
|
||||||
glProgramBinary(shader.handle, dump.binary_format, dump.binary.data(),
|
glProgramBinary(shader.handle, dump.binary_format, dump.binary.data(),
|
||||||
static_cast<GLsizei>(dump.binary.size()));
|
static_cast<GLsizei>(dump.binary.size()));
|
||||||
|
|
||||||
@ -87,42 +90,91 @@ static std::tuple<PicaVSConfig, Pica::Shader::ShaderSetup> BuildVSConfigFromRaw(
|
|||||||
setup};
|
setup};
|
||||||
}
|
}
|
||||||
|
|
||||||
class TrivialVertexShader {
|
/**
|
||||||
|
* An object representing a shader program staging. It can be either a shader object or a program
|
||||||
|
* object, depending on whether separable program is used.
|
||||||
|
*/
|
||||||
|
class OGLShaderStage {
|
||||||
public:
|
public:
|
||||||
explicit TrivialVertexShader(const Driver& driver) {
|
explicit OGLShaderStage(bool separable) {
|
||||||
const auto code = GLSL::GenerateTrivialVertexShader(driver.HasClipCullDistance(), true);
|
if (separable) {
|
||||||
program.Create(code, GL_VERTEX_SHADER);
|
shader_or_program = OGLProgram();
|
||||||
|
} else {
|
||||||
|
shader_or_program = OGLShader();
|
||||||
}
|
}
|
||||||
GLuint Get() const {
|
}
|
||||||
return program.handle;
|
|
||||||
|
void Create(const char* source, GLenum type) {
|
||||||
|
if (shader_or_program.index() == 0) {
|
||||||
|
std::get<OGLShader>(shader_or_program).Create(source, type);
|
||||||
|
} else {
|
||||||
|
OGLShader shader;
|
||||||
|
shader.Create(source, type);
|
||||||
|
OGLProgram& program = std::get<OGLProgram>(shader_or_program);
|
||||||
|
program.Create(true, std::array{shader.handle});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLuint GetHandle() const {
|
||||||
|
if (shader_or_program.index() == 0) {
|
||||||
|
return std::get<OGLShader>(shader_or_program).handle;
|
||||||
|
} else {
|
||||||
|
return std::get<OGLProgram>(shader_or_program).handle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inject(OGLProgram&& program) {
|
||||||
|
shader_or_program = std::move(program);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
OGLProgram program;
|
std::variant<OGLShader, OGLProgram> shader_or_program;
|
||||||
};
|
};
|
||||||
|
|
||||||
template <typename KeyConfigType, auto CodeGenerator, GLenum ShaderType>
|
class TrivialVertexShader {
|
||||||
|
public:
|
||||||
|
explicit TrivialVertexShader(const Driver& driver, bool separable) : program(separable) {
|
||||||
|
const auto code =
|
||||||
|
GLSL::GenerateTrivialVertexShader(driver.HasClipCullDistance(), separable);
|
||||||
|
program.Create(code.c_str(), GL_VERTEX_SHADER);
|
||||||
|
}
|
||||||
|
GLuint Get() const {
|
||||||
|
return program.GetHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
OGLShaderStage program;
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename KeyConfigType, std::string (*CodeGenerator)(const KeyConfigType&, bool),
|
||||||
|
GLenum ShaderType>
|
||||||
class ShaderCache {
|
class ShaderCache {
|
||||||
public:
|
public:
|
||||||
explicit ShaderCache() = default;
|
explicit ShaderCache(bool separable) : separable(separable) {}
|
||||||
|
|
||||||
std::tuple<GLuint, std::optional<std::string>> Get(const KeyConfigType& config) {
|
std::tuple<GLuint, std::optional<std::string>> Get(const KeyConfigType& config) {
|
||||||
auto [iter, new_shader] = shaders.try_emplace(config);
|
auto [iter, new_shader] = shaders.emplace(config, OGLShaderStage{separable});
|
||||||
OGLProgram& cached_shader = iter->second;
|
OGLShaderStage& cached_shader = iter->second;
|
||||||
std::optional<std::string> result{};
|
std::optional<std::string> result{};
|
||||||
if (new_shader) {
|
if (new_shader) {
|
||||||
result = CodeGenerator(config, true);
|
result = CodeGenerator(config, separable);
|
||||||
cached_shader.Create(result.value(), ShaderType);
|
cached_shader.Create(result->c_str(), ShaderType);
|
||||||
}
|
}
|
||||||
return {cached_shader.handle, std::move(result)};
|
return {cached_shader.GetHandle(), std::move(result)};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inject(const KeyConfigType& key, OGLProgram&& stage) {
|
void Inject(const KeyConfigType& key, OGLProgram&& program) {
|
||||||
|
OGLShaderStage stage{separable};
|
||||||
|
stage.Inject(std::move(program));
|
||||||
|
shaders.emplace(key, std::move(stage));
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inject(const KeyConfigType& key, OGLShaderStage&& stage) {
|
||||||
shaders.emplace(key, std::move(stage));
|
shaders.emplace(key, std::move(stage));
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<KeyConfigType, OGLProgram> shaders;
|
bool separable;
|
||||||
|
std::unordered_map<KeyConfigType, OGLShaderStage> shaders;
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is a cache designed for shaders translated from PICA shaders. The first cache matches the
|
// This is a cache designed for shaders translated from PICA shaders. The first cache matches the
|
||||||
@ -130,48 +182,59 @@ private:
|
|||||||
// GLSL code. The configuration is like this because there might be leftover code in the PICA shader
|
// GLSL code. The configuration is like this because there might be leftover code in the PICA shader
|
||||||
// program buffer from the previous shader, which is hashed into the config, resulting several
|
// program buffer from the previous shader, which is hashed into the config, resulting several
|
||||||
// different config values from the same shader program.
|
// different config values from the same shader program.
|
||||||
template <typename KeyConfigType, auto CodeGenerator, GLenum ShaderType>
|
template <typename KeyConfigType,
|
||||||
|
std::string (*CodeGenerator)(const Pica::Shader::ShaderSetup&, const KeyConfigType&,
|
||||||
|
bool),
|
||||||
|
GLenum ShaderType>
|
||||||
class ShaderDoubleCache {
|
class ShaderDoubleCache {
|
||||||
public:
|
public:
|
||||||
explicit ShaderDoubleCache() = default;
|
explicit ShaderDoubleCache(bool separable) : separable(separable) {}
|
||||||
|
|
||||||
std::tuple<GLuint, std::optional<std::string>> Get(const KeyConfigType& key,
|
std::tuple<GLuint, std::optional<std::string>> Get(const KeyConfigType& key,
|
||||||
const Pica::Shader::ShaderSetup& setup) {
|
const Pica::Shader::ShaderSetup& setup) {
|
||||||
std::optional<std::string> result{};
|
std::optional<std::string> result{};
|
||||||
auto map_it = shader_map.find(key);
|
auto map_it = shader_map.find(key);
|
||||||
if (map_it == shader_map.end()) {
|
if (map_it == shader_map.end()) {
|
||||||
const auto program = CodeGenerator(setup, key, separable);
|
auto program = CodeGenerator(setup, key, separable);
|
||||||
if (program.empty()) {
|
if (program.empty()) {
|
||||||
shader_map[key] = nullptr;
|
shader_map[key] = nullptr;
|
||||||
return {0, std::nullopt};
|
return {0, std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto [iter, new_shader] = shader_cache.try_emplace(program);
|
auto [iter, new_shader] = shader_cache.emplace(program, OGLShaderStage{separable});
|
||||||
OGLProgram& cached_shader = iter->second;
|
OGLShaderStage& cached_shader = iter->second;
|
||||||
if (new_shader) {
|
if (new_shader) {
|
||||||
result = program;
|
result = program;
|
||||||
cached_shader.Create(program, ShaderType);
|
cached_shader.Create(program.c_str(), ShaderType);
|
||||||
}
|
}
|
||||||
shader_map[key] = &cached_shader;
|
shader_map[key] = &cached_shader;
|
||||||
return {cached_shader.handle, std::move(result)};
|
return {cached_shader.GetHandle(), std::move(result)};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (map_it->second == nullptr) {
|
if (map_it->second == nullptr) {
|
||||||
return {0, std::nullopt};
|
return {0, std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {map_it->second->handle, std::nullopt};
|
return {map_it->second->GetHandle(), std::nullopt};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Inject(const KeyConfigType& key, std::string decomp, OGLProgram&& program) {
|
void Inject(const KeyConfigType& key, std::string decomp, OGLProgram&& program) {
|
||||||
const auto iter = shader_cache.emplace(std::move(decomp), std::move(program)).first;
|
OGLShaderStage stage{separable};
|
||||||
shader_map.insert_or_assign(key, &iter->second);
|
stage.Inject(std::move(program));
|
||||||
|
const auto iter = shader_cache.emplace(std::move(decomp), std::move(stage)).first;
|
||||||
|
OGLShaderStage& cached_shader = iter->second;
|
||||||
|
shader_map.insert_or_assign(key, &cached_shader);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Inject(const KeyConfigType& key, std::string decomp, OGLShaderStage&& stage) {
|
||||||
|
const auto iter = shader_cache.emplace(std::move(decomp), std::move(stage)).first;
|
||||||
|
OGLShaderStage& cached_shader = iter->second;
|
||||||
|
shader_map.insert_or_assign(key, &cached_shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool separable;
|
bool separable;
|
||||||
std::unordered_map<KeyConfigType, OGLProgram*> shader_map;
|
std::unordered_map<KeyConfigType, OGLShaderStage*> shader_map;
|
||||||
std::unordered_map<std::string, OGLProgram> shader_cache;
|
std::unordered_map<std::string, OGLShaderStage> shader_cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
using ProgrammableVertexShaders =
|
using ProgrammableVertexShaders =
|
||||||
@ -185,7 +248,11 @@ using FragmentShaders =
|
|||||||
|
|
||||||
class ShaderProgramManager::Impl {
|
class ShaderProgramManager::Impl {
|
||||||
public:
|
public:
|
||||||
explicit Impl(const Driver& driver) : trivial_vertex_shader(driver) {
|
explicit Impl(const Driver& driver, bool separable)
|
||||||
|
: separable(separable), programmable_vertex_shaders(separable),
|
||||||
|
trivial_vertex_shader(driver, separable), fixed_geometry_shaders(separable),
|
||||||
|
fragment_shaders(separable), disk_cache(separable) {
|
||||||
|
if (separable)
|
||||||
pipeline.Create();
|
pipeline.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -215,6 +282,8 @@ public:
|
|||||||
static_assert(offsetof(ShaderTuple, fs_hash) == sizeof(std::size_t) * 2,
|
static_assert(offsetof(ShaderTuple, fs_hash) == sizeof(std::size_t) * 2,
|
||||||
"ShaderTuple layout changed!");
|
"ShaderTuple layout changed!");
|
||||||
|
|
||||||
|
bool separable;
|
||||||
|
|
||||||
ShaderTuple current;
|
ShaderTuple current;
|
||||||
|
|
||||||
ProgrammableVertexShaders programmable_vertex_shaders;
|
ProgrammableVertexShaders programmable_vertex_shaders;
|
||||||
@ -228,10 +297,11 @@ public:
|
|||||||
ShaderDiskCache disk_cache;
|
ShaderDiskCache disk_cache;
|
||||||
};
|
};
|
||||||
|
|
||||||
ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, const Driver& driver_)
|
ShaderProgramManager::ShaderProgramManager(Frontend::EmuWindow& emu_window_, const Driver& driver_,
|
||||||
|
bool separable)
|
||||||
: emu_window{emu_window_}, driver{driver_},
|
: emu_window{emu_window_}, driver{driver_},
|
||||||
strict_context_required{emu_window.StrictContextRequired()}, impl{std::make_unique<Impl>(
|
strict_context_required{emu_window.StrictContextRequired()}, impl{std::make_unique<Impl>(
|
||||||
driver_)} {}
|
driver_, separable)} {}
|
||||||
|
|
||||||
ShaderProgramManager::~ShaderProgramManager() = default;
|
ShaderProgramManager::~ShaderProgramManager() = default;
|
||||||
|
|
||||||
@ -293,18 +363,30 @@ void ShaderProgramManager::UseFragmentShader(const Pica::Regs& regs, bool use_no
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ShaderProgramManager::ApplyTo(OpenGLState& state) {
|
void ShaderProgramManager::ApplyTo(OpenGLState& state) {
|
||||||
|
if (impl->separable) {
|
||||||
if (driver.HasBug(DriverBug::ShaderStageChangeFreeze)) {
|
if (driver.HasBug(DriverBug::ShaderStageChangeFreeze)) {
|
||||||
glUseProgramStages(impl->pipeline.handle,
|
glUseProgramStages(
|
||||||
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT,
|
impl->pipeline.handle,
|
||||||
0);
|
GL_VERTEX_SHADER_BIT | GL_GEOMETRY_SHADER_BIT | GL_FRAGMENT_SHADER_BIT, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
glUseProgramStages(impl->pipeline.handle, GL_VERTEX_SHADER_BIT, impl->current.vs);
|
glUseProgramStages(impl->pipeline.handle, GL_VERTEX_SHADER_BIT, impl->current.vs);
|
||||||
glUseProgramStages(impl->pipeline.handle, GL_GEOMETRY_SHADER_BIT, impl->current.gs);
|
glUseProgramStages(impl->pipeline.handle, GL_GEOMETRY_SHADER_BIT, impl->current.gs);
|
||||||
glUseProgramStages(impl->pipeline.handle, GL_FRAGMENT_SHADER_BIT, impl->current.fs);
|
glUseProgramStages(impl->pipeline.handle, GL_FRAGMENT_SHADER_BIT, impl->current.fs);
|
||||||
|
|
||||||
state.draw.shader_program = 0;
|
state.draw.shader_program = 0;
|
||||||
state.draw.program_pipeline = impl->pipeline.handle;
|
state.draw.program_pipeline = impl->pipeline.handle;
|
||||||
|
} else {
|
||||||
|
const u64 unique_identifier = impl->current.GetConfigHash();
|
||||||
|
OGLProgram& cached_program = impl->program_cache[unique_identifier];
|
||||||
|
if (cached_program.handle == 0) {
|
||||||
|
cached_program.Create(false,
|
||||||
|
std::array{impl->current.vs, impl->current.gs, impl->current.fs});
|
||||||
|
auto& disk_cache = impl->disk_cache;
|
||||||
|
disk_cache.SaveDumpToFile(unique_identifier, cached_program.handle,
|
||||||
|
VideoCore::g_hw_shader_accurate_mul);
|
||||||
|
}
|
||||||
|
state.draw.shader_program = cached_program.handle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||||
@ -318,7 +400,7 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
|||||||
|
|
||||||
// Load uncompressed precompiled file for non-separable shaders.
|
// Load uncompressed precompiled file for non-separable shaders.
|
||||||
// Precompiled file for separable shaders is compressed.
|
// Precompiled file for separable shaders is compressed.
|
||||||
auto [decompiled, dumps] = disk_cache.LoadPrecompiled();
|
auto [decompiled, dumps] = disk_cache.LoadPrecompiled(impl->separable);
|
||||||
|
|
||||||
if (stop_loading) {
|
if (stop_loading) {
|
||||||
return;
|
return;
|
||||||
@ -336,11 +418,11 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
|||||||
callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size());
|
callback(VideoCore::LoadCallbackStage::Decompile, 0, raws.size());
|
||||||
}
|
}
|
||||||
std::vector<std::size_t> load_raws_index;
|
std::vector<std::size_t> load_raws_index;
|
||||||
|
|
||||||
// Loads both decompiled and precompiled shaders from the cache. If either one is missing for
|
// Loads both decompiled and precompiled shaders from the cache. If either one is missing for
|
||||||
const auto LoadPrecompiledShader =
|
const auto LoadPrecompiledShader = [&](std::size_t begin, std::size_t end,
|
||||||
[&](std::size_t begin, std::size_t end, std::span<const ShaderDiskCacheRaw> raw_cache,
|
std::span<const ShaderDiskCacheRaw> raw_cache,
|
||||||
const ShaderDecompiledMap& decompiled_map, const ShaderDumpsMap& dump_map) {
|
const ShaderDecompiledMap& decompiled_map,
|
||||||
|
const ShaderDumpsMap& dump_map) {
|
||||||
for (std::size_t i = begin; i < end; ++i) {
|
for (std::size_t i = begin; i < end; ++i) {
|
||||||
if (stop_loading || compilation_failed) {
|
if (stop_loading || compilation_failed) {
|
||||||
return;
|
return;
|
||||||
@ -372,23 +454,24 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
|||||||
}
|
}
|
||||||
|
|
||||||
// If the shader is dumped, attempt to load it
|
// If the shader is dumped, attempt to load it
|
||||||
shader = GeneratePrecompiledProgram(dump->second, supported_formats);
|
shader =
|
||||||
|
GeneratePrecompiledProgram(dump->second, supported_formats, impl->separable);
|
||||||
if (shader.handle == 0) {
|
if (shader.handle == 0) {
|
||||||
// If any shader failed, stop trying to compile, delete the cache, and start
|
// If any shader failed, stop trying to compile, delete the cache, and start
|
||||||
// loading from raws.
|
// loading from raws
|
||||||
compilation_failed = true;
|
compilation_failed = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// We have both the binary shader and the decompiled, so inject it into the
|
// we have both the binary shader and the decompiled, so inject it into the
|
||||||
// cache.
|
// cache
|
||||||
if (raw.GetProgramType() == ProgramType::VS) {
|
if (raw.GetProgramType() == ProgramType::VS) {
|
||||||
auto [conf, setup] = BuildVSConfigFromRaw(raw, driver);
|
auto [conf, setup] = BuildVSConfigFromRaw(raw, driver);
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
impl->programmable_vertex_shaders.Inject(conf, decomp->second.code,
|
impl->programmable_vertex_shaders.Inject(conf, decomp->second.code,
|
||||||
std::move(shader));
|
std::move(shader));
|
||||||
} else if (raw.GetProgramType() == ProgramType::FS) {
|
} else if (raw.GetProgramType() == ProgramType::FS) {
|
||||||
PicaFSConfig conf(raw.GetRawShaderConfig(), false, driver.IsOpenGLES(),
|
PicaFSConfig conf(raw.GetRawShaderConfig(), false, driver.IsOpenGLES(), false,
|
||||||
false, driver.HasBlendMinMaxFactor());
|
driver.HasBlendMinMaxFactor());
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
impl->fragment_shaders.Inject(conf, std::move(shader));
|
impl->fragment_shaders.Inject(conf, std::move(shader));
|
||||||
} else {
|
} else {
|
||||||
@ -410,7 +493,42 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const auto LoadPrecompiledProgram = [&](const ShaderDecompiledMap& decompiled_map,
|
||||||
|
const ShaderDumpsMap& dump_map) {
|
||||||
|
std::size_t i{0};
|
||||||
|
for (const auto& dump : dump_map) {
|
||||||
|
if (stop_loading) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
const u64 unique_identifier{dump.first};
|
||||||
|
const auto decomp{decompiled_map.find(unique_identifier)};
|
||||||
|
|
||||||
|
// Only load the program if its sanitize_mul setting matches
|
||||||
|
if (decomp->second.sanitize_mul != VideoCore::g_hw_shader_accurate_mul) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the shader program is dumped, attempt to load it
|
||||||
|
OGLProgram shader =
|
||||||
|
GeneratePrecompiledProgram(dump.second, supported_formats, impl->separable);
|
||||||
|
if (shader.handle != 0) {
|
||||||
|
impl->program_cache.emplace(unique_identifier, std::move(shader));
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Frontend, "Failed to link Precompiled program!");
|
||||||
|
compilation_failed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (callback) {
|
||||||
|
callback(VideoCore::LoadCallbackStage::Decompile, ++i, dump_map.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (impl->separable) {
|
||||||
LoadPrecompiledShader(0, raws.size(), raws, decompiled, dumps);
|
LoadPrecompiledShader(0, raws.size(), raws, decompiled, dumps);
|
||||||
|
} else {
|
||||||
|
LoadPrecompiledProgram(decompiled, dumps);
|
||||||
|
}
|
||||||
|
|
||||||
bool load_all_raws = false;
|
bool load_all_raws = false;
|
||||||
if (compilation_failed) {
|
if (compilation_failed) {
|
||||||
@ -421,6 +539,11 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
|||||||
precompiled_cache_altered = true;
|
precompiled_cache_altered = true;
|
||||||
load_all_raws = true;
|
load_all_raws = true;
|
||||||
}
|
}
|
||||||
|
// TODO(SachinV): Skip loading raws until we implement a proper way to link non-seperable
|
||||||
|
// shaders.
|
||||||
|
if (!impl->separable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const std::size_t load_raws_size = load_all_raws ? raws.size() : load_raws_index.size();
|
const std::size_t load_raws_size = load_all_raws ? raws.size() : load_raws_index.size();
|
||||||
|
|
||||||
@ -447,25 +570,25 @@ void ShaderProgramManager::LoadDiskCache(const std::atomic_bool& stop_loading,
|
|||||||
GLuint handle{0};
|
GLuint handle{0};
|
||||||
std::string code;
|
std::string code;
|
||||||
// Otherwise decompile and build the shader at boot and save the result to the
|
// Otherwise decompile and build the shader at boot and save the result to the
|
||||||
// precompiled file.
|
// precompiled file
|
||||||
if (raw.GetProgramType() == ProgramType::VS) {
|
if (raw.GetProgramType() == ProgramType::VS) {
|
||||||
const auto [conf, setup] = BuildVSConfigFromRaw(raw, driver);
|
auto [conf, setup] = BuildVSConfigFromRaw(raw, driver);
|
||||||
code = GLSL::GenerateVertexShader(setup, conf, true);
|
code = GLSL::GenerateVertexShader(setup, conf, impl->separable);
|
||||||
OGLProgram program{};
|
OGLShaderStage stage{impl->separable};
|
||||||
program.Create(code, GL_VERTEX_SHADER);
|
stage.Create(code.c_str(), GL_VERTEX_SHADER);
|
||||||
handle = program.handle;
|
handle = stage.GetHandle();
|
||||||
sanitize_mul = conf.state.sanitize_mul;
|
sanitize_mul = conf.state.sanitize_mul;
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
impl->programmable_vertex_shaders.Inject(conf, code, std::move(program));
|
impl->programmable_vertex_shaders.Inject(conf, code, std::move(stage));
|
||||||
} else if (raw.GetProgramType() == ProgramType::FS) {
|
} else if (raw.GetProgramType() == ProgramType::FS) {
|
||||||
PicaFSConfig conf(raw.GetRawShaderConfig(), false, driver.IsOpenGLES(), false,
|
PicaFSConfig conf(raw.GetRawShaderConfig(), false, driver.IsOpenGLES(), false,
|
||||||
driver.HasBlendMinMaxFactor());
|
driver.HasBlendMinMaxFactor());
|
||||||
code = GLSL::GenerateFragmentShader(conf, true);
|
code = GLSL::GenerateFragmentShader(conf, impl->separable);
|
||||||
OGLProgram program{};
|
OGLShaderStage stage{impl->separable};
|
||||||
program.Create(code, GL_FRAGMENT_SHADER);
|
stage.Create(code.c_str(), GL_FRAGMENT_SHADER);
|
||||||
handle = program.handle;
|
handle = stage.GetHandle();
|
||||||
std::scoped_lock lock(mutex);
|
std::scoped_lock lock(mutex);
|
||||||
impl->fragment_shaders.Inject(conf, std::move(program));
|
impl->fragment_shaders.Inject(conf, std::move(stage));
|
||||||
} else {
|
} else {
|
||||||
// Unsupported shader type got stored somehow so nuke the cache
|
// Unsupported shader type got stored somehow so nuke the cache
|
||||||
LOG_ERROR(Frontend, "failed to load raw ProgramType {}", raw.GetProgramType());
|
LOG_ERROR(Frontend, "failed to load raw ProgramType {}", raw.GetProgramType());
|
||||||
|
@ -33,29 +33,22 @@ enum UniformBindings {
|
|||||||
/// A class that manage different shader stages and configures them with given config data.
|
/// A class that manage different shader stages and configures them with given config data.
|
||||||
class ShaderProgramManager {
|
class ShaderProgramManager {
|
||||||
public:
|
public:
|
||||||
explicit ShaderProgramManager(Frontend::EmuWindow& emu_window, const Driver& driver);
|
ShaderProgramManager(Frontend::EmuWindow& emu_window, const Driver& driver, bool separable);
|
||||||
~ShaderProgramManager();
|
~ShaderProgramManager();
|
||||||
|
|
||||||
/// Loads the pipeline cache stored to disk.
|
|
||||||
void LoadDiskCache(const std::atomic_bool& stop_loading,
|
void LoadDiskCache(const std::atomic_bool& stop_loading,
|
||||||
const VideoCore::DiskResourceLoadCallback& callback);
|
const VideoCore::DiskResourceLoadCallback& callback);
|
||||||
|
|
||||||
/// Binds a PICA decompiled vertex shader.
|
|
||||||
bool UseProgrammableVertexShader(const Pica::Regs& config, Pica::Shader::ShaderSetup& setup);
|
bool UseProgrammableVertexShader(const Pica::Regs& config, Pica::Shader::ShaderSetup& setup);
|
||||||
|
|
||||||
/// Binds a passthrough vertex shader.
|
|
||||||
void UseTrivialVertexShader();
|
void UseTrivialVertexShader();
|
||||||
|
|
||||||
/// Binds a passthrough geometry shader.
|
|
||||||
void UseFixedGeometryShader(const Pica::Regs& regs);
|
void UseFixedGeometryShader(const Pica::Regs& regs);
|
||||||
|
|
||||||
/// Binds no geometry shader.
|
|
||||||
void UseTrivialGeometryShader();
|
void UseTrivialGeometryShader();
|
||||||
|
|
||||||
/// Binds a fragment shader generated from PICA state.
|
|
||||||
void UseFragmentShader(const Pica::Regs& config, bool use_normal);
|
void UseFragmentShader(const Pica::Regs& config, bool use_normal);
|
||||||
|
|
||||||
/// Binds current shader state to provided OpenGLState.
|
|
||||||
void ApplyTo(OpenGLState& state);
|
void ApplyTo(OpenGLState& state);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
@ -13,9 +13,10 @@
|
|||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
std::string_view GetPreamble() {
|
GLuint LoadShader(std::string_view source, GLenum type) {
|
||||||
|
std::string preamble;
|
||||||
if (GLES) {
|
if (GLES) {
|
||||||
return R"(#version 320 es
|
preamble = R"(#version 320 es
|
||||||
|
|
||||||
#if defined(GL_ANDROID_extension_pack_es31a)
|
#if defined(GL_ANDROID_extension_pack_es31a)
|
||||||
#extension GL_ANDROID_extension_pack_es31a : enable
|
#extension GL_ANDROID_extension_pack_es31a : enable
|
||||||
@ -25,12 +26,9 @@ std::string_view GetPreamble() {
|
|||||||
#extension GL_EXT_clip_cull_distance : enable
|
#extension GL_EXT_clip_cull_distance : enable
|
||||||
#endif // defined(GL_EXT_clip_cull_distance)
|
#endif // defined(GL_EXT_clip_cull_distance)
|
||||||
)";
|
)";
|
||||||
|
} else {
|
||||||
|
preamble = "#version 430 core\n";
|
||||||
}
|
}
|
||||||
return "#version 430 core\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint LoadShader(std::string_view source, GLenum type) {
|
|
||||||
const auto preamble = GetPreamble();
|
|
||||||
|
|
||||||
std::string_view debug_type;
|
std::string_view debug_type;
|
||||||
switch (type) {
|
switch (type) {
|
||||||
@ -74,9 +72,11 @@ GLuint LoadShader(std::string_view source, GLenum type) {
|
|||||||
return shader_id;
|
return shader_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint LoadProgram(std::span<const GLuint> shaders) {
|
GLuint LoadProgram(bool separable_program, std::span<const GLuint> shaders) {
|
||||||
|
// Link the program
|
||||||
LOG_DEBUG(Render_OpenGL, "Linking program...");
|
LOG_DEBUG(Render_OpenGL, "Linking program...");
|
||||||
const GLuint program_id = glCreateProgram();
|
|
||||||
|
GLuint program_id = glCreateProgram();
|
||||||
|
|
||||||
for (GLuint shader : shaders) {
|
for (GLuint shader : shaders) {
|
||||||
if (shader != 0) {
|
if (shader != 0) {
|
||||||
@ -84,6 +84,10 @@ GLuint LoadProgram(std::span<const GLuint> shaders) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (separable_program) {
|
||||||
|
glProgramParameteri(program_id, GL_PROGRAM_SEPARABLE, GL_TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
glProgramParameteri(program_id, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
|
glProgramParameteri(program_id, GL_PROGRAM_BINARY_RETRIEVABLE_HINT, GL_TRUE);
|
||||||
glLinkProgram(program_id);
|
glLinkProgram(program_id);
|
||||||
|
|
||||||
|
@ -9,11 +9,6 @@
|
|||||||
|
|
||||||
namespace OpenGL {
|
namespace OpenGL {
|
||||||
|
|
||||||
/**
|
|
||||||
* Utility function to retrieve the preamble to compile an OpenGL GLSL shader
|
|
||||||
*/
|
|
||||||
std::string_view GetPreamble();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Utility function to create and compile an OpenGL GLSL shader
|
* Utility function to create and compile an OpenGL GLSL shader
|
||||||
* @param source String of the GLSL shader program
|
* @param source String of the GLSL shader program
|
||||||
@ -27,6 +22,6 @@ GLuint LoadShader(std::string_view source, GLenum type);
|
|||||||
* @param shaders ID of shaders to attach to the program
|
* @param shaders ID of shaders to attach to the program
|
||||||
* @returns Handle of the newly created OpenGL program object
|
* @returns Handle of the newly created OpenGL program object
|
||||||
*/
|
*/
|
||||||
GLuint LoadProgram(std::span<const GLuint> shaders);
|
GLuint LoadProgram(bool separable_program, std::span<const GLuint> shaders);
|
||||||
|
|
||||||
} // namespace OpenGL
|
} // namespace OpenGL
|
||||||
|
@ -59,7 +59,6 @@ OpenGLState::OpenGLState() {
|
|||||||
texture_buffer_lut_lf.texture_buffer = 0;
|
texture_buffer_lut_lf.texture_buffer = 0;
|
||||||
texture_buffer_lut_rg.texture_buffer = 0;
|
texture_buffer_lut_rg.texture_buffer = 0;
|
||||||
texture_buffer_lut_rgba.texture_buffer = 0;
|
texture_buffer_lut_rgba.texture_buffer = 0;
|
||||||
color_buffer.texture_2d = 0;
|
|
||||||
|
|
||||||
image_shadow_buffer = 0;
|
image_shadow_buffer = 0;
|
||||||
image_shadow_texture_px = 0;
|
image_shadow_texture_px = 0;
|
||||||
|
@ -1562,7 +1562,7 @@ DebugScope::DebugScope(TextureRuntime& runtime, Common::Vec4f color, std::string
|
|||||||
if (!has_debug_tool) {
|
if (!has_debug_tool) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
scheduler.Record([color, label = std::string(label)](vk::CommandBuffer cmdbuf) {
|
scheduler.Record([color, label](vk::CommandBuffer cmdbuf) {
|
||||||
const vk::DebugUtilsLabelEXT debug_label = {
|
const vk::DebugUtilsLabelEXT debug_label = {
|
||||||
.pLabelName = label.data(),
|
.pLabelName = label.data(),
|
||||||
.color = std::array{color[0], color[1], color[2], color[3]},
|
.color = std::array{color[0], color[1], color[2], color[3]},
|
||||||
|
Reference in New Issue
Block a user