Compare commits
1 Commits
file-watch
...
new-vmm
Author | SHA1 | Date | |
---|---|---|---|
|
c1876c322f |
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -59,7 +59,7 @@ jobs:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
macos:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
matrix:
|
||||
target: ["x86_64", "arm64"]
|
||||
@@ -92,7 +92,7 @@ jobs:
|
||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
macos-universal:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
needs: macos
|
||||
env:
|
||||
OS: macos
|
||||
@@ -234,7 +234,7 @@ jobs:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
ios:
|
||||
runs-on: macos-13
|
||||
runs-on: macos-latest
|
||||
if: ${{ !startsWith(github.ref, 'refs/tags/') }}
|
||||
env:
|
||||
CCACHE_DIR: ${{ github.workspace }}/.ccache
|
||||
|
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -79,15 +79,9 @@
|
||||
[submodule "sirit"]
|
||||
path = externals/sirit
|
||||
url = https://github.com/yuzu-emu/sirit
|
||||
[submodule "faad2"]
|
||||
path = externals/faad2/faad2
|
||||
url = https://github.com/knik0/faad2
|
||||
[submodule "library-headers"]
|
||||
path = externals/library-headers
|
||||
url = https://github.com/citra-emu/ext-library-headers.git
|
||||
[submodule "libadrenotools"]
|
||||
path = externals/libadrenotools
|
||||
url = https://github.com/bylaws/libadrenotools
|
||||
[submodule "oaknut"]
|
||||
path = externals/oaknut
|
||||
url = https://github.com/merryhime/oaknut.git
|
||||
|
@@ -81,6 +81,9 @@ CMAKE_DEPENDENT_OPTION(ENABLE_LIBUSB "Enable libusb for GameCube Adapter support
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_MF "Use Media Foundation decoder (preferred over FFmpeg)" ON "WIN32" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(ENABLE_AUDIOTOOLBOX "Use AudioToolbox decoder (preferred over FFmpeg)" ON "APPLE" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
|
||||
|
||||
# Compile options
|
||||
@@ -233,7 +236,7 @@ find_package(Threads REQUIRED)
|
||||
|
||||
if (ENABLE_QT)
|
||||
if (NOT USE_SYSTEM_QT)
|
||||
download_qt(6.6.0)
|
||||
download_qt(6.5.1)
|
||||
endif()
|
||||
|
||||
find_package(Qt6 REQUIRED COMPONENTS Widgets Multimedia Concurrent)
|
||||
|
@@ -67,22 +67,13 @@ function(download_qt target)
|
||||
|
||||
if (NOT EXISTS "${prefix}")
|
||||
message(STATUS "Downloading binaries for Qt...")
|
||||
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9")
|
||||
if (WIN32)
|
||||
set(aqt_path "${base_path}/aqt.exe")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
||||
https://github.com/miurahr/aqtinstall/releases/download/v3.1.7/aqt.exe
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||
WORKING_DIRECTORY ${base_path})
|
||||
elseif (APPLE)
|
||||
set(aqt_path "${base_path}/aqt-macos")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
execute_process(COMMAND chmod +x ${aqt_path})
|
||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||
WORKING_DIRECTORY ${base_path})
|
||||
else()
|
||||
# aqt does not offer binary releases for other platforms, so download and run from pip.
|
||||
set(aqt_install_path "${base_path}/aqt")
|
||||
|
@@ -10,20 +10,16 @@ set(HASH_FILES
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
|
||||
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.h"
|
||||
"${VIDEO_CORE}/shader/generator/spv_fs_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/spv_fs_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/spv_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/spv_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/shader.cpp"
|
||||
"${VIDEO_CORE}/shader/shader.h"
|
||||
"${VIDEO_CORE}/pica.cpp"
|
||||
|
10
dist/qt_themes/colorful_dark/icons/index.theme
vendored
10
dist/qt_themes/colorful_dark/icons/index.theme
vendored
@@ -2,13 +2,7 @@
|
||||
Name=colorful_dark
|
||||
Comment=Colorful theme (Dark style)
|
||||
Inherits=default
|
||||
Directories=16x16,48x48,256x256
|
||||
|
||||
Directories=16x16
|
||||
|
||||
[16x16]
|
||||
Size=16
|
||||
|
||||
[48x48]
|
||||
Size=48
|
||||
|
||||
[256x256]
|
||||
Size=256
|
||||
|
@@ -2,13 +2,7 @@
|
||||
Name=colorful_midnight_blue
|
||||
Comment=Colorful theme (Midnight Blue style)
|
||||
Inherits=default
|
||||
Directories=16x16,48x48,256x256
|
||||
Directories=16x16
|
||||
|
||||
[16x16]
|
||||
Size=16
|
||||
|
||||
[48x48]
|
||||
Size=48
|
||||
|
||||
[256x256]
|
||||
Size=256
|
||||
|
66
externals/CMakeLists.txt
vendored
66
externals/CMakeLists.txt
vendored
@@ -46,17 +46,11 @@ set(CATCH_INSTALL_EXTRAS OFF CACHE BOOL "")
|
||||
add_subdirectory(catch2)
|
||||
|
||||
# Crypto++
|
||||
if(USE_SYSTEM_CRYPTOPP)
|
||||
find_package(cryptopp REQUIRED)
|
||||
add_library(cryptopp INTERFACE)
|
||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||
else()
|
||||
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()
|
||||
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)
|
||||
|
||||
# dds-ktx
|
||||
add_library(dds-ktx INTERFACE)
|
||||
@@ -85,11 +79,6 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Oaknut
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
add_subdirectory(oaknut EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# Dynarmic
|
||||
if ("x86_64" IN_LIST ARCHITECTURE OR "arm64" IN_LIST ARCHITECTURE)
|
||||
if(USE_SYSTEM_DYNARMIC)
|
||||
@@ -161,12 +150,24 @@ endif()
|
||||
# Open Source Archives
|
||||
add_subdirectory(open_source_archives)
|
||||
|
||||
# faad2
|
||||
add_subdirectory(faad2 EXCLUDE_FROM_ALL)
|
||||
|
||||
# Dynamic library headers
|
||||
add_library(library-headers INTERFACE)
|
||||
|
||||
if (USE_SYSTEM_FDK_AAC_HEADERS)
|
||||
find_path(SYSTEM_FDK_AAC_INCLUDES NAMES fdk-aac/aacdecoder_lib.h)
|
||||
if (SYSTEM_FDK_AAC_INCLUDES STREQUAL "SYSTEM_FDK_AAC_INCLUDES-NOTFOUND")
|
||||
message(WARNING "System fdk-aac headers not found. Falling back on bundled headers.")
|
||||
else()
|
||||
message(STATUS "Using system fdk_aac headers.")
|
||||
target_include_directories(library-headers SYSTEM INTERFACE ${SYSTEM_FDK_AAC_INCLUDES})
|
||||
set(FOUND_FDK_AAC_HEADERS ON)
|
||||
endif()
|
||||
endif()
|
||||
if (NOT FOUND_FDK_AAC_HEADERS)
|
||||
message(STATUS "Using bundled fdk_aac headers.")
|
||||
target_include_directories(library-headers SYSTEM INTERFACE ./library-headers/fdk-aac/include)
|
||||
endif()
|
||||
|
||||
if (USE_SYSTEM_FFMPEG_HEADERS)
|
||||
find_path(SYSTEM_FFMPEG_INCLUDES NAMES libavutil/avutil.h)
|
||||
if (SYSTEM_FFMPEG_INCLUDES STREQUAL "SYSTEM_FFMPEG_INCLUDES-NOTFOUND")
|
||||
@@ -227,30 +228,15 @@ else()
|
||||
endif()
|
||||
|
||||
# ENet
|
||||
if(USE_SYSTEM_ENET)
|
||||
find_package(libenet REQUIRED)
|
||||
add_library(enet INTERFACE)
|
||||
target_link_libraries(enet INTERFACE libenet::libenet)
|
||||
else()
|
||||
add_subdirectory(enet)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
endif()
|
||||
add_subdirectory(enet)
|
||||
target_include_directories(enet INTERFACE ./enet/include)
|
||||
|
||||
# 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_TOOLS OFF CACHE BOOL "")
|
||||
set(BUNDLE_SPEEX ON CACHE BOOL "")
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
set(BUILD_TESTS OFF CACHE BOOL "")
|
||||
set(BUILD_TOOLS OFF CACHE BOOL "")
|
||||
set(BUNDLE_SPEEX ON CACHE BOOL "")
|
||||
add_subdirectory(cubeb EXCLUDE_FROM_ALL)
|
||||
endif()
|
||||
|
||||
# DiscordRPC
|
||||
|
@@ -15,12 +15,10 @@ option(USE_SYSTEM_DYNARMIC "Use the system dynarmic (instead of the bundled one)
|
||||
option(USE_SYSTEM_FMT "Use the system fmt (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_XBYAK "Use the system xbyak (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_INIH "Use the system inih (instead of the bundled one)" OFF)
|
||||
option(USE_SYSTEM_FDK_AAC_HEADERS "Use the system fdk-aac 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_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
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_SDL2 "Disable system SDL2" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
@@ -35,12 +33,10 @@ CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_DYNARMIC "Disable system Dynarmic" OFF "US
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FMT "Disable system fmt" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_XBYAK "Disable system xbyak" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_INIH "Disable system inih" OFF "USE_SYSTEM_LIBS" OFF)
|
||||
CMAKE_DEPENDENT_OPTION(DISABLE_SYSTEM_FDK_AAC_HEADERS "Disable system fdk_aac" 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_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
|
||||
SDL2
|
||||
@@ -55,12 +51,10 @@ set(LIB_VAR_LIST
|
||||
FMT
|
||||
XBYAK
|
||||
INIH
|
||||
FDK_AAC_HEADERS
|
||||
FFMPEG_HEADERS
|
||||
GLSLANG
|
||||
ZSTD
|
||||
ENET
|
||||
CRYPTOPP
|
||||
CUBEB
|
||||
)
|
||||
|
||||
# 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()
|
102
externals/faad2/CMakeLists.txt
vendored
102
externals/faad2/CMakeLists.txt
vendored
@@ -1,102 +0,0 @@
|
||||
# Copy source to build directory for some modifications.
|
||||
set(FAAD2_SOURCE_DIR "${CMAKE_CURRENT_BINARY_DIR}/faad2/libfaad")
|
||||
if (NOT EXISTS "${FAAD2_SOURCE_DIR}")
|
||||
file(COPY faad2/libfaad/ DESTINATION "${FAAD2_SOURCE_DIR}/")
|
||||
|
||||
# These are fixed defines for some reason and not controllable with compile flags.
|
||||
file(READ "${FAAD2_SOURCE_DIR}/common.h" FAAD2_COMMON_H)
|
||||
# Disable SBR decoding since we don't want it for AAC-LC.
|
||||
string(REGEX REPLACE "#define SBR_DEC" "" FAAD2_COMMON_H "${FAAD2_COMMON_H}")
|
||||
# Disable PS decoding. This can cause mono to be upmixed to stereo, which we don't want.
|
||||
string(REGEX REPLACE "#define PS_DEC" "" FAAD2_COMMON_H "${FAAD2_COMMON_H}")
|
||||
file(WRITE "${FAAD2_SOURCE_DIR}/common.h" "${FAAD2_COMMON_H}")
|
||||
endif()
|
||||
|
||||
# Source list from faad2/libfaad/Makefile.am, cut down to just what we need for AAC-LC.
|
||||
add_library(faad2 STATIC EXCLUDE_FROM_ALL
|
||||
"${FAAD2_SOURCE_DIR}/bits.c"
|
||||
"${FAAD2_SOURCE_DIR}/cfft.c"
|
||||
"${FAAD2_SOURCE_DIR}/common.c"
|
||||
"${FAAD2_SOURCE_DIR}/decoder.c"
|
||||
"${FAAD2_SOURCE_DIR}/drc.c"
|
||||
"${FAAD2_SOURCE_DIR}/error.c"
|
||||
"${FAAD2_SOURCE_DIR}/filtbank.c"
|
||||
"${FAAD2_SOURCE_DIR}/huffman.c"
|
||||
"${FAAD2_SOURCE_DIR}/is.c"
|
||||
"${FAAD2_SOURCE_DIR}/mdct.c"
|
||||
"${FAAD2_SOURCE_DIR}/mp4.c"
|
||||
"${FAAD2_SOURCE_DIR}/ms.c"
|
||||
"${FAAD2_SOURCE_DIR}/output.c"
|
||||
"${FAAD2_SOURCE_DIR}/pns.c"
|
||||
"${FAAD2_SOURCE_DIR}/pulse.c"
|
||||
"${FAAD2_SOURCE_DIR}/specrec.c"
|
||||
"${FAAD2_SOURCE_DIR}/syntax.c"
|
||||
"${FAAD2_SOURCE_DIR}/tns.c"
|
||||
)
|
||||
target_include_directories(faad2 PUBLIC faad2/include PRIVATE "${FAAD2_SOURCE_DIR}")
|
||||
|
||||
# Configure compile definitions.
|
||||
|
||||
# Read version from autoconf script for configuring constant.
|
||||
file(READ faad2/configure.ac CONFIGURE_SCRIPT)
|
||||
string(REGEX MATCH "AC_INIT\\(faad2, ([0-9.]+)\\)" _ ${CONFIGURE_SCRIPT})
|
||||
set(FAAD_VERSION ${CMAKE_MATCH_1})
|
||||
message(STATUS "Building faad2 version ${FAAD_VERSION}")
|
||||
|
||||
# Check for functions and headers.
|
||||
include(CheckFunctionExists)
|
||||
include(CheckIncludeFiles)
|
||||
check_function_exists(getpwuid HAVE_GETPWUID)
|
||||
check_function_exists(lrintf HAVE_LRINTF)
|
||||
check_function_exists(memcpy HAVE_MEMCPY)
|
||||
check_function_exists(strchr HAVE_STRCHR)
|
||||
check_function_exists(strsep HAVE_STRSEP)
|
||||
check_include_files(dlfcn.h HAVE_DLFCN_H)
|
||||
check_include_files(errno.h HAVE_ERRNO_H)
|
||||
check_include_files(float.h HAVE_FLOAT_H)
|
||||
check_include_files(inttypes.h HAVE_INTTYPES_H)
|
||||
check_include_files(IOKit/IOKitLib.h HAVE_IOKIT_IOKITLIB_H)
|
||||
check_include_files(limits.h HAVE_LIMITS_H)
|
||||
check_include_files(mathf.h HAVE_MATHF_H)
|
||||
check_include_files(stdint.h HAVE_STDINT_H)
|
||||
check_include_files(stdio.h HAVE_STDIO_H)
|
||||
check_include_files(stdlib.h HAVE_STDLIB_H)
|
||||
check_include_files(strings.h HAVE_STRINGS_H)
|
||||
check_include_files(string.h HAVE_STRING_H)
|
||||
check_include_files(sysfs/libsysfs.h HAVE_SYSFS_LIBSYSFS_H)
|
||||
check_include_files(sys/stat.h HAVE_SYS_STAT_H)
|
||||
check_include_files(sys/time.h HAVE_SYS_TIME_H)
|
||||
check_include_files(sys/types.h HAVE_SYS_TYPES_H)
|
||||
check_include_files(unistd.h HAVE_UNISTD_H)
|
||||
|
||||
# faad2 uses a relative include for its config.h which breaks under CMake.
|
||||
# We can use target_compile_definitions to pass on the configuration instead.
|
||||
target_compile_definitions(faad2 PRIVATE
|
||||
-DFAAD_VERSION=${FAAD_VERSION}
|
||||
-DPACKAGE_VERSION=\"${FAAD_VERSION}\"
|
||||
-DSTDC_HEADERS
|
||||
-DHAVE_GETPWUID=${HAVE_GETPWUID}
|
||||
-DHAVE_LRINTF=${HAVE_LRINTF}
|
||||
-DHAVE_MEMCPY=${HAVE_MEMCPY}
|
||||
-DHAVE_STRCHR=${HAVE_STRCHR}
|
||||
-DHAVE_STRSEP=${HAVE_STRSEP}
|
||||
-DHAVE_DLFCN_H=${HAVE_DLFCN_H}
|
||||
-DHAVE_ERRNO_H=${HAVE_ERRNO_H}
|
||||
-DHAVE_FLOAT_H=${HAVE_FLOAT_H}
|
||||
-DHAVE_INTTYPES_H=${HAVE_INTTYPES_H}
|
||||
-DHAVE_IOKIT_IOKITLIB_H=${HAVE_IOKIT_IOKITLIB_H}
|
||||
-DHAVE_LIMITS_H=${HAVE_LIMITS_H}
|
||||
-DHAVE_MATHF_H=${HAVE_MATHF_H}
|
||||
-DHAVE_STDINT_H=${HAVE_STDINT_H}
|
||||
-DHAVE_STDIO_H=${HAVE_STDIO_H}
|
||||
-DHAVE_STDLIB_H=${HAVE_STDLIB_H}
|
||||
-DHAVE_STRINGS_H=${HAVE_STRINGS_H}
|
||||
-DHAVE_STRING_H=${HAVE_STRING_H}
|
||||
-DHAVE_SYSFS_LIBSYSFS_H=${HAVE_SYSFS_LIBSYSFS_H}
|
||||
-DHAVE_SYS_STAT_H=${HAVE_SYS_STAT_H}
|
||||
-DHAVE_SYS_TIME_H=${HAVE_SYS_TIME_H}
|
||||
-DHAVE_SYS_TYPES_H=${HAVE_SYS_TYPES_H}
|
||||
-DHAVE_UNISTD_H=${HAVE_UNISTD_H}
|
||||
# Only compile for AAC-LC decoding.
|
||||
-DLC_ONLY_DECODER
|
||||
)
|
1
externals/faad2/faad2
vendored
1
externals/faad2/faad2
vendored
Submodule externals/faad2/faad2 deleted from 3918dee560
1
externals/oaknut
vendored
1
externals/oaknut
vendored
Submodule externals/oaknut deleted from e6eecc3f94
@@ -4,11 +4,15 @@ add_library(audio_core STATIC
|
||||
codec.h
|
||||
dsp_interface.cpp
|
||||
dsp_interface.h
|
||||
hle/adts.h
|
||||
hle/adts_reader.cpp
|
||||
hle/common.h
|
||||
hle/decoder.cpp
|
||||
hle/decoder.h
|
||||
hle/faad2_decoder.cpp
|
||||
hle/faad2_decoder.h
|
||||
hle/fdk_decoder.cpp
|
||||
hle/fdk_decoder.h
|
||||
hle/ffmpeg_decoder.cpp
|
||||
hle/ffmpeg_decoder.h
|
||||
hle/filter.cpp
|
||||
hle/filter.h
|
||||
hle/hle.cpp
|
||||
@@ -44,7 +48,36 @@ add_library(audio_core STATIC
|
||||
create_target_directory_groups(audio_core)
|
||||
|
||||
target_link_libraries(audio_core PUBLIC citra_common citra_core)
|
||||
target_link_libraries(audio_core PRIVATE faad2 SoundTouch teakra)
|
||||
target_link_libraries(audio_core PRIVATE SoundTouch teakra)
|
||||
|
||||
if(ENABLE_MF)
|
||||
target_sources(audio_core PRIVATE
|
||||
hle/wmf_decoder.cpp
|
||||
hle/wmf_decoder.h
|
||||
hle/wmf_decoder_utils.cpp
|
||||
hle/wmf_decoder_utils.h
|
||||
)
|
||||
# We dynamically load the required symbols from mf.dll and mfplat.dll but mfuuid is not a dll
|
||||
# just a static library of GUIDS so include that one directly.
|
||||
target_link_libraries(audio_core PRIVATE mfuuid.lib)
|
||||
target_compile_definitions(audio_core PUBLIC HAVE_MF)
|
||||
elseif(ENABLE_AUDIOTOOLBOX)
|
||||
target_sources(audio_core PRIVATE
|
||||
hle/audiotoolbox_decoder.cpp
|
||||
hle/audiotoolbox_decoder.h
|
||||
)
|
||||
find_library(AUDIOTOOLBOX AudioToolbox)
|
||||
target_link_libraries(audio_core PRIVATE ${AUDIOTOOLBOX})
|
||||
target_compile_definitions(audio_core PUBLIC HAVE_AUDIOTOOLBOX)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
target_sources(audio_core PRIVATE
|
||||
hle/mediandk_decoder.cpp
|
||||
hle/mediandk_decoder.h
|
||||
)
|
||||
target_link_libraries(audio_core PRIVATE mediandk)
|
||||
endif()
|
||||
|
||||
if(ENABLE_SDL2)
|
||||
target_link_libraries(audio_core PRIVATE SDL2::SDL2)
|
||||
|
28
src/audio_core/hle/adts.h
Normal file
28
src/audio_core/hle/adts.h
Normal file
@@ -0,0 +1,28 @@
|
||||
// Copyright 2019 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 AudioCore {
|
||||
|
||||
struct ADTSData {
|
||||
u8 header_length = 0;
|
||||
bool mpeg2 = false;
|
||||
u8 profile = 0;
|
||||
u8 channels = 0;
|
||||
u8 channel_idx = 0;
|
||||
u8 framecount = 0;
|
||||
u8 samplerate_idx = 0;
|
||||
u32 length = 0;
|
||||
u32 samplerate = 0;
|
||||
};
|
||||
|
||||
ADTSData ParseADTS(const u8* buffer);
|
||||
|
||||
// last two bytes of MF AAC decoder user data
|
||||
// see https://docs.microsoft.com/en-us/windows/desktop/medfound/aac-decoder#example-media-types
|
||||
u16 MFGetAACTag(const ADTSData& input);
|
||||
|
||||
} // namespace AudioCore
|
79
src/audio_core/hle/adts_reader.cpp
Normal file
79
src/audio_core/hle/adts_reader.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
#include <array>
|
||||
#include "adts.h"
|
||||
#include "common/bit_field.h"
|
||||
|
||||
namespace AudioCore {
|
||||
constexpr std::array<u32, 16> freq_table = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
|
||||
16000, 12000, 11025, 8000, 7350, 0, 0, 0};
|
||||
constexpr std::array<u8, 8> channel_table = {0, 1, 2, 3, 4, 5, 6, 8};
|
||||
|
||||
struct ADTSHeader {
|
||||
union {
|
||||
std::array<u8, 7> raw{};
|
||||
BitFieldBE<52, 12, u64> sync_word;
|
||||
BitFieldBE<51, 1, u64> mpeg2;
|
||||
BitFieldBE<49, 2, u64> layer;
|
||||
BitFieldBE<48, 1, u64> protection_absent;
|
||||
BitFieldBE<46, 2, u64> profile;
|
||||
BitFieldBE<42, 4, u64> samplerate_idx;
|
||||
BitFieldBE<41, 1, u64> private_bit;
|
||||
BitFieldBE<38, 3, u64> channel_idx;
|
||||
BitFieldBE<37, 1, u64> originality;
|
||||
BitFieldBE<36, 1, u64> home;
|
||||
BitFieldBE<35, 1, u64> copyright_id;
|
||||
BitFieldBE<34, 1, u64> copyright_id_start;
|
||||
BitFieldBE<21, 13, u64> frame_length;
|
||||
BitFieldBE<10, 11, u64> buffer_fullness;
|
||||
BitFieldBE<8, 2, u64> frame_count;
|
||||
};
|
||||
};
|
||||
|
||||
ADTSData ParseADTS(const u8* buffer) {
|
||||
ADTSHeader header;
|
||||
memcpy(header.raw.data(), buffer, sizeof(header.raw));
|
||||
|
||||
// sync word 0xfff
|
||||
if (header.sync_word != 0xfff) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ADTSData out{};
|
||||
// bit 16 = no CRC
|
||||
out.header_length = header.protection_absent ? 7 : 9;
|
||||
out.mpeg2 = static_cast<bool>(header.mpeg2);
|
||||
// bit 17 to 18
|
||||
out.profile = static_cast<u8>(header.profile) + 1;
|
||||
// bit 19 to 22
|
||||
out.samplerate_idx = static_cast<u8>(header.samplerate_idx);
|
||||
out.samplerate = header.samplerate_idx > 15 ? 0 : freq_table[header.samplerate_idx];
|
||||
// bit 24 to 26
|
||||
out.channel_idx = static_cast<u8>(header.channel_idx);
|
||||
out.channels = (header.channel_idx > 7) ? 0 : channel_table[header.channel_idx];
|
||||
// bit 55 to 56
|
||||
out.framecount = static_cast<u8>(header.frame_count + 1);
|
||||
// bit 31 to 43
|
||||
out.length = static_cast<u32>(header.frame_length);
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// last two bytes of MF AAC decoder user data
|
||||
// Audio object type (5 bits)
|
||||
// Sample rate profile (4 bits)
|
||||
// Channel configuration profile (4 bits)
|
||||
// Frame length flag (1 bit)
|
||||
// Depends on core coder (1 bit)
|
||||
// Extension flag (1 bit)
|
||||
u16 MFGetAACTag(const ADTSData& input) {
|
||||
u16 tag = 0;
|
||||
|
||||
tag |= input.profile << 11;
|
||||
tag |= input.samplerate_idx << 7;
|
||||
tag |= input.channel_idx << 3;
|
||||
|
||||
return tag;
|
||||
}
|
||||
} // namespace AudioCore
|
264
src/audio_core/hle/audiotoolbox_decoder.cpp
Normal file
264
src/audio_core/hle/audiotoolbox_decoder.cpp
Normal file
@@ -0,0 +1,264 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <AudioToolbox/AudioToolbox.h>
|
||||
#include "audio_core/audio_types.h"
|
||||
#include "audio_core/hle/adts.h"
|
||||
#include "audio_core/hle/audiotoolbox_decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
static constexpr auto bytes_per_sample = sizeof(s16);
|
||||
static constexpr auto aac_frames_per_packet = 1024;
|
||||
static constexpr auto error_out_of_data = -1932;
|
||||
|
||||
class AudioToolboxDecoder::Impl {
|
||||
public:
|
||||
explicit Impl(Memory::MemorySystem& memory);
|
||||
~Impl();
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request);
|
||||
|
||||
private:
|
||||
std::optional<BinaryMessage> Initalize(const BinaryMessage& request);
|
||||
std::optional<BinaryMessage> Decode(const BinaryMessage& request);
|
||||
|
||||
void Clear();
|
||||
bool InitializeDecoder(AudioCore::ADTSData& adts_header);
|
||||
|
||||
static OSStatus DataFunc(AudioConverterRef in_audio_converter, u32* io_number_data_packets,
|
||||
AudioBufferList* io_data,
|
||||
AudioStreamPacketDescription** out_data_packet_description,
|
||||
void* in_user_data);
|
||||
|
||||
Memory::MemorySystem& memory;
|
||||
|
||||
AudioCore::ADTSData adts_config;
|
||||
AudioStreamBasicDescription output_format = {};
|
||||
AudioConverterRef converter = nullptr;
|
||||
|
||||
u8* curr_data = nullptr;
|
||||
u32 curr_data_len = 0;
|
||||
|
||||
AudioStreamPacketDescription packet_description;
|
||||
};
|
||||
|
||||
AudioToolboxDecoder::Impl::Impl(Memory::MemorySystem& memory_) : memory(memory_) {}
|
||||
|
||||
std::optional<BinaryMessage> AudioToolboxDecoder::Impl::Initalize(const BinaryMessage& request) {
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
|
||||
Clear();
|
||||
return response;
|
||||
}
|
||||
|
||||
AudioToolboxDecoder::Impl::~Impl() {
|
||||
Clear();
|
||||
}
|
||||
|
||||
void AudioToolboxDecoder::Impl::Clear() {
|
||||
curr_data = nullptr;
|
||||
curr_data_len = 0;
|
||||
|
||||
adts_config = {};
|
||||
output_format = {};
|
||||
|
||||
if (converter) {
|
||||
AudioConverterDispose(converter);
|
||||
converter = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> AudioToolboxDecoder::Impl::ProcessRequest(
|
||||
const BinaryMessage& request) {
|
||||
if (request.header.codec != DecoderCodec::DecodeAAC) {
|
||||
LOG_ERROR(Audio_DSP, "AudioToolbox AAC Decoder cannot handle such codec: {}",
|
||||
static_cast<u16>(request.header.codec));
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (request.header.cmd) {
|
||||
case DecoderCommand::Init: {
|
||||
return Initalize(request);
|
||||
}
|
||||
case DecoderCommand::EncodeDecode: {
|
||||
return Decode(request);
|
||||
}
|
||||
case DecoderCommand::Shutdown:
|
||||
case DecoderCommand::SaveState:
|
||||
case DecoderCommand::LoadState: {
|
||||
LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
bool AudioToolboxDecoder::Impl::InitializeDecoder(AudioCore::ADTSData& adts_header) {
|
||||
if (converter) {
|
||||
if (adts_config.channels == adts_header.channels &&
|
||||
adts_config.samplerate == adts_header.samplerate) {
|
||||
return true;
|
||||
} else {
|
||||
Clear();
|
||||
}
|
||||
}
|
||||
|
||||
AudioStreamBasicDescription input_format = {
|
||||
.mSampleRate = static_cast<Float64>(adts_header.samplerate),
|
||||
.mFormatID = kAudioFormatMPEG4AAC,
|
||||
.mFramesPerPacket = aac_frames_per_packet,
|
||||
.mChannelsPerFrame = adts_header.channels,
|
||||
};
|
||||
|
||||
u32 bytes_per_frame = input_format.mChannelsPerFrame * bytes_per_sample;
|
||||
output_format = {
|
||||
.mSampleRate = input_format.mSampleRate,
|
||||
.mFormatID = kAudioFormatLinearPCM,
|
||||
.mFormatFlags = kLinearPCMFormatFlagIsSignedInteger | kLinearPCMFormatFlagIsPacked,
|
||||
.mBytesPerPacket = bytes_per_frame,
|
||||
.mFramesPerPacket = 1,
|
||||
.mBytesPerFrame = bytes_per_frame,
|
||||
.mChannelsPerFrame = input_format.mChannelsPerFrame,
|
||||
.mBitsPerChannel = bytes_per_sample * 8,
|
||||
};
|
||||
|
||||
auto status = AudioConverterNew(&input_format, &output_format, &converter);
|
||||
if (status != noErr) {
|
||||
LOG_ERROR(Audio_DSP, "Could not create AAC audio converter: {}", status);
|
||||
Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
adts_config = adts_header;
|
||||
return true;
|
||||
}
|
||||
|
||||
OSStatus AudioToolboxDecoder::Impl::DataFunc(
|
||||
AudioConverterRef in_audio_converter, u32* io_number_data_packets, AudioBufferList* io_data,
|
||||
AudioStreamPacketDescription** out_data_packet_description, void* in_user_data) {
|
||||
auto impl = reinterpret_cast<Impl*>(in_user_data);
|
||||
if (!impl || !impl->curr_data || impl->curr_data_len == 0) {
|
||||
*io_number_data_packets = 0;
|
||||
return error_out_of_data;
|
||||
}
|
||||
|
||||
io_data->mNumberBuffers = 1;
|
||||
io_data->mBuffers[0].mNumberChannels = 0;
|
||||
io_data->mBuffers[0].mDataByteSize = impl->curr_data_len;
|
||||
io_data->mBuffers[0].mData = impl->curr_data;
|
||||
*io_number_data_packets = 1;
|
||||
|
||||
if (out_data_packet_description != nullptr) {
|
||||
impl->packet_description.mStartOffset = 0;
|
||||
impl->packet_description.mVariableFramesInPacket = 0;
|
||||
impl->packet_description.mDataByteSize = impl->curr_data_len;
|
||||
*out_data_packet_description = &impl->packet_description;
|
||||
}
|
||||
|
||||
impl->curr_data = nullptr;
|
||||
impl->curr_data_len = 0;
|
||||
|
||||
return noErr;
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> AudioToolboxDecoder::Impl::Decode(const BinaryMessage& request) {
|
||||
BinaryMessage response{};
|
||||
response.header.codec = request.header.codec;
|
||||
response.header.cmd = request.header.cmd;
|
||||
response.decode_aac_response.size = request.decode_aac_request.size;
|
||||
|
||||
if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.src_addr + request.decode_aac_request.size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}",
|
||||
request.decode_aac_request.src_addr);
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto data =
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR);
|
||||
auto adts_header = AudioCore::ParseADTS(data);
|
||||
curr_data = data + adts_header.header_length;
|
||||
curr_data_len = request.decode_aac_request.size - adts_header.header_length;
|
||||
|
||||
if (!InitializeDecoder(adts_header)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// Up to 2048 samples, up to 2 channels each
|
||||
s16 decoder_output[4096];
|
||||
AudioBufferList out_buffer{1,
|
||||
{{
|
||||
output_format.mChannelsPerFrame,
|
||||
sizeof(decoder_output),
|
||||
decoder_output,
|
||||
}}};
|
||||
|
||||
u32 num_packets = sizeof(decoder_output) / output_format.mBytesPerPacket;
|
||||
auto status = AudioConverterFillComplexBuffer(converter, DataFunc, this, &num_packets,
|
||||
&out_buffer, nullptr);
|
||||
if (status != noErr && status != error_out_of_data) {
|
||||
LOG_ERROR(Audio_DSP, "Could not decode AAC data: {}", status);
|
||||
Clear();
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
// De-interleave samples.
|
||||
std::array<std::vector<s16>, 2> out_streams;
|
||||
auto num_frames = num_packets * output_format.mFramesPerPacket;
|
||||
for (u32 frame = 0; frame < num_frames; frame++) {
|
||||
for (u32 ch = 0; ch < output_format.mChannelsPerFrame; ch++) {
|
||||
out_streams[ch].push_back(
|
||||
decoder_output[(frame * output_format.mChannelsPerFrame) + ch]);
|
||||
}
|
||||
}
|
||||
|
||||
curr_data = nullptr;
|
||||
curr_data_len = 0;
|
||||
|
||||
response.decode_aac_response.sample_rate =
|
||||
GetSampleRateEnum(static_cast<u32>(output_format.mSampleRate));
|
||||
response.decode_aac_response.num_channels = output_format.mChannelsPerFrame;
|
||||
response.decode_aac_response.num_samples = num_frames;
|
||||
|
||||
// transfer the decoded buffer from vector to the FCRAM
|
||||
for (std::size_t ch = 0; ch < out_streams.size(); ch++) {
|
||||
if (!out_streams[ch].empty()) {
|
||||
auto byte_size = out_streams[ch].size() * bytes_per_sample;
|
||||
auto dst = ch == 0 ? request.decode_aac_request.dst_addr_ch0
|
||||
: request.decode_aac_request.dst_addr_ch1;
|
||||
if (dst < Memory::FCRAM_PADDR ||
|
||||
dst + byte_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch{} {:08x}", ch, dst);
|
||||
return {};
|
||||
}
|
||||
std::memcpy(memory.GetFCRAMPointer(dst - Memory::FCRAM_PADDR), out_streams[ch].data(),
|
||||
byte_size);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
AudioToolboxDecoder::AudioToolboxDecoder(Memory::MemorySystem& memory)
|
||||
: impl(std::make_unique<Impl>(memory)) {}
|
||||
|
||||
AudioToolboxDecoder::~AudioToolboxDecoder() = default;
|
||||
|
||||
std::optional<BinaryMessage> AudioToolboxDecoder::ProcessRequest(const BinaryMessage& request) {
|
||||
return impl->ProcessRequest(request);
|
||||
}
|
||||
|
||||
bool AudioToolboxDecoder::IsValid() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::HLE
|
@@ -8,10 +8,10 @@
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class FAAD2Decoder final : public DecoderBase {
|
||||
class AudioToolboxDecoder final : public DecoderBase {
|
||||
public:
|
||||
explicit FAAD2Decoder(Memory::MemorySystem& memory);
|
||||
~FAAD2Decoder() override;
|
||||
explicit AudioToolboxDecoder(Memory::MemorySystem& memory);
|
||||
~AudioToolboxDecoder() override;
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override;
|
||||
bool IsValid() const override;
|
||||
|
@@ -1,186 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <neaacdec.h>
|
||||
#include "audio_core/hle/faad2_decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class FAAD2Decoder::Impl {
|
||||
public:
|
||||
explicit Impl(Memory::MemorySystem& memory);
|
||||
~Impl();
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request);
|
||||
bool IsValid() const {
|
||||
return decoder != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<BinaryMessage> Initalize(const BinaryMessage& request);
|
||||
|
||||
std::optional<BinaryMessage> Decode(const BinaryMessage& request);
|
||||
|
||||
Memory::MemorySystem& memory;
|
||||
|
||||
NeAACDecHandle decoder = nullptr;
|
||||
};
|
||||
|
||||
FAAD2Decoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) {
|
||||
decoder = NeAACDecOpen();
|
||||
if (decoder == nullptr) {
|
||||
LOG_CRITICAL(Audio_DSP, "Could not open FAAD2 decoder.");
|
||||
return;
|
||||
}
|
||||
|
||||
auto config = NeAACDecGetCurrentConfiguration(decoder);
|
||||
config->defObjectType = LC;
|
||||
config->outputFormat = FAAD_FMT_16BIT;
|
||||
if (!NeAACDecSetConfiguration(decoder, config)) {
|
||||
LOG_CRITICAL(Audio_DSP, "Could not configure FAAD2 decoder.");
|
||||
NeAACDecClose(decoder);
|
||||
decoder = nullptr;
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Audio_DSP, "Created FAAD2 AAC decoder.");
|
||||
}
|
||||
|
||||
FAAD2Decoder::Impl::~Impl() {
|
||||
if (decoder) {
|
||||
NeAACDecClose(decoder);
|
||||
decoder = nullptr;
|
||||
|
||||
LOG_INFO(Audio_DSP, "Destroyed FAAD2 AAC decoder.");
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FAAD2Decoder::Impl::ProcessRequest(const BinaryMessage& request) {
|
||||
if (request.header.codec != DecoderCodec::DecodeAAC) {
|
||||
LOG_ERROR(Audio_DSP, "FAAD2 AAC Decoder cannot handle such codec: {}",
|
||||
static_cast<u16>(request.header.codec));
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (request.header.cmd) {
|
||||
case DecoderCommand::Init: {
|
||||
return Initalize(request);
|
||||
}
|
||||
case DecoderCommand::EncodeDecode: {
|
||||
return Decode(request);
|
||||
}
|
||||
case DecoderCommand::Shutdown:
|
||||
case DecoderCommand::SaveState:
|
||||
case DecoderCommand::LoadState: {
|
||||
LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FAAD2Decoder::Impl::Initalize(const BinaryMessage& request) {
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FAAD2Decoder::Impl::Decode(const BinaryMessage& request) {
|
||||
BinaryMessage response{};
|
||||
response.header.codec = request.header.codec;
|
||||
response.header.cmd = request.header.cmd;
|
||||
response.decode_aac_response.size = request.decode_aac_request.size;
|
||||
// This is a hack to continue games when a failure occurs.
|
||||
response.decode_aac_response.sample_rate = DecoderSampleRate::Rate48000;
|
||||
response.decode_aac_response.num_channels = 2;
|
||||
response.decode_aac_response.num_samples = 1024;
|
||||
|
||||
if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.src_addr + request.decode_aac_request.size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}",
|
||||
request.decode_aac_request.src_addr);
|
||||
return response;
|
||||
}
|
||||
u8* data = memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR);
|
||||
u32 data_len = request.decode_aac_request.size;
|
||||
|
||||
unsigned long sample_rate;
|
||||
u8 num_channels;
|
||||
auto init_result = NeAACDecInit(decoder, data, data_len, &sample_rate, &num_channels);
|
||||
if (init_result < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Could not initialize FAAD2 AAC decoder for request: {}", init_result);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Advance past the frame header if needed.
|
||||
data += init_result;
|
||||
data_len -= init_result;
|
||||
|
||||
std::array<std::vector<s16>, 2> out_streams;
|
||||
|
||||
while (data_len > 0) {
|
||||
NeAACDecFrameInfo frame_info;
|
||||
auto curr_sample_buffer =
|
||||
static_cast<s16*>(NeAACDecDecode(decoder, &frame_info, data, data_len));
|
||||
if (curr_sample_buffer == nullptr || frame_info.error != 0) {
|
||||
LOG_ERROR(Audio_DSP, "Failed to decode AAC buffer using FAAD2: {}", frame_info.error);
|
||||
return response;
|
||||
}
|
||||
|
||||
// Split the decode result into channels.
|
||||
u32 num_samples = frame_info.samples / frame_info.channels;
|
||||
for (u32 sample = 0; sample < num_samples; sample++) {
|
||||
for (u32 ch = 0; ch < frame_info.channels; ch++) {
|
||||
out_streams[ch].push_back(curr_sample_buffer[(sample * frame_info.channels) + ch]);
|
||||
}
|
||||
}
|
||||
|
||||
data += frame_info.bytesconsumed;
|
||||
data_len -= frame_info.bytesconsumed;
|
||||
}
|
||||
|
||||
// Transfer the decoded buffer from vector to the FCRAM.
|
||||
for (std::size_t ch = 0; ch < out_streams.size(); ch++) {
|
||||
if (out_streams[ch].empty()) {
|
||||
continue;
|
||||
}
|
||||
auto byte_size = out_streams[ch].size() * sizeof(s16);
|
||||
auto dst = ch == 0 ? request.decode_aac_request.dst_addr_ch0
|
||||
: request.decode_aac_request.dst_addr_ch1;
|
||||
if (dst < Memory::FCRAM_PADDR ||
|
||||
dst + byte_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch{} {:08x}", ch, dst);
|
||||
return response;
|
||||
}
|
||||
std::memcpy(memory.GetFCRAMPointer(dst - Memory::FCRAM_PADDR), out_streams[ch].data(),
|
||||
byte_size);
|
||||
}
|
||||
|
||||
// Set the output frame info.
|
||||
response.decode_aac_response.sample_rate = GetSampleRateEnum(sample_rate);
|
||||
response.decode_aac_response.num_channels = num_channels;
|
||||
response.decode_aac_response.num_samples = static_cast<u32_le>(out_streams[0].size());
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
FAAD2Decoder::FAAD2Decoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {}
|
||||
|
||||
FAAD2Decoder::~FAAD2Decoder() = default;
|
||||
|
||||
std::optional<BinaryMessage> FAAD2Decoder::ProcessRequest(const BinaryMessage& request) {
|
||||
return impl->ProcessRequest(request);
|
||||
}
|
||||
|
||||
bool FAAD2Decoder::IsValid() const {
|
||||
return impl->IsValid();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::HLE
|
236
src/audio_core/hle/fdk_decoder.cpp
Normal file
236
src/audio_core/hle/fdk_decoder.cpp
Normal file
@@ -0,0 +1,236 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/hle/fdk_decoder.h"
|
||||
#include "common/dynamic_library/fdk-aac.h"
|
||||
|
||||
using namespace DynamicLibrary;
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class FDKDecoder::Impl {
|
||||
public:
|
||||
explicit Impl(Memory::MemorySystem& memory);
|
||||
~Impl();
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request);
|
||||
bool IsValid() const {
|
||||
return decoder != nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<BinaryMessage> Initalize(const BinaryMessage& request);
|
||||
|
||||
std::optional<BinaryMessage> Decode(const BinaryMessage& request);
|
||||
|
||||
void Clear();
|
||||
|
||||
Memory::MemorySystem& memory;
|
||||
|
||||
HANDLE_AACDECODER decoder = nullptr;
|
||||
};
|
||||
|
||||
FDKDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) {
|
||||
if (!FdkAac::LoadFdkAac()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// allocate an array of LIB_INFO structures
|
||||
// if we don't pre-fill the whole segment with zeros, when we call `aacDecoder_GetLibInfo`
|
||||
// it will segfault, upon investigation, there is some code in fdk_aac depends on your initial
|
||||
// values in this array
|
||||
LIB_INFO decoder_info[FDK_MODULE_LAST] = {};
|
||||
// get library information and fill the struct
|
||||
if (FdkAac::aacDecoder_GetLibInfo(decoder_info) != 0) {
|
||||
LOG_ERROR(Audio_DSP, "Failed to retrieve fdk_aac library information!");
|
||||
return;
|
||||
}
|
||||
|
||||
LOG_INFO(Audio_DSP, "Using fdk_aac version {} (build date: {})", decoder_info[0].versionStr,
|
||||
decoder_info[0].build_date);
|
||||
|
||||
// choose the input format when initializing: 1 layer of ADTS
|
||||
decoder = FdkAac::aacDecoder_Open(TRANSPORT_TYPE::TT_MP4_ADTS, 1);
|
||||
// set maximum output channel to two (stereo)
|
||||
// if the input samples have more channels, fdk_aac will perform a downmix
|
||||
AAC_DECODER_ERROR ret = FdkAac::aacDecoder_SetParam(decoder, AAC_PCM_MAX_OUTPUT_CHANNELS, 2);
|
||||
if (ret != AAC_DEC_OK) {
|
||||
// unable to set this parameter reflects the decoder implementation might be broken
|
||||
// we'd better shuts down everything
|
||||
FdkAac::aacDecoder_Close(decoder);
|
||||
decoder = nullptr;
|
||||
LOG_ERROR(Audio_DSP, "Unable to set downmix parameter: {}", ret);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FDKDecoder::Impl::Initalize(const BinaryMessage& request) {
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
|
||||
if (decoder) {
|
||||
LOG_INFO(Audio_DSP, "FDK Decoder initialized");
|
||||
Clear();
|
||||
} else {
|
||||
LOG_ERROR(Audio_DSP, "Decoder not initialized");
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
FDKDecoder::Impl::~Impl() {
|
||||
if (decoder) {
|
||||
FdkAac::aacDecoder_Close(decoder);
|
||||
}
|
||||
}
|
||||
|
||||
void FDKDecoder::Impl::Clear() {
|
||||
s16 decoder_output[8192];
|
||||
// flush and re-sync the decoder, discarding the internal buffer
|
||||
// we actually don't care if this succeeds or not
|
||||
// FLUSH - flush internal buffer
|
||||
// INTR - treat the current internal buffer as discontinuous
|
||||
// CONCEAL - try to interpolate and smooth out the samples
|
||||
if (decoder) {
|
||||
FdkAac::aacDecoder_DecodeFrame(decoder, decoder_output, 8192,
|
||||
AACDEC_FLUSH & AACDEC_INTR & AACDEC_CONCEAL);
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FDKDecoder::Impl::ProcessRequest(const BinaryMessage& request) {
|
||||
if (request.header.codec != DecoderCodec::DecodeAAC) {
|
||||
LOG_ERROR(Audio_DSP, "FDK AAC Decoder cannot handle such codec: {}",
|
||||
static_cast<u16>(request.header.codec));
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (request.header.cmd) {
|
||||
case DecoderCommand::Init: {
|
||||
return Initalize(request);
|
||||
}
|
||||
case DecoderCommand::EncodeDecode: {
|
||||
return Decode(request);
|
||||
}
|
||||
case DecoderCommand::Shutdown:
|
||||
case DecoderCommand::SaveState:
|
||||
case DecoderCommand::LoadState: {
|
||||
LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FDKDecoder::Impl::Decode(const BinaryMessage& request) {
|
||||
BinaryMessage response{};
|
||||
response.header.codec = request.header.codec;
|
||||
response.header.cmd = request.header.cmd;
|
||||
response.decode_aac_response.size = request.decode_aac_request.size;
|
||||
|
||||
if (!decoder) {
|
||||
LOG_DEBUG(Audio_DSP, "Decoder not initalized");
|
||||
// This is a hack to continue games that are not compiled with the aac codec
|
||||
response.decode_aac_response.num_channels = 2;
|
||||
response.decode_aac_response.num_samples = 1024;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.src_addr + request.decode_aac_request.size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}",
|
||||
request.decode_aac_request.src_addr);
|
||||
return {};
|
||||
}
|
||||
u8* data = memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR);
|
||||
|
||||
std::array<std::vector<s16>, 2> out_streams;
|
||||
|
||||
u32 data_size = request.decode_aac_request.size;
|
||||
|
||||
// decoding loops
|
||||
AAC_DECODER_ERROR result = AAC_DEC_OK;
|
||||
// Up to 2048 samples, up to 2 channels each
|
||||
s16 decoder_output[4096];
|
||||
// note that we don't free this pointer as it is automatically freed by fdk_aac
|
||||
CStreamInfo* stream_info;
|
||||
// how many bytes to be queued into the decoder, decrementing from the buffer size
|
||||
u32 buffer_remaining = data_size;
|
||||
// alias the data_size as an u32
|
||||
u32 input_size = data_size;
|
||||
|
||||
while (buffer_remaining) {
|
||||
// queue the input buffer, fdk_aac will automatically slice out the buffer it needs
|
||||
// from the input buffer
|
||||
result = FdkAac::aacDecoder_Fill(decoder, &data, &input_size, &buffer_remaining);
|
||||
if (result != AAC_DEC_OK) {
|
||||
// there are some issues when queuing the input buffer
|
||||
LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples");
|
||||
return std::nullopt;
|
||||
}
|
||||
// get output from decoder
|
||||
result = FdkAac::aacDecoder_DecodeFrame(decoder, decoder_output,
|
||||
sizeof(decoder_output) / sizeof(s16), 0);
|
||||
if (result == AAC_DEC_OK) {
|
||||
// get the stream information
|
||||
stream_info = FdkAac::aacDecoder_GetStreamInfo(decoder);
|
||||
// fill the stream information for binary response
|
||||
response.decode_aac_response.sample_rate = GetSampleRateEnum(stream_info->sampleRate);
|
||||
response.decode_aac_response.num_channels = stream_info->numChannels;
|
||||
response.decode_aac_response.num_samples = stream_info->frameSize;
|
||||
// fill the output
|
||||
// the sample size = frame_size * channel_counts
|
||||
for (int sample = 0; sample < stream_info->frameSize; sample++) {
|
||||
for (int ch = 0; ch < stream_info->numChannels; ch++) {
|
||||
out_streams[ch].push_back(
|
||||
decoder_output[(sample * stream_info->numChannels) + ch]);
|
||||
}
|
||||
}
|
||||
} else if (result == AAC_DEC_TRANSPORT_SYNC_ERROR) {
|
||||
// decoder has some synchronization problems, try again with new samples,
|
||||
// using old samples might trigger this error again
|
||||
continue;
|
||||
} else {
|
||||
LOG_ERROR(Audio_DSP, "Error decoding the sample: {}", result);
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
// transfer the decoded buffer from vector to the FCRAM
|
||||
for (std::size_t ch = 0; ch < out_streams.size(); ch++) {
|
||||
if (!out_streams[ch].empty()) {
|
||||
auto byte_size = out_streams[ch].size() * sizeof(s16);
|
||||
auto dst = ch == 0 ? request.decode_aac_request.dst_addr_ch0
|
||||
: request.decode_aac_request.dst_addr_ch1;
|
||||
if (dst < Memory::FCRAM_PADDR ||
|
||||
dst + byte_size > Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch{} {:08x}", ch, dst);
|
||||
return {};
|
||||
}
|
||||
std::memcpy(memory.GetFCRAMPointer(dst - Memory::FCRAM_PADDR), out_streams[ch].data(),
|
||||
byte_size);
|
||||
}
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
FDKDecoder::FDKDecoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {}
|
||||
|
||||
FDKDecoder::~FDKDecoder() = default;
|
||||
|
||||
std::optional<BinaryMessage> FDKDecoder::ProcessRequest(const BinaryMessage& request) {
|
||||
return impl->ProcessRequest(request);
|
||||
}
|
||||
|
||||
bool FDKDecoder::IsValid() const {
|
||||
return impl->IsValid();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::HLE
|
23
src/audio_core/hle/fdk_decoder.h
Normal file
23
src/audio_core/hle/fdk_decoder.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/hle/decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class FDKDecoder final : public DecoderBase {
|
||||
public:
|
||||
explicit FDKDecoder(Memory::MemorySystem& memory);
|
||||
~FDKDecoder() override;
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override;
|
||||
bool IsValid() const override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::HLE
|
290
src/audio_core/hle/ffmpeg_decoder.cpp
Normal file
290
src/audio_core/hle/ffmpeg_decoder.cpp
Normal file
@@ -0,0 +1,290 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/hle/ffmpeg_decoder.h"
|
||||
#include "common/dynamic_library/ffmpeg.h"
|
||||
|
||||
using namespace DynamicLibrary;
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class FFMPEGDecoder::Impl {
|
||||
public:
|
||||
explicit Impl(Memory::MemorySystem& memory);
|
||||
~Impl();
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request);
|
||||
bool IsValid() const {
|
||||
return have_ffmpeg_dl;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<BinaryMessage> Initalize(const BinaryMessage& request);
|
||||
|
||||
void Clear();
|
||||
|
||||
std::optional<BinaryMessage> Decode(const BinaryMessage& request);
|
||||
|
||||
struct AVPacketDeleter {
|
||||
void operator()(AVPacket* packet) const {
|
||||
FFmpeg::av_packet_free(&packet);
|
||||
}
|
||||
};
|
||||
|
||||
struct AVCodecContextDeleter {
|
||||
void operator()(AVCodecContext* context) const {
|
||||
FFmpeg::avcodec_free_context(&context);
|
||||
}
|
||||
};
|
||||
|
||||
struct AVCodecParserContextDeleter {
|
||||
void operator()(AVCodecParserContext* parser) const {
|
||||
FFmpeg::av_parser_close(parser);
|
||||
}
|
||||
};
|
||||
|
||||
struct AVFrameDeleter {
|
||||
void operator()(AVFrame* frame) const {
|
||||
FFmpeg::av_frame_free(&frame);
|
||||
}
|
||||
};
|
||||
|
||||
bool initalized = false;
|
||||
bool have_ffmpeg_dl;
|
||||
|
||||
Memory::MemorySystem& memory;
|
||||
|
||||
const AVCodec* codec;
|
||||
std::unique_ptr<AVCodecContext, AVCodecContextDeleter> av_context;
|
||||
std::unique_ptr<AVCodecParserContext, AVCodecParserContextDeleter> parser;
|
||||
std::unique_ptr<AVPacket, AVPacketDeleter> av_packet;
|
||||
std::unique_ptr<AVFrame, AVFrameDeleter> decoded_frame;
|
||||
};
|
||||
|
||||
FFMPEGDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) {
|
||||
have_ffmpeg_dl = FFmpeg::LoadFFmpeg();
|
||||
}
|
||||
|
||||
FFMPEGDecoder::Impl::~Impl() = default;
|
||||
|
||||
std::optional<BinaryMessage> FFMPEGDecoder::Impl::ProcessRequest(const BinaryMessage& request) {
|
||||
if (request.header.codec != DecoderCodec::DecodeAAC) {
|
||||
LOG_ERROR(Audio_DSP, "Got wrong codec {}", static_cast<u16>(request.header.codec));
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (request.header.cmd) {
|
||||
case DecoderCommand::Init: {
|
||||
return Initalize(request);
|
||||
}
|
||||
case DecoderCommand::EncodeDecode: {
|
||||
return Decode(request);
|
||||
}
|
||||
case DecoderCommand::Shutdown:
|
||||
case DecoderCommand::SaveState:
|
||||
case DecoderCommand::LoadState: {
|
||||
LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FFMPEGDecoder::Impl::Initalize(const BinaryMessage& request) {
|
||||
if (initalized) {
|
||||
Clear();
|
||||
}
|
||||
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
|
||||
if (!have_ffmpeg_dl) {
|
||||
return response;
|
||||
}
|
||||
|
||||
av_packet.reset(FFmpeg::av_packet_alloc());
|
||||
|
||||
codec = FFmpeg::avcodec_find_decoder(AV_CODEC_ID_AAC);
|
||||
if (!codec) {
|
||||
LOG_ERROR(Audio_DSP, "Codec not found\n");
|
||||
return response;
|
||||
}
|
||||
|
||||
parser.reset(FFmpeg::av_parser_init(codec->id));
|
||||
if (!parser) {
|
||||
LOG_ERROR(Audio_DSP, "Parser not found\n");
|
||||
return response;
|
||||
}
|
||||
|
||||
av_context.reset(FFmpeg::avcodec_alloc_context3(codec));
|
||||
if (!av_context) {
|
||||
LOG_ERROR(Audio_DSP, "Could not allocate audio codec context\n");
|
||||
return response;
|
||||
}
|
||||
|
||||
if (FFmpeg::avcodec_open2(av_context.get(), codec, nullptr) < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Could not open codec\n");
|
||||
return response;
|
||||
}
|
||||
|
||||
initalized = true;
|
||||
return response;
|
||||
}
|
||||
|
||||
void FFMPEGDecoder::Impl::Clear() {
|
||||
if (!have_ffmpeg_dl) {
|
||||
return;
|
||||
}
|
||||
|
||||
av_context.reset();
|
||||
parser.reset();
|
||||
decoded_frame.reset();
|
||||
av_packet.reset();
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> FFMPEGDecoder::Impl::Decode(const BinaryMessage& request) {
|
||||
BinaryMessage response{};
|
||||
response.header.codec = request.header.codec;
|
||||
response.header.cmd = request.header.cmd;
|
||||
response.decode_aac_response.size = request.decode_aac_request.size;
|
||||
|
||||
if (!initalized) {
|
||||
LOG_DEBUG(Audio_DSP, "Decoder not initalized");
|
||||
// This is a hack to continue games that are not compiled with the aac codec
|
||||
response.decode_aac_response.num_channels = 2;
|
||||
response.decode_aac_response.num_samples = 1024;
|
||||
return response;
|
||||
}
|
||||
|
||||
if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.src_addr + request.decode_aac_request.size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}",
|
||||
request.decode_aac_request.src_addr);
|
||||
return {};
|
||||
}
|
||||
u8* data = memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR);
|
||||
|
||||
std::array<std::vector<u8>, 2> out_streams;
|
||||
|
||||
std::size_t data_size = request.decode_aac_request.size;
|
||||
while (data_size > 0) {
|
||||
if (!decoded_frame) {
|
||||
decoded_frame.reset(FFmpeg::av_frame_alloc());
|
||||
if (!decoded_frame) {
|
||||
LOG_ERROR(Audio_DSP, "Could not allocate audio frame");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
int ret = FFmpeg::av_parser_parse2(parser.get(), av_context.get(), &av_packet->data,
|
||||
&av_packet->size, data, static_cast<int>(data_size),
|
||||
AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Error while parsing");
|
||||
return {};
|
||||
}
|
||||
data += ret;
|
||||
data_size -= ret;
|
||||
|
||||
ret = FFmpeg::avcodec_send_packet(av_context.get(), av_packet.get());
|
||||
if (ret < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Error submitting the packet to the decoder");
|
||||
return {};
|
||||
}
|
||||
|
||||
if (av_packet->size) {
|
||||
while (ret >= 0) {
|
||||
ret = FFmpeg::avcodec_receive_frame(av_context.get(), decoded_frame.get());
|
||||
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
|
||||
break;
|
||||
else if (ret < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Error during decoding");
|
||||
return {};
|
||||
}
|
||||
int bytes_per_sample = FFmpeg::av_get_bytes_per_sample(av_context->sample_fmt);
|
||||
if (bytes_per_sample < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Failed to calculate data size");
|
||||
return {};
|
||||
}
|
||||
|
||||
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 100)
|
||||
auto num_channels = static_cast<u32>(decoded_frame->ch_layout.nb_channels);
|
||||
#else
|
||||
auto num_channels = static_cast<u32>(decoded_frame->channels);
|
||||
#endif
|
||||
|
||||
ASSERT(num_channels <= out_streams.size());
|
||||
|
||||
std::size_t size = bytes_per_sample * (decoded_frame->nb_samples);
|
||||
|
||||
response.decode_aac_response.sample_rate =
|
||||
GetSampleRateEnum(decoded_frame->sample_rate);
|
||||
response.decode_aac_response.num_channels = num_channels;
|
||||
response.decode_aac_response.num_samples += decoded_frame->nb_samples;
|
||||
|
||||
// FFmpeg converts to 32 signed floating point PCM, we need s16 PCM so we need to
|
||||
// convert it
|
||||
f32 val_float;
|
||||
for (std::size_t current_pos(0); current_pos < size;) {
|
||||
for (std::size_t channel(0); channel < num_channels; channel++) {
|
||||
std::memcpy(&val_float, decoded_frame->data[channel] + current_pos,
|
||||
sizeof(val_float));
|
||||
val_float = std::clamp(val_float, -1.0f, 1.0f);
|
||||
s16 val = static_cast<s16>(0x7FFF * val_float);
|
||||
out_streams[channel].push_back(val & 0xFF);
|
||||
out_streams[channel].push_back(val >> 8);
|
||||
}
|
||||
current_pos += sizeof(val_float);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (out_streams[0].size() != 0) {
|
||||
if (request.decode_aac_request.dst_addr_ch0 < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.dst_addr_ch0 + out_streams[0].size() >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}",
|
||||
request.decode_aac_request.dst_addr_ch0);
|
||||
return {};
|
||||
}
|
||||
std::memcpy(
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch0 - Memory::FCRAM_PADDR),
|
||||
out_streams[0].data(), out_streams[0].size());
|
||||
}
|
||||
|
||||
if (out_streams[1].size() != 0) {
|
||||
if (request.decode_aac_request.dst_addr_ch1 < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.dst_addr_ch1 + out_streams[1].size() >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}",
|
||||
request.decode_aac_request.dst_addr_ch1);
|
||||
return {};
|
||||
}
|
||||
std::memcpy(
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch1 - Memory::FCRAM_PADDR),
|
||||
out_streams[1].data(), out_streams[1].size());
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
FFMPEGDecoder::FFMPEGDecoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {}
|
||||
|
||||
FFMPEGDecoder::~FFMPEGDecoder() = default;
|
||||
|
||||
std::optional<BinaryMessage> FFMPEGDecoder::ProcessRequest(const BinaryMessage& request) {
|
||||
return impl->ProcessRequest(request);
|
||||
}
|
||||
|
||||
bool FFMPEGDecoder::IsValid() const {
|
||||
return impl->IsValid();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::HLE
|
23
src/audio_core/hle/ffmpeg_decoder.h
Normal file
23
src/audio_core/hle/ffmpeg_decoder.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/hle/decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class FFMPEGDecoder final : public DecoderBase {
|
||||
public:
|
||||
explicit FFMPEGDecoder(Memory::MemorySystem& memory);
|
||||
~FFMPEGDecoder() override;
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override;
|
||||
bool IsValid() const override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::HLE
|
@@ -8,15 +8,23 @@
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include <boost/serialization/weak_ptr.hpp>
|
||||
#include "audio_core/audio_types.h"
|
||||
#include "common/archives.h"
|
||||
#ifdef HAVE_MF
|
||||
#include "audio_core/hle/wmf_decoder.h"
|
||||
#elif HAVE_AUDIOTOOLBOX
|
||||
#include "audio_core/hle/audiotoolbox_decoder.h"
|
||||
#elif ANDROID
|
||||
#include "audio_core/hle/mediandk_decoder.h"
|
||||
#endif
|
||||
#include "audio_core/hle/common.h"
|
||||
#include "audio_core/hle/decoder.h"
|
||||
#include "audio_core/hle/faad2_decoder.h"
|
||||
#include "audio_core/hle/fdk_decoder.h"
|
||||
#include "audio_core/hle/ffmpeg_decoder.h"
|
||||
#include "audio_core/hle/hle.h"
|
||||
#include "audio_core/hle/mixers.h"
|
||||
#include "audio_core/hle/shared_memory.h"
|
||||
#include "audio_core/hle/source.h"
|
||||
#include "audio_core/sink.h"
|
||||
#include "common/archives.h"
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/hash.h"
|
||||
@@ -113,8 +121,26 @@ private:
|
||||
|
||||
static std::vector<std::function<std::unique_ptr<HLE::DecoderBase>(Memory::MemorySystem&)>>
|
||||
decoder_backends = {
|
||||
#if defined(HAVE_MF)
|
||||
[](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> {
|
||||
return std::make_unique<HLE::FAAD2Decoder>(memory);
|
||||
return std::make_unique<HLE::WMFDecoder>(memory);
|
||||
},
|
||||
#endif
|
||||
#if defined(HAVE_AUDIOTOOLBOX)
|
||||
[](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> {
|
||||
return std::make_unique<HLE::AudioToolboxDecoder>(memory);
|
||||
},
|
||||
#endif
|
||||
#if ANDROID
|
||||
[](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> {
|
||||
return std::make_unique<HLE::MediaNDKDecoder>(memory);
|
||||
},
|
||||
#endif
|
||||
[](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> {
|
||||
return std::make_unique<HLE::FDKDecoder>(memory);
|
||||
},
|
||||
[](Memory::MemorySystem& memory) -> std::unique_ptr<HLE::DecoderBase> {
|
||||
return std::make_unique<HLE::FFMPEGDecoder>(memory);
|
||||
},
|
||||
};
|
||||
|
||||
|
253
src/audio_core/hle/mediandk_decoder.cpp
Normal file
253
src/audio_core/hle/mediandk_decoder.cpp
Normal file
@@ -0,0 +1,253 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <media/NdkMediaCodec.h>
|
||||
#include <media/NdkMediaError.h>
|
||||
#include <media/NdkMediaFormat.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "audio_core/hle/adts.h"
|
||||
#include "audio_core/hle/mediandk_decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
struct AMediaCodecRelease {
|
||||
void operator()(AMediaCodec* codec) const {
|
||||
AMediaCodec_stop(codec);
|
||||
AMediaCodec_delete(codec);
|
||||
};
|
||||
};
|
||||
|
||||
class MediaNDKDecoder::Impl {
|
||||
public:
|
||||
explicit Impl(Memory::MemorySystem& memory);
|
||||
~Impl();
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request);
|
||||
|
||||
bool SetMediaType(const AudioCore::ADTSData& adts_data);
|
||||
|
||||
private:
|
||||
std::optional<BinaryMessage> Initalize(const BinaryMessage& request);
|
||||
std::optional<BinaryMessage> Decode(const BinaryMessage& request);
|
||||
|
||||
Memory::MemorySystem& memory;
|
||||
std::unique_ptr<AMediaCodec, AMediaCodecRelease> decoder;
|
||||
// default: 2 channles, 48000 samplerate
|
||||
AudioCore::ADTSData mADTSData{
|
||||
/*header_length*/ 7, /*mpeg2*/ false, /*profile*/ 2,
|
||||
/*channels*/ 2, /*channel_idx*/ 2, /*framecount*/ 0,
|
||||
/*samplerate_idx*/ 3, /*length*/ 0, /*samplerate*/ 48000};
|
||||
};
|
||||
|
||||
MediaNDKDecoder::Impl::Impl(Memory::MemorySystem& memory_) : memory(memory_) {
|
||||
SetMediaType(mADTSData);
|
||||
}
|
||||
|
||||
MediaNDKDecoder::Impl::~Impl() = default;
|
||||
|
||||
std::optional<BinaryMessage> MediaNDKDecoder::Impl::Initalize(const BinaryMessage& request) {
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
|
||||
bool MediaNDKDecoder::Impl::SetMediaType(const AudioCore::ADTSData& adts_data) {
|
||||
const char* mime = "audio/mp4a-latm";
|
||||
if (decoder && mADTSData.profile == adts_data.profile &&
|
||||
mADTSData.channel_idx == adts_data.channel_idx &&
|
||||
mADTSData.samplerate_idx == adts_data.samplerate_idx) {
|
||||
return true;
|
||||
}
|
||||
decoder.reset(AMediaCodec_createDecoderByType(mime));
|
||||
if (decoder == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
u8 csd_0[2];
|
||||
csd_0[0] = static_cast<u8>((adts_data.profile << 3) | (adts_data.samplerate_idx >> 1));
|
||||
csd_0[1] =
|
||||
static_cast<u8>(((adts_data.samplerate_idx << 7) & 0x80) | (adts_data.channel_idx << 3));
|
||||
AMediaFormat* format = AMediaFormat_new();
|
||||
AMediaFormat_setString(format, AMEDIAFORMAT_KEY_MIME, mime);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_SAMPLE_RATE, adts_data.samplerate);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_CHANNEL_COUNT, adts_data.channels);
|
||||
AMediaFormat_setInt32(format, AMEDIAFORMAT_KEY_IS_ADTS, 1);
|
||||
AMediaFormat_setBuffer(format, "csd-0", csd_0, sizeof(csd_0));
|
||||
|
||||
media_status_t status = AMediaCodec_configure(decoder.get(), format, NULL, NULL, 0);
|
||||
if (status != AMEDIA_OK) {
|
||||
AMediaFormat_delete(format);
|
||||
decoder.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
status = AMediaCodec_start(decoder.get());
|
||||
if (status != AMEDIA_OK) {
|
||||
AMediaFormat_delete(format);
|
||||
decoder.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
AMediaFormat_delete(format);
|
||||
mADTSData = adts_data;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> MediaNDKDecoder::Impl::ProcessRequest(const BinaryMessage& request) {
|
||||
if (request.header.codec != DecoderCodec::DecodeAAC) {
|
||||
LOG_ERROR(Audio_DSP, "AAC Decoder cannot handle such codec: {}",
|
||||
static_cast<u16>(request.header.codec));
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (request.header.cmd) {
|
||||
case DecoderCommand::Init: {
|
||||
return Initalize(request);
|
||||
}
|
||||
case DecoderCommand::EncodeDecode: {
|
||||
return Decode(request);
|
||||
}
|
||||
case DecoderCommand::Shutdown:
|
||||
case DecoderCommand::SaveState:
|
||||
case DecoderCommand::LoadState: {
|
||||
LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> MediaNDKDecoder::Impl::Decode(const BinaryMessage& request) {
|
||||
BinaryMessage response{};
|
||||
response.header.codec = request.header.codec;
|
||||
response.header.cmd = request.header.cmd;
|
||||
response.decode_aac_response.size = request.decode_aac_request.size;
|
||||
response.decode_aac_response.num_samples = 1024;
|
||||
|
||||
if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.src_addr + request.decode_aac_request.size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}",
|
||||
request.decode_aac_request.src_addr);
|
||||
return response;
|
||||
}
|
||||
|
||||
const u8* data =
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR);
|
||||
ADTSData adts_data = AudioCore::ParseADTS(data);
|
||||
SetMediaType(adts_data);
|
||||
response.decode_aac_response.sample_rate = GetSampleRateEnum(adts_data.samplerate);
|
||||
response.decode_aac_response.num_channels = adts_data.channels;
|
||||
if (!decoder) {
|
||||
LOG_ERROR(Audio_DSP, "Missing decoder for profile: {}, channels: {}, samplerate: {}",
|
||||
adts_data.profile, adts_data.channels, adts_data.samplerate);
|
||||
return {};
|
||||
}
|
||||
|
||||
// input
|
||||
constexpr int timeout = 160;
|
||||
std::size_t buffer_size = 0;
|
||||
u8* buffer = nullptr;
|
||||
ssize_t buffer_index = AMediaCodec_dequeueInputBuffer(decoder.get(), timeout);
|
||||
if (buffer_index < 0) {
|
||||
LOG_ERROR(Audio_DSP, "Failed to enqueue the input samples: {}", buffer_index);
|
||||
return response;
|
||||
}
|
||||
buffer = AMediaCodec_getInputBuffer(decoder.get(), buffer_index, &buffer_size);
|
||||
if (buffer_size < request.decode_aac_request.size) {
|
||||
return response;
|
||||
}
|
||||
std::memcpy(buffer, data, request.decode_aac_request.size);
|
||||
media_status_t status = AMediaCodec_queueInputBuffer(decoder.get(), buffer_index, 0,
|
||||
request.decode_aac_request.size, 0, 0);
|
||||
if (status != AMEDIA_OK) {
|
||||
LOG_WARNING(Audio_DSP, "Try queue input buffer again later!");
|
||||
return response;
|
||||
}
|
||||
|
||||
// output
|
||||
AMediaCodecBufferInfo info;
|
||||
std::array<std::vector<u16>, 2> out_streams;
|
||||
buffer_index = AMediaCodec_dequeueOutputBuffer(decoder.get(), &info, timeout);
|
||||
switch (buffer_index) {
|
||||
case AMEDIACODEC_INFO_TRY_AGAIN_LATER:
|
||||
LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: timeout!");
|
||||
break;
|
||||
case AMEDIACODEC_INFO_OUTPUT_BUFFERS_CHANGED:
|
||||
LOG_WARNING(Audio_DSP, "Failed to dequeue output buffer: buffers changed!");
|
||||
break;
|
||||
case AMEDIACODEC_INFO_OUTPUT_FORMAT_CHANGED: {
|
||||
AMediaFormat* format = AMediaCodec_getOutputFormat(decoder.get());
|
||||
LOG_WARNING(Audio_DSP, "output format: {}", AMediaFormat_toString(format));
|
||||
AMediaFormat_delete(format);
|
||||
buffer_index = AMediaCodec_dequeueOutputBuffer(decoder.get(), &info, timeout);
|
||||
}
|
||||
default: {
|
||||
int offset = info.offset;
|
||||
buffer = AMediaCodec_getOutputBuffer(decoder.get(), buffer_index, &buffer_size);
|
||||
while (offset < info.size) {
|
||||
for (int channel = 0; channel < response.decode_aac_response.num_channels; channel++) {
|
||||
u16 pcm_data;
|
||||
std::memcpy(&pcm_data, buffer + offset, sizeof(pcm_data));
|
||||
out_streams[channel].push_back(pcm_data);
|
||||
offset += sizeof(pcm_data);
|
||||
}
|
||||
}
|
||||
AMediaCodec_releaseOutputBuffer(decoder.get(), buffer_index, info.size != 0);
|
||||
}
|
||||
}
|
||||
|
||||
// transfer the decoded buffer from vector to the FCRAM
|
||||
size_t stream0_size = out_streams[0].size() * sizeof(u16);
|
||||
if (stream0_size != 0) {
|
||||
if (request.decode_aac_request.dst_addr_ch0 < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.dst_addr_ch0 + stream0_size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}",
|
||||
request.decode_aac_request.dst_addr_ch0);
|
||||
return response;
|
||||
}
|
||||
std::memcpy(
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch0 - Memory::FCRAM_PADDR),
|
||||
out_streams[0].data(), stream0_size);
|
||||
}
|
||||
|
||||
size_t stream1_size = out_streams[1].size() * sizeof(u16);
|
||||
if (stream1_size != 0) {
|
||||
if (request.decode_aac_request.dst_addr_ch1 < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.dst_addr_ch1 + stream1_size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}",
|
||||
request.decode_aac_request.dst_addr_ch1);
|
||||
return response;
|
||||
}
|
||||
std::memcpy(
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch1 - Memory::FCRAM_PADDR),
|
||||
out_streams[1].data(), stream1_size);
|
||||
}
|
||||
return response;
|
||||
}
|
||||
|
||||
MediaNDKDecoder::MediaNDKDecoder(Memory::MemorySystem& memory)
|
||||
: impl(std::make_unique<Impl>(memory)) {}
|
||||
|
||||
MediaNDKDecoder::~MediaNDKDecoder() = default;
|
||||
|
||||
std::optional<BinaryMessage> MediaNDKDecoder::ProcessRequest(const BinaryMessage& request) {
|
||||
return impl->ProcessRequest(request);
|
||||
}
|
||||
|
||||
bool MediaNDKDecoder::IsValid() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::HLE
|
22
src/audio_core/hle/mediandk_decoder.h
Normal file
22
src/audio_core/hle/mediandk_decoder.h
Normal file
@@ -0,0 +1,22 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/hle/decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class MediaNDKDecoder final : public DecoderBase {
|
||||
public:
|
||||
explicit MediaNDKDecoder(Memory::MemorySystem& memory);
|
||||
~MediaNDKDecoder() override;
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override;
|
||||
bool IsValid() const override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::HLE
|
313
src/audio_core/hle/wmf_decoder.cpp
Normal file
313
src/audio_core/hle/wmf_decoder.cpp
Normal file
@@ -0,0 +1,313 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "audio_core/hle/wmf_decoder.h"
|
||||
#include "audio_core/hle/wmf_decoder_utils.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
using namespace MFDecoder;
|
||||
|
||||
class WMFDecoder::Impl {
|
||||
public:
|
||||
explicit Impl(Memory::MemorySystem& memory);
|
||||
~Impl();
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request);
|
||||
bool IsValid() const {
|
||||
return is_valid;
|
||||
}
|
||||
|
||||
private:
|
||||
std::optional<BinaryMessage> Initalize(const BinaryMessage& request);
|
||||
|
||||
std::optional<BinaryMessage> Decode(const BinaryMessage& request);
|
||||
|
||||
MFOutputState DecodingLoop(AudioCore::ADTSData adts_header,
|
||||
std::array<std::vector<u8>, 2>& out_streams);
|
||||
|
||||
bool transform_initialized = false;
|
||||
bool format_selected = false;
|
||||
|
||||
Memory::MemorySystem& memory;
|
||||
|
||||
unique_mfptr<IMFTransform> transform;
|
||||
DWORD in_stream_id = 0;
|
||||
DWORD out_stream_id = 0;
|
||||
bool is_valid = false;
|
||||
bool mf_started = false;
|
||||
bool coinited = false;
|
||||
};
|
||||
|
||||
WMFDecoder::Impl::Impl(Memory::MemorySystem& memory) : memory(memory) {
|
||||
// Attempt to load the symbols for mf.dll
|
||||
if (!InitMFDLL()) {
|
||||
LOG_CRITICAL(Audio_DSP,
|
||||
"Unable to load mf.dll. AAC audio through media foundation unavailable");
|
||||
return;
|
||||
}
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
hr = CoInitialize(NULL);
|
||||
// S_FALSE will be returned when COM has already been initialized
|
||||
if (hr != S_OK && hr != S_FALSE) {
|
||||
ReportError("Failed to start COM components", hr);
|
||||
} else {
|
||||
coinited = true;
|
||||
}
|
||||
|
||||
// lite startup is faster and all what we need is included
|
||||
hr = MFDecoder::MFStartup(MF_VERSION, MFSTARTUP_LITE);
|
||||
if (hr != S_OK) {
|
||||
// Do you know you can't initialize MF in test mode or safe mode?
|
||||
ReportError("Failed to initialize Media Foundation", hr);
|
||||
} else {
|
||||
mf_started = true;
|
||||
}
|
||||
|
||||
LOG_INFO(Audio_DSP, "Media Foundation activated");
|
||||
|
||||
// initialize transform
|
||||
transform = MFDecoderInit();
|
||||
if (transform == nullptr) {
|
||||
LOG_CRITICAL(Audio_DSP, "Can't initialize decoder");
|
||||
return;
|
||||
}
|
||||
|
||||
hr = transform->GetStreamIDs(1, &in_stream_id, 1, &out_stream_id);
|
||||
if (hr == E_NOTIMPL) {
|
||||
// if not implemented, it means this MFT does not assign stream ID for you
|
||||
in_stream_id = 0;
|
||||
out_stream_id = 0;
|
||||
} else if (FAILED(hr)) {
|
||||
ReportError("Decoder failed to initialize the stream ID", hr);
|
||||
return;
|
||||
}
|
||||
transform_initialized = true;
|
||||
is_valid = true;
|
||||
}
|
||||
|
||||
WMFDecoder::Impl::~Impl() {
|
||||
if (transform_initialized) {
|
||||
MFFlush(transform.get());
|
||||
// delete the transform object before shutting down MF
|
||||
// otherwise access violation will occur
|
||||
transform.reset();
|
||||
}
|
||||
if (mf_started) {
|
||||
MFDecoder::MFShutdown();
|
||||
}
|
||||
if (coinited) {
|
||||
CoUninitialize();
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> WMFDecoder::Impl::ProcessRequest(const BinaryMessage& request) {
|
||||
if (request.header.codec != DecoderCodec::DecodeAAC) {
|
||||
LOG_ERROR(Audio_DSP, "Got unknown codec {}", static_cast<u16>(request.header.codec));
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
switch (request.header.cmd) {
|
||||
case DecoderCommand::Init: {
|
||||
LOG_INFO(Audio_DSP, "WMFDecoder initializing");
|
||||
return Initalize(request);
|
||||
}
|
||||
case DecoderCommand::EncodeDecode: {
|
||||
return Decode(request);
|
||||
}
|
||||
case DecoderCommand::Shutdown:
|
||||
case DecoderCommand::SaveState:
|
||||
case DecoderCommand::LoadState: {
|
||||
LOG_WARNING(Audio_DSP, "Got unimplemented binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
return response;
|
||||
}
|
||||
default:
|
||||
LOG_ERROR(Audio_DSP, "Got unknown binary request: {}",
|
||||
static_cast<u16>(request.header.cmd));
|
||||
return std::nullopt;
|
||||
}
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> WMFDecoder::Impl::Initalize(const BinaryMessage& request) {
|
||||
BinaryMessage response = request;
|
||||
response.header.result = ResultStatus::Success;
|
||||
|
||||
format_selected = false; // select format again if application request initialize the DSP
|
||||
return response;
|
||||
}
|
||||
|
||||
MFOutputState WMFDecoder::Impl::DecodingLoop(AudioCore::ADTSData adts_header,
|
||||
std::array<std::vector<u8>, 2>& out_streams) {
|
||||
std::optional<std::vector<f32>> output_buffer;
|
||||
|
||||
while (true) {
|
||||
auto [output_status, output] = ReceiveSample(transform.get(), out_stream_id);
|
||||
|
||||
// 0 -> okay; 3 -> okay but more data available (buffer too small)
|
||||
if (output_status == MFOutputState::OK || output_status == MFOutputState::HaveMoreData) {
|
||||
output_buffer = CopySampleToBuffer(output.get());
|
||||
|
||||
// the following was taken from ffmpeg version of the decoder
|
||||
f32 val_f32;
|
||||
for (std::size_t i = 0; i < output_buffer->size();) {
|
||||
for (std::size_t channel = 0; channel < adts_header.channels; channel++) {
|
||||
val_f32 = std::clamp(output_buffer->at(i), -1.0f, 1.0f);
|
||||
s16 val = static_cast<s16>(0x7FFF * val_f32);
|
||||
out_streams[channel].push_back(val & 0xFF);
|
||||
out_streams[channel].push_back(val >> 8);
|
||||
// i is incremented on per channel basis
|
||||
i++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If we return OK here, the decoder won't be in a state to receive new data and will fail
|
||||
// on the next call; instead treat it like the HaveMoreData case
|
||||
if (output_status == MFOutputState::OK)
|
||||
continue;
|
||||
|
||||
// for status = 2, reset MF
|
||||
if (output_status == MFOutputState::NeedReconfig) {
|
||||
format_selected = false;
|
||||
return MFOutputState::NeedReconfig;
|
||||
}
|
||||
|
||||
// for status = 3, try again with new buffer
|
||||
if (output_status == MFOutputState::HaveMoreData)
|
||||
continue;
|
||||
|
||||
// according to MS document, this is not an error (?!)
|
||||
if (output_status == MFOutputState::NeedMoreInput)
|
||||
return MFOutputState::NeedMoreInput;
|
||||
|
||||
return MFOutputState::FatalError; // return on other status
|
||||
}
|
||||
|
||||
return MFOutputState::FatalError;
|
||||
}
|
||||
|
||||
std::optional<BinaryMessage> WMFDecoder::Impl::Decode(const BinaryMessage& request) {
|
||||
BinaryMessage response{};
|
||||
response.header.codec = request.header.codec;
|
||||
response.header.cmd = request.header.cmd;
|
||||
response.decode_aac_response.size = request.decode_aac_request.size;
|
||||
response.decode_aac_response.num_channels = 2;
|
||||
response.decode_aac_response.num_samples = 1024;
|
||||
|
||||
if (!transform_initialized) {
|
||||
LOG_DEBUG(Audio_DSP, "Decoder not initialized");
|
||||
// This is a hack to continue games when decoder failed to initialize
|
||||
return response;
|
||||
}
|
||||
|
||||
if (request.decode_aac_request.src_addr < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.src_addr + request.decode_aac_request.size >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds src_addr {:08x}",
|
||||
request.decode_aac_request.src_addr);
|
||||
return std::nullopt;
|
||||
}
|
||||
const u8* data =
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.src_addr - Memory::FCRAM_PADDR);
|
||||
|
||||
std::array<std::vector<u8>, 2> out_streams;
|
||||
unique_mfptr<IMFSample> sample;
|
||||
MFInputState input_status = MFInputState::OK;
|
||||
MFOutputState output_status = MFOutputState::OK;
|
||||
std::optional<ADTSMeta> adts_meta = DetectMediaType(data, request.decode_aac_request.size);
|
||||
|
||||
if (!adts_meta) {
|
||||
LOG_ERROR(Audio_DSP, "Unable to deduce decoding parameters from ADTS stream");
|
||||
return response;
|
||||
}
|
||||
|
||||
response.decode_aac_response.sample_rate = GetSampleRateEnum(adts_meta->ADTSHeader.samplerate);
|
||||
response.decode_aac_response.num_channels = adts_meta->ADTSHeader.channels;
|
||||
|
||||
if (!format_selected) {
|
||||
LOG_DEBUG(Audio_DSP, "New ADTS stream: channels = {}, sample rate = {}",
|
||||
adts_meta->ADTSHeader.channels, adts_meta->ADTSHeader.samplerate);
|
||||
SelectInputMediaType(transform.get(), in_stream_id, adts_meta->ADTSHeader,
|
||||
adts_meta->AACTag, 14);
|
||||
SelectOutputMediaType(transform.get(), out_stream_id);
|
||||
SendSample(transform.get(), in_stream_id, nullptr);
|
||||
// cache the result from detect_mediatype and call select_*_mediatype only once
|
||||
// This could increase performance very slightly
|
||||
transform->ProcessMessage(MFT_MESSAGE_NOTIFY_BEGIN_STREAMING, 0);
|
||||
format_selected = true;
|
||||
}
|
||||
|
||||
sample = CreateSample(data, request.decode_aac_request.size, 1, 0);
|
||||
sample->SetUINT32(MFSampleExtension_CleanPoint, 1);
|
||||
|
||||
while (true) {
|
||||
input_status = SendSample(transform.get(), in_stream_id, sample.get());
|
||||
output_status = DecodingLoop(adts_meta->ADTSHeader, out_streams);
|
||||
|
||||
if (output_status == MFOutputState::FatalError) {
|
||||
// if the decode issues are caused by MFT not accepting new samples, try again
|
||||
// NOTICE: you are required to check the output even if you already knew/guessed
|
||||
// MFT didn't accept the input sample
|
||||
if (input_status == MFInputState::NotAccepted) {
|
||||
// try again
|
||||
continue;
|
||||
}
|
||||
|
||||
LOG_ERROR(Audio_DSP, "Errors occurred when receiving output");
|
||||
return response;
|
||||
} else if (output_status == MFOutputState::NeedReconfig) {
|
||||
// flush the transform
|
||||
MFFlush(transform.get());
|
||||
// decode again
|
||||
return this->Decode(request);
|
||||
}
|
||||
|
||||
break; // jump out of the loop if at least we don't have obvious issues
|
||||
}
|
||||
|
||||
if (out_streams[0].size() != 0) {
|
||||
if (request.decode_aac_request.dst_addr_ch0 < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.dst_addr_ch0 + out_streams[0].size() >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch0 {:08x}",
|
||||
request.decode_aac_request.dst_addr_ch0);
|
||||
return std::nullopt;
|
||||
}
|
||||
std::memcpy(
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch0 - Memory::FCRAM_PADDR),
|
||||
out_streams[0].data(), out_streams[0].size());
|
||||
}
|
||||
|
||||
if (out_streams[1].size() != 0) {
|
||||
if (request.decode_aac_request.dst_addr_ch1 < Memory::FCRAM_PADDR ||
|
||||
request.decode_aac_request.dst_addr_ch1 + out_streams[1].size() >
|
||||
Memory::FCRAM_PADDR + Memory::FCRAM_SIZE) {
|
||||
LOG_ERROR(Audio_DSP, "Got out of bounds dst_addr_ch1 {:08x}",
|
||||
request.decode_aac_request.dst_addr_ch1);
|
||||
return std::nullopt;
|
||||
}
|
||||
std::memcpy(
|
||||
memory.GetFCRAMPointer(request.decode_aac_request.dst_addr_ch1 - Memory::FCRAM_PADDR),
|
||||
out_streams[1].data(), out_streams[1].size());
|
||||
}
|
||||
|
||||
return response;
|
||||
}
|
||||
|
||||
WMFDecoder::WMFDecoder(Memory::MemorySystem& memory) : impl(std::make_unique<Impl>(memory)) {}
|
||||
|
||||
WMFDecoder::~WMFDecoder() = default;
|
||||
|
||||
std::optional<BinaryMessage> WMFDecoder::ProcessRequest(const BinaryMessage& request) {
|
||||
return impl->ProcessRequest(request);
|
||||
}
|
||||
|
||||
bool WMFDecoder::IsValid() const {
|
||||
return impl->IsValid();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::HLE
|
23
src/audio_core/hle/wmf_decoder.h
Normal file
23
src/audio_core/hle/wmf_decoder.h
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2018 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "audio_core/hle/decoder.h"
|
||||
|
||||
namespace AudioCore::HLE {
|
||||
|
||||
class WMFDecoder final : public DecoderBase {
|
||||
public:
|
||||
explicit WMFDecoder(Memory::MemorySystem& memory);
|
||||
~WMFDecoder() override;
|
||||
std::optional<BinaryMessage> ProcessRequest(const BinaryMessage& request) override;
|
||||
bool IsValid() const override;
|
||||
|
||||
private:
|
||||
class Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::HLE
|
464
src/audio_core/hle/wmf_decoder_utils.cpp
Normal file
464
src/audio_core/hle/wmf_decoder_utils.cpp
Normal file
@@ -0,0 +1,464 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
#include "common/logging/log.h"
|
||||
#include "common/string_util.h"
|
||||
#include "wmf_decoder_utils.h"
|
||||
|
||||
namespace MFDecoder {
|
||||
|
||||
// utility functions
|
||||
void ReportError(std::string msg, HRESULT hr) {
|
||||
if (SUCCEEDED(hr)) {
|
||||
return;
|
||||
}
|
||||
LPWSTR err;
|
||||
FormatMessageW(FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, hr,
|
||||
// hardcode to use en_US because if any user had problems with this
|
||||
// we can help them w/o translating anything
|
||||
// default is to use the language currently active on the operating system
|
||||
MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US), (LPWSTR)&err, 0, nullptr);
|
||||
if (err != nullptr) {
|
||||
LOG_CRITICAL(Audio_DSP, "{}: {}", msg, Common::UTF16ToUTF8(err));
|
||||
LocalFree(err);
|
||||
}
|
||||
LOG_CRITICAL(Audio_DSP, "{}: {:08x}", msg, hr);
|
||||
}
|
||||
|
||||
unique_mfptr<IMFTransform> MFDecoderInit(GUID audio_format) {
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
MFT_REGISTER_TYPE_INFO reg{};
|
||||
GUID category = MFT_CATEGORY_AUDIO_DECODER;
|
||||
IMFActivate** activate;
|
||||
unique_mfptr<IMFTransform> transform;
|
||||
UINT32 num_activate;
|
||||
|
||||
reg.guidMajorType = MFMediaType_Audio;
|
||||
reg.guidSubtype = audio_format;
|
||||
|
||||
hr = MFTEnumEx(category,
|
||||
MFT_ENUM_FLAG_SYNCMFT | MFT_ENUM_FLAG_LOCALMFT | MFT_ENUM_FLAG_SORTANDFILTER,
|
||||
®, nullptr, &activate, &num_activate);
|
||||
if (FAILED(hr) || num_activate < 1) {
|
||||
ReportError("Failed to enumerate decoders", hr);
|
||||
CoTaskMemFree(activate);
|
||||
return nullptr;
|
||||
}
|
||||
LOG_INFO(Audio_DSP, "Windows(R) Media Foundation found {} suitable decoder(s)", num_activate);
|
||||
for (unsigned int n = 0; n < num_activate; n++) {
|
||||
hr = activate[n]->ActivateObject(
|
||||
IID_IMFTransform,
|
||||
reinterpret_cast<void**>(static_cast<IMFTransform**>(Amp(transform))));
|
||||
if (FAILED(hr))
|
||||
transform = nullptr;
|
||||
activate[n]->Release();
|
||||
if (SUCCEEDED(hr))
|
||||
break;
|
||||
}
|
||||
if (transform == nullptr) {
|
||||
ReportError("Failed to initialize MFT", hr);
|
||||
CoTaskMemFree(activate);
|
||||
return nullptr;
|
||||
}
|
||||
CoTaskMemFree(activate);
|
||||
return transform;
|
||||
}
|
||||
|
||||
unique_mfptr<IMFSample> CreateSample(const void* data, DWORD len, DWORD alignment,
|
||||
LONGLONG duration) {
|
||||
HRESULT hr = S_OK;
|
||||
unique_mfptr<IMFMediaBuffer> buf;
|
||||
unique_mfptr<IMFSample> sample;
|
||||
|
||||
hr = MFCreateSample(Amp(sample));
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Unable to allocate a sample", hr);
|
||||
return nullptr;
|
||||
}
|
||||
// Yes, the argument for alignment is the actual alignment - 1
|
||||
hr = MFCreateAlignedMemoryBuffer(len, alignment - 1, Amp(buf));
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Unable to allocate a memory buffer for sample", hr);
|
||||
return nullptr;
|
||||
}
|
||||
if (data) {
|
||||
BYTE* buffer;
|
||||
// lock the MediaBuffer
|
||||
// this is actually not a thread-safe lock
|
||||
hr = buf->Lock(&buffer, nullptr, nullptr);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Unable to lock down MediaBuffer", hr);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::memcpy(buffer, data, len);
|
||||
|
||||
buf->SetCurrentLength(len);
|
||||
buf->Unlock();
|
||||
}
|
||||
|
||||
sample->AddBuffer(buf.get());
|
||||
hr = sample->SetSampleDuration(duration);
|
||||
if (FAILED(hr)) {
|
||||
// MFT will take a guess for you in this case
|
||||
ReportError("Unable to set sample duration, but continuing anyway", hr);
|
||||
}
|
||||
|
||||
return sample;
|
||||
}
|
||||
|
||||
bool SelectInputMediaType(IMFTransform* transform, int in_stream_id,
|
||||
const AudioCore::ADTSData& adts, const UINT8* user_data,
|
||||
UINT32 user_data_len, GUID audio_format) {
|
||||
HRESULT hr = S_OK;
|
||||
unique_mfptr<IMFMediaType> t;
|
||||
|
||||
// actually you can get rid of the whole block of searching and filtering mess
|
||||
// if you know the exact parameters of your media stream
|
||||
hr = MFCreateMediaType(Amp(t));
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Unable to create an empty MediaType", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
// basic definition
|
||||
t->SetGUID(MF_MT_MAJOR_TYPE, MFMediaType_Audio);
|
||||
t->SetGUID(MF_MT_SUBTYPE, audio_format);
|
||||
|
||||
t->SetUINT32(MF_MT_AAC_PAYLOAD_TYPE, 1);
|
||||
t->SetUINT32(MF_MT_AUDIO_NUM_CHANNELS, adts.channels);
|
||||
t->SetUINT32(MF_MT_AUDIO_SAMPLES_PER_SECOND, adts.samplerate);
|
||||
// 0xfe = 254 = "unspecified"
|
||||
t->SetUINT32(MF_MT_AAC_AUDIO_PROFILE_LEVEL_INDICATION, 254);
|
||||
t->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1);
|
||||
t->SetBlob(MF_MT_USER_DATA, user_data, user_data_len);
|
||||
hr = transform->SetInputType(in_stream_id, t.get(), 0);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("failed to select input types for MFT", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SelectOutputMediaType(IMFTransform* transform, int out_stream_id, GUID audio_format) {
|
||||
HRESULT hr = S_OK;
|
||||
UINT32 tmp;
|
||||
unique_mfptr<IMFMediaType> type;
|
||||
|
||||
// If you know what you need and what you are doing, you can specify the conditions instead of
|
||||
// searching but it's better to use search since MFT may or may not support your output
|
||||
// parameters
|
||||
for (DWORD i = 0;; i++) {
|
||||
hr = transform->GetOutputAvailableType(out_stream_id, i, Amp(type));
|
||||
if (hr == MF_E_NO_MORE_TYPES || hr == E_NOTIMPL) {
|
||||
return true;
|
||||
}
|
||||
if (FAILED(hr)) {
|
||||
ReportError("failed to get output types for MFT", hr);
|
||||
return false;
|
||||
}
|
||||
|
||||
hr = type->GetUINT32(MF_MT_AUDIO_BITS_PER_SAMPLE, &tmp);
|
||||
|
||||
if (FAILED(hr))
|
||||
continue;
|
||||
// select PCM-16 format
|
||||
if (tmp == 32) {
|
||||
hr = type->SetUINT32(MF_MT_AUDIO_BLOCK_ALIGNMENT, 1);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("failed to set MF_MT_AUDIO_BLOCK_ALIGNMENT for MFT on output stream",
|
||||
hr);
|
||||
return false;
|
||||
}
|
||||
hr = transform->SetOutputType(out_stream_id, type.get(), 0);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("failed to select output types for MFT", hr);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ReportError("MFT: Unable to find preferred output format", E_NOTIMPL);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::optional<ADTSMeta> DetectMediaType(const u8* buffer, std::size_t len) {
|
||||
if (len < 7) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
AudioCore::ADTSData tmp;
|
||||
ADTSMeta result;
|
||||
// see https://docs.microsoft.com/en-us/windows/desktop/api/mmreg/ns-mmreg-heaacwaveinfo_tag
|
||||
// for the meaning of the byte array below
|
||||
|
||||
// it might be a good idea to wrap the parameters into a struct
|
||||
// and pass that struct into the function but doing that will lead to messier code
|
||||
// const UINT8 aac_data[] = { 0x01, 0x00, 0xfe, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x11, 0x90
|
||||
// }; first byte: 0: raw aac 1: adts 2: adif 3: latm/laos
|
||||
UINT8 aac_tmp[] = {0x01, 0x00, 0xfe, 00, 00, 00, 00, 00, 00, 00, 00, 00, 0x00, 0x00};
|
||||
uint16_t tag = 0;
|
||||
|
||||
tmp = AudioCore::ParseADTS(buffer);
|
||||
if (tmp.length == 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
tag = MFGetAACTag(tmp);
|
||||
aac_tmp[12] |= (tag & 0xff00) >> 8;
|
||||
aac_tmp[13] |= (tag & 0x00ff);
|
||||
std::memcpy(&(result.ADTSHeader), &tmp, sizeof(AudioCore::ADTSData));
|
||||
std::memcpy(&(result.AACTag), aac_tmp, 14);
|
||||
return result;
|
||||
}
|
||||
|
||||
void MFFlush(IMFTransform* transform) {
|
||||
HRESULT hr = transform->ProcessMessage(MFT_MESSAGE_COMMAND_FLUSH, 0);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("MFT: Flush command failed", hr);
|
||||
}
|
||||
hr = transform->ProcessMessage(MFT_MESSAGE_NOTIFY_END_OF_STREAM, 0);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Failed to end streaming for MFT", hr);
|
||||
}
|
||||
}
|
||||
|
||||
MFInputState SendSample(IMFTransform* transform, DWORD in_stream_id, IMFSample* in_sample) {
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
if (in_sample) {
|
||||
hr = transform->ProcessInput(in_stream_id, in_sample, 0);
|
||||
if (hr == MF_E_NOTACCEPTING) {
|
||||
return MFInputState::NotAccepted; // try again
|
||||
} else if (FAILED(hr)) {
|
||||
ReportError("MFT: Failed to process input", hr);
|
||||
return MFInputState::FatalError;
|
||||
} // FAILED(hr)
|
||||
} else {
|
||||
hr = transform->ProcessMessage(MFT_MESSAGE_COMMAND_DRAIN, 0);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("MFT: Failed to drain when processing input", hr);
|
||||
}
|
||||
}
|
||||
|
||||
return MFInputState::OK;
|
||||
}
|
||||
|
||||
std::tuple<MFOutputState, unique_mfptr<IMFSample>> ReceiveSample(IMFTransform* transform,
|
||||
DWORD out_stream_id) {
|
||||
HRESULT hr;
|
||||
MFT_OUTPUT_DATA_BUFFER out_buffers;
|
||||
MFT_OUTPUT_STREAM_INFO out_info;
|
||||
DWORD status = 0;
|
||||
unique_mfptr<IMFSample> sample;
|
||||
bool mft_create_sample = false;
|
||||
|
||||
hr = transform->GetOutputStreamInfo(out_stream_id, &out_info);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
ReportError("MFT: Failed to get stream info", hr);
|
||||
return std::make_tuple(MFOutputState::FatalError, std::move(sample));
|
||||
}
|
||||
mft_create_sample = (out_info.dwFlags & MFT_OUTPUT_STREAM_PROVIDES_SAMPLES) ||
|
||||
(out_info.dwFlags & MFT_OUTPUT_STREAM_CAN_PROVIDE_SAMPLES);
|
||||
|
||||
while (true) {
|
||||
status = 0;
|
||||
|
||||
if (!mft_create_sample) {
|
||||
sample = CreateSample(nullptr, out_info.cbSize, out_info.cbAlignment);
|
||||
if (!sample.get()) {
|
||||
ReportError("MFT: Unable to allocate memory for samples", hr);
|
||||
return std::make_tuple(MFOutputState::FatalError, std::move(sample));
|
||||
}
|
||||
}
|
||||
|
||||
out_buffers.dwStreamID = out_stream_id;
|
||||
out_buffers.pSample = sample.get();
|
||||
|
||||
hr = transform->ProcessOutput(0, 1, &out_buffers, &status);
|
||||
|
||||
if (!FAILED(hr)) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (hr == MF_E_TRANSFORM_NEED_MORE_INPUT) {
|
||||
// Most likely reasons: data corrupted; your actions not expected by MFT
|
||||
return std::make_tuple(MFOutputState::NeedMoreInput, std::move(sample));
|
||||
}
|
||||
|
||||
if (hr == MF_E_TRANSFORM_STREAM_CHANGE) {
|
||||
ReportError("MFT: stream format changed, re-configuration required", hr);
|
||||
return std::make_tuple(MFOutputState::NeedReconfig, std::move(sample));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (out_buffers.dwStatus & MFT_OUTPUT_DATA_BUFFER_INCOMPLETE) {
|
||||
// this status is also unreliable but whatever
|
||||
return std::make_tuple(MFOutputState::HaveMoreData, std::move(sample));
|
||||
}
|
||||
|
||||
if (out_buffers.pSample == nullptr) {
|
||||
ReportError("MFT: decoding failure", hr);
|
||||
return std::make_tuple(MFOutputState::FatalError, std::move(sample));
|
||||
}
|
||||
|
||||
return std::make_tuple(MFOutputState::OK, std::move(sample));
|
||||
}
|
||||
|
||||
std::optional<std::vector<f32>> CopySampleToBuffer(IMFSample* sample) {
|
||||
unique_mfptr<IMFMediaBuffer> buffer;
|
||||
HRESULT hr = S_OK;
|
||||
std::optional<std::vector<f32>> output;
|
||||
std::vector<f32> output_buffer;
|
||||
BYTE* data;
|
||||
DWORD len = 0;
|
||||
|
||||
hr = sample->GetTotalLength(&len);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Failed to get the length of sample buffer", hr);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
hr = sample->ConvertToContiguousBuffer(Amp(buffer));
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Failed to get sample buffer", hr);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
hr = buffer->Lock(&data, nullptr, nullptr);
|
||||
if (FAILED(hr)) {
|
||||
ReportError("Failed to lock the buffer", hr);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
output_buffer.resize(len / sizeof(f32));
|
||||
std::memcpy(output_buffer.data(), data, len);
|
||||
output = output_buffer;
|
||||
|
||||
// if buffer unlock fails, then... whatever, we have already got data
|
||||
buffer->Unlock();
|
||||
return output;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
struct LibraryDeleter {
|
||||
using pointer = HMODULE;
|
||||
void operator()(HMODULE h) const {
|
||||
if (h != nullptr)
|
||||
FreeLibrary(h);
|
||||
}
|
||||
};
|
||||
|
||||
std::unique_ptr<HMODULE, LibraryDeleter> mf_dll{nullptr};
|
||||
std::unique_ptr<HMODULE, LibraryDeleter> mfplat_dll{nullptr};
|
||||
|
||||
} // namespace
|
||||
|
||||
bool InitMFDLL() {
|
||||
|
||||
mf_dll.reset(LoadLibrary(TEXT("mf.dll")));
|
||||
if (!mf_dll) {
|
||||
DWORD error_message_id = GetLastError();
|
||||
LPSTR message_buffer = nullptr;
|
||||
size_t size =
|
||||
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPSTR>(&message_buffer), 0, nullptr);
|
||||
|
||||
std::string message(message_buffer, size);
|
||||
|
||||
LocalFree(message_buffer);
|
||||
LOG_ERROR(Audio_DSP, "Could not load mf.dll: {}", message);
|
||||
return false;
|
||||
}
|
||||
|
||||
mfplat_dll.reset(LoadLibrary(TEXT("mfplat.dll")));
|
||||
if (!mfplat_dll) {
|
||||
DWORD error_message_id = GetLastError();
|
||||
LPSTR message_buffer = nullptr;
|
||||
size_t size =
|
||||
FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_message_id, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<LPSTR>(&message_buffer), 0, nullptr);
|
||||
|
||||
std::string message(message_buffer, size);
|
||||
|
||||
LocalFree(message_buffer);
|
||||
LOG_ERROR(Audio_DSP, "Could not load mfplat.dll: {}", message);
|
||||
return false;
|
||||
}
|
||||
|
||||
MFStartup = Symbol<HRESULT(ULONG, DWORD)>(mfplat_dll.get(), "MFStartup");
|
||||
if (!MFStartup) {
|
||||
LOG_ERROR(Audio_DSP, "Cannot load function MFStartup");
|
||||
return false;
|
||||
}
|
||||
|
||||
MFShutdown = Symbol<HRESULT(void)>(mfplat_dll.get(), "MFShutdown");
|
||||
if (!MFShutdown) {
|
||||
LOG_ERROR(Audio_DSP, "Cannot load function MFShutdown");
|
||||
return false;
|
||||
}
|
||||
|
||||
MFShutdownObject = Symbol<HRESULT(IUnknown*)>(mf_dll.get(), "MFShutdownObject");
|
||||
if (!MFShutdownObject) {
|
||||
LOG_ERROR(Audio_DSP, "Cannot load function MFShutdownObject");
|
||||
return false;
|
||||
}
|
||||
|
||||
MFCreateAlignedMemoryBuffer = Symbol<HRESULT(DWORD, DWORD, IMFMediaBuffer**)>(
|
||||
mfplat_dll.get(), "MFCreateAlignedMemoryBuffer");
|
||||
if (!MFCreateAlignedMemoryBuffer) {
|
||||
LOG_ERROR(Audio_DSP, "Cannot load function MFCreateAlignedMemoryBuffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
MFCreateSample = Symbol<HRESULT(IMFSample**)>(mfplat_dll.get(), "MFCreateSample");
|
||||
if (!MFCreateSample) {
|
||||
LOG_ERROR(Audio_DSP, "Cannot load function MFCreateSample");
|
||||
return false;
|
||||
}
|
||||
|
||||
MFTEnumEx =
|
||||
Symbol<HRESULT(GUID, UINT32, const MFT_REGISTER_TYPE_INFO*, const MFT_REGISTER_TYPE_INFO*,
|
||||
IMFActivate***, UINT32*)>(mfplat_dll.get(), "MFTEnumEx");
|
||||
if (!MFTEnumEx) {
|
||||
LOG_ERROR(Audio_DSP, "Cannot load function MFTEnumEx");
|
||||
return false;
|
||||
}
|
||||
|
||||
MFCreateMediaType = Symbol<HRESULT(IMFMediaType**)>(mfplat_dll.get(), "MFCreateMediaType");
|
||||
if (!MFCreateMediaType) {
|
||||
LOG_ERROR(Audio_DSP, "Cannot load function MFCreateMediaType");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Symbol<HRESULT(ULONG, DWORD)> MFStartup;
|
||||
Symbol<HRESULT(void)> MFShutdown;
|
||||
Symbol<HRESULT(IUnknown*)> MFShutdownObject;
|
||||
Symbol<HRESULT(DWORD, DWORD, IMFMediaBuffer**)> MFCreateAlignedMemoryBuffer;
|
||||
Symbol<HRESULT(IMFSample**)> MFCreateSample;
|
||||
Symbol<HRESULT(GUID, UINT32, const MFT_REGISTER_TYPE_INFO*, const MFT_REGISTER_TYPE_INFO*,
|
||||
IMFActivate***, UINT32*)>
|
||||
MFTEnumEx;
|
||||
Symbol<HRESULT(IMFMediaType**)> MFCreateMediaType;
|
||||
|
||||
} // namespace MFDecoder
|
125
src/audio_core/hle/wmf_decoder_utils.h
Normal file
125
src/audio_core/hle/wmf_decoder_utils.h
Normal file
@@ -0,0 +1,125 @@
|
||||
// Copyright 2019 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
#include <vector>
|
||||
#include <comdef.h>
|
||||
#include <mfapi.h>
|
||||
#include <mferror.h>
|
||||
#include <mfidl.h>
|
||||
#include <mftransform.h>
|
||||
|
||||
#include "adts.h"
|
||||
|
||||
namespace MFDecoder {
|
||||
|
||||
template <typename T>
|
||||
struct Symbol {
|
||||
Symbol() = default;
|
||||
Symbol(HMODULE dll, const char* name) {
|
||||
if (dll) {
|
||||
ptr_symbol = reinterpret_cast<T*>(GetProcAddress(dll, name));
|
||||
}
|
||||
}
|
||||
|
||||
operator T*() const {
|
||||
return ptr_symbol;
|
||||
}
|
||||
|
||||
explicit operator bool() const {
|
||||
return ptr_symbol != nullptr;
|
||||
}
|
||||
|
||||
T* ptr_symbol = nullptr;
|
||||
};
|
||||
|
||||
// Runtime load the MF symbols to prevent mf.dll not found errors on citra load
|
||||
extern Symbol<HRESULT(ULONG, DWORD)> MFStartup;
|
||||
extern Symbol<HRESULT(void)> MFShutdown;
|
||||
extern Symbol<HRESULT(IUnknown*)> MFShutdownObject;
|
||||
extern Symbol<HRESULT(DWORD, DWORD, IMFMediaBuffer**)> MFCreateAlignedMemoryBuffer;
|
||||
extern Symbol<HRESULT(IMFSample**)> MFCreateSample;
|
||||
extern Symbol<HRESULT(GUID, UINT32, const MFT_REGISTER_TYPE_INFO*, const MFT_REGISTER_TYPE_INFO*,
|
||||
IMFActivate***, UINT32*)>
|
||||
MFTEnumEx;
|
||||
extern Symbol<HRESULT(IMFMediaType**)> MFCreateMediaType;
|
||||
|
||||
enum class MFOutputState { FatalError, OK, NeedMoreInput, NeedReconfig, HaveMoreData };
|
||||
enum class MFInputState { FatalError, OK, NotAccepted };
|
||||
|
||||
// utility functions / templates
|
||||
template <class T>
|
||||
struct MFRelease {
|
||||
void operator()(T* pointer) const {
|
||||
pointer->Release();
|
||||
};
|
||||
};
|
||||
|
||||
template <>
|
||||
struct MFRelease<IMFTransform> {
|
||||
void operator()(IMFTransform* pointer) const {
|
||||
MFShutdownObject(pointer);
|
||||
pointer->Release();
|
||||
};
|
||||
};
|
||||
|
||||
// wrapper facilities for dealing with pointers
|
||||
template <typename T>
|
||||
using unique_mfptr = std::unique_ptr<T, MFRelease<T>>;
|
||||
|
||||
template <typename SmartPtr, typename RawPtr>
|
||||
class AmpImpl {
|
||||
public:
|
||||
AmpImpl(SmartPtr& smart_ptr) : smart_ptr(smart_ptr) {}
|
||||
~AmpImpl() {
|
||||
smart_ptr.reset(raw_ptr);
|
||||
}
|
||||
|
||||
operator RawPtr*() {
|
||||
return &raw_ptr;
|
||||
}
|
||||
|
||||
private:
|
||||
SmartPtr& smart_ptr;
|
||||
RawPtr raw_ptr = nullptr;
|
||||
};
|
||||
|
||||
template <typename SmartPtr>
|
||||
auto Amp(SmartPtr& smart_ptr) {
|
||||
return AmpImpl<SmartPtr, decltype(smart_ptr.get())>(smart_ptr);
|
||||
}
|
||||
|
||||
// convient function for formatting error messages
|
||||
void ReportError(std::string msg, HRESULT hr);
|
||||
|
||||
// data type for transferring ADTS metadata between functions
|
||||
struct ADTSMeta {
|
||||
AudioCore::ADTSData ADTSHeader;
|
||||
u8 AACTag[14];
|
||||
};
|
||||
|
||||
// exported functions
|
||||
|
||||
/// Loads the symbols from mf.dll at runtime. Returns false if the symbols can't be loaded
|
||||
bool InitMFDLL();
|
||||
unique_mfptr<IMFTransform> MFDecoderInit(GUID audio_format = MFAudioFormat_AAC);
|
||||
unique_mfptr<IMFSample> CreateSample(const void* data, DWORD len, DWORD alignment = 1,
|
||||
LONGLONG duration = 0);
|
||||
bool SelectInputMediaType(IMFTransform* transform, int in_stream_id,
|
||||
const AudioCore::ADTSData& adts, const UINT8* user_data,
|
||||
UINT32 user_data_len, GUID audio_format = MFAudioFormat_AAC);
|
||||
std::optional<ADTSMeta> DetectMediaType(const u8* buffer, std::size_t len);
|
||||
bool SelectOutputMediaType(IMFTransform* transform, int out_stream_id,
|
||||
GUID audio_format = MFAudioFormat_PCM);
|
||||
void MFFlush(IMFTransform* transform);
|
||||
MFInputState SendSample(IMFTransform* transform, DWORD in_stream_id, IMFSample* in_sample);
|
||||
std::tuple<MFOutputState, unique_mfptr<IMFSample>> ReceiveSample(IMFTransform* transform,
|
||||
DWORD out_stream_id);
|
||||
std::optional<std::vector<f32>> CopySampleToBuffer(IMFSample* sample);
|
||||
|
||||
} // namespace MFDecoder
|
@@ -42,8 +42,10 @@ private:
|
||||
SDL_GLContext context;
|
||||
};
|
||||
|
||||
static SDL_Window* CreateGLWindow(const std::string& window_title, bool gles) {
|
||||
if (gles) {
|
||||
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary)
|
||||
: EmuWindow_SDL2{system_, is_secondary} {
|
||||
// Initialize the window
|
||||
if (Settings::values.use_gles) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
@@ -52,16 +54,7 @@ static SDL_Window* CreateGLWindow(const std::string& window_title, bool gles) {
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
|
||||
}
|
||||
return SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Core::kScreenTopWidth,
|
||||
Core::kScreenTopHeight + Core::kScreenBottomHeight,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
}
|
||||
|
||||
EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, bool is_secondary)
|
||||
: EmuWindow_SDL2{system_, is_secondary} {
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8);
|
||||
SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8);
|
||||
@@ -78,16 +71,16 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, boo
|
||||
|
||||
std::string window_title = fmt::format("Citra {} | {}-{}", Common::g_build_fullname,
|
||||
Common::g_scm_branch, Common::g_scm_desc);
|
||||
render_window =
|
||||
SDL_CreateWindow(window_title.c_str(),
|
||||
SDL_WINDOWPOS_UNDEFINED, // x position
|
||||
SDL_WINDOWPOS_UNDEFINED, // y position
|
||||
Core::kScreenTopWidth, Core::kScreenTopHeight + Core::kScreenBottomHeight,
|
||||
SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
|
||||
|
||||
// First, try to create a context with the requested type.
|
||||
render_window = CreateGLWindow(window_title, Settings::values.use_gles.GetValue());
|
||||
if (render_window == nullptr) {
|
||||
// On failure, fall back to context with flipped type.
|
||||
render_window = CreateGLWindow(window_title, !Settings::values.use_gles.GetValue());
|
||||
if (render_window == nullptr) {
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
LOG_CRITICAL(Frontend, "Failed to create SDL2 window: {}", SDL_GetError());
|
||||
exit(1);
|
||||
}
|
||||
|
||||
strict_context_required = std::strcmp(SDL_GetCurrentVideoDriver(), "wayland") == 0;
|
||||
@@ -113,11 +106,7 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(Core::System& system_, bool fullscreen, boo
|
||||
}
|
||||
|
||||
render_window_id = SDL_GetWindowID(render_window);
|
||||
|
||||
int profile_mask = 0;
|
||||
SDL_GL_GetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, &profile_mask);
|
||||
auto gl_load_func =
|
||||
profile_mask == SDL_GL_CONTEXT_PROFILE_ES ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
|
||||
if (!gl_load_func(static_cast<GLADloadproc>(SDL_GL_GetProcAddress))) {
|
||||
LOG_CRITICAL(Frontend, "Failed to initialize GL functions: {}", SDL_GetError());
|
||||
|
@@ -138,50 +138,37 @@ void EmuThread::run() {
|
||||
}
|
||||
|
||||
#ifdef HAS_OPENGL
|
||||
static std::unique_ptr<QOpenGLContext> CreateQOpenGLContext(bool gles) {
|
||||
QSurfaceFormat format;
|
||||
if (gles) {
|
||||
format.setRenderableType(QSurfaceFormat::RenderableType::OpenGLES);
|
||||
format.setVersion(3, 2);
|
||||
} else {
|
||||
format.setRenderableType(QSurfaceFormat::RenderableType::OpenGL);
|
||||
format.setVersion(4, 3);
|
||||
}
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
|
||||
if (Settings::values.renderer_debug) {
|
||||
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
|
||||
}
|
||||
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
format.setSwapInterval(0);
|
||||
|
||||
auto context = std::make_unique<QOpenGLContext>();
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create OpenGL context with GLES = {}", gles);
|
||||
return nullptr;
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
class OpenGLSharedContext : public Frontend::GraphicsContext {
|
||||
public:
|
||||
/// Create the original context that should be shared from
|
||||
explicit OpenGLSharedContext() {
|
||||
// First, try to create a context with the requested type.
|
||||
context = CreateQOpenGLContext(Settings::values.use_gles.GetValue());
|
||||
if (context == nullptr) {
|
||||
// On failure, fall back to context with flipped type.
|
||||
context = CreateQOpenGLContext(!Settings::values.use_gles.GetValue());
|
||||
if (context == nullptr) {
|
||||
LOG_ERROR(Frontend, "Unable to create any OpenGL context.");
|
||||
}
|
||||
QSurfaceFormat format;
|
||||
|
||||
if (Settings::values.use_gles) {
|
||||
format.setRenderableType(QSurfaceFormat::RenderableType::OpenGLES);
|
||||
format.setVersion(3, 2);
|
||||
} else {
|
||||
format.setRenderableType(QSurfaceFormat::RenderableType::OpenGL);
|
||||
format.setVersion(4, 3);
|
||||
}
|
||||
format.setProfile(QSurfaceFormat::CoreProfile);
|
||||
|
||||
if (Settings::values.renderer_debug) {
|
||||
format.setOption(QSurfaceFormat::FormatOption::DebugContext);
|
||||
}
|
||||
|
||||
// TODO: expose a setting for buffer value (ie default/single/double/triple)
|
||||
format.setSwapBehavior(QSurfaceFormat::DefaultSwapBehavior);
|
||||
format.setSwapInterval(0);
|
||||
|
||||
context = std::make_unique<QOpenGLContext>();
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create main openGL context");
|
||||
}
|
||||
|
||||
offscreen_surface = std::make_unique<QOffscreenSurface>(nullptr);
|
||||
offscreen_surface->setFormat(context->format());
|
||||
offscreen_surface->setFormat(format);
|
||||
offscreen_surface->create();
|
||||
surface = offscreen_surface.get();
|
||||
}
|
||||
@@ -197,7 +184,7 @@ public:
|
||||
context->setShareContext(share_context);
|
||||
context->setFormat(format);
|
||||
if (!context->create()) {
|
||||
LOG_ERROR(Frontend, "Unable to create shared OpenGL context");
|
||||
LOG_ERROR(Frontend, "Unable to create shared openGL context");
|
||||
}
|
||||
|
||||
surface = main_surface;
|
||||
@@ -207,10 +194,6 @@ public:
|
||||
OpenGLSharedContext::DoneCurrent();
|
||||
}
|
||||
|
||||
bool IsGLES() override {
|
||||
return context->format().renderableType() == QSurfaceFormat::RenderableType::OpenGLES;
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
context->swapBuffers(surface);
|
||||
}
|
||||
@@ -756,9 +739,8 @@ bool GRenderWindow::LoadOpenGL() {
|
||||
#ifdef HAS_OPENGL
|
||||
auto context = CreateSharedContext();
|
||||
auto scope = context->Acquire();
|
||||
const auto gles = context->IsGLES();
|
||||
|
||||
auto gl_load_func = gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
auto gl_load_func = Settings::values.use_gles ? gladLoadGLES2Loader : gladLoadGLLoader;
|
||||
if (!gl_load_func(GetProcAddressGL)) {
|
||||
QMessageBox::warning(
|
||||
this, tr("Error while initializing OpenGL!"),
|
||||
@@ -769,14 +751,14 @@ bool GRenderWindow::LoadOpenGL() {
|
||||
const QString renderer =
|
||||
QString::fromUtf8(reinterpret_cast<const char*>(glGetString(GL_RENDERER)));
|
||||
|
||||
if (!gles && !GLAD_GL_VERSION_4_3) {
|
||||
if (!Settings::values.use_gles && !GLAD_GL_VERSION_4_3) {
|
||||
LOG_ERROR(Frontend, "GPU does not support OpenGL 4.3: {}", renderer.toStdString());
|
||||
QMessageBox::warning(this, tr("Error while initializing OpenGL 4.3!"),
|
||||
tr("Your GPU may not support OpenGL 4.3, or you do not have the "
|
||||
"latest graphics driver.<br><br>GL Renderer:<br>%1")
|
||||
.arg(renderer));
|
||||
return false;
|
||||
} else if (gles && !GLAD_GL_ES_VERSION_3_2) {
|
||||
} else if (Settings::values.use_gles && !GLAD_GL_ES_VERSION_3_2) {
|
||||
LOG_ERROR(Frontend, "GPU does not support OpenGL ES 3.2: {}", renderer.toStdString());
|
||||
QMessageBox::warning(this, tr("Error while initializing OpenGL ES 3.2!"),
|
||||
tr("Your GPU may not support OpenGL ES 3.2, or you do not have the "
|
||||
|
@@ -258,15 +258,13 @@ void GameList::OnUpdateThemedIcons() {
|
||||
for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); 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>()) {
|
||||
case GameListItemType::InstalledDir:
|
||||
child->setData(QIcon::fromTheme(QStringLiteral("sd_card")).pixmap(icon_size),
|
||||
child->setData(QIcon::fromTheme(QStringLiteral("sd_card")).pixmap(48),
|
||||
Qt::DecorationRole);
|
||||
break;
|
||||
case GameListItemType::SystemDir:
|
||||
child->setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(icon_size),
|
||||
Qt::DecorationRole);
|
||||
child->setData(QIcon::fromTheme(QStringLiteral("chip")).pixmap(48), Qt::DecorationRole);
|
||||
break;
|
||||
case GameListItemType::CustomDir: {
|
||||
const UISettings::GameDir& game_dir =
|
||||
@@ -274,12 +272,11 @@ void GameList::OnUpdateThemedIcons() {
|
||||
const QString icon_name = QFileInfo::exists(game_dir.path)
|
||||
? QStringLiteral("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;
|
||||
}
|
||||
case GameListItemType::AddDir:
|
||||
child->setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(icon_size),
|
||||
Qt::DecorationRole);
|
||||
child->setData(QIcon::fromTheme(QStringLiteral("plus")).pixmap(48), Qt::DecorationRole);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -560,9 +557,7 @@ void GameList::AddGamePopup(QMenu& context_menu, const QString& path, const QStr
|
||||
QAction* properties = context_menu.addAction(tr("Properties"));
|
||||
|
||||
const u32 program_id_high = (program_id >> 32) & 0xFFFFFFFF;
|
||||
// TODO: Use proper bitmasks for these kinds of checks.
|
||||
const bool is_application = program_id_high == 0x00040000 || program_id_high == 0x00040002 ||
|
||||
program_id_high == 0x00040010;
|
||||
const bool is_application = program_id_high == 0x00040000 || program_id_high == 0x00040010;
|
||||
|
||||
bool opengl_cache_exists = false;
|
||||
ForEachOpenGLCacheFile(
|
||||
|
@@ -2741,10 +2741,7 @@ void GMainWindow::filterBarSetChecked(bool state) {
|
||||
}
|
||||
|
||||
void GMainWindow::UpdateUITheme() {
|
||||
const QString icons_base_path = QStringLiteral(":/icons/");
|
||||
const QString default_theme = QStringLiteral("default");
|
||||
const QString default_theme_path = icons_base_path + default_theme;
|
||||
|
||||
const QString default_icons = QStringLiteral(":/icons/default");
|
||||
const QString& current_theme = UISettings::values.theme;
|
||||
const bool is_default_theme = current_theme == QString::fromUtf8(UISettings::themes[0].second);
|
||||
QStringList theme_paths(default_theme_paths);
|
||||
@@ -2762,8 +2759,8 @@ void GMainWindow::UpdateUITheme() {
|
||||
qApp->setStyleSheet({});
|
||||
setStyleSheet({});
|
||||
}
|
||||
theme_paths.append(default_theme_path);
|
||||
QIcon::setThemeName(default_theme);
|
||||
theme_paths.append(default_icons);
|
||||
QIcon::setThemeName(default_icons);
|
||||
} else {
|
||||
const QString theme_uri(QLatin1Char{':'} + current_theme + QStringLiteral("/style.qss"));
|
||||
QFile f(theme_uri);
|
||||
@@ -2775,9 +2772,9 @@ void GMainWindow::UpdateUITheme() {
|
||||
LOG_ERROR(Frontend, "Unable to set style, stylesheet file not found");
|
||||
}
|
||||
|
||||
const QString current_theme_path = icons_base_path + current_theme;
|
||||
theme_paths.append({default_theme_path, current_theme_path});
|
||||
QIcon::setThemeName(current_theme);
|
||||
const QString theme_name = QStringLiteral(":/icons/") + current_theme;
|
||||
theme_paths.append({default_icons, theme_name});
|
||||
QIcon::setThemeName(theme_name);
|
||||
}
|
||||
|
||||
QIcon::setThemeSearchPaths(theme_paths);
|
||||
|
@@ -17,7 +17,7 @@
|
||||
#include "common/file_util.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
#ifdef Q_OS_MACOS
|
||||
#ifdef Q_OS_OSX
|
||||
#define DEFAULT_TOOL_PATH QStringLiteral("../../../../maintenancetool")
|
||||
#else
|
||||
#define DEFAULT_TOOL_PATH QStringLiteral("../maintenancetool")
|
||||
@@ -102,7 +102,7 @@ QString UpdaterPrivate::ToSystemExe(QString base_path) {
|
||||
return base_path + QStringLiteral(".exe");
|
||||
else
|
||||
return base_path;
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#elif defined(Q_OS_OSX)
|
||||
if (base_path.endsWith(QStringLiteral(".app")))
|
||||
base_path.truncate(base_path.lastIndexOf(QStringLiteral(".")));
|
||||
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 {
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_MACOS)
|
||||
#if defined(Q_OS_UNIX) && !defined(Q_OS_OSX)
|
||||
const auto appimage_path = QProcessEnvironment::systemEnvironment()
|
||||
.value(QStringLiteral("APPIMAGE"), {})
|
||||
.toStdString();
|
||||
|
@@ -21,20 +21,16 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
"${VIDEO_CORE}/renderer_opengl/gl_shader_util.h"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.cpp"
|
||||
"${VIDEO_CORE}/renderer_vulkan/vk_shader_util.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_fs_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_decompiler.h"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/glsl_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/pica_fs_config.h"
|
||||
"${VIDEO_CORE}/shader/generator/shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/shader_uniforms.h"
|
||||
"${VIDEO_CORE}/shader/generator/spv_fs_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/spv_fs_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/generator/spv_shader_gen.cpp"
|
||||
"${VIDEO_CORE}/shader/generator/spv_shader_gen.h"
|
||||
"${VIDEO_CORE}/shader/shader.cpp"
|
||||
"${VIDEO_CORE}/shader/shader.h"
|
||||
"${VIDEO_CORE}/pica.cpp"
|
||||
@@ -57,8 +53,6 @@ add_custom_command(OUTPUT scm_rev.cpp
|
||||
add_library(citra_common STATIC
|
||||
aarch64/cpu_detect.cpp
|
||||
aarch64/cpu_detect.h
|
||||
aarch64/oaknut_abi.h
|
||||
aarch64/oaknut_util.h
|
||||
alignment.h
|
||||
android_storage.h
|
||||
android_storage.cpp
|
||||
@@ -82,6 +76,8 @@ add_library(citra_common STATIC
|
||||
construct.h
|
||||
dynamic_library/dynamic_library.cpp
|
||||
dynamic_library/dynamic_library.h
|
||||
dynamic_library/fdk-aac.cpp
|
||||
dynamic_library/fdk-aac.h
|
||||
dynamic_library/ffmpeg.cpp
|
||||
dynamic_library/ffmpeg.h
|
||||
error.cpp
|
||||
@@ -89,9 +85,8 @@ add_library(citra_common STATIC
|
||||
expected.h
|
||||
file_util.cpp
|
||||
file_util.h
|
||||
file_watcher.cpp
|
||||
file_watcher.h
|
||||
hash.h
|
||||
intrusive_list.h
|
||||
linear_disk_cache.h
|
||||
literals.h
|
||||
logging/backend.cpp
|
||||
@@ -113,8 +108,11 @@ add_library(citra_common STATIC
|
||||
microprofile.h
|
||||
microprofileui.h
|
||||
misc.cpp
|
||||
page_table.cpp
|
||||
page_table.h
|
||||
param_package.cpp
|
||||
param_package.h
|
||||
parent_of_member.h
|
||||
polyfill_thread.h
|
||||
precompiled_headers.h
|
||||
quaternion.h
|
||||
@@ -130,7 +128,6 @@ add_library(citra_common STATIC
|
||||
serialization/boost_flat_set.h
|
||||
serialization/boost_small_vector.hpp
|
||||
serialization/boost_vector.hpp
|
||||
static_lru_cache.h
|
||||
string_literal.h
|
||||
string_util.cpp
|
||||
string_util.h
|
||||
@@ -187,10 +184,6 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
target_link_libraries(citra_common PRIVATE xbyak)
|
||||
endif()
|
||||
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
target_link_libraries(citra_common PRIVATE oaknut)
|
||||
endif()
|
||||
|
||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(citra_common PRIVATE precompiled_headers.h)
|
||||
endif()
|
||||
|
@@ -1,155 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/arch.h"
|
||||
#if CITRA_ARCH(arm64)
|
||||
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
#include <oaknut/oaknut.hpp>
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace Common::A64 {
|
||||
|
||||
constexpr std::size_t RegToIndex(const oaknut::Reg& reg) {
|
||||
ASSERT(reg.index() != 31); // ZR not allowed
|
||||
return reg.index() + (reg.is_vector() ? 32 : 0);
|
||||
}
|
||||
|
||||
constexpr oaknut::XReg IndexToXReg(std::size_t reg_index) {
|
||||
ASSERT(reg_index <= 30);
|
||||
return oaknut::XReg(static_cast<int>(reg_index));
|
||||
}
|
||||
|
||||
constexpr oaknut::VReg IndexToVReg(std::size_t reg_index) {
|
||||
ASSERT(reg_index >= 32 && reg_index < 64);
|
||||
return oaknut::QReg(static_cast<int>(reg_index - 32));
|
||||
}
|
||||
|
||||
constexpr oaknut::Reg IndexToReg(std::size_t reg_index) {
|
||||
if (reg_index < 32) {
|
||||
return IndexToXReg(reg_index);
|
||||
} else {
|
||||
return IndexToVReg(reg_index);
|
||||
}
|
||||
}
|
||||
|
||||
inline constexpr std::bitset<64> BuildRegSet(std::initializer_list<oaknut::Reg> regs) {
|
||||
std::bitset<64> bits;
|
||||
for (const oaknut::Reg& reg : regs) {
|
||||
bits.set(RegToIndex(reg));
|
||||
}
|
||||
return bits;
|
||||
}
|
||||
|
||||
constexpr inline std::bitset<64> ABI_ALL_GPRS(0x00000000'7FFFFFFF);
|
||||
constexpr inline std::bitset<64> ABI_ALL_FPRS(0xFFFFFFFF'00000000);
|
||||
|
||||
constexpr inline oaknut::XReg ABI_RETURN = oaknut::util::X0;
|
||||
constexpr inline oaknut::XReg ABI_PARAM1 = oaknut::util::X0;
|
||||
constexpr inline oaknut::XReg ABI_PARAM2 = oaknut::util::X1;
|
||||
constexpr inline oaknut::XReg ABI_PARAM3 = oaknut::util::X2;
|
||||
constexpr inline oaknut::XReg ABI_PARAM4 = oaknut::util::X3;
|
||||
|
||||
constexpr std::bitset<64> ABI_ALL_CALLER_SAVED = 0xffffffff'4000ffff;
|
||||
constexpr std::bitset<64> ABI_ALL_CALLEE_SAVED = 0x0000ff00'7ff80000;
|
||||
|
||||
struct ABIFrameInfo {
|
||||
u32 subtraction;
|
||||
u32 fprs_offset;
|
||||
};
|
||||
|
||||
inline ABIFrameInfo ABI_CalculateFrameSize(std::bitset<64> regs, std::size_t frame_size) {
|
||||
const size_t gprs_count = (regs & ABI_ALL_GPRS).count();
|
||||
const size_t fprs_count = (regs & ABI_ALL_FPRS).count();
|
||||
|
||||
const size_t gprs_size = (gprs_count + 1) / 2 * 16;
|
||||
const size_t fprs_size = fprs_count * 16;
|
||||
|
||||
size_t total_size = 0;
|
||||
total_size += gprs_size;
|
||||
const size_t fprs_base_subtraction = total_size;
|
||||
total_size += fprs_size;
|
||||
total_size += frame_size;
|
||||
|
||||
return ABIFrameInfo{static_cast<u32>(total_size), static_cast<u32>(fprs_base_subtraction)};
|
||||
}
|
||||
|
||||
inline void ABI_PushRegisters(oaknut::CodeGenerator& code, std::bitset<64> regs,
|
||||
std::size_t frame_size = 0) {
|
||||
using namespace oaknut;
|
||||
using namespace oaknut::util;
|
||||
auto frame_info = ABI_CalculateFrameSize(regs, frame_size);
|
||||
|
||||
// Allocate stack-space
|
||||
if (frame_info.subtraction != 0) {
|
||||
code.SUB(SP, SP, frame_info.subtraction);
|
||||
}
|
||||
|
||||
// TODO(wunk): Push pairs of registers at a time with STP
|
||||
std::size_t offset = 0;
|
||||
for (std::size_t i = 0; i < 32; ++i) {
|
||||
if (regs[i] && ABI_ALL_GPRS[i]) {
|
||||
const XReg reg = IndexToXReg(i);
|
||||
code.STR(reg, SP, offset);
|
||||
offset += 8;
|
||||
}
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
for (std::size_t i = 32; i < 64; ++i) {
|
||||
if (regs[i] && ABI_ALL_FPRS[i]) {
|
||||
const VReg reg = IndexToVReg(i);
|
||||
code.STR(reg.toQ(), SP, u16(frame_info.fprs_offset + offset));
|
||||
offset += 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Allocate frame-space
|
||||
if (frame_size != 0) {
|
||||
code.SUB(SP, SP, frame_size);
|
||||
}
|
||||
}
|
||||
|
||||
inline void ABI_PopRegisters(oaknut::CodeGenerator& code, std::bitset<64> regs,
|
||||
std::size_t frame_size = 0) {
|
||||
using namespace oaknut;
|
||||
using namespace oaknut::util;
|
||||
auto frame_info = ABI_CalculateFrameSize(regs, frame_size);
|
||||
|
||||
// Free frame-space
|
||||
if (frame_size != 0) {
|
||||
code.ADD(SP, SP, frame_size);
|
||||
}
|
||||
|
||||
// TODO(wunk): Pop pairs of registers at a time with LDP
|
||||
std::size_t offset = 0;
|
||||
for (std::size_t i = 0; i < 32; ++i) {
|
||||
if (regs[i] && ABI_ALL_GPRS[i]) {
|
||||
const XReg reg = IndexToXReg(i);
|
||||
code.LDR(reg, SP, offset);
|
||||
offset += 8;
|
||||
}
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
for (std::size_t i = 32; i < 64; ++i) {
|
||||
if (regs[i] && ABI_ALL_FPRS[i]) {
|
||||
const VReg reg = IndexToVReg(i);
|
||||
code.LDR(reg.toQ(), SP, frame_info.fprs_offset + offset);
|
||||
offset += 16;
|
||||
}
|
||||
}
|
||||
|
||||
// Free stack-space
|
||||
if (frame_info.subtraction != 0) {
|
||||
code.ADD(SP, SP, frame_info.subtraction);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common::A64
|
||||
|
||||
#endif // CITRA_ARCH(arm64)
|
@@ -1,43 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "common/arch.h"
|
||||
#if CITRA_ARCH(arm64)
|
||||
|
||||
#include <type_traits>
|
||||
#include <oaknut/oaknut.hpp>
|
||||
#include "common/aarch64/oaknut_abi.h"
|
||||
|
||||
namespace Common::A64 {
|
||||
|
||||
// BL can only reach targets within +-128MiB(24 bits)
|
||||
inline bool IsWithin128M(uintptr_t ref, uintptr_t target) {
|
||||
const u64 distance = target - (ref + 4);
|
||||
return !(distance >= 0x800'0000ULL && distance <= ~0x800'0000ULL);
|
||||
}
|
||||
|
||||
inline bool IsWithin128M(const oaknut::CodeGenerator& code, uintptr_t target) {
|
||||
return IsWithin128M(code.ptr<uintptr_t>(), target);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline void CallFarFunction(oaknut::CodeGenerator& code, const T f) {
|
||||
static_assert(std::is_pointer_v<T>, "Argument must be a (function) pointer.");
|
||||
const std::uintptr_t addr = reinterpret_cast<std::uintptr_t>(f);
|
||||
if (IsWithin128M(code, addr)) {
|
||||
code.BL(reinterpret_cast<const void*>(f));
|
||||
} else {
|
||||
// X16(IP0) and X17(IP1) is the standard veneer register
|
||||
// LR is also available as an intermediate register
|
||||
// https://developer.arm.com/documentation/102374/0101/Procedure-Call-Standard
|
||||
code.MOVP2R(oaknut::util::X16, reinterpret_cast<const void*>(f));
|
||||
code.BLR(oaknut::util::X16);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Common::A64
|
||||
|
||||
#endif // CITRA_ARCH(arm64)
|
@@ -110,6 +110,14 @@ __declspec(dllimport) void __stdcall DebugBreak(void);
|
||||
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.
|
||||
// Call directly after the command or use the error num.
|
||||
// This function might change the error code.
|
||||
|
57
src/common/dynamic_library/fdk-aac.cpp
Normal file
57
src/common/dynamic_library/fdk-aac.cpp
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "common/dynamic_library/dynamic_library.h"
|
||||
#include "common/dynamic_library/fdk-aac.h"
|
||||
#include "common/logging/log.h"
|
||||
|
||||
namespace DynamicLibrary::FdkAac {
|
||||
|
||||
aacDecoder_GetLibInfo_func aacDecoder_GetLibInfo;
|
||||
aacDecoder_Open_func aacDecoder_Open;
|
||||
aacDecoder_Close_func aacDecoder_Close;
|
||||
aacDecoder_SetParam_func aacDecoder_SetParam;
|
||||
aacDecoder_GetStreamInfo_func aacDecoder_GetStreamInfo;
|
||||
aacDecoder_DecodeFrame_func aacDecoder_DecodeFrame;
|
||||
aacDecoder_Fill_func aacDecoder_Fill;
|
||||
|
||||
static std::unique_ptr<Common::DynamicLibrary> fdk_aac;
|
||||
|
||||
#define LOAD_SYMBOL(library, name) \
|
||||
any_failed = any_failed || (name = library->GetSymbol<name##_func>(#name)) == nullptr
|
||||
|
||||
bool LoadFdkAac() {
|
||||
if (fdk_aac) {
|
||||
return true;
|
||||
}
|
||||
|
||||
fdk_aac = std::make_unique<Common::DynamicLibrary>("fdk-aac", 2);
|
||||
if (!fdk_aac->IsLoaded()) {
|
||||
LOG_WARNING(Common, "Could not dynamically load libfdk-aac: {}", fdk_aac->GetLoadError());
|
||||
fdk_aac.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
auto any_failed = false;
|
||||
LOAD_SYMBOL(fdk_aac, aacDecoder_GetLibInfo);
|
||||
LOAD_SYMBOL(fdk_aac, aacDecoder_Open);
|
||||
LOAD_SYMBOL(fdk_aac, aacDecoder_Close);
|
||||
LOAD_SYMBOL(fdk_aac, aacDecoder_SetParam);
|
||||
LOAD_SYMBOL(fdk_aac, aacDecoder_GetStreamInfo);
|
||||
LOAD_SYMBOL(fdk_aac, aacDecoder_DecodeFrame);
|
||||
LOAD_SYMBOL(fdk_aac, aacDecoder_Fill);
|
||||
|
||||
if (any_failed) {
|
||||
LOG_WARNING(Common, "Could not find all required functions in libfdk-aac.");
|
||||
fdk_aac.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
LOG_INFO(Common, "Successfully loaded libfdk-aac.");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace DynamicLibrary::FdkAac
|
34
src/common/dynamic_library/fdk-aac.h
Normal file
34
src/common/dynamic_library/fdk-aac.h
Normal file
@@ -0,0 +1,34 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
extern "C" {
|
||||
#include <fdk-aac/aacdecoder_lib.h>
|
||||
}
|
||||
|
||||
namespace DynamicLibrary::FdkAac {
|
||||
|
||||
typedef INT (*aacDecoder_GetLibInfo_func)(LIB_INFO* info);
|
||||
typedef HANDLE_AACDECODER (*aacDecoder_Open_func)(TRANSPORT_TYPE transportFmt, UINT nrOfLayers);
|
||||
typedef void (*aacDecoder_Close_func)(HANDLE_AACDECODER self);
|
||||
typedef AAC_DECODER_ERROR (*aacDecoder_SetParam_func)(const HANDLE_AACDECODER self,
|
||||
const AACDEC_PARAM param, const INT value);
|
||||
typedef CStreamInfo* (*aacDecoder_GetStreamInfo_func)(HANDLE_AACDECODER self);
|
||||
typedef AAC_DECODER_ERROR (*aacDecoder_DecodeFrame_func)(HANDLE_AACDECODER self, INT_PCM* pTimeData,
|
||||
const INT timeDataSize, const UINT flags);
|
||||
typedef AAC_DECODER_ERROR (*aacDecoder_Fill_func)(HANDLE_AACDECODER self, UCHAR* pBuffer[],
|
||||
const UINT bufferSize[], UINT* bytesValid);
|
||||
|
||||
extern aacDecoder_GetLibInfo_func aacDecoder_GetLibInfo;
|
||||
extern aacDecoder_Open_func aacDecoder_Open;
|
||||
extern aacDecoder_Close_func aacDecoder_Close;
|
||||
extern aacDecoder_SetParam_func aacDecoder_SetParam;
|
||||
extern aacDecoder_GetStreamInfo_func aacDecoder_GetStreamInfo;
|
||||
extern aacDecoder_DecodeFrame_func aacDecoder_DecodeFrame;
|
||||
extern aacDecoder_Fill_func aacDecoder_Fill;
|
||||
|
||||
bool LoadFdkAac();
|
||||
|
||||
} // namespace DynamicLibrary::FdkAac
|
@@ -534,6 +534,7 @@ u64 ScanDirectoryTree(const std::string& directory, FSTEntry& parent_entry,
|
||||
}
|
||||
|
||||
void GetAllFilesFromNestedEntries(FSTEntry& directory, std::vector<FSTEntry>& output) {
|
||||
std::vector<FSTEntry> files;
|
||||
for (auto& entry : directory.children) {
|
||||
if (entry.isDirectory) {
|
||||
GetAllFilesFromNestedEntries(entry, output);
|
||||
@@ -1154,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);
|
||||
}
|
||||
|
||||
#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) {
|
||||
if (!IsOpen()) {
|
||||
m_good = false;
|
||||
|
@@ -294,18 +294,6 @@ public:
|
||||
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>
|
||||
std::size_t WriteArray(const T* data, std::size_t length) {
|
||||
static_assert(std::is_trivially_copyable_v<T>,
|
||||
@@ -324,12 +312,6 @@ public:
|
||||
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>
|
||||
std::size_t WriteBytes(const T* data, std::size_t length) {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "T must be trivially copyable");
|
||||
@@ -381,8 +363,6 @@ public:
|
||||
|
||||
private:
|
||||
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);
|
||||
|
||||
bool Open();
|
||||
|
@@ -1,148 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <windows.h>
|
||||
#include <thread>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/file_watcher.h"
|
||||
|
||||
namespace Common {
|
||||
|
||||
static FileAction Win32ActionToFileAction(DWORD action) {
|
||||
switch (action) {
|
||||
case FILE_ACTION_ADDED:
|
||||
return FileAction::Added;
|
||||
case FILE_ACTION_REMOVED:
|
||||
return FileAction::Removed;
|
||||
case FILE_ACTION_MODIFIED:
|
||||
return FileAction::Modified;
|
||||
case FILE_ACTION_RENAMED_OLD_NAME:
|
||||
return FileAction::RenamedOldName;
|
||||
case FILE_ACTION_RENAMED_NEW_NAME:
|
||||
return FileAction::RenamedNewName;
|
||||
default:
|
||||
UNREACHABLE_MSG("Unknown action {}", action);
|
||||
return FileAction::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
struct FileWatcher::Impl {
|
||||
explicit Impl(const std::string& path, FileWatcher::Callback&& callback_)
|
||||
: callback{callback_} {
|
||||
// Create file handle for the directory we are watching.
|
||||
dir_handle =
|
||||
CreateFile(path.c_str(), FILE_LIST_DIRECTORY | GENERIC_READ,
|
||||
FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING,
|
||||
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, NULL);
|
||||
ASSERT_MSG(dir_handle != INVALID_HANDLE_VALUE, "Unable to create watch file");
|
||||
|
||||
// Create an event that will terminate the thread when fired.
|
||||
termination_event = CreateEvent(NULL, TRUE, FALSE, NULL);
|
||||
ASSERT_MSG(termination_event != INVALID_HANDLE_VALUE, "Unable to create watch event");
|
||||
|
||||
// Create an event that will wake up the watcher thread on filesystem changes.
|
||||
overlapped.hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
ASSERT_MSG(overlapped.hEvent != INVALID_HANDLE_VALUE, "Unable to create watch event");
|
||||
|
||||
// Create the watcher thread.
|
||||
watch_thread = std::thread([this] { WatcherThread(); });
|
||||
}
|
||||
|
||||
~Impl() {
|
||||
// Signal watcher thread to terminate.
|
||||
SetEvent(termination_event);
|
||||
|
||||
// Wait for said termination.
|
||||
if (watch_thread.joinable()) {
|
||||
watch_thread.join();
|
||||
}
|
||||
|
||||
// Close used handles.
|
||||
CancelIo(dir_handle);
|
||||
GetOverlappedResult(dir_handle, &overlapped, &num_bytes_read, TRUE);
|
||||
CloseHandle(termination_event);
|
||||
CloseHandle(overlapped.hEvent);
|
||||
}
|
||||
|
||||
void WatcherThread() {
|
||||
const std::array wait_handles{overlapped.hEvent, termination_event};
|
||||
while (is_running) {
|
||||
bool result =
|
||||
ReadDirectoryChangesW(dir_handle, buffer.data(), buffer.size(), TRUE,
|
||||
FILE_NOTIFY_CHANGE_FILE_NAME |
|
||||
FILE_NOTIFY_CHANGE_DIR_NAME |
|
||||
FILE_NOTIFY_CHANGE_LAST_WRITE, NULL, &overlapped, NULL);
|
||||
ASSERT_MSG(result, "Unable to read directory changes: {}", GetLastErrorMsg());
|
||||
|
||||
// Sleep until we receive a file changed notification or a termination event.
|
||||
switch (
|
||||
WaitForMultipleObjects(wait_handles.size(), wait_handles.data(), FALSE, INFINITE)) {
|
||||
case WAIT_OBJECT_0: {
|
||||
// Retrieve asynchronously the data from ReadDirectoryChangesW.
|
||||
result = GetOverlappedResult(dir_handle, &overlapped, &num_bytes_read, TRUE);
|
||||
ASSERT_MSG(result, "Unable to retrieve overlapped result: {}", GetLastErrorMsg());
|
||||
|
||||
// Notify about file changes.
|
||||
NotifyFileChanges();
|
||||
break;
|
||||
}
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
is_running = false;
|
||||
break;
|
||||
case WAIT_FAILED:
|
||||
UNREACHABLE_MSG("Failed waiting for file watcher events: {}", GetLastErrorMsg());
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void NotifyFileChanges() {
|
||||
// If no data was read we have nothing to do.
|
||||
if (num_bytes_read == 0) [[unlikely]] {
|
||||
return;
|
||||
}
|
||||
|
||||
u32 next_entry_offset{};
|
||||
while (true) {
|
||||
// Retrieve file notify information.
|
||||
auto fni = reinterpret_cast<FILE_NOTIFY_INFORMATION*>(buffer.data() + next_entry_offset);
|
||||
|
||||
// Call the callback function informing about the change.
|
||||
if (fni->Action != 0) {
|
||||
std::string file_name(fni->FileNameLength / sizeof(WCHAR), ' ');
|
||||
WideCharToMultiByte(CP_UTF8, 0, fni->FileName,
|
||||
fni->FileNameLength / sizeof(WCHAR), file_name.data(), file_name.size(), NULL, NULL);
|
||||
const FileAction action = Win32ActionToFileAction(fni->Action);
|
||||
callback(file_name, action);
|
||||
}
|
||||
|
||||
// If this was the last action, break.
|
||||
if (fni->NextEntryOffset == 0) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Move to next fni structure.
|
||||
next_entry_offset += fni->NextEntryOffset;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr size_t DirectoryWatcherBufferSize = 4096;
|
||||
FileWatcher::Callback callback;
|
||||
HANDLE dir_handle{};
|
||||
HANDLE termination_event{};
|
||||
OVERLAPPED overlapped{};
|
||||
std::array<u8, DirectoryWatcherBufferSize> buffer{};
|
||||
std::atomic_bool is_running{true};
|
||||
DWORD num_bytes_read{};
|
||||
std::thread watch_thread;
|
||||
};
|
||||
|
||||
FileWatcher::FileWatcher(const std::string& log_dir, Callback&& callback)
|
||||
: impl{std::make_unique<Impl>(log_dir, std::move(callback))} {}
|
||||
|
||||
FileWatcher::~FileWatcher() = default;
|
||||
|
||||
} // namespace Common
|
@@ -1,33 +0,0 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
|
||||
namespace Common {
|
||||
|
||||
enum class FileAction : u8 {
|
||||
Added,
|
||||
Removed,
|
||||
Modified,
|
||||
RenamedOldName,
|
||||
RenamedNewName,
|
||||
Invalid = std::numeric_limits<u8>::max(),
|
||||
};
|
||||
|
||||
class FileWatcher {
|
||||
using Callback = std::function<void(const std::string&, FileAction)>;
|
||||
|
||||
public:
|
||||
explicit FileWatcher(const std::string& log_dir, Callback&& callback);
|
||||
~FileWatcher();
|
||||
|
||||
private:
|
||||
struct Impl;
|
||||
std::unique_ptr<Impl> impl;
|
||||
};
|
||||
|
||||
} // namespace Common
|
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/kernel.cpp
|
||||
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.h
|
||||
hle/kernel/mutex.cpp
|
||||
@@ -473,6 +494,7 @@ add_library(citra_core STATIC
|
||||
tracer/citrace.h
|
||||
tracer/recorder.cpp
|
||||
tracer/recorder.h
|
||||
hle/kernel/slab_helpers.h
|
||||
)
|
||||
|
||||
create_target_directory_groups(citra_core)
|
||||
|
@@ -86,20 +86,6 @@ public:
|
||||
*/
|
||||
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:
|
||||
std::unique_ptr<DelayGenerator> delay_generator;
|
||||
|
||||
|
@@ -131,14 +131,6 @@ public:
|
||||
}
|
||||
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:
|
||||
std::shared_ptr<RomFSReader> romfs_file;
|
||||
|
||||
|
@@ -53,14 +53,6 @@ public:
|
||||
|
||||
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:
|
||||
struct File;
|
||||
struct Directory {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
#include <cryptopp/aes.h>
|
||||
#include <cryptopp/modes.h>
|
||||
#include "common/archives.h"
|
||||
@@ -10,102 +9,17 @@ SERIALIZE_EXPORT_IMPL(FileSys::DirectRomFSReader)
|
||||
namespace FileSys {
|
||||
|
||||
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)
|
||||
return 0; // Crypto++ does not like zero size buffer
|
||||
|
||||
const auto segments = BreakupRead(offset, length);
|
||||
size_t read_progress = 0;
|
||||
|
||||
// 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) {
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||
d.Seek(crypto_offset + offset);
|
||||
d.ProcessData(buffer, buffer, length);
|
||||
}
|
||||
LOG_TRACE(Service_FS, "RomFS Cache SKIP: offset={}, length={}", offset, length);
|
||||
return length;
|
||||
file.Seek(file_offset + offset, SEEK_SET);
|
||||
std::size_t read_length = std::min(length, static_cast<std::size_t>(data_size) - offset);
|
||||
read_length = file.ReadBytes(buffer, read_length);
|
||||
if (is_encrypted) {
|
||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption d(key.data(), key.size(), ctr.data());
|
||||
d.Seek(crypto_offset + offset);
|
||||
d.ProcessData(buffer, buffer, read_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_TRACE(Service_FS, "RomFS Cache MISS: page={}, length={}, into={}", page, seg.second,
|
||||
(seg.first - page));
|
||||
} else {
|
||||
LOG_TRACE(Service_FS, "RomFS 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;
|
||||
return read_length;
|
||||
}
|
||||
|
||||
} // namespace FileSys
|
||||
|
@@ -1,14 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <shared_mutex>
|
||||
#include <boost/serialization/array.hpp>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include <boost/serialization/export.hpp>
|
||||
#include "common/alignment.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/file_util.h"
|
||||
#include "common/static_lru_cache.h"
|
||||
|
||||
namespace FileSys {
|
||||
|
||||
@@ -21,8 +18,6 @@ public:
|
||||
|
||||
virtual std::size_t GetSize() const = 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:
|
||||
template <class Archive>
|
||||
@@ -53,10 +48,6 @@ public:
|
||||
|
||||
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:
|
||||
bool is_encrypted;
|
||||
FileUtil::IOFile file;
|
||||
@@ -66,23 +57,8 @@ private:
|
||||
u64 crypto_offset;
|
||||
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;
|
||||
|
||||
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>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& boost::serialization::base_object<RomFSReader>(*this);
|
||||
|
@@ -77,11 +77,6 @@ class GraphicsContext {
|
||||
public:
|
||||
virtual ~GraphicsContext();
|
||||
|
||||
/// Checks whether this context uses OpenGL ES.
|
||||
virtual bool IsGLES() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Inform the driver to swap the front/back buffers and present the current image
|
||||
virtual void SwapBuffers(){};
|
||||
|
||||
|
@@ -6,6 +6,7 @@
|
||||
#include "common/archives.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/global.h"
|
||||
#include "core/hle/kernel/address_arbiter.h"
|
||||
#include "core/hle/kernel/errors.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
@@ -20,7 +21,7 @@ void AddressArbiter::WaitThread(std::shared_ptr<Thread> thread, VAddr wait_addre
|
||||
waiting_threads.emplace_back(std::move(thread));
|
||||
}
|
||||
|
||||
u64 AddressArbiter::ResumeAllThreads(VAddr address) {
|
||||
void AddressArbiter::ResumeAllThreads(VAddr address) {
|
||||
// Determine which threads are waiting on this address, those should be woken up.
|
||||
auto itr = std::stable_partition(waiting_threads.begin(), waiting_threads.end(),
|
||||
[address](const auto& thread) {
|
||||
@@ -30,15 +31,13 @@ u64 AddressArbiter::ResumeAllThreads(VAddr address) {
|
||||
});
|
||||
|
||||
// Wake up all the found threads
|
||||
const u64 num_threads = std::distance(itr, waiting_threads.end());
|
||||
std::for_each(itr, waiting_threads.end(), [](auto& thread) { thread->ResumeFromWait(); });
|
||||
|
||||
// Remove the woken up threads from the wait list.
|
||||
waiting_threads.erase(itr, waiting_threads.end());
|
||||
return num_threads;
|
||||
}
|
||||
|
||||
bool AddressArbiter::ResumeHighestPriorityThread(VAddr address) {
|
||||
std::shared_ptr<Thread> AddressArbiter::ResumeHighestPriorityThread(VAddr address) {
|
||||
// Determine which threads are waiting on this address, those should be considered for wakeup.
|
||||
auto matches_start = std::stable_partition(
|
||||
waiting_threads.begin(), waiting_threads.end(), [address](const auto& thread) {
|
||||
@@ -55,15 +54,14 @@ bool AddressArbiter::ResumeHighestPriorityThread(VAddr address) {
|
||||
return lhs->current_priority < rhs->current_priority;
|
||||
});
|
||||
|
||||
if (itr == waiting_threads.end()) {
|
||||
return false;
|
||||
}
|
||||
if (itr == waiting_threads.end())
|
||||
return nullptr;
|
||||
|
||||
auto thread = *itr;
|
||||
thread->ResumeFromWait();
|
||||
waiting_threads.erase(itr);
|
||||
|
||||
return true;
|
||||
waiting_threads.erase(itr);
|
||||
return thread;
|
||||
}
|
||||
|
||||
AddressArbiter::AddressArbiter(KernelSystem& kernel)
|
||||
@@ -109,28 +107,17 @@ ResultCode AddressArbiter::ArbitrateAddress(std::shared_ptr<Thread> thread, Arbi
|
||||
switch (type) {
|
||||
|
||||
// Signal thread(s) waiting for arbitrate address...
|
||||
case ArbitrationType::Signal: {
|
||||
u64 num_threads{};
|
||||
|
||||
case ArbitrationType::Signal:
|
||||
// Negative value means resume all threads
|
||||
if (value < 0) {
|
||||
num_threads = ResumeAllThreads(address);
|
||||
ResumeAllThreads(address);
|
||||
} else {
|
||||
// Resume first N threads
|
||||
for (s32 i = 0; i < value; i++) {
|
||||
num_threads += ResumeHighestPriorityThread(address);
|
||||
}
|
||||
}
|
||||
|
||||
// Prevents lag from low priority threads that spam svcArbitrateAddress and wake no threads
|
||||
// The tick count is taken directly from official HOS kernel. The priority value is one less
|
||||
// than official kernel as the affected FMV threads dont meet the priority threshold of 50.
|
||||
// TODO: Revisit this when scheduler is rewritten and adjust if there isn't a problem there.
|
||||
if (num_threads == 0 && thread->current_priority >= 49) {
|
||||
kernel.current_cpu->GetTimer().AddTicks(1614u);
|
||||
for (int i = 0; i < value; i++)
|
||||
ResumeHighestPriorityThread(address);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Wait current thread (acquire the arbiter)...
|
||||
case ArbitrationType::WaitIfLessThan:
|
||||
if ((s32)kernel.memory.Read32(address) < value) {
|
||||
|
@@ -65,11 +65,11 @@ private:
|
||||
void WaitThread(std::shared_ptr<Thread> thread, VAddr wait_address);
|
||||
|
||||
/// Resume all threads found to be waiting on the address under this address arbiter
|
||||
u64 ResumeAllThreads(VAddr address);
|
||||
void ResumeAllThreads(VAddr address);
|
||||
|
||||
/// Resume one thread found to be waiting on the address under this address arbiter and return
|
||||
/// the resumed thread.
|
||||
bool ResumeHighestPriorityThread(VAddr address);
|
||||
std::shared_ptr<Thread> ResumeHighestPriorityThread(VAddr address);
|
||||
|
||||
/// Threads waiting for the address arbiter to be signaled.
|
||||
std::vector<std::shared_ptr<Thread>> waiting_threads;
|
||||
|
@@ -221,13 +221,6 @@ public:
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client thread that made the service request.
|
||||
*/
|
||||
std::shared_ptr<Thread> ClientThread() const {
|
||||
return thread;
|
||||
}
|
||||
|
||||
class WakeupCallback {
|
||||
public:
|
||||
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/serialization/atomic.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/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/memory.h"
|
||||
#include "core/hle/kernel/process.h"
|
||||
@@ -29,6 +31,7 @@ KernelSystem::KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
||||
: memory(memory), timing(timing),
|
||||
prepare_reschedule_callback(std::move(prepare_reschedule_callback)), memory_mode(memory_mode),
|
||||
n3ds_hw_caps(n3ds_hw_caps) {
|
||||
slab_heap_container = std::make_unique<SlabHeapContainer>();
|
||||
std::generate(memory_regions.begin(), memory_regions.end(),
|
||||
[] { return std::make_shared<MemoryRegionInfo>(); });
|
||||
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)
|
||||
|
||||
} // namespace Kernel
|
||||
|
@@ -130,6 +130,9 @@ private:
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class KSlabHeap;
|
||||
|
||||
class KernelSystem {
|
||||
public:
|
||||
explicit KernelSystem(Memory::MemorySystem& memory, Core::Timing& timing,
|
||||
@@ -260,6 +263,10 @@ public:
|
||||
MemoryPermission other_permissions,
|
||||
std::string name = "Unknown Applet");
|
||||
|
||||
/// Gets the slab heap for the specified kernel object type.
|
||||
template <typename T>
|
||||
KSlabHeap<T>& SlabHeap();
|
||||
|
||||
u32 GenerateObjectID();
|
||||
|
||||
/// Retrieves a process from the current list of processes.
|
||||
@@ -369,6 +376,10 @@ private:
|
||||
MemoryMode memory_mode;
|
||||
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;
|
||||
template <class Archive>
|
||||
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; \
|
||||
if (CONCAT2(check_result_L, __LINE__).IsError()) \
|
||||
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);
|
||||
u64 offset = rp.Pop<u64>();
|
||||
u32 length = rp.Pop<u32>();
|
||||
auto& buffer = rp.PopMappedBuffer();
|
||||
LOG_TRACE(Service_FS, "Read {}: offset=0x{:x} length=0x{:08X}", GetName(), offset, length);
|
||||
|
||||
const FileSessionSlot* file = GetSessionData(ctx.Session());
|
||||
@@ -75,94 +76,22 @@ void File::Read(Kernel::HLERequestContext& ctx) {
|
||||
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);
|
||||
std::unique_ptr<u8*> data = std::make_unique<u8*>(static_cast<u8*>(operator new(length)));
|
||||
const auto read = backend->Read(offset, length, *data);
|
||||
if (read.Failed()) {
|
||||
rb.Push(read.Code());
|
||||
rb.Push<u32>(0);
|
||||
} else {
|
||||
buffer.Write(*data, 0, *read);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(static_cast<u32>(*read));
|
||||
}
|
||||
rb.PushMappedBuffer(buffer);
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||
|
||||
std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)};
|
||||
ctx.SleepClientThread("file::read", read_timeout_ns, nullptr);
|
||||
return;
|
||||
std::vector<u8> data(length);
|
||||
ResultVal<std::size_t> read = backend->Read(offset, data.size(), data.data());
|
||||
if (read.Failed()) {
|
||||
rb.Push(read.Code());
|
||||
rb.Push<u32>(0);
|
||||
} else {
|
||||
buffer.Write(data.data(), 0, *read);
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
rb.Push<u32>(static_cast<u32>(*read));
|
||||
}
|
||||
rb.PushMappedBuffer(buffer);
|
||||
|
||||
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);
|
||||
std::chrono::nanoseconds read_timeout_ns{backend->GetReadDelayNs(length)};
|
||||
ctx.SleepClientThread("file::read", read_timeout_ns, nullptr);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
const auto process_id = rp.Pop<u32>();
|
||||
@@ -707,20 +687,8 @@ void FS_USER::GetProgramLaunchInfo(Kernel::HLERequestContext& ctx) {
|
||||
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.PushRaw<ProgramInfo>(program_info);
|
||||
rb.PushRaw(program_info_result.Unwrap());
|
||||
}
|
||||
|
||||
void FS_USER::ObsoletedCreateExtSaveData(Kernel::HLERequestContext& ctx) {
|
||||
@@ -807,12 +775,12 @@ void FS_USER::AddSeed(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||
void FS_USER::SetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
const u64 value = rp.Pop<u64>();
|
||||
const u32 secure_value_slot = rp.Pop<u32>();
|
||||
const u32 unique_id = rp.Pop<u32>();
|
||||
const u8 title_variation = rp.Pop<u8>();
|
||||
u64 value = rp.Pop<u64>();
|
||||
u32 secure_value_slot = rp.Pop<u32>();
|
||||
u32 unique_id = rp.Pop<u32>();
|
||||
u8 title_variation = rp.Pop<u8>();
|
||||
|
||||
// TODO: Generate and Save the Secure Value
|
||||
|
||||
@@ -826,11 +794,12 @@ void FS_USER::ObsoletedSetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push(RESULT_SUCCESS);
|
||||
}
|
||||
|
||||
void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||
void FS_USER::GetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
const u32 secure_value_slot = rp.Pop<u32>();
|
||||
const u32 unique_id = rp.Pop<u32>();
|
||||
const u8 title_variation = rp.Pop<u8>();
|
||||
|
||||
u32 secure_value_slot = rp.Pop<u32>();
|
||||
u32 unique_id = rp.Pop<u32>();
|
||||
u8 title_variation = rp.Pop<u8>();
|
||||
|
||||
LOG_WARNING(
|
||||
Service_FS,
|
||||
@@ -847,77 +816,7 @@ void FS_USER::ObsoletedGetSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||
rb.Push<u64>(0); // the secure value
|
||||
}
|
||||
|
||||
void FS_USER::SetThisSaveDataSecureValue(Kernel::HLERequestContext& ctx) {
|
||||
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) {
|
||||
void FS_USER::Register(u32 process_id, u64 program_id, const std::string& filepath) {
|
||||
const MediaType media_type = GetMediaTypeFromPath(filepath);
|
||||
program_info_map.insert_or_assign(process_id, ProgramInfo{program_id, media_type});
|
||||
if (media_type == MediaType::GameCard) {
|
||||
@@ -929,19 +828,6 @@ std::string FS_USER::GetCurrentGamecardPath() const {
|
||||
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) {
|
||||
// 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"},
|
||||
{0x082C, nullptr, "CardNorDirectCpuWriteWithoutVerify"},
|
||||
{0x082D, nullptr, "CardNorDirectSectorEraseWithoutVerify"},
|
||||
{0x082E, &FS_USER::GetProductInfo, "GetProductInfo"},
|
||||
{0x082E, nullptr, "GetProductInfo"},
|
||||
{0x082F, &FS_USER::GetProgramLaunchInfo, "GetProgramLaunchInfo"},
|
||||
{0x0830, &FS_USER::ObsoletedCreateExtSaveData, "Obsoleted_3_0_CreateExtSaveData"},
|
||||
{0x0831, nullptr, "CreateSharedExtSaveData"},
|
||||
@@ -1098,16 +984,12 @@ FS_USER::FS_USER(Core::System& system)
|
||||
{0x0862, &FS_USER::SetPriority, "SetPriority"},
|
||||
{0x0863, &FS_USER::GetPriority, "GetPriority"},
|
||||
{0x0864, nullptr, "GetNandInfo"},
|
||||
{0x0865, &FS_USER::ObsoletedSetSaveDataSecureValue, "SetSaveDataSecureValue"},
|
||||
{0x0866, &FS_USER::ObsoletedGetSaveDataSecureValue, "GetSaveDataSecureValue"},
|
||||
{0x0865, &FS_USER::SetSaveDataSecureValue, "SetSaveDataSecureValue"},
|
||||
{0x0866, &FS_USER::GetSaveDataSecureValue, "GetSaveDataSecureValue"},
|
||||
{0x0867, nullptr, "ControlSecureSave"},
|
||||
{0x0868, nullptr, "GetMediaType"},
|
||||
{0x0869, nullptr, "GetNandEraseCount"},
|
||||
{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"},
|
||||
{0x087D, &FS_USER::GetNumSeeds, "GetNumSeeds"},
|
||||
{0x0886, nullptr, "CheckUpdatedDat"},
|
||||
|
@@ -4,12 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <unordered_map>
|
||||
#include <boost/serialization/base_object.hpp>
|
||||
#include "common/common_types.h"
|
||||
#include "core/file_sys/errors.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Core {
|
||||
@@ -49,23 +47,12 @@ class FS_USER final : public ServiceFramework<FS_USER, ClientSlot> {
|
||||
public:
|
||||
explicit FS_USER(Core::System& system);
|
||||
|
||||
// On real HW this is part of FSReg (FSReg:Register). But since that module is only used by
|
||||
// loader and pm, which we HLEed, we can just directly use it here
|
||||
void RegisterProgramInfo(u32 process_id, u64 program_id, const std::string& filepath);
|
||||
// On real HW this is part of FS:Reg. But since that module is only used by loader and pm, which
|
||||
// we HLEed, we can just directly use it here
|
||||
void Register(u32 process_id, u64 program_id, const std::string& filepath);
|
||||
|
||||
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.
|
||||
ResultVal<ProgramInfo> GetProgramLaunchInfo(u32 process_id) const {
|
||||
auto info = program_info_map.find(process_id);
|
||||
@@ -522,17 +509,6 @@ private:
|
||||
*/
|
||||
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.
|
||||
* Inputs:
|
||||
@@ -624,7 +600,7 @@ private:
|
||||
* 0 : 0x08650140
|
||||
* 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.
|
||||
@@ -639,57 +615,6 @@ private:
|
||||
* 2 : If Secure Value doesn't exist, 0, if it exists, 1
|
||||
* 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);
|
||||
|
||||
static ResultVal<u16> GetSpecialContentIndexFromGameCard(u64 title_id, SpecialContentType type);
|
||||
@@ -699,8 +624,6 @@ private:
|
||||
std::unordered_map<u32, ProgramInfo> program_info_map;
|
||||
std::string current_gamecard_path;
|
||||
|
||||
std::unordered_map<u32, ProductInfo> product_info_map;
|
||||
|
||||
u32 priority = -1; ///< For SetPriority and GetPriority service functions
|
||||
|
||||
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
|
||||
auto 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);
|
||||
|
||||
|
@@ -174,16 +174,7 @@ ResultStatus AppLoader_NCCH::LoadExec(std::shared_ptr<Kernel::Process>& process)
|
||||
auto fs_user =
|
||||
Core::System::GetInstance().ServiceManager().GetService<Service::FS::FS_USER>(
|
||||
"fs:USER");
|
||||
fs_user->RegisterProgramInfo(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);
|
||||
fs_user->Register(process->process_id, process->codeset->program_id, filepath);
|
||||
|
||||
process->Run(priority, stack_size);
|
||||
return ResultStatus::Success;
|
||||
|
@@ -329,6 +329,11 @@ public:
|
||||
*/
|
||||
u8* GetPointer(VAddr vaddr);
|
||||
|
||||
template <typename T>
|
||||
T* GetPointer(VAddr vaddr) {
|
||||
return reinterpret_cast<T*>(GetPointer(vaddr));
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a pointer to the given address.
|
||||
*
|
||||
|
@@ -12,10 +12,11 @@ add_executable(tests
|
||||
core/memory/vm_manager.cpp
|
||||
precompiled_headers.h
|
||||
audio_core/hle/hle.cpp
|
||||
audio_core/hle/adts_reader.cpp
|
||||
audio_core/lle/lle.cpp
|
||||
audio_core/audio_fixures.h
|
||||
audio_core/decoder_tests.cpp
|
||||
video_core/shader/shader_jit_compiler.cpp
|
||||
video_core/shader/shader_jit_x64_compiler.cpp
|
||||
)
|
||||
|
||||
create_target_directory_groups(tests)
|
||||
|
77
src/tests/audio_core/hle/adts_reader.cpp
Normal file
77
src/tests/audio_core/hle/adts_reader.cpp
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <catch2/catch_test_macros.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "audio_core/hle/adts.h"
|
||||
|
||||
namespace {
|
||||
constexpr std::array<u32, 16> freq_table = {96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050,
|
||||
16000, 12000, 11025, 8000, 7350, 0, 0, 0};
|
||||
constexpr std::array<u8, 8> channel_table = {0, 1, 2, 3, 4, 5, 6, 8};
|
||||
|
||||
AudioCore::ADTSData ParseADTS_Old(const unsigned char* buffer) {
|
||||
u32 tmp = 0;
|
||||
AudioCore::ADTSData out{};
|
||||
|
||||
// sync word 0xfff
|
||||
tmp = (buffer[0] << 8) | (buffer[1] & 0xf0);
|
||||
if ((tmp & 0xffff) != 0xfff0) {
|
||||
out.length = 0;
|
||||
return out;
|
||||
}
|
||||
// bit 16 = no CRC
|
||||
out.header_length = (buffer[1] & 0x1) ? 7 : 9;
|
||||
out.mpeg2 = (buffer[1] >> 3) & 0x1;
|
||||
// bit 17 to 18
|
||||
out.profile = (buffer[2] >> 6) + 1;
|
||||
// bit 19 to 22
|
||||
tmp = (buffer[2] >> 2) & 0xf;
|
||||
out.samplerate_idx = tmp;
|
||||
out.samplerate = (tmp > 15) ? 0 : freq_table[tmp];
|
||||
// bit 24 to 26
|
||||
tmp = ((buffer[2] & 0x1) << 2) | ((buffer[3] >> 6) & 0x3);
|
||||
out.channel_idx = tmp;
|
||||
out.channels = (tmp > 7) ? 0 : channel_table[tmp];
|
||||
|
||||
// bit 55 to 56
|
||||
out.framecount = (buffer[6] & 0x3) + 1;
|
||||
|
||||
// bit 31 to 43
|
||||
tmp = (buffer[3] & 0x3) << 11;
|
||||
tmp |= (buffer[4] << 3) & 0x7f8;
|
||||
tmp |= (buffer[5] >> 5) & 0x7;
|
||||
|
||||
out.length = tmp;
|
||||
|
||||
return out;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
TEST_CASE("ParseADTS fuzz", "[audio_core][hle]") {
|
||||
for (u32 i = 0; i < 0x10000; i++) {
|
||||
std::array<u8, 7> adts_header;
|
||||
std::string adts_header_string = "ADTS Header: ";
|
||||
for (auto& it : adts_header) {
|
||||
it = static_cast<u8>(rand());
|
||||
adts_header_string.append(fmt::format("{:2X} ", it));
|
||||
}
|
||||
INFO(adts_header_string);
|
||||
|
||||
AudioCore::ADTSData out_old_impl =
|
||||
ParseADTS_Old(reinterpret_cast<const unsigned char*>(adts_header.data()));
|
||||
AudioCore::ADTSData out = AudioCore::ParseADTS(adts_header.data());
|
||||
|
||||
REQUIRE(out_old_impl.length == out.length);
|
||||
REQUIRE(out_old_impl.channels == out.channels);
|
||||
REQUIRE(out_old_impl.channel_idx == out.channel_idx);
|
||||
REQUIRE(out_old_impl.framecount == out.framecount);
|
||||
REQUIRE(out_old_impl.header_length == out.header_length);
|
||||
REQUIRE(out_old_impl.mpeg2 == out.mpeg2);
|
||||
REQUIRE(out_old_impl.profile == out.profile);
|
||||
REQUIRE(out_old_impl.samplerate == out.samplerate);
|
||||
REQUIRE(out_old_impl.samplerate_idx == out.samplerate_idx);
|
||||
}
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Copyright 2017 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/arch.h"
|
||||
#if CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
|
||||
#if CITRA_ARCH(x86_64)
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
@@ -14,11 +14,7 @@
|
||||
#include <fmt/format.h>
|
||||
#include <nihstro/inline_assembly.h>
|
||||
#include "video_core/shader/shader_interpreter.h"
|
||||
#if CITRA_ARCH(x86_64)
|
||||
#include "video_core/shader/shader_jit_x64_compiler.h"
|
||||
#elif CITRA_ARCH(arm64)
|
||||
#include "video_core/shader/shader_jit_a64_compiler.h"
|
||||
#endif
|
||||
|
||||
using JitShader = Pica::Shader::JitShader;
|
||||
using ShaderInterpreter = Pica::Shader::InterpreterEngine;
|
||||
@@ -35,18 +31,6 @@ static constexpr Common::Vec4f vec4_zero = Common::Vec4f::AssignToAll(0.0f);
|
||||
|
||||
namespace Catch {
|
||||
template <>
|
||||
struct StringMaker<Common::Vec2f> {
|
||||
static std::string convert(Common::Vec2f value) {
|
||||
return fmt::format("({}, {})", value.x, value.y);
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct StringMaker<Common::Vec3f> {
|
||||
static std::string convert(Common::Vec3f value) {
|
||||
return fmt::format("({}, {}, {})", value.r(), value.g(), value.b());
|
||||
}
|
||||
};
|
||||
template <>
|
||||
struct StringMaker<Common::Vec4f> {
|
||||
static std::string convert(Common::Vec4f value) {
|
||||
return fmt::format("({}, {}, {}, {})", value.r(), value.g(), value.b(), value.a());
|
||||
@@ -75,11 +59,6 @@ public:
|
||||
shader_jit.Compile(&shader_setup->program_code, &shader_setup->swizzle_data);
|
||||
}
|
||||
|
||||
explicit ShaderTest(std::unique_ptr<Pica::Shader::ShaderSetup> input_shader_setup)
|
||||
: shader_setup(std::move(input_shader_setup)) {
|
||||
shader_jit.Compile(&shader_setup->program_code, &shader_setup->swizzle_data);
|
||||
}
|
||||
|
||||
Common::Vec4f Run(std::span<const Common::Vec4f> inputs) {
|
||||
Pica::Shader::UnitState shader_unit;
|
||||
RunJit(shader_unit, inputs);
|
||||
@@ -165,41 +144,6 @@ TEST_CASE("ADD", "[video_core][shader][shader_jit]") {
|
||||
REQUIRE(std::isinf(shader.Run({INFINITY, -1.0f}).x));
|
||||
}
|
||||
|
||||
TEST_CASE("CALL", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input = SourceRegister::MakeInput(0);
|
||||
const auto sh_output = DestRegister::MakeOutput(0);
|
||||
|
||||
auto shader_setup = CompileShaderSetup({
|
||||
{OpCode::Id::NOP}, // call foo
|
||||
{OpCode::Id::END},
|
||||
// .proc foo
|
||||
{OpCode::Id::NOP}, // call ex2
|
||||
{OpCode::Id::END},
|
||||
// .proc ex2
|
||||
{OpCode::Id::EX2, sh_output, sh_input},
|
||||
{OpCode::Id::END},
|
||||
});
|
||||
|
||||
// nihstro does not support the CALL* instructions, so the instruction-binary must be manually
|
||||
// inserted here:
|
||||
nihstro::Instruction CALL = {};
|
||||
CALL.opcode = nihstro::OpCode(nihstro::OpCode::Id::CALL);
|
||||
|
||||
// call foo
|
||||
CALL.flow_control.dest_offset = 2;
|
||||
CALL.flow_control.num_instructions = 1;
|
||||
shader_setup->program_code[0] = CALL.hex;
|
||||
|
||||
// call ex2
|
||||
CALL.flow_control.dest_offset = 4;
|
||||
CALL.flow_control.num_instructions = 1;
|
||||
shader_setup->program_code[2] = CALL.hex;
|
||||
|
||||
auto shader = ShaderTest(std::move(shader_setup));
|
||||
|
||||
REQUIRE(shader.Run(0.f).x == Catch::Approx(1.f));
|
||||
}
|
||||
|
||||
TEST_CASE("DP3", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input1 = SourceRegister::MakeInput(0);
|
||||
const auto sh_input2 = SourceRegister::MakeInput(1);
|
||||
@@ -451,39 +395,6 @@ TEST_CASE("RSQ", "[video_core][shader][shader_jit]") {
|
||||
REQUIRE(shader.Run({0.0625f}).x == Catch::Approx(4.0f).margin(0.004f));
|
||||
}
|
||||
|
||||
TEST_CASE("Uniform Read", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input = SourceRegister::MakeInput(0);
|
||||
const auto sh_c0 = SourceRegister::MakeFloat(0);
|
||||
const auto sh_output = DestRegister::MakeOutput(0);
|
||||
|
||||
auto shader = ShaderTest({
|
||||
// mova a0.x, sh_input.x
|
||||
{OpCode::Id::MOVA, DestRegister{}, "x", sh_input, "x", SourceRegister{}, "",
|
||||
nihstro::InlineAsm::RelativeAddress::A1},
|
||||
// mov sh_output.xyzw, c0[a0.x].xyzw
|
||||
{OpCode::Id::MOV, sh_output, "xyzw", sh_c0, "xyzw", SourceRegister{}, "",
|
||||
nihstro::InlineAsm::RelativeAddress::A1},
|
||||
{OpCode::Id::END},
|
||||
});
|
||||
|
||||
// Prepare shader uniforms
|
||||
std::array<Common::Vec4f, 96> f_uniforms = {};
|
||||
for (u32 i = 0; i < 96; ++i) {
|
||||
const float color = (i * 2.0f) / 255.0f;
|
||||
const auto color_f24 = Pica::f24::FromFloat32(color);
|
||||
shader.shader_setup->uniforms.f[i] = {color_f24, color_f24, color_f24, Pica::f24::One()};
|
||||
f_uniforms[i] = {color, color, color, 1.0f};
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < 96; ++i) {
|
||||
const float index = static_cast<float>(i);
|
||||
// Add some fractional values to test proper float->integer truncation
|
||||
const float fractional = (i % 17) / 17.0f;
|
||||
|
||||
REQUIRE(shader.Run(index + fractional) == f_uniforms[i]);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Address Register Offset", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input = SourceRegister::MakeInput(0);
|
||||
const auto sh_c40 = SourceRegister::MakeFloat(40);
|
||||
@@ -534,83 +445,23 @@ TEST_CASE("Address Register Offset", "[video_core][shader][shader_jit]") {
|
||||
REQUIRE(shader.Run(-129.f) == f_uniforms[40]);
|
||||
}
|
||||
|
||||
TEST_CASE("Dest Mask", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input = SourceRegister::MakeInput(0);
|
||||
const auto sh_output = DestRegister::MakeOutput(0);
|
||||
// TODO: Requires fix from https://github.com/neobrain/nihstro/issues/68
|
||||
// TEST_CASE("MAD", "[video_core][shader][shader_jit]") {
|
||||
// const auto sh_input1 = SourceRegister::MakeInput(0);
|
||||
// const auto sh_input2 = SourceRegister::MakeInput(1);
|
||||
// const auto sh_input3 = SourceRegister::MakeInput(2);
|
||||
// const auto sh_output = DestRegister::MakeOutput(0);
|
||||
|
||||
const auto shader = [&sh_input, &sh_output](const char* dest_mask) {
|
||||
return std::unique_ptr<ShaderTest>(new ShaderTest{
|
||||
{OpCode::Id::MOV, sh_output, dest_mask, sh_input, "xyzw", SourceRegister{}, ""},
|
||||
{OpCode::Id::END},
|
||||
});
|
||||
};
|
||||
// auto shader = ShaderTest({
|
||||
// {OpCode::Id::MAD, sh_output, sh_input1, sh_input2, sh_input3},
|
||||
// {OpCode::Id::END},
|
||||
// });
|
||||
|
||||
const Common::Vec4f iota_vec = {1.0f, 2.0f, 3.0f, 4.0f};
|
||||
// REQUIRE(shader.Run({vec4_inf, vec4_zero, vec4_zero}).x == 0.0f);
|
||||
// REQUIRE(std::isnan(shader.Run({vec4_nan, vec4_zero, vec4_zero}).x));
|
||||
|
||||
REQUIRE(shader("x")->Run({iota_vec}).x == iota_vec.x);
|
||||
REQUIRE(shader("y")->Run({iota_vec}).y == iota_vec.y);
|
||||
REQUIRE(shader("z")->Run({iota_vec}).z == iota_vec.z);
|
||||
REQUIRE(shader("w")->Run({iota_vec}).w == iota_vec.w);
|
||||
REQUIRE(shader("xy")->Run({iota_vec}).xy() == iota_vec.xy());
|
||||
REQUIRE(shader("xz")->Run({iota_vec}).xz() == iota_vec.xz());
|
||||
REQUIRE(shader("xw")->Run({iota_vec}).xw() == iota_vec.xw());
|
||||
REQUIRE(shader("yz")->Run({iota_vec}).yz() == iota_vec.yz());
|
||||
REQUIRE(shader("yw")->Run({iota_vec}).yw() == iota_vec.yw());
|
||||
REQUIRE(shader("zw")->Run({iota_vec}).zw() == iota_vec.zw());
|
||||
REQUIRE(shader("xyz")->Run({iota_vec}).xyz() == iota_vec.xyz());
|
||||
REQUIRE(shader("xyw")->Run({iota_vec}).xyw() == iota_vec.xyw());
|
||||
REQUIRE(shader("xzw")->Run({iota_vec}).xzw() == iota_vec.xzw());
|
||||
REQUIRE(shader("yzw")->Run({iota_vec}).yzw() == iota_vec.yzw());
|
||||
REQUIRE(shader("xyzw")->Run({iota_vec}) == iota_vec);
|
||||
}
|
||||
|
||||
TEST_CASE("MAD", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input1 = SourceRegister::MakeInput(0);
|
||||
const auto sh_input2 = SourceRegister::MakeInput(1);
|
||||
const auto sh_input3 = SourceRegister::MakeInput(2);
|
||||
const auto sh_output = DestRegister::MakeOutput(0);
|
||||
|
||||
auto shader_setup = CompileShaderSetup({
|
||||
// TODO: Requires fix from https://github.com/neobrain/nihstro/issues/68
|
||||
// {OpCode::Id::MAD, sh_output, sh_input1, sh_input2, sh_input3},
|
||||
{OpCode::Id::NOP},
|
||||
{OpCode::Id::END},
|
||||
});
|
||||
|
||||
// nihstro does not support the MAD* instructions, so the instruction-binary must be manually
|
||||
// inserted here:
|
||||
nihstro::Instruction MAD = {};
|
||||
MAD.opcode = nihstro::OpCode::Id::MAD;
|
||||
MAD.mad.operand_desc_id = 0;
|
||||
MAD.mad.src1 = sh_input1;
|
||||
MAD.mad.src2 = sh_input2;
|
||||
MAD.mad.src3 = sh_input3;
|
||||
MAD.mad.dest = sh_output;
|
||||
shader_setup->program_code[0] = MAD.hex;
|
||||
|
||||
nihstro::SwizzlePattern swizzle = {};
|
||||
swizzle.dest_mask = 0b1111;
|
||||
swizzle.SetSelectorSrc1(0, SwizzlePattern::Selector::x);
|
||||
swizzle.SetSelectorSrc1(1, SwizzlePattern::Selector::y);
|
||||
swizzle.SetSelectorSrc1(2, SwizzlePattern::Selector::z);
|
||||
swizzle.SetSelectorSrc1(3, SwizzlePattern::Selector::w);
|
||||
swizzle.SetSelectorSrc2(0, SwizzlePattern::Selector::x);
|
||||
swizzle.SetSelectorSrc2(1, SwizzlePattern::Selector::y);
|
||||
swizzle.SetSelectorSrc2(2, SwizzlePattern::Selector::z);
|
||||
swizzle.SetSelectorSrc2(3, SwizzlePattern::Selector::w);
|
||||
swizzle.SetSelectorSrc3(0, SwizzlePattern::Selector::x);
|
||||
swizzle.SetSelectorSrc3(1, SwizzlePattern::Selector::y);
|
||||
swizzle.SetSelectorSrc3(2, SwizzlePattern::Selector::z);
|
||||
swizzle.SetSelectorSrc3(3, SwizzlePattern::Selector::w);
|
||||
shader_setup->swizzle_data[0] = swizzle.hex;
|
||||
|
||||
auto shader = ShaderTest(std::move(shader_setup));
|
||||
|
||||
REQUIRE(shader.Run({vec4_zero, vec4_zero, vec4_zero}) == vec4_zero);
|
||||
REQUIRE(shader.Run({vec4_one, vec4_one, vec4_one}) == (vec4_one * 2.0f));
|
||||
REQUIRE(shader.Run({vec4_inf, vec4_zero, vec4_zero}) == vec4_zero);
|
||||
REQUIRE(shader.Run({vec4_nan, vec4_zero, vec4_zero}) == vec4_nan);
|
||||
}
|
||||
// REQUIRE(shader.Run({vec4_one, vec4_one, vec4_one}).x == 2.0f);
|
||||
// }
|
||||
|
||||
TEST_CASE("Nested Loop", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input = SourceRegister::MakeInput(0);
|
||||
@@ -667,42 +518,4 @@ TEST_CASE("Nested Loop", "[video_core][shader][shader_jit]") {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Source Swizzle", "[video_core][shader][shader_jit]") {
|
||||
const auto sh_input = SourceRegister::MakeInput(0);
|
||||
const auto sh_output = DestRegister::MakeOutput(0);
|
||||
|
||||
const auto shader = [&sh_input, &sh_output](const char* swizzle) {
|
||||
return std::unique_ptr<ShaderTest>(new ShaderTest{
|
||||
{OpCode::Id::MOV, sh_output, "xyzw", sh_input, swizzle, SourceRegister{}, ""},
|
||||
{OpCode::Id::END},
|
||||
});
|
||||
};
|
||||
|
||||
const Common::Vec4f iota_vec = {1.0f, 2.0f, 3.0f, 4.0f};
|
||||
|
||||
REQUIRE(shader("x")->Run({iota_vec}).x == iota_vec.x);
|
||||
REQUIRE(shader("y")->Run({iota_vec}).x == iota_vec.y);
|
||||
REQUIRE(shader("z")->Run({iota_vec}).x == iota_vec.z);
|
||||
REQUIRE(shader("w")->Run({iota_vec}).x == iota_vec.w);
|
||||
REQUIRE(shader("xy")->Run({iota_vec}).xy() == iota_vec.xy());
|
||||
REQUIRE(shader("xz")->Run({iota_vec}).xy() == iota_vec.xz());
|
||||
REQUIRE(shader("xw")->Run({iota_vec}).xy() == iota_vec.xw());
|
||||
REQUIRE(shader("yz")->Run({iota_vec}).xy() == iota_vec.yz());
|
||||
REQUIRE(shader("yw")->Run({iota_vec}).xy() == iota_vec.yw());
|
||||
REQUIRE(shader("zw")->Run({iota_vec}).xy() == iota_vec.zw());
|
||||
REQUIRE(shader("yy")->Run({iota_vec}).xy() == iota_vec.yy());
|
||||
REQUIRE(shader("wx")->Run({iota_vec}).xy() == iota_vec.wx());
|
||||
REQUIRE(shader("xyz")->Run({iota_vec}).xyz() == iota_vec.xyz());
|
||||
REQUIRE(shader("xyw")->Run({iota_vec}).xyz() == iota_vec.xyw());
|
||||
REQUIRE(shader("xzw")->Run({iota_vec}).xyz() == iota_vec.xzw());
|
||||
REQUIRE(shader("yzw")->Run({iota_vec}).xyz() == iota_vec.yzw());
|
||||
REQUIRE(shader("yyy")->Run({iota_vec}).xyz() == iota_vec.yyy());
|
||||
REQUIRE(shader("yxw")->Run({iota_vec}).xyz() == iota_vec.yxw());
|
||||
REQUIRE(shader("xyzw")->Run({iota_vec}) == iota_vec);
|
||||
REQUIRE(shader("wzxy")->Run({iota_vec}) ==
|
||||
Common::Vec4f(iota_vec.w, iota_vec.z, iota_vec.x, iota_vec.y));
|
||||
REQUIRE(shader("yyyy")->Run({iota_vec}) ==
|
||||
Common::Vec4f(iota_vec.y, iota_vec.y, iota_vec.y, iota_vec.y));
|
||||
}
|
||||
|
||||
#endif // CITRA_ARCH(x86_64) || CITRA_ARCH(arm64)
|
||||
#endif // CITRA_ARCH(x86_64)
|
@@ -135,29 +135,20 @@ add_library(video_core STATIC
|
||||
renderer_vulkan/vk_texture_runtime.cpp
|
||||
renderer_vulkan/vk_texture_runtime.h
|
||||
shader/debug_data.h
|
||||
shader/generator/glsl_fs_shader_gen.cpp
|
||||
shader/generator/glsl_fs_shader_gen.h
|
||||
shader/generator/glsl_shader_decompiler.cpp
|
||||
shader/generator/glsl_shader_decompiler.h
|
||||
shader/generator/glsl_shader_gen.cpp
|
||||
shader/generator/glsl_shader_gen.h
|
||||
shader/generator/pica_fs_config.cpp
|
||||
shader/generator/pica_fs_config.h
|
||||
shader/generator/profile.h
|
||||
shader/generator/shader_gen.cpp
|
||||
shader/generator/shader_gen.h
|
||||
shader/generator/shader_uniforms.cpp
|
||||
shader/generator/shader_uniforms.h
|
||||
shader/generator/spv_fs_shader_gen.cpp
|
||||
shader/generator/spv_fs_shader_gen.h
|
||||
shader/generator/spv_shader_gen.cpp
|
||||
shader/generator/spv_shader_gen.h
|
||||
shader/shader.cpp
|
||||
shader/shader.h
|
||||
shader/shader_interpreter.cpp
|
||||
shader/shader_interpreter.h
|
||||
shader/shader_jit_a64.cpp
|
||||
shader/shader_jit_a64_compiler.cpp
|
||||
shader/shader_jit_a64.h
|
||||
shader/shader_jit_a64_compiler.h
|
||||
shader/shader_jit_x64.cpp
|
||||
shader/shader_jit_x64_compiler.cpp
|
||||
shader/shader_jit_x64.h
|
||||
@@ -186,10 +177,6 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
target_link_libraries(video_core PUBLIC xbyak)
|
||||
endif()
|
||||
|
||||
if ("arm64" IN_LIST ARCHITECTURE)
|
||||
target_link_libraries(video_core PUBLIC oaknut)
|
||||
endif()
|
||||
|
||||
if (CITRA_USE_PRECOMPILED_HEADERS)
|
||||
target_precompile_headers(video_core PRIVATE precompiled_headers.h)
|
||||
endif()
|
||||
|
@@ -78,11 +78,12 @@ static void WriteUniformFloatReg(ShaderRegs& config, Shader::ShaderSetup& setup,
|
||||
(float_regs_counter >= 3 && !uniform_setup.IsFloat32())) {
|
||||
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),
|
||||
(int)uniform_setup.index);
|
||||
} else {
|
||||
auto& uniform = setup.uniforms.f[uniform_setup.index];
|
||||
|
||||
// NOTE: The destination component order indeed is "backwards"
|
||||
if (uniform_setup.IsFloat32()) {
|
||||
|
@@ -1,10 +1,9 @@
|
||||
// Copyright 2023 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
#pragma clang optimize off
|
||||
|
||||
#include <json.hpp>
|
||||
#include "common/file_util.h"
|
||||
#include "common/file_watcher.h"
|
||||
#include "common/memory_detect.h"
|
||||
#include "common/microprofile.h"
|
||||
#include "common/settings.h"
|
||||
@@ -17,7 +16,6 @@
|
||||
#include "video_core/custom_textures/custom_tex_manager.h"
|
||||
#include "video_core/rasterizer_cache/surface_params.h"
|
||||
#include "video_core/rasterizer_cache/utils.h"
|
||||
#include "video_core/renderer_base.h"
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
@@ -65,17 +63,17 @@ void CustomTexManager::TickFrame() {
|
||||
return;
|
||||
}
|
||||
std::size_t num_uploads = 0;
|
||||
for (auto it = async_actions.begin(); it != async_actions.end();) {
|
||||
for (auto it = async_uploads.begin(); it != async_uploads.end();) {
|
||||
if (num_uploads >= MAX_UPLOADS_PER_TICK) {
|
||||
return;
|
||||
}
|
||||
switch (it->material->state) {
|
||||
case DecodeState::Decoded:
|
||||
it->func(it->material);
|
||||
it->func();
|
||||
num_uploads++;
|
||||
[[fallthrough]];
|
||||
case DecodeState::Failed:
|
||||
it = async_actions.erase(it);
|
||||
it = async_uploads.erase(it);
|
||||
continue;
|
||||
default:
|
||||
it++;
|
||||
@@ -104,7 +102,7 @@ void CustomTexManager::FindCustomTextures() {
|
||||
if (file.isDirectory) {
|
||||
continue;
|
||||
}
|
||||
custom_textures.emplace_back(std::make_unique<CustomTexture>(image_interface));
|
||||
custom_textures.push_back(std::make_unique<CustomTexture>(image_interface));
|
||||
CustomTexture* const texture{custom_textures.back().get()};
|
||||
if (!ParseFilename(file, texture)) {
|
||||
continue;
|
||||
@@ -294,17 +292,16 @@ Material* CustomTexManager::GetMaterial(u64 data_hash) {
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
bool CustomTexManager::Decode(Material* material, AsyncFunc&& upload) {
|
||||
bool CustomTexManager::Decode(Material* material, std::function<bool()>&& upload) {
|
||||
if (!async_custom_loading) {
|
||||
material->LoadFromDisk(flip_png_files);
|
||||
return upload(material);
|
||||
return upload();
|
||||
}
|
||||
if (material->IsUnloaded()) {
|
||||
material->state = DecodeState::Pending;
|
||||
workers->QueueWork([material, this] { material->LoadFromDisk(flip_png_files); });
|
||||
}
|
||||
std::scoped_lock lock{async_actions_mutex};
|
||||
async_actions.push_back({
|
||||
async_uploads.push_back({
|
||||
.material = material,
|
||||
.func = std::move(upload),
|
||||
});
|
||||
@@ -377,14 +374,6 @@ std::vector<FileUtil::FSTEntry> CustomTexManager::GetTextures(u64 title_id) {
|
||||
FileUtil::CreateFullPath(load_path);
|
||||
}
|
||||
|
||||
const auto callback = [this](const std::string& file, Common::FileAction action) {
|
||||
OnFileAction(file, action);
|
||||
};
|
||||
|
||||
// Create a file watcher to monitor any changes to the textures directory for hot-reloading.
|
||||
file_watcher = std::make_unique<Common::FileWatcher>(load_path, callback);
|
||||
|
||||
// Retrieve all texture files.
|
||||
FileUtil::FSTEntry texture_dir;
|
||||
std::vector<FileUtil::FSTEntry> textures;
|
||||
FileUtil::ScanDirectoryTree(load_path, texture_dir, 64);
|
||||
@@ -397,24 +386,4 @@ void CustomTexManager::CreateWorkers() {
|
||||
workers = std::make_unique<Common::ThreadWorker>(num_workers, "Custom textures");
|
||||
}
|
||||
|
||||
void CustomTexManager::OnFileAction(const std::string& file, Common::FileAction action) {
|
||||
const auto invalidate = [this](const Material* material) -> bool {
|
||||
for (const SurfaceParams* params : material->loaded_to) {
|
||||
system.Renderer().Rasterizer()->InvalidateRegion(params->addr, params->size);
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const std::string filename{FileUtil::GetFilename(file)};
|
||||
const auto& hashes = path_to_hash_map[filename];
|
||||
std::scoped_lock lock{async_actions_mutex};
|
||||
|
||||
for (const Hash hash : hashes) {
|
||||
async_actions.push_back({
|
||||
.material = material_map[hash].get(),
|
||||
.func = std::move(invalidate),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace VideoCore
|
||||
|
@@ -12,11 +12,6 @@
|
||||
#include "video_core/custom_textures/material.h"
|
||||
#include "video_core/rasterizer_interface.h"
|
||||
|
||||
namespace Common {
|
||||
class FileWatcher;
|
||||
enum class FileAction : u8;
|
||||
} // namespace Common
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
@@ -29,10 +24,12 @@ namespace VideoCore {
|
||||
|
||||
class SurfaceParams;
|
||||
|
||||
class CustomTexManager {
|
||||
using Hash = u64;
|
||||
using AsyncFunc = std::function<bool(const Material*)>;
|
||||
struct AsyncUpload {
|
||||
const Material* material;
|
||||
std::function<bool()> func;
|
||||
};
|
||||
|
||||
class CustomTexManager {
|
||||
public:
|
||||
explicit CustomTexManager(Core::System& system);
|
||||
~CustomTexManager();
|
||||
@@ -60,7 +57,7 @@ public:
|
||||
Material* GetMaterial(u64 data_hash);
|
||||
|
||||
/// Decodes the textures in material to a consumable format and uploads it.
|
||||
bool Decode(Material* material, AsyncFunc&& func);
|
||||
bool Decode(Material* material, std::function<bool()>&& upload);
|
||||
|
||||
/// True when mipmap uploads should be skipped (legacy packs only)
|
||||
bool SkipMipmaps() const noexcept {
|
||||
@@ -82,25 +79,15 @@ private:
|
||||
/// Creates the thread workers.
|
||||
void CreateWorkers();
|
||||
|
||||
/// Callback for when a custom texture file is modified.
|
||||
void OnFileAction(const std::string& file, Common::FileAction action);
|
||||
|
||||
private:
|
||||
struct AsyncAction {
|
||||
const Material* material;
|
||||
AsyncFunc func;
|
||||
};
|
||||
|
||||
Core::System& system;
|
||||
Frontend::ImageInterface& image_interface;
|
||||
std::unordered_set<Hash> dumped_textures;
|
||||
std::unordered_map<Hash, std::unique_ptr<Material>> material_map;
|
||||
std::unordered_map<std::string, std::vector<Hash>> path_to_hash_map;
|
||||
std::unordered_set<u64> dumped_textures;
|
||||
std::unordered_map<u64, std::unique_ptr<Material>> material_map;
|
||||
std::unordered_map<std::string, std::vector<u64>> path_to_hash_map;
|
||||
std::vector<std::unique_ptr<CustomTexture>> custom_textures;
|
||||
std::mutex async_actions_mutex;
|
||||
std::list<AsyncAction> async_actions;
|
||||
std::list<AsyncUpload> async_uploads;
|
||||
std::unique_ptr<Common::ThreadWorker> workers;
|
||||
std::unique_ptr<Common::FileWatcher> file_watcher;
|
||||
bool textures_loaded{false};
|
||||
bool async_custom_loading{true};
|
||||
bool skip_mipmap{false};
|
||||
|
@@ -18,8 +18,6 @@ class ImageInterface;
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
class SurfaceParams;
|
||||
|
||||
enum class MapType : u32 {
|
||||
Color = 0,
|
||||
Normal = 1,
|
||||
@@ -74,7 +72,6 @@ struct Material {
|
||||
u64 hash;
|
||||
CustomPixelFormat format;
|
||||
std::array<CustomTexture*, MAX_MAPS> textures;
|
||||
mutable std::vector<SurfaceParams*> loaded_to;
|
||||
std::atomic<DecodeState> state{};
|
||||
|
||||
void LoadFromDisk(bool flip_png) noexcept;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user