Compare commits
30 Commits
Author | SHA1 | Date | |
---|---|---|---|
2855c5414c | |||
a17ada2b2a | |||
69e758d738 | |||
f4768cd26c | |||
e0d2c1308e | |||
4f9fc88bb3 | |||
d857743075 | |||
b5042a5257 | |||
e524542a40 | |||
3a4ebb1413 | |||
cbe8987036 | |||
da5aa70fc9 | |||
749a721aa2 | |||
bb003c2bd4 | |||
7638f87f74 | |||
aa6809e2a8 | |||
5e02be75a3 | |||
b9c9beeee5 | |||
de993dcfbd | |||
3c9157b1ec | |||
0c40c10022 | |||
2766118e33 | |||
06b26691ba | |||
d41ce64f7b | |||
1165a708d5 | |||
19784355f9 | |||
aa6a29d7e1 | |||
106364e01e | |||
d5a1bd07f3 | |||
8afa27718c |
59
.github/workflows/build.yml
vendored
59
.github/workflows/build.yml
vendored
@ -12,13 +12,13 @@ jobs:
|
||||
if: ${{ !github.head_ref }}
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Pack
|
||||
run: ./.ci/source.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: source
|
||||
path: artifacts/
|
||||
@ -37,11 +37,11 @@ jobs:
|
||||
OS: linux
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -53,7 +53,7 @@ jobs:
|
||||
run: ./.ci/pack.sh
|
||||
if: ${{ matrix.target == 'appimage' }}
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
if: ${{ matrix.target == 'appimage' }}
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
@ -70,24 +70,24 @@ jobs:
|
||||
OS: macos
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-${{ matrix.target }}-
|
||||
- name: Install tools
|
||||
run: brew install ccache glslang ninja
|
||||
run: brew install ccache ninja
|
||||
- name: Build
|
||||
run: ./.ci/macos.sh
|
||||
- name: Prepare outputs for caching
|
||||
run: mv build/bundle $OS-$TARGET
|
||||
- name: Cache outputs for universal build
|
||||
uses: actions/cache/save@v3
|
||||
uses: actions/cache/save@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-${{ env.TARGET }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
@ -98,15 +98,15 @@ jobs:
|
||||
OS: macos
|
||||
TARGET: universal
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
- name: Download x86_64 build from cache
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-x86_64
|
||||
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
fail-on-cache-miss: true
|
||||
- name: Download ARM64 build from cache
|
||||
uses: actions/cache/restore@v3
|
||||
uses: actions/cache/restore@v4
|
||||
with:
|
||||
path: ${{ env.OS }}-arm64
|
||||
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
|
||||
@ -118,7 +118,7 @@ jobs:
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
@ -137,11 +137,11 @@ jobs:
|
||||
OS: windows
|
||||
TARGET: ${{ matrix.target }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
|
||||
@ -153,13 +153,6 @@ jobs:
|
||||
- name: Install extra tools (MSVC)
|
||||
run: choco install ccache ninja wget
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
- name: Set up Vulkan SDK (MSVC)
|
||||
uses: humbletim/setup-vulkan-sdk@v1.2.0
|
||||
if: ${{ matrix.target == 'msvc' }}
|
||||
with:
|
||||
vulkan-query-version: latest
|
||||
vulkan-components: SPIRV-Tools, Glslang
|
||||
vulkan-use-cache: true
|
||||
- name: Set up MSYS2
|
||||
uses: msys2/setup-msys2@v2
|
||||
if: ${{ matrix.target == 'msys2' }}
|
||||
@ -168,10 +161,8 @@ jobs:
|
||||
update: true
|
||||
install: git make p7zip
|
||||
pacboy: >-
|
||||
toolchain:p ccache:p cmake:p ninja:p glslang:p
|
||||
toolchain:p ccache:p cmake:p ninja:p
|
||||
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations:p
|
||||
- name: Test glslang
|
||||
run: glslang --version || glslangValidator --version
|
||||
- name: Disable line ending translation
|
||||
run: git config --global core.autocrlf input
|
||||
- name: Build
|
||||
@ -179,7 +170,7 @@ jobs:
|
||||
- name: Pack
|
||||
run: ./.ci/pack.sh
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: artifacts/
|
||||
@ -192,11 +183,11 @@ jobs:
|
||||
OS: android
|
||||
TARGET: universal
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: |
|
||||
~/.gradle/caches
|
||||
@ -215,7 +206,7 @@ jobs:
|
||||
run: |
|
||||
sudo add-apt-repository -y ppa:theofficialgman/gpu-tools
|
||||
sudo apt-get update -y
|
||||
sudo apt-get install ccache glslang-dev glslang-tools apksigner -y
|
||||
sudo apt-get install ccache apksigner -y
|
||||
- name: Build
|
||||
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
|
||||
env:
|
||||
@ -228,7 +219,7 @@ jobs:
|
||||
env:
|
||||
UNPACKED: 1
|
||||
- name: Upload
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: ${{ env.OS }}-${{ env.TARGET }}
|
||||
path: src/android/app/artifacts/
|
||||
@ -242,18 +233,18 @@ jobs:
|
||||
OS: ios
|
||||
TARGET: arm64
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
- name: Set up cache
|
||||
uses: actions/cache@v3
|
||||
uses: actions/cache@v4
|
||||
with:
|
||||
path: ${{ env.CCACHE_DIR }}
|
||||
key: ${{ runner.os }}-ios-${{ github.sha }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-ios-
|
||||
- name: Install tools
|
||||
run: brew install ccache glslang ninja
|
||||
run: brew install ccache ninja
|
||||
- name: Build
|
||||
run: ./.ci/ios.sh
|
||||
release:
|
||||
@ -261,7 +252,7 @@ jobs:
|
||||
needs: [windows, linux, macos-universal, android, source]
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
steps:
|
||||
- uses: actions/download-artifact@v3
|
||||
- uses: actions/download-artifact@v4
|
||||
- name: Create release
|
||||
uses: actions/create-release@v1
|
||||
env:
|
||||
|
2
.github/workflows/format.yml
vendored
2
.github/workflows/format.yml
vendored
@ -13,7 +13,7 @@ jobs:
|
||||
image: citraemu/build-environments:linux-fresh
|
||||
options: -u 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Build
|
||||
|
16
.github/workflows/publish.yml
vendored
16
.github/workflows/publish.yml
vendored
@ -20,11 +20,11 @@ jobs:
|
||||
if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
name: Pre-checkout
|
||||
with:
|
||||
submodules: false
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
id: check-changes
|
||||
name: 'Check for new changes'
|
||||
env:
|
||||
@ -38,7 +38,7 @@ jobs:
|
||||
return checkBaseChanges(github, context);
|
||||
- run: npm install execa@5
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
name: Checkout
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
with:
|
||||
@ -46,7 +46,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
name: 'Update and tag new commits'
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
env:
|
||||
@ -62,11 +62,11 @@ jobs:
|
||||
if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
# this checkout is required to make sure the GitHub Actions scripts are available
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
name: Pre-checkout
|
||||
with:
|
||||
submodules: false
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
id: check-changes
|
||||
name: 'Check for new changes'
|
||||
env:
|
||||
@ -79,7 +79,7 @@ jobs:
|
||||
return checkCanaryChanges(github, context);
|
||||
- run: npm install execa@5
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
name: Checkout
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
with:
|
||||
@ -87,7 +87,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
submodules: true
|
||||
token: ${{ secrets.ALT_GITHUB_TOKEN }}
|
||||
- uses: actions/github-script@v6
|
||||
- uses: actions/github-script@v7
|
||||
name: 'Check and merge canary changes'
|
||||
if: ${{ steps.check-changes.outputs.result == 'true' }}
|
||||
env:
|
||||
|
2
.github/workflows/transifex.yml
vendored
2
.github/workflows/transifex.yml
vendored
@ -10,7 +10,7 @@ jobs:
|
||||
container: citraemu/build-environments:linux-fresh
|
||||
if: ${{ github.repository == 'citra-emu/citra' }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
|
@ -85,8 +85,6 @@ option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
|
||||
|
||||
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
|
||||
|
||||
# Compile options
|
||||
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
|
||||
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
|
||||
@ -249,6 +247,26 @@ if (ENABLE_QT)
|
||||
if (ENABLE_QT_TRANSLATION)
|
||||
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED QT_TARGET_PATH)
|
||||
# Determine the location of the compile target's Qt.
|
||||
get_target_property(qtcore_path Qt6::Core LOCATION_Release)
|
||||
string(FIND "${qtcore_path}" "/bin/" qtcore_path_bin_pos REVERSE)
|
||||
string(FIND "${qtcore_path}" "/lib/" qtcore_path_lib_pos REVERSE)
|
||||
if (qtcore_path_bin_pos GREATER qtcore_path_lib_pos)
|
||||
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_bin_pos} QT_TARGET_PATH)
|
||||
else()
|
||||
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_lib_pos} QT_TARGET_PATH)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED QT_HOST_PATH)
|
||||
# Use the same for host Qt if none is defined.
|
||||
set(QT_HOST_PATH "${QT_TARGET_PATH}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Using target Qt at ${QT_TARGET_PATH}")
|
||||
message(STATUS "Using host Qt at ${QT_HOST_PATH}")
|
||||
endif()
|
||||
|
||||
# Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic)
|
||||
@ -424,7 +442,8 @@ else()
|
||||
endif()
|
||||
|
||||
# Create target for outputting distributable bundles.
|
||||
if (CITRA_ENABLE_BUNDLE_TARGET)
|
||||
# Not supported for mobile platforms as distributables are built differently.
|
||||
if (NOT ANDROID AND NOT IOS)
|
||||
include(BundleTarget)
|
||||
if (ENABLE_SDL2_FRONTEND)
|
||||
bundle_target(citra)
|
||||
|
@ -2,37 +2,104 @@
|
||||
if (BUNDLE_TARGET_EXECUTE)
|
||||
# --- Bundling method logic ---
|
||||
|
||||
function(symlink_safe_copy from to)
|
||||
if (WIN32)
|
||||
# Use cmake copy for maximum compatibility.
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${to}"
|
||||
RESULT_VARIABLE cp_result)
|
||||
else()
|
||||
# Use native copy to turn symlinks into normal files.
|
||||
execute_process(COMMAND cp -L "${from}" "${to}"
|
||||
RESULT_VARIABLE cp_result)
|
||||
endif()
|
||||
if (NOT cp_result EQUAL "0")
|
||||
message(FATAL_ERROR "cp \"${from}\" \"${to}\" failed: ${cp_result}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(bundle_qt executable_path)
|
||||
if (WIN32)
|
||||
# Perform standalone bundling first to copy over all used libraries, as windeployqt does not do this.
|
||||
bundle_standalone("${executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
|
||||
|
||||
get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY)
|
||||
find_program(windeployqt_executable windeployqt6)
|
||||
|
||||
# Create a qt.conf file pointing to the app directory.
|
||||
# This ensures Qt can find its plugins.
|
||||
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nprefix = .")
|
||||
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nPrefix = .")
|
||||
|
||||
find_program(windeployqt_executable windeployqt6 PATHS "${QT_HOST_PATH}/bin")
|
||||
find_program(qtpaths_executable qtpaths6 PATHS "${QT_HOST_PATH}/bin")
|
||||
|
||||
# TODO: Hack around windeployqt's poor cross-compilation support by
|
||||
# TODO: making a local copy with a prefix pointing to the target Qt.
|
||||
if (NOT "${QT_HOST_PATH}" STREQUAL "${QT_TARGET_PATH}")
|
||||
set(windeployqt_dir "${BINARY_PATH}/windeployqt_copy")
|
||||
file(MAKE_DIRECTORY "${windeployqt_dir}")
|
||||
symlink_safe_copy("${windeployqt_executable}" "${windeployqt_dir}/windeployqt.exe")
|
||||
symlink_safe_copy("${qtpaths_executable}" "${windeployqt_dir}/qtpaths.exe")
|
||||
symlink_safe_copy("${QT_HOST_PATH}/bin/Qt6Core.dll" "${windeployqt_dir}")
|
||||
|
||||
if (EXISTS "${QT_TARGET_PATH}/share")
|
||||
# Unix-style Qt; we need to wire up the paths manually.
|
||||
file(WRITE "${windeployqt_dir}/qt.conf" "\
|
||||
[Paths]\n
|
||||
Prefix = ${QT_TARGET_PATH}\n \
|
||||
ArchData = ${QT_TARGET_PATH}/share/qt6\n \
|
||||
Binaries = ${QT_TARGET_PATH}/bin\n \
|
||||
Data = ${QT_TARGET_PATH}/share/qt6\n \
|
||||
Documentation = ${QT_TARGET_PATH}/share/qt6/doc\n \
|
||||
Headers = ${QT_TARGET_PATH}/include/qt6\n \
|
||||
Libraries = ${QT_TARGET_PATH}/lib\n \
|
||||
LibraryExecutables = ${QT_TARGET_PATH}/share/qt6/bin\n \
|
||||
Plugins = ${QT_TARGET_PATH}/share/qt6/plugins\n \
|
||||
QmlImports = ${QT_TARGET_PATH}/share/qt6/qml\n \
|
||||
Translations = ${QT_TARGET_PATH}/share/qt6/translations\n \
|
||||
")
|
||||
else()
|
||||
# Windows-style Qt; the defaults should suffice.
|
||||
file(WRITE "${windeployqt_dir}/qt.conf" "[Paths]\nPrefix = ${QT_TARGET_PATH}")
|
||||
endif()
|
||||
|
||||
set(windeployqt_executable "${windeployqt_dir}/windeployqt.exe")
|
||||
set(qtpaths_executable "${windeployqt_dir}/qtpaths.exe")
|
||||
endif()
|
||||
|
||||
message(STATUS "Executing windeployqt for executable ${executable_path}")
|
||||
execute_process(COMMAND "${windeployqt_executable}" "${executable_path}"
|
||||
--qtpaths "${qtpaths_executable}"
|
||||
--no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --no-translations
|
||||
--plugindir "${executable_parent_dir}/plugins")
|
||||
--plugindir "${executable_parent_dir}/plugins"
|
||||
RESULT_VARIABLE windeployqt_result)
|
||||
if (NOT windeployqt_result EQUAL "0")
|
||||
message(FATAL_ERROR "windeployqt failed: ${windeployqt_result}")
|
||||
endif()
|
||||
|
||||
# Remove the FFmpeg multimedia plugin as we don't include FFmpeg.
|
||||
# We want to use the Windows media plugin instead, which is also included.
|
||||
file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll")
|
||||
elseif (APPLE)
|
||||
get_filename_component(executable_name "${executable_path}" NAME_WE)
|
||||
find_program(MACDEPLOYQT_EXECUTABLE macdeployqt6)
|
||||
find_program(macdeployqt_executable macdeployqt6 PATHS "${QT_HOST_PATH}/bin")
|
||||
|
||||
message(STATUS "Executing macdeployqt for executable ${executable_path}")
|
||||
message(STATUS "Executing macdeployqt at \"${macdeployqt_executable}\" for executable \"${executable_path}\"")
|
||||
execute_process(
|
||||
COMMAND "${MACDEPLOYQT_EXECUTABLE}"
|
||||
COMMAND "${macdeployqt_executable}"
|
||||
"${executable_path}"
|
||||
"-executable=${executable_path}/Contents/MacOS/${executable_name}"
|
||||
-always-overwrite)
|
||||
-always-overwrite
|
||||
RESULT_VARIABLE macdeployqt_result)
|
||||
if (NOT macdeployqt_result EQUAL "0")
|
||||
message(FATAL_ERROR "macdeployqt failed: ${macdeployqt_result}")
|
||||
endif()
|
||||
|
||||
# Bundling libraries can rewrite path information and break code signatures of system libraries.
|
||||
# Perform an ad-hoc re-signing on the whole app bundle to fix this.
|
||||
execute_process(COMMAND codesign --deep -fs - "${executable_path}")
|
||||
execute_process(COMMAND codesign --deep -fs - "${executable_path}"
|
||||
RESULT_VARIABLE codesign_result)
|
||||
if (NOT codesign_result EQUAL "0")
|
||||
message(FATAL_ERROR "codesign failed: ${codesign_result}")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported OS for Qt bundling.")
|
||||
endif()
|
||||
@ -44,9 +111,9 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
|
||||
if (enable_qt)
|
||||
# Find qmake to make sure the plugin uses the right version of Qt.
|
||||
find_program(QMAKE_EXECUTABLE qmake6)
|
||||
find_program(qmake_executable qmake6 PATHS "${QT_HOST_PATH}/bin")
|
||||
|
||||
set(extra_linuxdeploy_env "QMAKE=${QMAKE_EXECUTABLE}")
|
||||
set(extra_linuxdeploy_env "QMAKE=${qmake_executable}")
|
||||
set(extra_linuxdeploy_args --plugin qt)
|
||||
endif()
|
||||
|
||||
@ -59,7 +126,11 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
--executable "${executable_path}"
|
||||
--icon-file "${source_path}/dist/citra.svg"
|
||||
--desktop-file "${source_path}/dist/${executable_name}.desktop"
|
||||
--appdir "${appdir_path}")
|
||||
--appdir "${appdir_path}"
|
||||
RESULT_VARIABLE linuxdeploy_appdir_result)
|
||||
if (NOT linuxdeploy_appdir_result EQUAL "0")
|
||||
message(FATAL_ERROR "linuxdeploy failed to create AppDir: ${linuxdeploy_appdir_result}")
|
||||
endif()
|
||||
|
||||
if (enable_qt)
|
||||
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
|
||||
@ -82,7 +153,11 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
"OUTPUT=${bundle_dir}/${executable_name}.AppImage"
|
||||
"${linuxdeploy_executable}"
|
||||
--output appimage
|
||||
--appdir "${appdir_path}")
|
||||
--appdir "${appdir_path}"
|
||||
RESULT_VARIABLE linuxdeploy_appimage_result)
|
||||
if (NOT linuxdeploy_appimage_result EQUAL "0")
|
||||
message(FATAL_ERROR "linuxdeploy failed to create AppImage: ${linuxdeploy_appimage_result}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(bundle_standalone executable_path original_executable_path bundle_library_paths)
|
||||
@ -109,16 +184,23 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
file(MAKE_DIRECTORY ${lib_dir})
|
||||
foreach (lib_file IN LISTS resolved_deps)
|
||||
message(STATUS "Bundling library ${lib_file}")
|
||||
# Use native copy to turn symlinks into normal files.
|
||||
execute_process(COMMAND cp -L "${lib_file}" "${lib_dir}")
|
||||
symlink_safe_copy("${lib_file}" "${lib_dir}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Add libs directory to executable rpath where applicable.
|
||||
if (APPLE)
|
||||
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}")
|
||||
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}"
|
||||
RESULT_VARIABLE install_name_tool_result)
|
||||
if (NOT install_name_tool_result EQUAL "0")
|
||||
message(FATAL_ERROR "install_name_tool failed: ${install_name_tool_result}")
|
||||
endif()
|
||||
elseif (UNIX)
|
||||
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}")
|
||||
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}"
|
||||
RESULT_VARIABLE patchelf_result)
|
||||
if (NOT patchelf_result EQUAL "0")
|
||||
message(FATAL_ERROR "patchelf failed: ${patchelf_result}")
|
||||
endif()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
@ -127,7 +209,7 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
set(bundle_dir ${BINARY_PATH}/bundle)
|
||||
|
||||
# On Linux, always bundle an AppImage.
|
||||
if (DEFINED LINUXDEPLOY)
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
if (IN_PLACE)
|
||||
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
|
||||
endif()
|
||||
@ -146,14 +228,12 @@ if (BUNDLE_TARGET_EXECUTE)
|
||||
|
||||
if (BUNDLE_QT)
|
||||
bundle_qt("${bundled_executable_path}")
|
||||
endif()
|
||||
|
||||
if (WIN32 OR NOT BUNDLE_QT)
|
||||
else()
|
||||
bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
# --- Bundling target creation logic ---
|
||||
elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
|
||||
# --- linuxdeploy download logic ---
|
||||
|
||||
# Downloads and extracts a linuxdeploy component.
|
||||
function(download_linuxdeploy_component base_dir name executable_name)
|
||||
@ -161,7 +241,7 @@ else()
|
||||
if (NOT EXISTS "${executable_file}")
|
||||
message(STATUS "Downloading ${executable_name}")
|
||||
file(DOWNLOAD
|
||||
"https://github.com/linuxdeploy/${name}/releases/download/continuous/${executable_name}"
|
||||
"https://github.com/${name}/releases/download/continuous/${executable_name}"
|
||||
"${executable_file}" SHOW_PROGRESS)
|
||||
file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
|
||||
|
||||
@ -170,7 +250,11 @@ else()
|
||||
message(STATUS "Extracting ${executable_name}")
|
||||
execute_process(
|
||||
COMMAND "${executable_file}" --appimage-extract
|
||||
WORKING_DIRECTORY "${base_dir}")
|
||||
WORKING_DIRECTORY "${base_dir}"
|
||||
RESULT_VARIABLE extract_result)
|
||||
if (NOT extract_result EQUAL "0")
|
||||
message(FATAL_ERROR "AppImage extract failed: ${extract_result}")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "Copying ${executable_name}")
|
||||
file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/")
|
||||
@ -178,89 +262,102 @@ else()
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
|
||||
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-${LINUXDEPLOY_ARCH}.AppImage")
|
||||
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "darealshinji/linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt.sh")
|
||||
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy" "linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage")
|
||||
else()
|
||||
# --- Bundling target creation logic ---
|
||||
|
||||
# Creates the base bundle target with common files and pre-bundle steps.
|
||||
function(create_base_bundle_target)
|
||||
message(STATUS "Creating base bundle target")
|
||||
|
||||
add_custom_target(bundle)
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
|
||||
|
||||
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
|
||||
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
|
||||
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
|
||||
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Adds a target to the bundle target, packing in required libraries.
|
||||
# If in_place is true, the bundling will be done in-place as part of the specified target.
|
||||
function(bundle_target_internal target_name in_place)
|
||||
# Create base bundle target if it does not exist.
|
||||
if (NOT in_place AND NOT TARGET bundle)
|
||||
message(STATUS "Creating base bundle target")
|
||||
|
||||
add_custom_target(bundle)
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
|
||||
add_custom_command(
|
||||
TARGET bundle
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
|
||||
create_base_bundle_target()
|
||||
endif()
|
||||
|
||||
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_FILE:${target_name}>")
|
||||
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
|
||||
if (target_name MATCHES ".*qt")
|
||||
set(BUNDLE_QT ON)
|
||||
set(bundle_qt ON)
|
||||
if (APPLE)
|
||||
# For Qt targets on Apple, expect an app bundle.
|
||||
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_BUNDLE_DIR:${target_name}>")
|
||||
set(bundle_executable_path "$<TARGET_BUNDLE_DIR:${target_name}>")
|
||||
endif()
|
||||
else()
|
||||
set(BUNDLE_QT OFF)
|
||||
set(bundle_qt OFF)
|
||||
endif()
|
||||
|
||||
# Build a list of library search paths from prefix paths.
|
||||
foreach(prefix_path IN LISTS CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
|
||||
foreach(prefix_path IN LISTS CMAKE_FIND_ROOT_PATH CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
|
||||
if (WIN32)
|
||||
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin")
|
||||
list(APPEND bundle_library_paths "${prefix_path}/bin")
|
||||
endif()
|
||||
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib")
|
||||
list(APPEND bundle_library_paths "${prefix_path}/lib")
|
||||
endforeach()
|
||||
foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH)
|
||||
list(APPEND BUNDLE_LIBRARY_PATHS "${library_path}")
|
||||
list(APPEND bundle_library_paths "${library_path}")
|
||||
endforeach()
|
||||
|
||||
# On Linux, prepare linuxdeploy and any required plugins.
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
set(LINUXDEPLOY_BASE "${CMAKE_BINARY_DIR}/externals/linuxdeploy")
|
||||
|
||||
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
|
||||
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-x86_64.AppImage")
|
||||
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt-x86_64.sh")
|
||||
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy" "linuxdeploy-x86_64.AppImage")
|
||||
|
||||
set(EXTRA_BUNDLE_ARGS "-DLINUXDEPLOY=${LINUXDEPLOY_BASE}/squashfs-root/AppRun")
|
||||
endif()
|
||||
|
||||
if (in_place)
|
||||
message(STATUS "Adding in-place bundling to ${target_name}")
|
||||
set(DEST_TARGET ${target_name})
|
||||
set(dest_target ${target_name})
|
||||
else()
|
||||
message(STATUS "Adding ${target_name} to bundle target")
|
||||
set(DEST_TARGET bundle)
|
||||
set(dest_target bundle)
|
||||
add_dependencies(bundle ${target_name})
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET ${DEST_TARGET} POST_BUILD
|
||||
add_custom_command(TARGET ${dest_target} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND}
|
||||
"-DCMAKE_PREFIX_PATH=\"${CMAKE_PREFIX_PATH}\""
|
||||
"-DQT_HOST_PATH=\"${QT_HOST_PATH}\""
|
||||
"-DQT_TARGET_PATH=\"${QT_TARGET_PATH}\""
|
||||
"-DBUNDLE_TARGET_EXECUTE=1"
|
||||
"-DTARGET=${target_name}"
|
||||
"-DSOURCE_PATH=${CMAKE_SOURCE_DIR}"
|
||||
"-DBINARY_PATH=${CMAKE_BINARY_DIR}"
|
||||
"-DEXECUTABLE_PATH=${BUNDLE_EXECUTABLE_PATH}"
|
||||
"-DBUNDLE_LIBRARY_PATHS=\"${BUNDLE_LIBRARY_PATHS}\""
|
||||
"-DBUNDLE_QT=${BUNDLE_QT}"
|
||||
"-DEXECUTABLE_PATH=${bundle_executable_path}"
|
||||
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
|
||||
"-DBUNDLE_QT=${bundle_qt}"
|
||||
"-DIN_PLACE=${in_place}"
|
||||
${EXTRA_BUNDLE_ARGS}
|
||||
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
|
||||
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
endfunction()
|
||||
|
@ -1,21 +1,20 @@
|
||||
|
||||
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
|
||||
|
||||
# This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
|
||||
# Params:
|
||||
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
|
||||
function(download_qt target)
|
||||
# Determines parameters based on the host and target for downloading the right Qt binaries.
|
||||
function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out)
|
||||
if (target MATCHES "tools_.*")
|
||||
set(DOWNLOAD_QT_TOOL ON)
|
||||
set(tool ON)
|
||||
else()
|
||||
set(DOWNLOAD_QT_TOOL OFF)
|
||||
set(tool OFF)
|
||||
endif()
|
||||
|
||||
# Determine installation parameters for OS, architecture, and compiler
|
||||
if (WIN32)
|
||||
set(host "windows")
|
||||
set(type "desktop")
|
||||
if (NOT DOWNLOAD_QT_TOOL)
|
||||
|
||||
if (NOT tool)
|
||||
if (MINGW)
|
||||
set(arch "win64_mingw")
|
||||
set(arch_path "mingw_64")
|
||||
@ -28,21 +27,35 @@ function(download_qt target)
|
||||
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
|
||||
endif()
|
||||
set(arch "win64_${arch_path}")
|
||||
|
||||
# In case we're cross-compiling, prepare to also fetch the correct host Qt tools.
|
||||
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64")
|
||||
set(host_arch_path "msvc2019_64")
|
||||
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64")
|
||||
# TODO: msvc2019_arm64 doesn't include some of the required tools for some reason,
|
||||
# TODO: so until it does, just use msvc2019_64 under x86_64 emulation.
|
||||
# set(host_arch_path "msvc2019_arm64")
|
||||
set(host_arch_path "msvc2019_64")
|
||||
endif()
|
||||
set(host_arch "win64_${host_arch_path}")
|
||||
else()
|
||||
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
|
||||
endif()
|
||||
endif()
|
||||
elseif (APPLE)
|
||||
set(host "mac")
|
||||
if (IOS AND NOT DOWNLOAD_QT_TOOL)
|
||||
set(type "desktop")
|
||||
set(arch "clang_64")
|
||||
set(arch_path "macos")
|
||||
|
||||
if (IOS AND NOT tool)
|
||||
set(host_type "${type}")
|
||||
set(host_arch "${arch}")
|
||||
set(host_arch_path "${arch_path}")
|
||||
|
||||
set(type "ios")
|
||||
set(arch "ios")
|
||||
set(arch_path "ios")
|
||||
set(host_arch_path "macos")
|
||||
else()
|
||||
set(type "desktop")
|
||||
set(arch "clang_64")
|
||||
set(arch_path "macos")
|
||||
endif()
|
||||
else()
|
||||
set(host "linux")
|
||||
@ -51,38 +64,64 @@ function(download_qt target)
|
||||
set(arch_path "linux")
|
||||
endif()
|
||||
|
||||
get_external_prefix(qt base_path)
|
||||
file(MAKE_DIRECTORY "${base_path}")
|
||||
set(${host_out} "${host}" PARENT_SCOPE)
|
||||
set(${type_out} "${type}" PARENT_SCOPE)
|
||||
set(${arch_out} "${arch}" PARENT_SCOPE)
|
||||
set(${arch_path_out} "${arch_path}" PARENT_SCOPE)
|
||||
if (DEFINED host_type)
|
||||
set(${host_type_out} "${host_type}" PARENT_SCOPE)
|
||||
else()
|
||||
set(${host_type_out} "${type}" PARENT_SCOPE)
|
||||
endif()
|
||||
if (DEFINED host_arch)
|
||||
set(${host_arch_out} "${host_arch}" PARENT_SCOPE)
|
||||
else()
|
||||
set(${host_arch_out} "${arch}" PARENT_SCOPE)
|
||||
endif()
|
||||
if (DEFINED host_arch_path)
|
||||
set(${host_arch_path_out} "${host_arch_path}" PARENT_SCOPE)
|
||||
else()
|
||||
set(${host_arch_path_out} "${arch_path}" PARENT_SCOPE)
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
# Download Qt binaries for a specifc configuration.
|
||||
function(download_qt_configuration prefix_out target host type arch arch_path base_path)
|
||||
if (target MATCHES "tools_.*")
|
||||
set(tool ON)
|
||||
else()
|
||||
set(tool OFF)
|
||||
endif()
|
||||
|
||||
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
|
||||
if (DOWNLOAD_QT_TOOL)
|
||||
if (tool)
|
||||
set(prefix "${base_path}/Tools")
|
||||
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
|
||||
else()
|
||||
set(prefix "${base_path}/${target}/${arch_path}")
|
||||
if (host_arch_path)
|
||||
set(host_flag "--autodesktop")
|
||||
set(host_prefix "${base_path}/${target}/${host_arch_path}")
|
||||
endif()
|
||||
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag}
|
||||
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
||||
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
|
||||
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
|
||||
endif()
|
||||
|
||||
if (NOT EXISTS "${prefix}")
|
||||
message(STATUS "Downloading binaries for Qt...")
|
||||
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
|
||||
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
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
if (NOT EXISTS "${aqt_path}")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt.exe
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
endif()
|
||||
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)
|
||||
if (NOT EXISTS "${aqt_path}")
|
||||
file(DOWNLOAD
|
||||
${AQT_PREBUILD_BASE_URL}/aqt-macos
|
||||
${aqt_path} SHOW_PROGRESS)
|
||||
endif()
|
||||
execute_process(COMMAND chmod +x ${aqt_path})
|
||||
execute_process(COMMAND ${aqt_path} ${install_args}
|
||||
WORKING_DIRECTORY ${base_path})
|
||||
@ -96,18 +135,38 @@ function(download_qt target)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
|
||||
WORKING_DIRECTORY ${base_path})
|
||||
endif()
|
||||
|
||||
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Using downloaded Qt binaries at ${prefix}")
|
||||
set(${prefix_out} "${prefix}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Add the Qt prefix path so CMake can locate it.
|
||||
# This function downloads Qt using aqt.
|
||||
# The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
|
||||
# QT_TARGET_PATH is set to the Qt for the compile target platform.
|
||||
# QT_HOST_PATH is set to a host-compatible Qt, for running tools.
|
||||
# Params:
|
||||
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
|
||||
function(download_qt target)
|
||||
determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path)
|
||||
|
||||
get_external_prefix(qt base_path)
|
||||
file(MAKE_DIRECTORY "${base_path}")
|
||||
|
||||
download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}")
|
||||
if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}")
|
||||
download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}")
|
||||
else()
|
||||
set(host_prefix "${prefix}")
|
||||
endif()
|
||||
|
||||
set(QT_TARGET_PATH "${prefix}" CACHE STRING "")
|
||||
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
|
||||
|
||||
# Add the target Qt prefix path so CMake can locate it.
|
||||
list(APPEND CMAKE_PREFIX_PATH "${prefix}")
|
||||
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
|
||||
|
||||
if (DEFINED host_prefix)
|
||||
message(STATUS "Using downloaded host Qt binaries at ${host_prefix}")
|
||||
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
|
||||
endif()
|
||||
endfunction()
|
||||
|
||||
function(download_moltenvk)
|
||||
|
8
dist/dumpkeys/DumpKeys.gm9
vendored
8
dist/dumpkeys/DumpKeys.gm9
vendored
@ -287,5 +287,13 @@ dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
|
||||
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
|
||||
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
|
||||
|
||||
# Dump seeddb.bin as well
|
||||
|
||||
set SEEDDB_IN "0:/gm9/out/seeddb.bin"
|
||||
set SEEDDB_OUT "0:/gm9/seeddb.bin"
|
||||
|
||||
sdump -w seeddb.bin
|
||||
cp -w $[SEEDDB_IN] $[SEEDDB_OUT]
|
||||
|
||||
@Exit
|
||||
|
||||
|
2
dist/dumpkeys/README.md
vendored
2
dist/dumpkeys/README.md
vendored
@ -6,5 +6,5 @@ Usage:
|
||||
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
|
||||
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
|
||||
3. Wait for the script to complete and return you to the GodMode9 main menu.
|
||||
4. Power off your system and copy the "gm9/aes_keys.txt" file off of your SD card into "(Citra directory)/sysdata/".
|
||||
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Citra directory)/sysdata/".
|
||||
|
||||
|
1
dist/languages/.tx/config
vendored
1
dist/languages/.tx/config
vendored
@ -11,3 +11,4 @@ type = QT
|
||||
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
|
||||
source_file = ../../src/android/app/src/main/res/values/strings.xml
|
||||
type = ANDROID
|
||||
lang_map = es_ES:es, hu_HU:hu, ru_RU:ru, pt_BR:pt, zh_CN:zh
|
||||
|
35
externals/CMakeLists.txt
vendored
35
externals/CMakeLists.txt
vendored
@ -57,6 +57,12 @@ if(USE_SYSTEM_CRYPTOPP)
|
||||
add_library(cryptopp INTERFACE)
|
||||
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
|
||||
else()
|
||||
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
|
||||
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
|
||||
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
|
||||
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
|
||||
endif()
|
||||
|
||||
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
|
||||
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
|
||||
@ -235,6 +241,18 @@ endif()
|
||||
|
||||
# DiscordRPC
|
||||
if (USE_DISCORD_PRESENCE)
|
||||
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
|
||||
include(TestBigEndian)
|
||||
test_big_endian(RAPIDJSON_BIG_ENDIAN)
|
||||
if(RAPIDJSON_BIG_ENDIAN)
|
||||
add_compile_definitions(RAPIDJSON_ENDIAN=1)
|
||||
else()
|
||||
add_compile_definitions(RAPIDJSON_ENDIAN=0)
|
||||
endif()
|
||||
|
||||
# Apply a dummy CLANG_FORMAT_SUFFIX to disable discord-rpc's unnecessary automatic clang-format.
|
||||
set(CLANG_FORMAT_SUFFIX "dummy")
|
||||
|
||||
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
|
||||
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
|
||||
endif()
|
||||
@ -276,11 +294,20 @@ endif()
|
||||
add_library(httplib INTERFACE)
|
||||
if(USE_SYSTEM_CPP_HTTPLIB)
|
||||
find_package(CppHttp 0.14.1)
|
||||
if(CppHttp_FOUND)
|
||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||
else()
|
||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||
# Detect if system cpphttplib is a shared library
|
||||
# this breaks building as Citra relies on functions that are moved
|
||||
# into the shared object.
|
||||
get_target_property(HTTP_LIBS httplib::httplib INTERFACE_LINK_LIBRARIES)
|
||||
if(HTTP_LIBS)
|
||||
message(WARNING "Shared cpp-http (${HTTP_LIBS}) not supported. Falling back to bundled...")
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
else()
|
||||
if(CppHttp_FOUND)
|
||||
target_link_libraries(httplib INTERFACE httplib::httplib)
|
||||
else()
|
||||
message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
endif()
|
||||
endif()
|
||||
else()
|
||||
target_include_directories(httplib SYSTEM INTERFACE ./httplib)
|
||||
|
2
externals/cryptopp-cmake
vendored
2
externals/cryptopp-cmake
vendored
Submodule externals/cryptopp-cmake updated: 9327192b00...a99c80c266
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: ca0e264f4f...30f1a3c628
2
externals/oaknut
vendored
2
externals/oaknut
vendored
Submodule externals/oaknut updated: 9d091109de...6b1d57ea7e
@ -10,7 +10,7 @@ plugins {
|
||||
id("org.jetbrains.kotlin.android")
|
||||
id("de.undercouch.download") version "5.5.0"
|
||||
id("kotlin-parcelize")
|
||||
kotlin("plugin.serialization") version "1.8.21"
|
||||
kotlin("plugin.serialization") version "1.9.22"
|
||||
id("androidx.navigation.safeargs.kotlin")
|
||||
}
|
||||
|
||||
@ -173,23 +173,23 @@ android {
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.recyclerview:recyclerview:1.3.2")
|
||||
implementation("androidx.activity:activity-ktx:1.8.0")
|
||||
implementation("androidx.activity:activity-ktx:1.8.2")
|
||||
implementation("androidx.fragment:fragment-ktx:1.6.2")
|
||||
implementation("androidx.appcompat:appcompat:1.6.1")
|
||||
implementation("androidx.documentfile:documentfile:1.0.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
|
||||
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0")
|
||||
implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
|
||||
implementation("com.google.android.material:material:1.9.0")
|
||||
implementation("androidx.core:core-splashscreen:1.0.1")
|
||||
implementation("androidx.work:work-runtime:2.8.1")
|
||||
implementation("androidx.work:work-runtime:2.9.0")
|
||||
implementation("org.ini4j:ini4j:0.5.4")
|
||||
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
|
||||
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6")
|
||||
implementation("androidx.navigation:navigation-ui-ktx:2.7.6")
|
||||
implementation("info.debatty:java-string-similarity:2.0.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
|
||||
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2")
|
||||
implementation("androidx.preference:preference-ktx:1.2.1")
|
||||
implementation("io.coil-kt:coil:2.2.2")
|
||||
implementation("io.coil-kt:coil:2.5.0")
|
||||
}
|
||||
|
||||
// Download Vulkan Validation Layers from the KhronosGroup GitHub.
|
||||
|
@ -42,6 +42,9 @@
|
||||
android:banner="@mipmap/ic_launcher"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
|
||||
<meta-data android:name="android.game_mode_config"
|
||||
android:resource="@xml/game_mode_config" />
|
||||
|
||||
<activity
|
||||
android:name="org.citra.citra_emu.ui.main.MainActivity"
|
||||
android:theme="@style/Theme.Citra.Splash.Main"
|
||||
|
@ -9,10 +9,13 @@ import android.app.Application
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import org.citra.citra_emu.utils.DirectoryInitialization
|
||||
import org.citra.citra_emu.utils.DocumentsTree
|
||||
import org.citra.citra_emu.utils.GpuDriverHelper
|
||||
import org.citra.citra_emu.utils.PermissionsHandler
|
||||
import org.citra.citra_emu.utils.Log
|
||||
import org.citra.citra_emu.utils.MemoryUtil
|
||||
|
||||
class CitraApplication : Application() {
|
||||
private fun createNotificationChannel() {
|
||||
@ -53,9 +56,20 @@ class CitraApplication : Application() {
|
||||
}
|
||||
|
||||
NativeLibrary.logDeviceInfo()
|
||||
logDeviceInfo()
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
fun logDeviceInfo() {
|
||||
Log.info("Device Manufacturer - ${Build.MANUFACTURER}")
|
||||
Log.info("Device Model - ${Build.MODEL}")
|
||||
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.R) {
|
||||
Log.info("SoC Manufacturer - ${Build.SOC_MANUFACTURER}")
|
||||
Log.info("SoC Model - ${Build.SOC_MODEL}")
|
||||
}
|
||||
Log.info("Total System Memory - ${MemoryUtil.getDeviceRAM()}")
|
||||
}
|
||||
|
||||
companion object {
|
||||
private var application: CitraApplication? = null
|
||||
|
||||
|
@ -413,12 +413,12 @@ object NativeLibrary {
|
||||
}
|
||||
|
||||
fun setEmulationActivity(emulationActivity: EmulationActivity?) {
|
||||
Log.verbose("[NativeLibrary] Registering EmulationActivity.")
|
||||
Log.debug("[NativeLibrary] Registering EmulationActivity.")
|
||||
sEmulationActivity = WeakReference(emulationActivity)
|
||||
}
|
||||
|
||||
fun clearEmulationActivity() {
|
||||
Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
|
||||
Log.debug("[NativeLibrary] Unregistering EmulationActivity.")
|
||||
sEmulationActivity.clear()
|
||||
}
|
||||
|
||||
|
@ -94,14 +94,14 @@ object DirectoryInitialization {
|
||||
val dataPath = PermissionsHandler.citraDirectory
|
||||
if (dataPath.toString().isNotEmpty()) {
|
||||
userPath = dataPath.toString()
|
||||
Log.debug("[DirectoryInitialization] User Dir: $userPath")
|
||||
android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath")
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) {
|
||||
Log.verbose("[DirectoryInitialization] Copying File $asset to $output")
|
||||
Log.debug("[DirectoryInitialization] Copying File $asset to $output")
|
||||
try {
|
||||
if (!output.exists() || overwrite) {
|
||||
val inputStream = context.assets.open(asset)
|
||||
@ -121,7 +121,7 @@ object DirectoryInitialization {
|
||||
overwrite: Boolean,
|
||||
context: Context
|
||||
) {
|
||||
Log.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
||||
Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
|
||||
try {
|
||||
var createdFolder = false
|
||||
for (file in context.assets.list(assetFolder)!!) {
|
||||
|
@ -4,34 +4,17 @@
|
||||
|
||||
package org.citra.citra_emu.utils
|
||||
|
||||
import android.util.Log
|
||||
import org.citra.citra_emu.BuildConfig
|
||||
|
||||
/**
|
||||
* Contains methods that call through to [android.util.Log], but
|
||||
* with the same TAG automatically provided. Also no-ops VERBOSE and DEBUG log
|
||||
* levels in release builds.
|
||||
*/
|
||||
object Log {
|
||||
// Tracks whether we should share the old log or the current log
|
||||
var gameLaunched = false
|
||||
private const val TAG = "Citra Frontend"
|
||||
|
||||
fun verbose(message: String?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.v(TAG, message!!)
|
||||
}
|
||||
}
|
||||
external fun debug(message: String)
|
||||
|
||||
fun debug(message: String?) {
|
||||
if (BuildConfig.DEBUG) {
|
||||
Log.d(TAG, message!!)
|
||||
}
|
||||
}
|
||||
external fun warning(message: String)
|
||||
|
||||
fun info(message: String?) = Log.i(TAG, message!!)
|
||||
external fun info(message: String)
|
||||
|
||||
fun warning(message: String?) = Log.w(TAG, message!!)
|
||||
external fun error(message: String)
|
||||
|
||||
fun error(message: String?) = Log.e(TAG, message!!)
|
||||
external fun critical(message: String)
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
package org.citra.citra_emu.utils
|
||||
|
||||
import android.app.ActivityManager
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import org.citra.citra_emu.CitraApplication
|
||||
import org.citra.citra_emu.R
|
||||
import java.util.Locale
|
||||
import kotlin.math.ceil
|
||||
|
||||
object MemoryUtil {
|
||||
private val context get() = CitraApplication.appContext
|
||||
|
||||
private val Float.hundredths: String
|
||||
get() = String.format(Locale.ROOT, "%.2f", this)
|
||||
|
||||
const val Kb: Float = 1024F
|
||||
const val Mb = Kb * 1024
|
||||
const val Gb = Mb * 1024
|
||||
const val Tb = Gb * 1024
|
||||
const val Pb = Tb * 1024
|
||||
const val Eb = Pb * 1024
|
||||
|
||||
fun bytesToSizeUnit(size: Float, roundUp: Boolean = false): String =
|
||||
when {
|
||||
size < Kb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
size.hundredths,
|
||||
context.getString(R.string.memory_byte_shorthand)
|
||||
)
|
||||
}
|
||||
size < Mb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Kb) else (size / Kb).hundredths,
|
||||
context.getString(R.string.memory_kilobyte)
|
||||
)
|
||||
}
|
||||
size < Gb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Mb) else (size / Mb).hundredths,
|
||||
context.getString(R.string.memory_megabyte)
|
||||
)
|
||||
}
|
||||
size < Tb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Gb) else (size / Gb).hundredths,
|
||||
context.getString(R.string.memory_gigabyte)
|
||||
)
|
||||
}
|
||||
size < Pb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Tb) else (size / Tb).hundredths,
|
||||
context.getString(R.string.memory_terabyte)
|
||||
)
|
||||
}
|
||||
size < Eb -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Pb) else (size / Pb).hundredths,
|
||||
context.getString(R.string.memory_petabyte)
|
||||
)
|
||||
}
|
||||
else -> {
|
||||
context.getString(
|
||||
R.string.memory_formatted,
|
||||
if (roundUp) ceil(size / Eb) else (size / Eb).hundredths,
|
||||
context.getString(R.string.memory_exabyte)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val totalMemory: Float
|
||||
get() {
|
||||
val memInfo = ActivityManager.MemoryInfo()
|
||||
with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
|
||||
getMemoryInfo(memInfo)
|
||||
}
|
||||
|
||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
|
||||
memInfo.advertisedMem.toFloat()
|
||||
} else {
|
||||
memInfo.totalMem.toFloat()
|
||||
}
|
||||
}
|
||||
|
||||
fun isLessThan(minimum: Int, size: Float): Boolean =
|
||||
when (size) {
|
||||
Kb -> totalMemory < Mb && totalMemory < minimum
|
||||
Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
|
||||
Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
|
||||
Tb -> totalMemory < Pb && (totalMemory / Tb) < minimum
|
||||
Pb -> totalMemory < Eb && (totalMemory / Pb) < minimum
|
||||
Eb -> totalMemory / Eb < minimum
|
||||
else -> totalMemory < Kb && totalMemory < minimum
|
||||
}
|
||||
|
||||
// Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
|
||||
// the potential error created by memInfo.totalMem
|
||||
fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory, true)
|
||||
}
|
@ -28,6 +28,7 @@ add_library(citra-android SHARED
|
||||
ndk_motion.cpp
|
||||
ndk_motion.h
|
||||
system_save_game.cpp
|
||||
native_log.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)
|
||||
|
30
src/android/app/src/main/jni/native_log.cpp
Normal file
30
src/android/app/src/main/jni/native_log.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <common/logging/log.h>
|
||||
#include <jni.h>
|
||||
#include "android_common/android_common.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_debug(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_DEBUG(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_warning(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_WARNING(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_info(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_INFO(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_error(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_ERROR(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
void Java_org_citra_citra_1emu_utils_Log_critical(JNIEnv* env, jobject obj, jstring jmessage) {
|
||||
LOG_CRITICAL(Frontend, "{}", GetJString(env, jmessage));
|
||||
}
|
||||
|
||||
} // extern "C"
|
@ -442,6 +442,17 @@
|
||||
<string name="cia_install_error_encrypted">\"%s\" must be decrypted before being used with Citra.\n A real 3DS is required</string>
|
||||
<string name="cia_install_error_unknown">An unknown error occurred while installing \"%s\".\n Please see the log for more details</string>
|
||||
|
||||
<!-- Memory Sizes -->
|
||||
<string name="memory_formatted">%1$s %2$s</string>
|
||||
<string name="memory_byte">Byte</string>
|
||||
<string name="memory_byte_shorthand">B</string>
|
||||
<string name="memory_kilobyte">KB</string>
|
||||
<string name="memory_megabyte">MB</string>
|
||||
<string name="memory_gigabyte">GB</string>
|
||||
<string name="memory_terabyte">TB</string>
|
||||
<string name="memory_petabyte">PB</string>
|
||||
<string name="memory_exabyte">EB</string>
|
||||
|
||||
<!-- Theme Modes -->
|
||||
<string name="change_theme_mode">Change Theme Mode</string>
|
||||
<string name="theme_mode_follow_system">Follow System</string>
|
||||
|
7
src/android/app/src/main/res/xml/game_mode_config.xml
Normal file
7
src/android/app/src/main/res/xml/game_mode_config.xml
Normal file
@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<game-mode-config
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:supportsBatteryGameMode="true"
|
||||
android:supportsPerformanceGameMode="true"
|
||||
android:allowGameDownscaling="false"
|
||||
android:allowGameFpsOverride="false"/>
|
@ -4,10 +4,10 @@
|
||||
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
plugins {
|
||||
id("com.android.application") version "8.1.2" apply false
|
||||
id("com.android.library") version "8.1.2" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.8.21" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
|
||||
id("com.android.application") version "8.2.1" apply false
|
||||
id("com.android.library") version "8.2.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "1.9.22" apply false
|
||||
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22"
|
||||
}
|
||||
|
||||
tasks.register("clean").configure {
|
||||
@ -19,6 +19,6 @@ buildscript {
|
||||
google()
|
||||
}
|
||||
dependencies {
|
||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.5")
|
||||
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6")
|
||||
}
|
||||
}
|
||||
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
|
||||
|
@ -316,7 +316,7 @@ struct SourceStatus {
|
||||
u16_le sync_count; ///< Is set by the DSP to the value of SourceConfiguration::sync_count
|
||||
u32_dsp buffer_position; ///< Number of samples into the current buffer
|
||||
u16_le current_buffer_id; ///< Updated when a buffer finishes playing
|
||||
INSERT_PADDING_DSPWORDS(1);
|
||||
u16_le last_buffer_id; ///< Updated when all buffers in the queue finish playing
|
||||
};
|
||||
|
||||
Status status[num_sources];
|
||||
|
@ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||
b.buffer_id,
|
||||
state.mono_or_stereo,
|
||||
state.format,
|
||||
true,
|
||||
{}, // 0 in u32_dsp
|
||||
false,
|
||||
true, // from_queue
|
||||
0, // play_position
|
||||
false, // has_played
|
||||
});
|
||||
}
|
||||
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
|
||||
@ -321,16 +321,19 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
|
||||
void Source::GenerateFrame() {
|
||||
current_frame.fill({});
|
||||
|
||||
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
||||
if (state.current_buffer.empty()) {
|
||||
// TODO(SachinV): Should dequeue happen at the end of the frame generation?
|
||||
if (DequeueBuffer()) {
|
||||
return;
|
||||
}
|
||||
state.enabled = false;
|
||||
state.buffer_update = true;
|
||||
state.last_buffer_id = state.current_buffer_id;
|
||||
state.current_buffer_id = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
std::size_t frame_position = 0;
|
||||
|
||||
state.current_sample_number = state.next_sample_number;
|
||||
while (frame_position < current_frame.size()) {
|
||||
if (state.current_buffer.empty() && !DequeueBuffer()) {
|
||||
break;
|
||||
@ -357,7 +360,7 @@ void Source::GenerateFrame() {
|
||||
}
|
||||
// TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
|
||||
// over time
|
||||
state.next_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
|
||||
state.current_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
|
||||
|
||||
state.filters.ProcessFrame(current_frame);
|
||||
}
|
||||
@ -408,9 +411,9 @@ bool Source::DequeueBuffer() {
|
||||
|
||||
// the first playthrough starts at play_position, loops start at the beginning of the buffer
|
||||
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
|
||||
state.next_sample_number = state.current_sample_number;
|
||||
state.current_buffer_physical_address = buf.physical_address;
|
||||
state.current_buffer_id = buf.buffer_id;
|
||||
state.last_buffer_id = 0;
|
||||
state.buffer_update = buf.from_queue && !buf.has_played;
|
||||
|
||||
if (buf.is_looping) {
|
||||
@ -418,8 +421,17 @@ bool Source::DequeueBuffer() {
|
||||
state.input_queue.push(buf);
|
||||
}
|
||||
|
||||
LOG_TRACE(Audio_DSP, "source_id={} buffer_id={} from_queue={} current_buffer.size()={}",
|
||||
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size());
|
||||
// Because our interpolation consumes samples instead of using an index,
|
||||
// let's just consume the samples up to the current sample number.
|
||||
state.current_buffer.erase(
|
||||
state.current_buffer.begin(),
|
||||
std::next(state.current_buffer.begin(), state.current_sample_number));
|
||||
|
||||
LOG_TRACE(Audio_DSP,
|
||||
"source_id={} buffer_id={} from_queue={} current_buffer.size()={}, "
|
||||
"buf.has_played={}, buf.play_position={}",
|
||||
source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size(), buf.has_played,
|
||||
buf.play_position);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -432,9 +444,10 @@ SourceStatus::Status Source::GetCurrentStatus() {
|
||||
ret.is_enabled = state.enabled;
|
||||
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
|
||||
state.buffer_update = false;
|
||||
ret.current_buffer_id = state.current_buffer_id;
|
||||
ret.buffer_position = state.current_sample_number;
|
||||
ret.sync_count = state.sync_count;
|
||||
ret.buffer_position = state.current_sample_number;
|
||||
ret.current_buffer_id = state.current_buffer_id;
|
||||
ret.last_buffer_id = state.last_buffer_id;
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
@ -87,8 +87,8 @@ private:
|
||||
Format format;
|
||||
|
||||
bool from_queue;
|
||||
u32_dsp play_position; // = 0;
|
||||
bool has_played; // = false;
|
||||
u32 play_position; // = 0;
|
||||
bool has_played; // = false;
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
@ -136,14 +136,14 @@ private:
|
||||
// Current buffer
|
||||
|
||||
u32 current_sample_number = 0;
|
||||
u32 next_sample_number = 0;
|
||||
PAddr current_buffer_physical_address = 0;
|
||||
AudioInterp::StereoBuffer16 current_buffer = {};
|
||||
|
||||
// buffer_id state
|
||||
|
||||
bool buffer_update = false;
|
||||
u32 current_buffer_id = 0;
|
||||
u16 last_buffer_id = 0;
|
||||
u16 current_buffer_id = 0;
|
||||
|
||||
// Decoding state
|
||||
|
||||
@ -170,7 +170,6 @@ private:
|
||||
ar& mono_or_stereo;
|
||||
ar& format;
|
||||
ar& current_sample_number;
|
||||
ar& next_sample_number;
|
||||
ar& current_buffer_physical_address;
|
||||
ar& current_buffer;
|
||||
ar& buffer_update;
|
||||
|
@ -54,7 +54,7 @@ const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> Config:
|
||||
// This must be in alphabetical order according to action name as it must have the same order as
|
||||
// UISetting::values.shortcuts, which is alphabetically ordered.
|
||||
// clang-format off
|
||||
const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
|
||||
const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
|
||||
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
|
||||
@ -71,6 +71,11 @@ const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
|
||||
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
|
||||
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), Qt::WidgetWithChildrenShortcut}},
|
||||
{QStringLiteral("Load from Newest Slot"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+V"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Multiplayer Browse Public Game Lobby"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+B"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Create Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+N"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Direct Connect to Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+Shift"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Leave Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+L"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Multiplayer Show Current Room"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+R"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
|
||||
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
|
||||
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
|
||||
@ -557,6 +562,15 @@ void Config::ReadMultiplayerValues() {
|
||||
UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
|
||||
UISettings::values.room_description =
|
||||
ReadSetting(QStringLiteral("room_description"), QString{}).toString();
|
||||
UISettings::values.multiplayer_filter_text =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_text"), QString{}).toString();
|
||||
UISettings::values.multiplayer_filter_games_owned =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_games_owned"), false).toBool();
|
||||
UISettings::values.multiplayer_filter_hide_empty =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_hide_empty"), false).toBool();
|
||||
UISettings::values.multiplayer_filter_hide_full =
|
||||
ReadSetting(QStringLiteral("multiplayer_filter_hide_full"), false).toBool();
|
||||
|
||||
// Read ban list back
|
||||
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
|
||||
UISettings::values.ban_list.first.resize(size);
|
||||
@ -1074,6 +1088,15 @@ void Config::SaveMultiplayerValues() {
|
||||
WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
|
||||
WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
|
||||
QString{});
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_text"),
|
||||
UISettings::values.multiplayer_filter_text, QString{});
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_games_owned"),
|
||||
UISettings::values.multiplayer_filter_games_owned, false);
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_hide_empty"),
|
||||
UISettings::values.multiplayer_filter_hide_empty, false);
|
||||
WriteSetting(QStringLiteral("multiplayer_filter_hide_full"),
|
||||
UISettings::values.multiplayer_filter_hide_full, false);
|
||||
|
||||
// Write ban list
|
||||
qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
|
||||
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {
|
||||
|
@ -26,7 +26,7 @@ public:
|
||||
|
||||
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons;
|
||||
static const std::array<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
|
||||
static const std::array<UISettings::Shortcut, 30> default_hotkeys;
|
||||
static const std::array<UISettings::Shortcut, 35> default_hotkeys;
|
||||
|
||||
private:
|
||||
void Initialize(const std::string& config_name);
|
||||
|
@ -647,6 +647,13 @@ void GMainWindow::InitializeHotkeys() {
|
||||
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame"));
|
||||
link_action_shortcut(ui->action_Load_from_Newest_Slot, QStringLiteral("Load from Newest Slot"));
|
||||
link_action_shortcut(ui->action_Save_to_Oldest_Slot, QStringLiteral("Save to Oldest Slot"));
|
||||
link_action_shortcut(ui->action_View_Lobby,
|
||||
QStringLiteral("Multiplayer Browse Public Game Lobby"));
|
||||
link_action_shortcut(ui->action_Start_Room, QStringLiteral("Multiplayer Create Room"));
|
||||
link_action_shortcut(ui->action_Connect_To_Room,
|
||||
QStringLiteral("Multiplayer Direct Connect to Room"));
|
||||
link_action_shortcut(ui->action_Show_Room, QStringLiteral("Multiplayer Show Current Room"));
|
||||
link_action_shortcut(ui->action_Leave_Room, QStringLiteral("Multiplayer Leave Room"));
|
||||
|
||||
const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) {
|
||||
// This action will fire specifically when secondary_window is in focus
|
||||
@ -3190,8 +3197,10 @@ int main(int argc, char* argv[]) {
|
||||
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
|
||||
|
||||
#ifdef __APPLE__
|
||||
std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
|
||||
chdir(bin_path.c_str());
|
||||
auto bundle_dir = FileUtil::GetBundleDirectory();
|
||||
if (bundle_dir) {
|
||||
FileUtil::SetCurrentDir(bundle_dir.value() + "..");
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_OPENGL
|
||||
|
@ -80,9 +80,8 @@ void DirectConnectWindow::Connect() {
|
||||
// Store settings
|
||||
UISettings::values.nickname = ui->nickname->text();
|
||||
UISettings::values.ip = ui->ip->text();
|
||||
UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty())
|
||||
? ui->port->text()
|
||||
: UISettings::values.port;
|
||||
UISettings::values.port =
|
||||
!ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port;
|
||||
|
||||
// attempt to connect in a different thread
|
||||
QFuture<void> f = QtConcurrent::run([&] {
|
||||
|
@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
|
||||
|
||||
// UI Buttons
|
||||
connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby);
|
||||
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
|
||||
connect(ui->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
|
||||
connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
|
||||
connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull);
|
||||
connect(ui->search, &QLineEdit::textChanged, proxy, &LobbyFilterProxyModel::SetFilterSearch);
|
||||
connect(ui->room_list, &QTreeView::doubleClicked, this, &Lobby::OnJoinRoom);
|
||||
connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
|
||||
|
||||
@ -74,6 +74,12 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
|
||||
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
|
||||
&Lobby::OnRefreshLobby);
|
||||
|
||||
// Load persistent filters after events are connected to make sure they apply
|
||||
ui->search->setText(UISettings::values.multiplayer_filter_text);
|
||||
ui->games_owned->setChecked(UISettings::values.multiplayer_filter_games_owned);
|
||||
ui->hide_empty->setChecked(UISettings::values.multiplayer_filter_hide_empty);
|
||||
ui->hide_full->setChecked(UISettings::values.multiplayer_filter_hide_full);
|
||||
|
||||
// manually start a refresh when the window is opening
|
||||
// TODO(jroweboy): if this refresh is slow for people with bad internet, then don't do it as
|
||||
// part of the constructor, but offload the refresh until after the window shown. perhaps emit a
|
||||
@ -180,6 +186,10 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
|
||||
UISettings::values.nickname = ui->nickname->text();
|
||||
UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
|
||||
UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).toString();
|
||||
UISettings::values.multiplayer_filter_text = ui->search->text();
|
||||
UISettings::values.multiplayer_filter_games_owned = ui->games_owned->isChecked();
|
||||
UISettings::values.multiplayer_filter_hide_empty = ui->hide_empty->isChecked();
|
||||
UISettings::values.multiplayer_filter_hide_full = ui->hide_full->isChecked();
|
||||
}
|
||||
|
||||
void Lobby::ResetModel() {
|
||||
|
@ -188,12 +188,37 @@ public:
|
||||
}
|
||||
|
||||
QVariant data(int role) const override {
|
||||
if (role != Qt::DisplayRole) {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole: {
|
||||
auto members = data(MemberListRole).toList();
|
||||
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
|
||||
data(MaxPlayerRole).toString());
|
||||
}
|
||||
case Qt::ForegroundRole: {
|
||||
auto members = data(MemberListRole).toList();
|
||||
auto max_players = data(MaxPlayerRole).toInt();
|
||||
const QColor room_full_color(255, 48, 32);
|
||||
const QColor room_almost_full_color(255, 140, 32);
|
||||
const QColor room_has_players_color(32, 160, 32);
|
||||
const QColor room_empty_color(128, 128, 128);
|
||||
|
||||
if (members.size() >= max_players) {
|
||||
return QBrush(room_full_color);
|
||||
} else if (members.size() == (max_players - 1)) {
|
||||
return QBrush(room_almost_full_color);
|
||||
} else if (members.size() == 0) {
|
||||
return QBrush(room_empty_color);
|
||||
} else if (members.size() > 0 && members.size() < (max_players - 1)) {
|
||||
return QBrush(room_has_players_color);
|
||||
}
|
||||
|
||||
// FIXME: How to return a value that tells Qt not to modify the
|
||||
// text color from the default (as if Qt::ForegroundRole wasn't overridden)?
|
||||
return QBrush(nullptr);
|
||||
}
|
||||
default:
|
||||
return LobbyItem::data(role);
|
||||
}
|
||||
auto members = data(MemberListRole).toList();
|
||||
return QStringLiteral("%1 / %2").arg(QString::number(members.size()),
|
||||
data(MaxPlayerRole).toString());
|
||||
}
|
||||
|
||||
bool operator<(const QStandardItem& other) const override {
|
||||
|
@ -138,6 +138,11 @@ struct Values {
|
||||
QString room_description;
|
||||
std::pair<std::vector<std::string>, std::vector<std::string>> ban_list;
|
||||
|
||||
QString multiplayer_filter_text;
|
||||
bool multiplayer_filter_games_owned;
|
||||
bool multiplayer_filter_hide_empty;
|
||||
bool multiplayer_filter_hide_full;
|
||||
|
||||
// logging
|
||||
Settings::Setting<bool> show_console{false, "showConsole"};
|
||||
};
|
||||
|
@ -13,7 +13,6 @@
|
||||
#endif
|
||||
|
||||
// The user data dir
|
||||
#define ROOT_DIR "."
|
||||
#define USERDATA_DIR "user"
|
||||
#ifdef USER_DIR
|
||||
#define EMU_DATA_DIR USER_DIR
|
||||
|
@ -634,6 +634,10 @@ std::optional<std::string> GetCurrentDir() {
|
||||
std::string strDir = dir;
|
||||
#endif
|
||||
free(dir);
|
||||
|
||||
if (!strDir.ends_with(DIR_SEP)) {
|
||||
strDir += DIR_SEP;
|
||||
}
|
||||
return strDir;
|
||||
} // namespace FileUtil
|
||||
|
||||
@ -646,17 +650,36 @@ bool SetCurrentDir(const std::string& directory) {
|
||||
}
|
||||
|
||||
#if defined(__APPLE__)
|
||||
std::string GetBundleDirectory() {
|
||||
CFURLRef BundleRef;
|
||||
char AppBundlePath[MAXPATHLEN];
|
||||
std::optional<std::string> GetBundleDirectory() {
|
||||
// Get the main bundle for the app
|
||||
BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
|
||||
CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
|
||||
CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
|
||||
CFRelease(BundleRef);
|
||||
CFRelease(BundlePath);
|
||||
CFBundleRef bundle_ref = CFBundleGetMainBundle();
|
||||
if (!bundle_ref) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return AppBundlePath;
|
||||
CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref);
|
||||
if (!bundle_url_ref) {
|
||||
return {};
|
||||
}
|
||||
SCOPE_EXIT({ CFRelease(bundle_url_ref); });
|
||||
|
||||
CFStringRef bundle_path_ref = CFURLCopyFileSystemPath(bundle_url_ref, kCFURLPOSIXPathStyle);
|
||||
if (!bundle_path_ref) {
|
||||
return {};
|
||||
}
|
||||
SCOPE_EXIT({ CFRelease(bundle_path_ref); });
|
||||
|
||||
char app_bundle_path[MAXPATHLEN];
|
||||
if (!CFStringGetFileSystemRepresentation(bundle_path_ref, app_bundle_path,
|
||||
sizeof(app_bundle_path))) {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string path_str(app_bundle_path);
|
||||
if (!path_str.ends_with(DIR_SEP)) {
|
||||
path_str += DIR_SEP;
|
||||
}
|
||||
return path_str;
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -732,22 +755,6 @@ static const std::string& GetHomeDirectory() {
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string GetSysDirectory() {
|
||||
std::string sysDir;
|
||||
|
||||
#if defined(__APPLE__)
|
||||
sysDir = GetBundleDirectory();
|
||||
sysDir += DIR_SEP;
|
||||
sysDir += SYSDATA_DIR;
|
||||
#else
|
||||
sysDir = SYSDATA_DIR;
|
||||
#endif
|
||||
sysDir += DIR_SEP;
|
||||
|
||||
LOG_DEBUG(Common_Filesystem, "Setting to {}:", sysDir);
|
||||
return sysDir;
|
||||
}
|
||||
|
||||
namespace {
|
||||
std::unordered_map<UserPath, std::string> g_paths;
|
||||
std::unordered_map<UserPath, std::string> g_default_paths;
|
||||
@ -777,8 +784,10 @@ void SetUserPath(const std::string& path) {
|
||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
#else
|
||||
if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
|
||||
user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
|
||||
auto current_dir = FileUtil::GetCurrentDir();
|
||||
if (current_dir.has_value() &&
|
||||
FileUtil::Exists(current_dir.value() + USERDATA_DIR DIR_SEP)) {
|
||||
user_path = current_dir.value() + USERDATA_DIR DIR_SEP;
|
||||
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
|
||||
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
|
||||
} else {
|
||||
|
@ -193,11 +193,8 @@ void SetCurrentRomPath(const std::string& path);
|
||||
// Update the Global Path with the new value
|
||||
void UpdateUserPath(UserPath path, const std::string& filename);
|
||||
|
||||
// Returns the path to where the sys file are
|
||||
[[nodiscard]] std::string GetSysDirectory();
|
||||
|
||||
#ifdef __APPLE__
|
||||
[[nodiscard]] std::string GetBundleDirectory();
|
||||
[[nodiscard]] std::optional<std::string> GetBundleDirectory();
|
||||
#endif
|
||||
|
||||
#ifdef _WIN32
|
||||
|
@ -451,7 +451,7 @@ void SetColorConsoleBackendEnabled(bool enabled) {
|
||||
}
|
||||
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
unsigned int line_num, const char* function, fmt::string_view format,
|
||||
const fmt::format_args& args) {
|
||||
if (!initialization_in_progress_suppress_logging) {
|
||||
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,
|
||||
|
@ -24,12 +24,12 @@ constexpr const char* TrimSourcePath(std::string_view source) {
|
||||
|
||||
/// Logs a message to the global logger, using fmt
|
||||
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
|
||||
unsigned int line_num, const char* function, const char* format,
|
||||
unsigned int line_num, const char* function, fmt::string_view format,
|
||||
const fmt::format_args& args);
|
||||
|
||||
template <typename... Args>
|
||||
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
|
||||
const char* function, const char* format, const Args&... args) {
|
||||
const char* function, fmt::format_string<Args...> format, const Args&... args) {
|
||||
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
|
@ -327,6 +327,10 @@ add_library(citra_core STATIC
|
||||
hle/service/ldr_ro/cro_helper.h
|
||||
hle/service/ldr_ro/ldr_ro.cpp
|
||||
hle/service/ldr_ro/ldr_ro.h
|
||||
hle/service/mcu/mcu_hwc.cpp
|
||||
hle/service/mcu/mcu_hwc.h
|
||||
hle/service/mcu/mcu.cpp
|
||||
hle/service/mcu/mcu.h
|
||||
hle/service/mic/mic_u.cpp
|
||||
hle/service/mic/mic_u.h
|
||||
hle/service/mvd/mvd.cpp
|
||||
|
@ -14,18 +14,18 @@ namespace ConfigMem {
|
||||
Handler::Handler() {
|
||||
std::memset(&config_mem, 0, sizeof(config_mem));
|
||||
|
||||
// Values extracted from firmware 11.2.0-35E
|
||||
config_mem.kernel_version_min = 0x34;
|
||||
// Values extracted from firmware 11.17.0-50E
|
||||
config_mem.kernel_version_min = 0x3a;
|
||||
config_mem.kernel_version_maj = 0x2;
|
||||
config_mem.ns_tid = 0x0004013000008002;
|
||||
config_mem.sys_core_ver = 0x2;
|
||||
config_mem.unit_info = 0x1; // Bit 0 set for Retail
|
||||
config_mem.prev_firm = 0x1;
|
||||
config_mem.ctr_sdk_ver = 0x0000F297;
|
||||
config_mem.firm_version_min = 0x34;
|
||||
config_mem.ctr_sdk_ver = 0x0000F450;
|
||||
config_mem.firm_version_min = 0x3a;
|
||||
config_mem.firm_version_maj = 0x2;
|
||||
config_mem.firm_sys_core_ver = 0x2;
|
||||
config_mem.firm_ctr_sdk_ver = 0x0000F297;
|
||||
config_mem.firm_ctr_sdk_ver = 0x0000F450;
|
||||
}
|
||||
|
||||
ConfigMemDef& Handler::GetConfigMem() {
|
||||
|
@ -210,10 +210,10 @@ void Process::Set3dsxKernelCaps() {
|
||||
};
|
||||
|
||||
// Similar to Rosalina, we set kernel version to a recent one.
|
||||
// This is 11.2.0, to be consistent with core/hle/kernel/config_mem.cpp
|
||||
// This is 11.17.0, to be consistent with core/hle/kernel/config_mem.cpp
|
||||
// TODO: refactor kernel version out so it is configurable and consistent
|
||||
// among all relevant places.
|
||||
kernel_version = 0x234;
|
||||
kernel_version = 0x23a;
|
||||
}
|
||||
|
||||
void Process::Run(s32 main_thread_priority, u32 stack_size) {
|
||||
|
@ -373,7 +373,10 @@ ResultVal<AppletManager::InitializeResult> AppletManager::Initialize(AppletId ap
|
||||
if (active_slot == AppletSlot::Error) {
|
||||
active_slot = slot;
|
||||
|
||||
// Wake up the application.
|
||||
// APT automatically calls enable on the first registered applet.
|
||||
Enable(attributes);
|
||||
|
||||
// Wake up the applet.
|
||||
SendParameter({
|
||||
.sender_id = AppletId::None,
|
||||
.destination_id = app_id,
|
||||
@ -398,7 +401,8 @@ Result AppletManager::Enable(AppletAttributes attributes) {
|
||||
auto slot_data = GetAppletSlot(slot);
|
||||
slot_data->registered = true;
|
||||
|
||||
if (slot_data->attributes.applet_pos == AppletPos::System &&
|
||||
if (slot_data->applet_id != AppletId::None &&
|
||||
slot_data->attributes.applet_pos == AppletPos::System &&
|
||||
slot_data->attributes.is_home_menu) {
|
||||
slot_data->attributes.raw |= attributes.raw;
|
||||
LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.",
|
||||
@ -786,16 +790,23 @@ Result AppletManager::PrepareToStartSystemApplet(AppletId applet_id) {
|
||||
|
||||
Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object,
|
||||
const std::vector<u8>& buffer) {
|
||||
auto source_applet_id = AppletId::None;
|
||||
auto source_applet_id = AppletId::Application;
|
||||
if (last_system_launcher_slot != AppletSlot::Error) {
|
||||
const auto slot_data = GetAppletSlot(last_system_launcher_slot);
|
||||
source_applet_id = slot_data->applet_id;
|
||||
const auto launcher_slot_data = GetAppletSlot(last_system_launcher_slot);
|
||||
source_applet_id = launcher_slot_data->applet_id;
|
||||
|
||||
// If a system applet is launching another system applet, reset the slot to avoid conflicts.
|
||||
// This is needed because system applets won't necessarily call CloseSystemApplet before
|
||||
// exiting.
|
||||
if (last_system_launcher_slot == AppletSlot::SystemApplet) {
|
||||
slot_data->Reset();
|
||||
// APT generally clears and terminates the caller of StartSystemApplet. This helps in
|
||||
// situations such as a system applet launching another system applet, which would
|
||||
// otherwise deadlock.
|
||||
// TODO: In real APT, the check for AppletSlot::Application does not exist; there is
|
||||
// TODO: something wrong with our implementation somewhere that makes this necessary.
|
||||
// TODO: Otherwise, games that attempt to launch system applets will be cleared and
|
||||
// TODO: emulation will crash.
|
||||
if (!launcher_slot_data->registered ||
|
||||
(last_system_launcher_slot != AppletSlot::Application &&
|
||||
!launcher_slot_data->attributes.no_exit_on_system_applet)) {
|
||||
launcher_slot_data->Reset();
|
||||
// TODO: Implement launcher process termination.
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -152,6 +152,7 @@ union AppletAttributes {
|
||||
u32 raw;
|
||||
|
||||
BitField<0, 3, AppletPos> applet_pos;
|
||||
BitField<28, 1, u32> no_exit_on_system_applet;
|
||||
BitField<29, 1, u32> is_home_menu;
|
||||
|
||||
AppletAttributes() : raw(0) {}
|
||||
|
@ -7,6 +7,7 @@
|
||||
#include "common/archives.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
@ -522,7 +523,13 @@ void Y2R_U::StartConversion(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
HW::Y2R::PerformConversion(system.Memory(), conversion);
|
||||
|
||||
completion_event->Signal();
|
||||
if (is_busy_conversion) {
|
||||
system.CoreTiming().RemoveEvent(completion_signal_event);
|
||||
}
|
||||
|
||||
static constexpr s64 MinY2RDelay = 50000;
|
||||
system.CoreTiming().ScheduleEvent(MinY2RDelay, completion_signal_event);
|
||||
is_busy_conversion = true;
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -533,6 +540,10 @@ void Y2R_U::StartConversion(Kernel::HLERequestContext& ctx) {
|
||||
void Y2R_U::StopConversion(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp(ctx);
|
||||
|
||||
if (is_busy_conversion) {
|
||||
system.CoreTiming().RemoveEvent(completion_signal_event);
|
||||
}
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
|
||||
@ -544,7 +555,7 @@ void Y2R_U::IsBusyConversion(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 0);
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push<u8>(0); // StartConversion always finishes immediately
|
||||
rb.Push(is_busy_conversion);
|
||||
|
||||
LOG_DEBUG(Service_Y2R, "called");
|
||||
}
|
||||
@ -558,34 +569,38 @@ void Y2R_U::SetPackageParameter(Kernel::HLERequestContext& ctx) {
|
||||
conversion.rotation = params.rotation;
|
||||
conversion.block_alignment = params.block_alignment;
|
||||
|
||||
Result result = conversion.SetInputLineWidth(params.input_line_width);
|
||||
|
||||
if (result.IsError())
|
||||
goto cleanup;
|
||||
|
||||
result = conversion.SetInputLines(params.input_lines);
|
||||
|
||||
if (result.IsError())
|
||||
goto cleanup;
|
||||
|
||||
result = conversion.SetStandardCoefficient(params.standard_coefficient);
|
||||
|
||||
if (result.IsError())
|
||||
goto cleanup;
|
||||
|
||||
conversion.padding = params.padding;
|
||||
conversion.alpha = params.alpha;
|
||||
|
||||
cleanup:
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(result);
|
||||
|
||||
LOG_DEBUG(Service_Y2R,
|
||||
"called input_format={} output_format={} rotation={} block_alignment={} "
|
||||
"input_line_width={} input_lines={} standard_coefficient={} reserved={} alpha={:X}",
|
||||
params.input_format, params.output_format, params.rotation, params.block_alignment,
|
||||
params.input_line_width, params.input_lines, params.standard_coefficient,
|
||||
params.padding, params.alpha);
|
||||
|
||||
auto result = conversion.SetInputLineWidth(params.input_line_width);
|
||||
|
||||
SCOPE_EXIT({
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(1, 0);
|
||||
rb.Push(result);
|
||||
});
|
||||
|
||||
if (result.IsError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
result = conversion.SetInputLines(params.input_lines);
|
||||
|
||||
if (result.IsError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
result = conversion.SetStandardCoefficient(params.standard_coefficient);
|
||||
|
||||
if (result.IsError()) {
|
||||
return;
|
||||
}
|
||||
|
||||
conversion.padding = params.padding;
|
||||
conversion.alpha = params.alpha;
|
||||
}
|
||||
|
||||
void Y2R_U::PingProcess(Kernel::HLERequestContext& ctx) {
|
||||
@ -697,6 +712,11 @@ Y2R_U::Y2R_U(Core::System& system) : ServiceFramework("y2r:u", 1), system(system
|
||||
RegisterHandlers(functions);
|
||||
|
||||
completion_event = system.Kernel().CreateEvent(Kernel::ResetType::OneShot, "Y2R:Completed");
|
||||
completion_signal_event =
|
||||
system.CoreTiming().RegisterEvent("Y2R Completion Signal Event", [this](uintptr_t, s64) {
|
||||
completion_event->Signal();
|
||||
is_busy_conversion = false;
|
||||
});
|
||||
}
|
||||
|
||||
Y2R_U::~Y2R_U() = default;
|
||||
|
@ -349,11 +349,13 @@ private:
|
||||
Core::System& system;
|
||||
|
||||
std::shared_ptr<Kernel::Event> completion_event;
|
||||
Core::TimingEventType* completion_signal_event;
|
||||
ConversionConfiguration conversion{};
|
||||
DitheringWeightParams dithering_weight_params{};
|
||||
bool temporal_dithering_enabled = false;
|
||||
bool transfer_end_interrupt_enabled = false;
|
||||
bool spacial_dithering_enabled = false;
|
||||
bool is_busy_conversion = false;
|
||||
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int);
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -18,6 +18,7 @@
|
||||
#include <boost/serialization/vector.hpp>
|
||||
#include <boost/serialization/weak_ptr.hpp>
|
||||
#include <httplib.h>
|
||||
#include "common/thread.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/shared_memory.h"
|
||||
#include "core/hle/service/service.h"
|
||||
@ -48,12 +49,25 @@ constexpr u32 TotalRequestMethods = 8;
|
||||
|
||||
enum class RequestState : u8 {
|
||||
NotStarted = 0x1, // Request has not started yet.
|
||||
InProgress = 0x5, // Request in progress, sending request over the network.
|
||||
ReadyToDownloadContent = 0x7, // Ready to download the content. (needs verification)
|
||||
ReadyToDownload = 0x8, // Ready to download?
|
||||
ConnectingToServer = 0x5, // Request in progress, connecting to server.
|
||||
SendingRequest = 0x6, // Request in progress, sending HTTP request.
|
||||
ReceivingResponse = 0x7, // Request in progress, receiving HTTP response.
|
||||
ReadyToDownloadContent = 0x8, // Ready to download the content.
|
||||
TimedOut = 0xA, // Request timed out?
|
||||
};
|
||||
|
||||
enum class PostDataEncoding : u8 {
|
||||
Auto = 0x0,
|
||||
AsciiForm = 0x1,
|
||||
MultipartForm = 0x2,
|
||||
};
|
||||
|
||||
enum class PostDataType : u8 {
|
||||
AsciiForm = 0x0,
|
||||
MultipartForm = 0x1,
|
||||
Raw = 0x2,
|
||||
};
|
||||
|
||||
enum class ClientCertID : u32 {
|
||||
Default = 0x40, // Default client cert
|
||||
};
|
||||
@ -197,6 +211,41 @@ public:
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
struct Param {
|
||||
Param(const std::vector<u8>& value)
|
||||
: name(value.begin(), value.end()), value(value.begin(), value.end()){};
|
||||
Param(const std::string& name, const std::string& value) : name(name), value(value){};
|
||||
Param(const std::string& name, const std::vector<u8>& value)
|
||||
: name(name), value(value.begin(), value.end()), is_binary(true){};
|
||||
std::string name;
|
||||
std::string value;
|
||||
bool is_binary = false;
|
||||
|
||||
httplib::MultipartFormData ToMultipartForm() const {
|
||||
httplib::MultipartFormData form;
|
||||
form.name = name;
|
||||
form.content = value;
|
||||
if (is_binary) {
|
||||
form.content_type = "application/octet-stream";
|
||||
// TODO(DaniElectra): httplib doesn't support setting Content-Transfer-Encoding,
|
||||
// while the 3DS sets Content-Transfer-Encoding: binary if a binary value is set
|
||||
}
|
||||
|
||||
return form;
|
||||
}
|
||||
|
||||
private:
|
||||
template <class Archive>
|
||||
void serialize(Archive& ar, const unsigned int) {
|
||||
ar& name;
|
||||
ar& value;
|
||||
ar& is_binary;
|
||||
}
|
||||
friend class boost::serialization::access;
|
||||
};
|
||||
|
||||
using Params = std::multimap<std::string, Param>;
|
||||
|
||||
Handle handle;
|
||||
u32 session_id;
|
||||
std::string url;
|
||||
@ -208,8 +257,14 @@ public:
|
||||
u32 socket_buffer_size;
|
||||
std::vector<RequestHeader> headers;
|
||||
const ClCertAData* clcert_data;
|
||||
httplib::Params post_data;
|
||||
Params post_data;
|
||||
std::string post_data_raw;
|
||||
PostDataEncoding post_data_encoding = PostDataEncoding::Auto;
|
||||
PostDataType post_data_type;
|
||||
std::string multipart_boundary;
|
||||
bool force_multipart = false;
|
||||
bool chunked_request = false;
|
||||
u32 chunked_content_length;
|
||||
|
||||
std::future<void> request_future;
|
||||
std::atomic<u64> current_download_size_bytes;
|
||||
@ -217,12 +272,19 @@ public:
|
||||
std::size_t current_copied_data;
|
||||
bool uses_default_client_cert{};
|
||||
httplib::Response response;
|
||||
Common::Event finish_post_data;
|
||||
|
||||
void ParseAsciiPostData();
|
||||
std::string ParseMultipartFormData();
|
||||
void MakeRequest();
|
||||
void MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_info,
|
||||
std::vector<Context::RequestHeader>& pending_headers);
|
||||
void MakeRequestSSL(httplib::Request& request, const URLInfo& url_info,
|
||||
std::vector<Context::RequestHeader>& pending_headers);
|
||||
bool ContentProvider(size_t offset, size_t length, httplib::DataSink& sink);
|
||||
bool ChunkedContentProvider(size_t offset, httplib::DataSink& sink);
|
||||
std::size_t HandleHeaderWrite(std::vector<Context::RequestHeader>& pending_headers,
|
||||
httplib::Stream& strm, httplib::Headers& httplib_headers);
|
||||
};
|
||||
|
||||
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
|
||||
@ -308,6 +370,16 @@ private:
|
||||
*/
|
||||
void CancelConnection(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::GetRequestState service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : Request state
|
||||
*/
|
||||
void GetRequestState(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::GetDownloadSizeState service function
|
||||
* Inputs:
|
||||
@ -418,6 +490,21 @@ private:
|
||||
*/
|
||||
void AddPostDataAscii(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::AddPostDataBinary service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Form name buffer size, including null-terminator.
|
||||
* 3 : Form value buffer size
|
||||
* 4 : (FormNameSize<<14) | 0xC02
|
||||
* 5 : Form name data pointer
|
||||
* 6 : (FormValueSize<<4) | 10
|
||||
* 7 : Form value data pointer
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void AddPostDataBinary(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::AddPostDataRaw service function
|
||||
* Inputs:
|
||||
@ -430,6 +517,140 @@ private:
|
||||
*/
|
||||
void AddPostDataRaw(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::SetPostDataType service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Post data type
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void SetPostDataType(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::SendPostDataAscii service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Form name buffer size, including null-terminator.
|
||||
* 3 : Form value buffer size, including null-terminator.
|
||||
* 4 : (FormNameSize<<14) | 0xC02
|
||||
* 5 : Form name data pointer
|
||||
* 6 : (FormValueSize<<4) | 10
|
||||
* 7 : Form value data pointer
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void SendPostDataAscii(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::SendPostDataAsciiTimeout service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Form name buffer size, including null-terminator.
|
||||
* 3 : Form value buffer size, including null-terminator.
|
||||
* 4-5 : u64 nanoseconds delay
|
||||
* 6 : (FormNameSize<<14) | 0xC02
|
||||
* 7 : Form name data pointer
|
||||
* 8 : (FormValueSize<<4) | 10
|
||||
* 9 : Form value data pointer
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void SendPostDataAsciiTimeout(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* SendPostDataAsciiImpl:
|
||||
* Implements SendPostDataAscii and SendPostDataAsciiTimeout service functions
|
||||
*/
|
||||
void SendPostDataAsciiImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||
|
||||
/**
|
||||
* HTTP_C::SendPostDataBinary service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Form name buffer size, including null-terminator.
|
||||
* 3 : Form value buffer size
|
||||
* 4 : (FormNameSize<<14) | 0xC02
|
||||
* 5 : Form name data pointer
|
||||
* 6 : (FormValueSize<<4) | 10
|
||||
* 7 : Form value data pointer
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void SendPostDataBinary(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::SendPostDataBinaryTimeout service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Form name buffer size, including null-terminator.
|
||||
* 3 : Form value buffer size
|
||||
* 4-5 : u64 nanoseconds delay
|
||||
* 6 : (FormNameSize<<14) | 0xC02
|
||||
* 7 : Form name data pointer
|
||||
* 8 : (FormValueSize<<4) | 10
|
||||
* 9 : Form value data pointer
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void SendPostDataBinaryTimeout(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* SendPostDataBinaryImpl:
|
||||
* Implements SendPostDataBinary and SendPostDataBinaryTimeout service functions
|
||||
*/
|
||||
void SendPostDataBinaryImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||
|
||||
/**
|
||||
* HTTP_C::SendPostDataRaw service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Post data length
|
||||
* 3-4: (Mapped buffer) Post data
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2-3: (Mapped buffer) Post data
|
||||
*/
|
||||
void SendPostDataRaw(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::SendPostDataRawTimeout service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Post data length
|
||||
* 3-4: u64 nanoseconds delay
|
||||
* 5-6: (Mapped buffer) Post data
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2-3: (Mapped buffer) Post data
|
||||
*/
|
||||
void SendPostDataRawTimeout(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* SendPostDataRawImpl:
|
||||
* Implements SendPostDataRaw and SendPostDataRawTimeout service functions
|
||||
*/
|
||||
void SendPostDataRawImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||
|
||||
/**
|
||||
* HTTP_C::NotifyFinishSendPostData service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void NotifyFinishSendPostData(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::SetPostDataEncoding service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Post data encoding
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void SetPostDataEncoding(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::GetResponseHeader service function
|
||||
* Inputs:
|
||||
@ -445,6 +666,28 @@ private:
|
||||
*/
|
||||
void GetResponseHeader(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::GetResponseHeaderTimeout service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Header name length
|
||||
* 3 : Return value length
|
||||
* 4-5 : u64 nanoseconds delay
|
||||
* 6-7 : (Static buffer) Header name
|
||||
* 8-9 : (Mapped buffer) Header value
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
* 2 : Header value copied size
|
||||
* 3-4: (Mapped buffer) Header value
|
||||
*/
|
||||
void GetResponseHeaderTimeout(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* GetResponseHeaderImpl:
|
||||
* Implements GetResponseHeader and GetResponseHeaderTimeout service functions
|
||||
*/
|
||||
void GetResponseHeaderImpl(Kernel::HLERequestContext& ctx, bool timeout);
|
||||
|
||||
/**
|
||||
* HTTP_C::GetResponseStatusCode service function
|
||||
* Inputs:
|
||||
@ -578,6 +821,17 @@ private:
|
||||
*/
|
||||
void SetKeepAlive(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::SetPostDataTypeSize service function
|
||||
* Inputs:
|
||||
* 1 : Context handle
|
||||
* 2 : Post data type
|
||||
* 3 : Content length size
|
||||
* Outputs:
|
||||
* 1 : Result of function, 0 on success, otherwise error code
|
||||
*/
|
||||
void SetPostDataTypeSize(Kernel::HLERequestContext& ctx);
|
||||
|
||||
/**
|
||||
* HTTP_C::Finalize service function
|
||||
* Outputs:
|
||||
|
16
src/core/hle/service/mcu/mcu.cpp
Normal file
16
src/core/hle/service/mcu/mcu.cpp
Normal file
@ -0,0 +1,16 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "core/core.h"
|
||||
#include "core/hle/service/mcu/mcu.h"
|
||||
#include "core/hle/service/mcu/mcu_hwc.h"
|
||||
|
||||
namespace Service::MCU {
|
||||
|
||||
void InstallInterfaces(Core::System& system) {
|
||||
auto& service_manager = system.ServiceManager();
|
||||
std::make_shared<HWC>()->InstallAsService(service_manager);
|
||||
}
|
||||
|
||||
} // namespace Service::MCU
|
15
src/core/hle/service/mcu/mcu.h
Normal file
15
src/core/hle/service/mcu/mcu.h
Normal file
@ -0,0 +1,15 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
namespace Service::MCU {
|
||||
|
||||
void InstallInterfaces(Core::System& system);
|
||||
|
||||
} // namespace Service::MCU
|
36
src/core/hle/service/mcu/mcu_hwc.cpp
Normal file
36
src/core/hle/service/mcu/mcu_hwc.cpp
Normal file
@ -0,0 +1,36 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include "common/archives.h"
|
||||
#include "core/hle/service/mcu/mcu_hwc.h"
|
||||
|
||||
SERIALIZE_EXPORT_IMPL(Service::MCU::HWC)
|
||||
|
||||
namespace Service::MCU {
|
||||
|
||||
HWC::HWC() : ServiceFramework("mcu::HWC", 1) {
|
||||
static const FunctionInfo functions[] = {
|
||||
// clang-format off
|
||||
{0x0001, nullptr, "ReadRegister"},
|
||||
{0x0002, nullptr, "WriteRegister"},
|
||||
{0x0003, nullptr, "GetInfoRegisters"},
|
||||
{0x0004, nullptr, "GetBatteryVoltage"},
|
||||
{0x0005, nullptr, "GetBatteryLevel"},
|
||||
{0x0006, nullptr, "SetPowerLEDPattern"},
|
||||
{0x0007, nullptr, "SetWifiLEDState"},
|
||||
{0x0008, nullptr, "SetCameraLEDPattern"},
|
||||
{0x0009, nullptr, "Set3DLEDState"},
|
||||
{0x000A, nullptr, "SetInfoLEDPattern"},
|
||||
{0x000B, nullptr, "GetSoundVolume"},
|
||||
{0x000C, nullptr, "SetTopScreenFlicker"},
|
||||
{0x000D, nullptr, "SetBottomScreenFlicker"},
|
||||
{0x000F, nullptr, "GetRtcTime"},
|
||||
{0x0010, nullptr, "GetMcuFwVerHigh"},
|
||||
{0x0011, nullptr, "GetMcuFwVerLow"},
|
||||
// clang-format on
|
||||
};
|
||||
RegisterHandlers(functions);
|
||||
}
|
||||
|
||||
} // namespace Service::MCU
|
21
src/core/hle/service/mcu/mcu_hwc.h
Normal file
21
src/core/hle/service/mcu/mcu_hwc.h
Normal file
@ -0,0 +1,21 @@
|
||||
// Copyright 2024 Citra Emulator Project
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "core/hle/service/service.h"
|
||||
|
||||
namespace Service::MCU {
|
||||
|
||||
class HWC final : public ServiceFramework<HWC> {
|
||||
public:
|
||||
explicit HWC();
|
||||
|
||||
private:
|
||||
SERVICE_SERIALIZATION_SIMPLE
|
||||
};
|
||||
|
||||
} // namespace Service::MCU
|
||||
|
||||
BOOST_CLASS_EXPORT_KEY(Service::MCU::HWC)
|
@ -35,6 +35,7 @@
|
||||
#include "core/hle/service/http/http_c.h"
|
||||
#include "core/hle/service/ir/ir.h"
|
||||
#include "core/hle/service/ldr_ro/ldr_ro.h"
|
||||
#include "core/hle/service/mcu/mcu.h"
|
||||
#include "core/hle/service/mic/mic_u.h"
|
||||
#include "core/hle/service/mvd/mvd.h"
|
||||
#include "core/hle/service/ndm/ndm_u.h"
|
||||
@ -101,7 +102,7 @@ const std::array<ServiceModuleInfo, 41> service_module_map{
|
||||
{"CDC", 0x00040130'00001802, nullptr},
|
||||
{"GPIO", 0x00040130'00001B02, nullptr},
|
||||
{"I2C", 0x00040130'00001E02, nullptr},
|
||||
{"MCU", 0x00040130'00001F02, nullptr},
|
||||
{"MCU", 0x00040130'00001F02, MCU::InstallInterfaces},
|
||||
{"MP", 0x00040130'00002A02, nullptr},
|
||||
{"PDN", 0x00040130'00002102, nullptr},
|
||||
{"SPI", 0x00040130'00002302, nullptr}}};
|
||||
|
@ -598,9 +598,10 @@ static_assert(std::is_trivially_copyable_v<CTRPollFD>,
|
||||
union CTRSockAddr {
|
||||
/// Structure to represent a raw sockaddr
|
||||
struct {
|
||||
u8 len; ///< The length of the entire structure, only the set fields count
|
||||
u8 sa_family; ///< The address family of the sockaddr
|
||||
u8 sa_data[0x1A]; ///< The extra data, this varies, depending on the address family
|
||||
u8 len; ///< The length of the entire structure, only the set fields count
|
||||
u8 sa_family; ///< The address family of the sockaddr
|
||||
std::array<u8, 0x1A>
|
||||
sa_data; ///< The extra data, this varies, depending on the address family
|
||||
} raw;
|
||||
|
||||
/// Structure to represent the 3ds' sockaddr_in structure
|
||||
@ -612,36 +613,57 @@ union CTRSockAddr {
|
||||
} in;
|
||||
static_assert(sizeof(CTRSockAddrIn) == 8, "Invalid CTRSockAddrIn size");
|
||||
|
||||
struct CTRSockAddrIn6 {
|
||||
u8 len; ///< The length of the entire structure
|
||||
u8 sin6_family; ///< The address family of the sockaddr_in6
|
||||
u16 sin6_port; ///< The port associated with this sockaddr_in6
|
||||
std::array<u8, 0x10> sin6_addr; ///< The actual address of the sockaddr_in6
|
||||
u32 sin6_flowinfo; ///< The flow info of the sockaddr_in6
|
||||
u32 sin6_scope_id; ///< The scope ID of the sockaddr_in6
|
||||
} in6;
|
||||
static_assert(sizeof(CTRSockAddrIn6) == 28, "Invalid CTRSockAddrIn6 size");
|
||||
|
||||
/// Convert a 3DS CTRSockAddr to a platform-specific sockaddr
|
||||
static sockaddr ToPlatform(CTRSockAddr const& ctr_addr) {
|
||||
sockaddr result;
|
||||
ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn),
|
||||
static std::pair<sockaddr_storage, socklen_t> ToPlatform(CTRSockAddr const& ctr_addr) {
|
||||
sockaddr_storage result{};
|
||||
socklen_t result_len = sizeof(result.ss_family);
|
||||
ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn) ||
|
||||
ctr_addr.raw.len == sizeof(CTRSockAddrIn6),
|
||||
"Unhandled address size (len) in CTRSockAddr::ToPlatform");
|
||||
result.sa_family = SocketDomainToPlatform(ctr_addr.raw.sa_family);
|
||||
std::memset(result.sa_data, 0, sizeof(result.sa_data));
|
||||
result.ss_family = SocketDomainToPlatform(ctr_addr.raw.sa_family);
|
||||
|
||||
// We can not guarantee ABI compatibility between platforms so we copy the fields manually
|
||||
switch (result.sa_family) {
|
||||
switch (result.ss_family) {
|
||||
case AF_INET: {
|
||||
sockaddr_in* result_in = reinterpret_cast<sockaddr_in*>(&result);
|
||||
result_in->sin_port = ctr_addr.in.sin_port;
|
||||
result_in->sin_addr.s_addr = ctr_addr.in.sin_addr;
|
||||
std::memset(result_in->sin_zero, 0, sizeof(result_in->sin_zero));
|
||||
result_len = sizeof(sockaddr_in);
|
||||
break;
|
||||
}
|
||||
case AF_INET6: {
|
||||
sockaddr_in6* result_in6 = reinterpret_cast<sockaddr_in6*>(&result);
|
||||
result_in6->sin6_port = ctr_addr.in6.sin6_port;
|
||||
memcpy(&result_in6->sin6_addr, ctr_addr.in6.sin6_addr.data(),
|
||||
sizeof(result_in6->sin6_addr));
|
||||
result_in6->sin6_flowinfo = ctr_addr.in6.sin6_flowinfo;
|
||||
result_in6->sin6_scope_id = ctr_addr.in6.sin6_scope_id;
|
||||
result_len = sizeof(sockaddr_in6);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
|
||||
break;
|
||||
}
|
||||
return result;
|
||||
return std::make_pair(result, result_len);
|
||||
}
|
||||
|
||||
/// Convert a platform-specific sockaddr to a 3DS CTRSockAddr
|
||||
static CTRSockAddr FromPlatform(sockaddr const& addr) {
|
||||
static CTRSockAddr FromPlatform(sockaddr_storage const& addr) {
|
||||
CTRSockAddr result;
|
||||
result.raw.sa_family = static_cast<u8>(SocketDomainFromPlatform(addr.sa_family));
|
||||
result.raw.sa_family = static_cast<u8>(SocketDomainFromPlatform(addr.ss_family));
|
||||
// We can not guarantee ABI compatibility between platforms so we copy the fields manually
|
||||
switch (addr.sa_family) {
|
||||
switch (addr.ss_family) {
|
||||
case AF_INET: {
|
||||
sockaddr_in const* addr_in = reinterpret_cast<sockaddr_in const*>(&addr);
|
||||
result.raw.len = sizeof(CTRSockAddrIn);
|
||||
@ -649,6 +671,15 @@ union CTRSockAddr {
|
||||
result.in.sin_addr = addr_in->sin_addr.s_addr;
|
||||
break;
|
||||
}
|
||||
case AF_INET6: {
|
||||
sockaddr_in6 const* addr_in6 = reinterpret_cast<sockaddr_in6 const*>(&addr);
|
||||
result.raw.len = sizeof(CTRSockAddrIn6);
|
||||
result.in6.sin6_port = addr_in6->sin6_port;
|
||||
memcpy(result.in6.sin6_addr.data(), &addr_in6->sin6_addr, sizeof(result.in6.sin6_addr));
|
||||
result.in6.sin6_flowinfo = addr_in6->sin6_flowinfo;
|
||||
result.in6.sin6_scope_id = addr_in6->sin6_scope_id;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
|
||||
break;
|
||||
@ -707,7 +738,8 @@ struct CTRAddrInfo {
|
||||
.ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)),
|
||||
.ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)),
|
||||
.ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)),
|
||||
.ai_addr = CTRSockAddr::FromPlatform(*addr.ai_addr),
|
||||
.ai_addr =
|
||||
CTRSockAddr::FromPlatform(*reinterpret_cast<sockaddr_storage*>(addr.ai_addr)),
|
||||
};
|
||||
ctr_addr.ai_addrlen = static_cast<s32_le>(ctr_addr.ai_addr.raw.len);
|
||||
if (addr.ai_canonname)
|
||||
@ -840,9 +872,9 @@ void SOC_U::Bind(Kernel::HLERequestContext& ctx) {
|
||||
CTRSockAddr ctr_sock_addr;
|
||||
std::memcpy(&ctr_sock_addr, sock_addr_buf.data(), std::min<size_t>(len, sizeof(ctr_sock_addr)));
|
||||
|
||||
sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr);
|
||||
auto [sock_addr, sock_addr_len] = CTRSockAddr::ToPlatform(ctr_sock_addr);
|
||||
|
||||
s32 ret = ::bind(holder.socket_fd, &sock_addr, sizeof(sock_addr));
|
||||
s32 ret = ::bind(holder.socket_fd, reinterpret_cast<sockaddr*>(&sock_addr), sock_addr_len);
|
||||
|
||||
if (ret != 0)
|
||||
ret = TranslateError(GET_ERRNO);
|
||||
@ -937,7 +969,7 @@ void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
|
||||
// Output
|
||||
s32 ret{};
|
||||
int accept_error;
|
||||
sockaddr addr;
|
||||
sockaddr_storage addr;
|
||||
};
|
||||
|
||||
auto async_data = std::make_shared<AsyncData>();
|
||||
@ -950,7 +982,8 @@ void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
|
||||
[async_data](Kernel::HLERequestContext& ctx) {
|
||||
socklen_t addr_len = sizeof(async_data->addr);
|
||||
async_data->ret = static_cast<u32>(
|
||||
::accept(async_data->fd_info->socket_fd, &async_data->addr, &addr_len));
|
||||
::accept(async_data->fd_info->socket_fd,
|
||||
reinterpret_cast<sockaddr*>(&async_data->addr), &addr_len));
|
||||
async_data->accept_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
|
||||
return 0;
|
||||
},
|
||||
@ -981,8 +1014,8 @@ void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
|
||||
ctr_addr_buf.resize(async_data->max_addr_len);
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->socket_handle,
|
||||
static_cast<s32>(async_data->ret));
|
||||
LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->pid,
|
||||
async_data->socket_handle, static_cast<s32>(async_data->ret));
|
||||
|
||||
IPC::RequestBuilder rb(ctx, 0x04, 2, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1109,10 +1142,10 @@ void SOC_U::SendToOther(Kernel::HLERequestContext& ctx) {
|
||||
CTRSockAddr ctr_dest_addr;
|
||||
std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(),
|
||||
std::min<size_t>(addr_len, sizeof(ctr_dest_addr)));
|
||||
sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
|
||||
ret = static_cast<s32>(::sendto(holder.socket_fd,
|
||||
reinterpret_cast<const char*>(input_buff.data()), len,
|
||||
flags, &dest_addr, sizeof(dest_addr)));
|
||||
auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr);
|
||||
ret = static_cast<s32>(
|
||||
::sendto(holder.socket_fd, reinterpret_cast<const char*>(input_buff.data()), len, flags,
|
||||
reinterpret_cast<sockaddr*>(&dest_addr), dest_addr_len));
|
||||
} else {
|
||||
ret = static_cast<s32>(::sendto(holder.socket_fd,
|
||||
reinterpret_cast<const char*>(input_buff.data()), len,
|
||||
@ -1159,10 +1192,10 @@ s32 SOC_U::SendToImpl(SocketHolder& holder, u32 len, u32 flags, u32 addr_len,
|
||||
CTRSockAddr ctr_dest_addr;
|
||||
std::memcpy(&ctr_dest_addr, dest_addr_buff,
|
||||
std::min<size_t>(addr_len, sizeof(ctr_dest_addr)));
|
||||
sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
|
||||
ret = static_cast<s32>(::sendto(holder.socket_fd,
|
||||
reinterpret_cast<const char*>(input_buff.data()), len,
|
||||
flags, &dest_addr, sizeof(dest_addr)));
|
||||
auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr);
|
||||
ret = static_cast<s32>(
|
||||
::sendto(holder.socket_fd, reinterpret_cast<const char*>(input_buff.data()), len, flags,
|
||||
reinterpret_cast<sockaddr*>(&dest_addr), dest_addr_len));
|
||||
} else {
|
||||
ret = static_cast<s32>(::sendto(holder.socket_fd,
|
||||
reinterpret_cast<const char*>(input_buff.data()), len,
|
||||
@ -1294,7 +1327,7 @@ void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
ctx.RunAsync(
|
||||
[async_data](Kernel::HLERequestContext& ctx) {
|
||||
sockaddr src_addr;
|
||||
sockaddr_storage src_addr;
|
||||
socklen_t src_addr_len = sizeof(src_addr);
|
||||
CTRSockAddr ctr_src_addr;
|
||||
// Windows, why do you have to be so special...
|
||||
@ -1302,10 +1335,10 @@ void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
|
||||
RecvBusyWaitForEvent(*async_data->fd_info);
|
||||
}
|
||||
if (async_data->addr_len > 0) {
|
||||
async_data->ret = static_cast<s32>(
|
||||
::recvfrom(async_data->fd_info->socket_fd,
|
||||
reinterpret_cast<char*>(async_data->output_buff.data()),
|
||||
async_data->len, async_data->flags, &src_addr, &src_addr_len));
|
||||
async_data->ret = static_cast<s32>(::recvfrom(
|
||||
async_data->fd_info->socket_fd,
|
||||
reinterpret_cast<char*>(async_data->output_buff.data()), async_data->len,
|
||||
async_data->flags, reinterpret_cast<sockaddr*>(&src_addr), &src_addr_len));
|
||||
if (async_data->ret >= 0 && src_addr_len > 0) {
|
||||
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
|
||||
std::memcpy(async_data->addr_buff.data(), &ctr_src_addr,
|
||||
@ -1411,7 +1444,7 @@ void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
ctx.RunAsync(
|
||||
[async_data](Kernel::HLERequestContext& ctx) {
|
||||
sockaddr src_addr;
|
||||
sockaddr_storage src_addr;
|
||||
socklen_t src_addr_len = sizeof(src_addr);
|
||||
CTRSockAddr ctr_src_addr;
|
||||
if (async_data->is_blocking) {
|
||||
@ -1419,10 +1452,10 @@ void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
if (async_data->addr_len > 0) {
|
||||
// Only get src adr if input adr available
|
||||
async_data->ret = static_cast<s32>(
|
||||
::recvfrom(async_data->fd_info->socket_fd,
|
||||
reinterpret_cast<char*>(async_data->output_buff.data()),
|
||||
async_data->len, async_data->flags, &src_addr, &src_addr_len));
|
||||
async_data->ret = static_cast<s32>(::recvfrom(
|
||||
async_data->fd_info->socket_fd,
|
||||
reinterpret_cast<char*>(async_data->output_buff.data()), async_data->len,
|
||||
async_data->flags, reinterpret_cast<sockaddr*>(&src_addr), &src_addr_len));
|
||||
if (async_data->ret >= 0 && src_addr_len > 0) {
|
||||
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
|
||||
std::memcpy(async_data->addr_buff.data(), &ctr_src_addr,
|
||||
@ -1558,9 +1591,10 @@ void SOC_U::GetSockName(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
SocketHolder& holder = socket_holder_optional->get();
|
||||
|
||||
sockaddr dest_addr;
|
||||
sockaddr_storage dest_addr;
|
||||
socklen_t dest_addr_len = sizeof(dest_addr);
|
||||
s32 ret = ::getsockname(holder.socket_fd, &dest_addr, &dest_addr_len);
|
||||
s32 ret =
|
||||
::getsockname(holder.socket_fd, reinterpret_cast<sockaddr*>(&dest_addr), &dest_addr_len);
|
||||
|
||||
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
||||
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
|
||||
@ -1647,10 +1681,11 @@ void SOC_U::GetHostByAddr(Kernel::HLERequestContext& ctx) {
|
||||
[[maybe_unused]] u32 out_buf_len = rp.Pop<u32>();
|
||||
auto addr = rp.PopStaticBuffer();
|
||||
|
||||
sockaddr platform_addr = CTRSockAddr::ToPlatform(*reinterpret_cast<CTRSockAddr*>(addr.data()));
|
||||
auto [platform_addr, platform_addr_len] =
|
||||
CTRSockAddr::ToPlatform(*reinterpret_cast<CTRSockAddr*>(addr.data()));
|
||||
|
||||
struct hostent* result =
|
||||
::gethostbyaddr(reinterpret_cast<char*>(&platform_addr), sizeof(platform_addr), type);
|
||||
::gethostbyaddr(reinterpret_cast<char*>(&platform_addr), platform_addr_len, type);
|
||||
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
@ -1698,9 +1733,10 @@ void SOC_U::GetPeerName(Kernel::HLERequestContext& ctx) {
|
||||
}
|
||||
SocketHolder& holder = socket_holder_optional->get();
|
||||
|
||||
sockaddr dest_addr;
|
||||
sockaddr_storage dest_addr;
|
||||
socklen_t dest_addr_len = sizeof(dest_addr);
|
||||
const int ret = ::getpeername(holder.socket_fd, &dest_addr, &dest_addr_len);
|
||||
const int ret =
|
||||
::getpeername(holder.socket_fd, reinterpret_cast<sockaddr*>(&dest_addr), &dest_addr_len);
|
||||
|
||||
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
|
||||
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
|
||||
@ -1741,7 +1777,7 @@ void SOC_U::Connect(Kernel::HLERequestContext& ctx) {
|
||||
struct AsyncData {
|
||||
// Input
|
||||
SocketHolder* fd_info;
|
||||
sockaddr input_addr;
|
||||
std::pair<sockaddr_storage, socklen_t> input_addr;
|
||||
u32 socket_handle;
|
||||
u32 pid;
|
||||
|
||||
@ -1763,8 +1799,9 @@ void SOC_U::Connect(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
ctx.RunAsync(
|
||||
[async_data](Kernel::HLERequestContext& ctx) {
|
||||
async_data->ret = ::connect(async_data->fd_info->socket_fd, &async_data->input_addr,
|
||||
sizeof(async_data->input_addr));
|
||||
async_data->ret = ::connect(async_data->fd_info->socket_fd,
|
||||
reinterpret_cast<sockaddr*>(&async_data->input_addr.first),
|
||||
async_data->input_addr.second);
|
||||
async_data->connect_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
|
||||
return 0;
|
||||
},
|
||||
@ -2047,14 +2084,15 @@ void SOC_U::GetNameInfoImpl(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
CTRSockAddr ctr_sa;
|
||||
std::memcpy(&ctr_sa, sa_buff.data(), socklen);
|
||||
sockaddr sa = CTRSockAddr::ToPlatform(ctr_sa);
|
||||
auto [sa, sa_len] = CTRSockAddr::ToPlatform(ctr_sa);
|
||||
|
||||
std::vector<u8> host(hostlen);
|
||||
std::vector<u8> serv(servlen);
|
||||
char* host_data = hostlen > 0 ? reinterpret_cast<char*>(host.data()) : nullptr;
|
||||
char* serv_data = servlen > 0 ? reinterpret_cast<char*>(serv.data()) : nullptr;
|
||||
|
||||
s32 ret = getnameinfo(&sa, sizeof(sa), host_data, hostlen, serv_data, servlen, flags);
|
||||
s32 ret = getnameinfo(reinterpret_cast<sockaddr*>(&sa), sa_len, host_data, hostlen, serv_data,
|
||||
servlen, flags);
|
||||
if (ret == SOCKET_ERROR_VALUE) {
|
||||
ret = TranslateError(GET_ERRNO);
|
||||
}
|
||||
|
@ -152,6 +152,7 @@ static void SaveBanList(const Network::Room::BanList& ban_list, const std::strin
|
||||
static void InitializeLogging(const std::string& log_file) {
|
||||
Common::Log::Initialize(log_file);
|
||||
Common::Log::SetColorConsoleBackendEnabled(true);
|
||||
Common::Log::Start();
|
||||
}
|
||||
|
||||
/// Application entry point
|
||||
|
@ -429,18 +429,20 @@ Common::ParamPackage SDLState::GetSDLControllerButtonBindByGUID(
|
||||
|
||||
#if SDL_VERSION_ATLEAST(2, 0, 6)
|
||||
{
|
||||
const SDL_ExtendedGameControllerBind extended_bind =
|
||||
controller->bindings[mapped_button];
|
||||
if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) {
|
||||
params.Set("direction", "-");
|
||||
} else {
|
||||
params.Set("direction", "+");
|
||||
if (mapped_button != SDL_CONTROLLER_BUTTON_INVALID) {
|
||||
const SDL_ExtendedGameControllerBind extended_bind =
|
||||
controller->bindings[mapped_button];
|
||||
if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) {
|
||||
params.Set("direction", "-");
|
||||
} else {
|
||||
params.Set("direction", "+");
|
||||
}
|
||||
params.Set("threshold", (extended_bind.input.axis.axis_min +
|
||||
(extended_bind.input.axis.axis_max -
|
||||
extended_bind.input.axis.axis_min) /
|
||||
2.0f) /
|
||||
SDL_JOYSTICK_AXIS_MAX);
|
||||
}
|
||||
params.Set(
|
||||
"threshold",
|
||||
(extended_bind.input.axis.axis_min +
|
||||
(extended_bind.input.axis.axis_max - extended_bind.input.axis.axis_min) / 2.0f) /
|
||||
SDL_JOYSTICK_AXIS_MAX);
|
||||
}
|
||||
#else
|
||||
params.Set("direction", "+"); // lacks extended_bind, so just a guess
|
||||
|
@ -9,6 +9,7 @@ add_executable(tests
|
||||
core/memory/vm_manager.cpp
|
||||
precompiled_headers.h
|
||||
audio_core/hle/hle.cpp
|
||||
audio_core/hle/source.cpp
|
||||
audio_core/lle/lle.cpp
|
||||
audio_core/audio_fixures.h
|
||||
audio_core/decoder_tests.cpp
|
||||
|
379
src/tests/audio_core/hle/source.cpp
Normal file
379
src/tests/audio_core/hle/source.cpp
Normal file
@ -0,0 +1,379 @@
|
||||
#include <cstdio>
|
||||
#include <catch2/catch_template_test_macros.hpp>
|
||||
#include "audio_core/hle/shared_memory.h"
|
||||
#include "common/settings.h"
|
||||
#include "tests/audio_core/merryhime_3ds_audio/merry_audio/merry_audio.h"
|
||||
|
||||
TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 1",
|
||||
"[audio_core][hle]") {
|
||||
// World's worst triangle wave generator.
|
||||
// Generates PCM16.
|
||||
auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) {
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
u32 data = (i % freq) * 256;
|
||||
audio_buffer[i] = (data << 16) | (data & 0xFFFF);
|
||||
}
|
||||
|
||||
DSP_FlushDataCache(audio_buffer, size);
|
||||
};
|
||||
|
||||
constexpr size_t NUM_SAMPLES = 160 * 1;
|
||||
u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
|
||||
fillBuffer(audio_buffer, NUM_SAMPLES, 160);
|
||||
u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
|
||||
fillBuffer(audio_buffer2, NUM_SAMPLES, 80);
|
||||
u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
|
||||
fillBuffer(audio_buffer3, NUM_SAMPLES, 40);
|
||||
|
||||
MerryAudio::AudioState state;
|
||||
{
|
||||
std::vector<u8> dspfirm;
|
||||
SECTION("HLE") {
|
||||
// The test case assumes HLE AudioCore doesn't require a valid firmware
|
||||
InitDspCore(Settings::AudioEmulation::HLE);
|
||||
dspfirm = {0};
|
||||
}
|
||||
SECTION("LLE Sanity") {
|
||||
InitDspCore(Settings::AudioEmulation::LLE);
|
||||
dspfirm = loadDspFirmFromFile();
|
||||
}
|
||||
if (!dspfirm.size()) {
|
||||
SKIP("Couldn't load firmware\n");
|
||||
return;
|
||||
}
|
||||
auto ret = audioInit(dspfirm);
|
||||
if (!ret) {
|
||||
INFO("Couldn't init audio\n");
|
||||
goto end;
|
||||
}
|
||||
state = *ret;
|
||||
}
|
||||
|
||||
state.waitForSync();
|
||||
initSharedMem(state);
|
||||
state.notifyDsp();
|
||||
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
|
||||
{
|
||||
u16 buffer_id = 0;
|
||||
size_t next_queue_position = 0;
|
||||
|
||||
state.write().source_configurations->config[0].play_position = 0;
|
||||
state.write().source_configurations->config[0].physical_address =
|
||||
osConvertVirtToPhys(audio_buffer3);
|
||||
state.write().source_configurations->config[0].length = NUM_SAMPLES;
|
||||
state.write().source_configurations->config[0].mono_or_stereo.Assign(
|
||||
AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo);
|
||||
state.write().source_configurations->config[0].format.Assign(
|
||||
AudioCore::HLE::SourceConfiguration::Configuration::Format::PCM16);
|
||||
state.write().source_configurations->config[0].fade_in.Assign(false);
|
||||
state.write().source_configurations->config[0].adpcm_dirty.Assign(false);
|
||||
state.write().source_configurations->config[0].is_looping.Assign(false);
|
||||
state.write().source_configurations->config[0].buffer_id = ++buffer_id;
|
||||
state.write().source_configurations->config[0].partial_reset_flag.Assign(true);
|
||||
state.write().source_configurations->config[0].play_position_dirty.Assign(true);
|
||||
state.write().source_configurations->config[0].embedded_buffer_dirty.Assign(true);
|
||||
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].length =
|
||||
NUM_SAMPLES;
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty =
|
||||
false;
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].is_looping =
|
||||
false;
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id =
|
||||
++buffer_id;
|
||||
state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position;
|
||||
next_queue_position = (next_queue_position + 1) % 4;
|
||||
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
|
||||
state.write().source_configurations->config[0].enable = true;
|
||||
state.write().source_configurations->config[0].enable_dirty.Assign(true);
|
||||
|
||||
state.notifyDsp();
|
||||
|
||||
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
|
||||
state.waitForSync();
|
||||
if (!state.read().source_statuses->status[0].is_enabled) {
|
||||
state.write().source_configurations->config[0].enable = true;
|
||||
state.write().source_configurations->config[0].enable_dirty.Assign(true);
|
||||
}
|
||||
|
||||
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
|
||||
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
|
||||
state.read().source_statuses->status[0].current_buffer_id == 0) {
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.physical_address =
|
||||
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.length = NUM_SAMPLES;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.adpcm_dirty = false;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.is_looping = false;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.buffer_id = ++buffer_id;
|
||||
state.write().source_configurations->config[0].buffers_dirty |=
|
||||
1 << next_queue_position;
|
||||
next_queue_position = (next_queue_position + 1) % 4;
|
||||
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
|
||||
}
|
||||
}
|
||||
|
||||
state.notifyDsp();
|
||||
}
|
||||
|
||||
// current_buffer_id should be 0 if the queue is not empty
|
||||
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
|
||||
|
||||
// Let the queue finish playing
|
||||
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
}
|
||||
|
||||
// TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue,
|
||||
// that differs from the HLE implementation
|
||||
// REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5);
|
||||
|
||||
// current_buffer_id should be equal to buffer_id once the queue is empty
|
||||
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
|
||||
}
|
||||
|
||||
end:
|
||||
audioExit(state);
|
||||
}
|
||||
|
||||
TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 2",
|
||||
"[audio_core][hle]") {
|
||||
// World's worst triangle wave generator.
|
||||
// Generates PCM16.
|
||||
auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) {
|
||||
for (size_t i = 0; i < size; i++) {
|
||||
u32 data = (i % freq) * 256;
|
||||
audio_buffer[i] = (data << 16) | (data & 0xFFFF);
|
||||
}
|
||||
|
||||
DSP_FlushDataCache(audio_buffer, size);
|
||||
};
|
||||
|
||||
constexpr size_t NUM_SAMPLES = 160 * 1;
|
||||
u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
|
||||
fillBuffer(audio_buffer, NUM_SAMPLES, 160);
|
||||
u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
|
||||
fillBuffer(audio_buffer2, NUM_SAMPLES, 80);
|
||||
u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
|
||||
fillBuffer(audio_buffer3, NUM_SAMPLES, 40);
|
||||
|
||||
MerryAudio::AudioState state;
|
||||
{
|
||||
std::vector<u8> dspfirm;
|
||||
SECTION("HLE") {
|
||||
// The test case assumes HLE AudioCore doesn't require a valid firmware
|
||||
InitDspCore(Settings::AudioEmulation::HLE);
|
||||
dspfirm = {0};
|
||||
}
|
||||
SECTION("LLE Sanity") {
|
||||
InitDspCore(Settings::AudioEmulation::LLE);
|
||||
dspfirm = loadDspFirmFromFile();
|
||||
}
|
||||
if (!dspfirm.size()) {
|
||||
SKIP("Couldn't load firmware\n");
|
||||
return;
|
||||
}
|
||||
auto ret = audioInit(dspfirm);
|
||||
if (!ret) {
|
||||
INFO("Couldn't init audio\n");
|
||||
goto end;
|
||||
}
|
||||
state = *ret;
|
||||
}
|
||||
|
||||
state.waitForSync();
|
||||
initSharedMem(state);
|
||||
state.notifyDsp();
|
||||
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
|
||||
{
|
||||
u16 buffer_id = 0;
|
||||
size_t next_queue_position = 0;
|
||||
|
||||
state.write().source_configurations->config[0].play_position = 0;
|
||||
state.write().source_configurations->config[0].physical_address =
|
||||
osConvertVirtToPhys(audio_buffer3);
|
||||
state.write().source_configurations->config[0].length = NUM_SAMPLES;
|
||||
state.write().source_configurations->config[0].mono_or_stereo.Assign(
|
||||
AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo);
|
||||
state.write().source_configurations->config[0].format.Assign(
|
||||
AudioCore::HLE::SourceConfiguration::Configuration::Format::PCM16);
|
||||
state.write().source_configurations->config[0].fade_in.Assign(false);
|
||||
state.write().source_configurations->config[0].adpcm_dirty.Assign(false);
|
||||
state.write().source_configurations->config[0].is_looping.Assign(false);
|
||||
state.write().source_configurations->config[0].buffer_id = ++buffer_id;
|
||||
state.write().source_configurations->config[0].partial_reset_flag.Assign(true);
|
||||
state.write().source_configurations->config[0].play_position_dirty.Assign(true);
|
||||
state.write().source_configurations->config[0].embedded_buffer_dirty.Assign(true);
|
||||
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].length =
|
||||
NUM_SAMPLES;
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty =
|
||||
false;
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].is_looping =
|
||||
false;
|
||||
state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id =
|
||||
++buffer_id;
|
||||
state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position;
|
||||
next_queue_position = (next_queue_position + 1) % 4;
|
||||
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
|
||||
state.write().source_configurations->config[0].enable = true;
|
||||
state.write().source_configurations->config[0].enable_dirty.Assign(true);
|
||||
|
||||
state.notifyDsp();
|
||||
|
||||
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
|
||||
state.waitForSync();
|
||||
if (!state.read().source_statuses->status[0].is_enabled) {
|
||||
state.write().source_configurations->config[0].enable = true;
|
||||
state.write().source_configurations->config[0].enable_dirty.Assign(true);
|
||||
}
|
||||
|
||||
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
|
||||
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
|
||||
state.read().source_statuses->status[0].current_buffer_id == 0) {
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.physical_address =
|
||||
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.length = NUM_SAMPLES;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.adpcm_dirty = false;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.is_looping = false;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.buffer_id = ++buffer_id;
|
||||
state.write().source_configurations->config[0].buffers_dirty |=
|
||||
1 << next_queue_position;
|
||||
next_queue_position = (next_queue_position + 1) % 4;
|
||||
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
|
||||
}
|
||||
}
|
||||
|
||||
state.notifyDsp();
|
||||
}
|
||||
|
||||
// current_buffer_id should be 0 if the queue is not empty
|
||||
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
|
||||
|
||||
// Let the queue finish playing
|
||||
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
}
|
||||
|
||||
// TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue,
|
||||
// that differs from the HLE implementation
|
||||
// REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5);
|
||||
|
||||
// current_buffer_id should be equal to buffer_id once the queue is empty
|
||||
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
|
||||
|
||||
// Restart Playing
|
||||
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
|
||||
state.waitForSync();
|
||||
if (!state.read().source_statuses->status[0].is_enabled) {
|
||||
state.write().source_configurations->config[0].enable = true;
|
||||
state.write().source_configurations->config[0].enable_dirty.Assign(true);
|
||||
}
|
||||
|
||||
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
|
||||
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
|
||||
state.read().source_statuses->status[0].current_buffer_id == 0) {
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.physical_address =
|
||||
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.length = NUM_SAMPLES;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.adpcm_dirty = false;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.is_looping = false;
|
||||
state.write()
|
||||
.source_configurations->config[0]
|
||||
.buffers[next_queue_position]
|
||||
.buffer_id = ++buffer_id;
|
||||
state.write().source_configurations->config[0].buffers_dirty |=
|
||||
1 << next_queue_position;
|
||||
next_queue_position = (next_queue_position + 1) % 4;
|
||||
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
|
||||
}
|
||||
}
|
||||
|
||||
state.notifyDsp();
|
||||
}
|
||||
|
||||
// current_buffer_id should be 0 if the queue is not empty
|
||||
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
|
||||
|
||||
// Let the queue finish playing
|
||||
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
|
||||
state.waitForSync();
|
||||
state.notifyDsp();
|
||||
}
|
||||
|
||||
// current_buffer_id should be equal to buffer_id once the queue is empty
|
||||
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
|
||||
}
|
||||
|
||||
end:
|
||||
audioExit(state);
|
||||
}
|
@ -26,17 +26,6 @@ set(SHADER_FILES
|
||||
vulkan_blit_depth_stencil.frag
|
||||
)
|
||||
|
||||
find_program(GLSLANG "glslang")
|
||||
if ("${GLSLANG}" STREQUAL "GLSLANG-NOTFOUND")
|
||||
find_program(GLSLANG "glslangValidator")
|
||||
if ("${GLSLANG}" STREQUAL "GLSLANG-NOTFOUND")
|
||||
message(FATAL_ERROR "Required program `glslang` (or `glslangValidator`) not found.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
set(MACROS "-Dgl_VertexID=gl_VertexIndex")
|
||||
set(QUIET_FLAG "--quiet")
|
||||
|
||||
set(SHADER_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
|
||||
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
|
||||
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
||||
@ -44,57 +33,22 @@ set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
|
||||
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
|
||||
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake)
|
||||
|
||||
# Check if `--quiet` is available on host's glslang version
|
||||
# glslang prints to STDERR iff an unrecognized flag is passed to it
|
||||
execute_process(
|
||||
COMMAND
|
||||
${GLSLANG} ${QUIET_FLAG}
|
||||
ERROR_VARIABLE
|
||||
GLSLANG_ERROR
|
||||
# STDOUT variable defined to silence unnecessary output during CMake configuration
|
||||
OUTPUT_VARIABLE
|
||||
GLSLANG_OUTPUT
|
||||
)
|
||||
|
||||
if (NOT GLSLANG_ERROR STREQUAL "")
|
||||
message(WARNING "Refusing to use unavailable flag `${QUIET_FLAG}` on `${GLSLANG}`")
|
||||
set(QUIET_FLAG "")
|
||||
endif()
|
||||
|
||||
foreach(FILENAME IN ITEMS ${SHADER_FILES})
|
||||
string(REPLACE "." "_" SHADER_NAME ${FILENAME})
|
||||
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
|
||||
# Skip generating source headers on Vulkan exclusive files
|
||||
if (NOT ${FILENAME} MATCHES "vulkan.*")
|
||||
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${SOURCE_HEADER_FILE}
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE}
|
||||
MAIN_DEPENDENCY
|
||||
${SOURCE_FILE}
|
||||
DEPENDS
|
||||
${INPUT_FILE}
|
||||
# HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified
|
||||
)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
|
||||
endif()
|
||||
# Skip compiling to SPIR-V OpenGL exclusive files
|
||||
if (NOT ${FILENAME} MATCHES "opengl.*")
|
||||
get_filename_component(FILE_NAME ${SHADER_NAME} NAME)
|
||||
string(TOUPPER ${FILE_NAME}_SPV SPIRV_VARIABLE_NAME)
|
||||
set(SPIRV_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}_spv.h)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${SPIRV_HEADER_FILE}
|
||||
COMMAND
|
||||
${GLSLANG} --target-env vulkan1.1 --glsl-version 450 ${QUIET_FLAG} ${MACROS} --variable-name ${SPIRV_VARIABLE_NAME} -o ${SPIRV_HEADER_FILE} ${SOURCE_FILE}
|
||||
MAIN_DEPENDENCY
|
||||
${SOURCE_FILE}
|
||||
)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${SPIRV_HEADER_FILE})
|
||||
endif()
|
||||
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
${SOURCE_HEADER_FILE}
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE}
|
||||
MAIN_DEPENDENCY
|
||||
${SOURCE_FILE}
|
||||
DEPENDS
|
||||
${INPUT_FILE}
|
||||
# HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified
|
||||
)
|
||||
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_FILE})
|
||||
endforeach()
|
||||
|
||||
set(SHADER_SOURCES ${SHADER_FILES})
|
||||
|
@ -8,6 +8,10 @@ layout(location = 0) out vec2 dst_coord;
|
||||
|
||||
layout(location = 0) uniform mediump ivec2 dst_size;
|
||||
|
||||
#ifdef VULKAN
|
||||
#define gl_VertexID gl_VertexIndex
|
||||
#endif
|
||||
|
||||
const vec2 vertices[4] =
|
||||
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
||||
|
||||
|
@ -10,6 +10,7 @@ out gl_PerVertex {
|
||||
layout(location = 0) out vec2 texcoord;
|
||||
|
||||
#ifdef VULKAN
|
||||
#define gl_VertexID gl_VertexIndex
|
||||
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
|
||||
#define END_PUSH_CONSTANTS };
|
||||
#define UNIFORM(n)
|
||||
|
@ -5,6 +5,10 @@
|
||||
//? #version 430 core
|
||||
layout(location = 0) out vec2 tex_coord;
|
||||
|
||||
#ifdef VULKAN
|
||||
#define gl_VertexID gl_VertexIndex
|
||||
#endif
|
||||
|
||||
const vec2 vertices[4] =
|
||||
vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));
|
||||
|
||||
|
@ -846,7 +846,7 @@ void RasterizerAccelerated::SyncTextureBorderColor(int tex_index) {
|
||||
}
|
||||
|
||||
void RasterizerAccelerated::SyncClipPlane() {
|
||||
const bool enable_clip1 = regs.rasterizer.clip_enable != 0;
|
||||
const u32 enable_clip1 = regs.rasterizer.clip_enable != 0;
|
||||
const auto raw_clip_coef = regs.rasterizer.GetClipCoef();
|
||||
const Common::Vec4f new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(),
|
||||
raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()};
|
||||
|
@ -148,8 +148,6 @@ inline GLenum BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
|
||||
// Range check table for input
|
||||
if (index >= blend_func_table.size()) {
|
||||
LOG_CRITICAL(Render_OpenGL, "Unknown blend factor {}", index);
|
||||
UNREACHABLE();
|
||||
|
||||
return GL_ONE;
|
||||
}
|
||||
|
||||
|
@ -695,7 +695,7 @@ Common::Vec4<u8> RasterizerSoftware::WriteTevConfig(
|
||||
* with some basic arithmetic. Alpha combiners can be configured separately but work
|
||||
* analogously.
|
||||
**/
|
||||
Common::Vec4<u8> combiner_output = primary_color;
|
||||
Common::Vec4<u8> combiner_output = {0, 0, 0, 0};
|
||||
Common::Vec4<u8> combiner_buffer = {0, 0, 0, 0};
|
||||
Common::Vec4<u8> next_combiner_buffer =
|
||||
Common::MakeVec(regs.texturing.tev_combiner_buffer_color.r.Value(),
|
||||
@ -746,9 +746,15 @@ Common::Vec4<u8> RasterizerSoftware::WriteTevConfig(
|
||||
* combiner_output.rgb(), but instead store it in a temporary variable until
|
||||
* alpha combining has been done.
|
||||
**/
|
||||
const auto source1 = tev_stage_index == 0 && tev_stage.color_source1 == Source::Previous
|
||||
? tev_stage.color_source3.Value()
|
||||
: tev_stage.color_source1.Value();
|
||||
const auto source2 = tev_stage_index == 0 && tev_stage.color_source2 == Source::Previous
|
||||
? tev_stage.color_source3.Value()
|
||||
: tev_stage.color_source2.Value();
|
||||
const std::array<Common::Vec3<u8>, 3> color_result = {
|
||||
GetColorModifier(tev_stage.color_modifier1, get_source(tev_stage.color_source1)),
|
||||
GetColorModifier(tev_stage.color_modifier2, get_source(tev_stage.color_source2)),
|
||||
GetColorModifier(tev_stage.color_modifier1, get_source(source1)),
|
||||
GetColorModifier(tev_stage.color_modifier2, get_source(source2)),
|
||||
GetColorModifier(tev_stage.color_modifier3, get_source(tev_stage.color_source3)),
|
||||
};
|
||||
const Common::Vec3<u8> color_output = ColorCombine(tev_stage.color_op, color_result);
|
||||
|
@ -96,7 +96,10 @@ inline vk::BlendFactor BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
|
||||
}};
|
||||
|
||||
const auto index = static_cast<std::size_t>(factor);
|
||||
ASSERT_MSG(index < blend_func_table.size(), "Unknown blend factor {}", index);
|
||||
if (index >= blend_func_table.size()) {
|
||||
LOG_CRITICAL(Render_Vulkan, "Unknown blend factor {}", index);
|
||||
return vk::BlendFactor::eOne;
|
||||
}
|
||||
return blend_func_table[index];
|
||||
}
|
||||
|
||||
|
@ -15,10 +15,10 @@
|
||||
#include "video_core/renderer_vulkan/vk_memory_util.h"
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
|
||||
#include "video_core/host_shaders/vulkan_present_anaglyph_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_vert_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_present_anaglyph_frag.h"
|
||||
#include "video_core/host_shaders/vulkan_present_frag.h"
|
||||
#include "video_core/host_shaders/vulkan_present_interlaced_frag.h"
|
||||
#include "video_core/host_shaders/vulkan_present_vert.h"
|
||||
|
||||
#include <vk_mem_alloc.h>
|
||||
|
||||
@ -225,10 +225,14 @@ void RendererVulkan::LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuff
|
||||
|
||||
void RendererVulkan::CompileShaders() {
|
||||
vk::Device device = instance.GetDevice();
|
||||
present_vertex_shader = CompileSPV(VULKAN_PRESENT_VERT_SPV, device);
|
||||
present_shaders[0] = CompileSPV(VULKAN_PRESENT_FRAG_SPV, device);
|
||||
present_shaders[1] = CompileSPV(VULKAN_PRESENT_ANAGLYPH_FRAG_SPV, device);
|
||||
present_shaders[2] = CompileSPV(VULKAN_PRESENT_INTERLACED_FRAG_SPV, device);
|
||||
present_vertex_shader =
|
||||
Compile(HostShaders::VULKAN_PRESENT_VERT, vk::ShaderStageFlagBits::eVertex, device);
|
||||
present_shaders[0] =
|
||||
Compile(HostShaders::VULKAN_PRESENT_FRAG, vk::ShaderStageFlagBits::eFragment, device);
|
||||
present_shaders[1] = Compile(HostShaders::VULKAN_PRESENT_ANAGLYPH_FRAG,
|
||||
vk::ShaderStageFlagBits::eFragment, device);
|
||||
present_shaders[2] = Compile(HostShaders::VULKAN_PRESENT_INTERLACED_FRAG,
|
||||
vk::ShaderStageFlagBits::eFragment, device);
|
||||
|
||||
auto properties = instance.GetPhysicalDevice().getProperties();
|
||||
for (std::size_t i = 0; i < present_samplers.size(); i++) {
|
||||
|
@ -10,10 +10,10 @@
|
||||
#include "video_core/renderer_vulkan/vk_shader_util.h"
|
||||
#include "video_core/renderer_vulkan/vk_texture_runtime.h"
|
||||
|
||||
#include "video_core/host_shaders/format_reinterpreter/vulkan_d24s8_to_rgba8_comp_spv.h"
|
||||
#include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
|
||||
#include "video_core/host_shaders/vulkan_depth_to_buffer_comp_spv.h"
|
||||
#include "video_core/host_shaders/format_reinterpreter/vulkan_d24s8_to_rgba8_comp.h"
|
||||
#include "video_core/host_shaders/full_screen_triangle_vert.h"
|
||||
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag.h"
|
||||
#include "video_core/host_shaders/vulkan_depth_to_buffer_comp.h"
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
@ -189,10 +189,14 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, Descrip
|
||||
PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))},
|
||||
two_textures_pipeline_layout{
|
||||
device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))},
|
||||
full_screen_vert{CompileSPV(FULL_SCREEN_TRIANGLE_VERT_SPV, device)},
|
||||
d24s8_to_rgba8_comp{CompileSPV(VULKAN_D24S8_TO_RGBA8_COMP_SPV, device)},
|
||||
depth_to_buffer_comp{CompileSPV(VULKAN_DEPTH_TO_BUFFER_COMP_SPV, device)},
|
||||
blit_depth_stencil_frag{CompileSPV(VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV, device)},
|
||||
full_screen_vert{Compile(HostShaders::FULL_SCREEN_TRIANGLE_VERT,
|
||||
vk::ShaderStageFlagBits::eVertex, device)},
|
||||
d24s8_to_rgba8_comp{Compile(HostShaders::VULKAN_D24S8_TO_RGBA8_COMP,
|
||||
vk::ShaderStageFlagBits::eCompute, device)},
|
||||
depth_to_buffer_comp{Compile(HostShaders::VULKAN_DEPTH_TO_BUFFER_COMP,
|
||||
vk::ShaderStageFlagBits::eCompute, device)},
|
||||
blit_depth_stencil_frag{Compile(HostShaders::VULKAN_BLIT_DEPTH_STENCIL_FRAG,
|
||||
vk::ShaderStageFlagBits::eFragment, device)},
|
||||
d24s8_to_rgba8_pipeline{MakeComputePipeline(d24s8_to_rgba8_comp, compute_pipeline_layout)},
|
||||
depth_to_buffer_pipeline{
|
||||
MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)},
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
#include <span>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/settings.h"
|
||||
@ -153,6 +154,12 @@ Instance::Instance(Core::TelemetrySession& telemetry, Frontend::EmuWindow& windo
|
||||
physical_device = physical_devices[physical_device_index];
|
||||
available_extensions = GetSupportedExtensions(physical_device);
|
||||
properties = physical_device.getProperties();
|
||||
if (properties.apiVersion < TargetVulkanApiVersion) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Vulkan {}.{} is required, but only {}.{} is supported by device!",
|
||||
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion),
|
||||
VK_VERSION_MAJOR(properties.apiVersion), VK_VERSION_MINOR(properties.apiVersion)));
|
||||
}
|
||||
|
||||
CollectTelemetryParameters(telemetry);
|
||||
CreateDevice();
|
||||
@ -629,7 +636,7 @@ void Instance::CreateAllocator() {
|
||||
.device = *device,
|
||||
.pVulkanFunctions = &functions,
|
||||
.instance = *instance,
|
||||
.vulkanApiVersion = properties.apiVersion,
|
||||
.vulkanApiVersion = TargetVulkanApiVersion,
|
||||
};
|
||||
|
||||
const VkResult result = vmaCreateAllocator(&allocator_info, &allocator);
|
||||
@ -670,7 +677,7 @@ void Instance::CollectToolingInfo() {
|
||||
if (!tooling_info) {
|
||||
return;
|
||||
}
|
||||
const auto tools = physical_device.getToolProperties();
|
||||
const auto tools = physical_device.getToolPropertiesEXT();
|
||||
for (const vk::PhysicalDeviceToolProperties& tool : tools) {
|
||||
const std::string_view name = tool.name;
|
||||
LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name);
|
||||
|
@ -17,6 +17,7 @@
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/logging/log.h"
|
||||
@ -290,13 +291,14 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library,
|
||||
}
|
||||
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
|
||||
|
||||
if (!VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion) {
|
||||
throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!");
|
||||
}
|
||||
|
||||
const u32 available_version = vk::enumerateInstanceVersion();
|
||||
if (available_version < VK_API_VERSION_1_1) {
|
||||
throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!");
|
||||
const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion
|
||||
? vk::enumerateInstanceVersion()
|
||||
: VK_API_VERSION_1_0;
|
||||
if (available_version < TargetVulkanApiVersion) {
|
||||
throw std::runtime_error(fmt::format(
|
||||
"Vulkan {}.{} is required, but only {}.{} is supported by instance!",
|
||||
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion),
|
||||
VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version)));
|
||||
}
|
||||
|
||||
const auto extensions = GetInstanceExtensions(window_type, enable_validation);
|
||||
@ -306,7 +308,7 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library,
|
||||
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
|
||||
.pEngineName = "Citra Vulkan",
|
||||
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
|
||||
.apiVersion = VK_API_VERSION_1_3,
|
||||
.apiVersion = TargetVulkanApiVersion,
|
||||
};
|
||||
|
||||
boost::container::static_vector<const char*, 2> layers;
|
||||
|
@ -19,6 +19,8 @@ enum class WindowSystemType : u8;
|
||||
|
||||
namespace Vulkan {
|
||||
|
||||
constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_1;
|
||||
|
||||
using DebugCallback =
|
||||
std::variant<vk::UniqueDebugUtilsMessengerEXT, vk::UniqueDebugReportCallbackEXT>;
|
||||
|
||||
|
@ -3,6 +3,7 @@
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <boost/container/small_vector.hpp>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#include "common/literals.h"
|
||||
#include "common/microprofile.h"
|
||||
@ -119,9 +120,9 @@ u32 UnpackDepthStencil(const VideoCore::StagingData& data, vk::Format dest) {
|
||||
}
|
||||
|
||||
boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
|
||||
vk::ImageAspectFlags aspect, std::span<const vk::Image> images, std::size_t num_images) {
|
||||
vk::ImageAspectFlags aspect, std::span<const vk::Image> images) {
|
||||
boost::container::small_vector<vk::ImageMemoryBarrier, 3> barriers;
|
||||
for (std::size_t i = 0; i < num_images; i++) {
|
||||
for (const vk::Image& image : images) {
|
||||
barriers.push_back(vk::ImageMemoryBarrier{
|
||||
.srcAccessMask = vk::AccessFlagBits::eNone,
|
||||
.dstAccessMask = vk::AccessFlagBits::eNone,
|
||||
@ -129,7 +130,7 @@ boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
|
||||
.newLayout = vk::ImageLayout::eGeneral,
|
||||
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
|
||||
.image = images[i],
|
||||
.image = image,
|
||||
.subresourceRange{
|
||||
.aspectMask = aspect,
|
||||
.baseMipLevel = 0,
|
||||
@ -219,11 +220,10 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T
|
||||
}
|
||||
|
||||
vk::UniqueFramebuffer MakeFramebuffer(vk::Device device, vk::RenderPass render_pass, u32 width,
|
||||
u32 height, std::span<const vk::ImageView> attachments,
|
||||
u32 num_attachments) {
|
||||
u32 height, std::span<const vk::ImageView> attachments) {
|
||||
const vk::FramebufferCreateInfo framebuffer_info = {
|
||||
.renderPass = render_pass,
|
||||
.attachmentCount = num_attachments,
|
||||
.attachmentCount = static_cast<u32>(attachments.size()),
|
||||
.pAttachments = attachments.data(),
|
||||
.width = width,
|
||||
.height = height,
|
||||
@ -715,8 +715,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
|
||||
ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1,
|
||||
"Image allocation parameters are invalid");
|
||||
|
||||
u32 num_images = 0;
|
||||
std::array<vk::Image, 3> raw_images;
|
||||
boost::container::static_vector<vk::Image, 3> raw_images;
|
||||
|
||||
vk::ImageCreateFlags flags{};
|
||||
if (texture_type == VideoCore::TextureType::CubeMap) {
|
||||
@ -729,18 +728,18 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
|
||||
const bool need_format_list = is_mutable && instance->IsImageFormatListSupported();
|
||||
handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage,
|
||||
flags, traits.aspect, need_format_list, DebugName(false));
|
||||
raw_images[num_images++] = handles[0].image;
|
||||
raw_images.emplace_back(handles[0].image);
|
||||
|
||||
if (res_scale != 1) {
|
||||
handles[1] =
|
||||
MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format,
|
||||
traits.usage, flags, traits.aspect, need_format_list, DebugName(true));
|
||||
raw_images[num_images++] = handles[1].image;
|
||||
raw_images.emplace_back(handles[1].image);
|
||||
}
|
||||
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images, num_images);
|
||||
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images);
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
@ -758,8 +757,7 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
|
||||
const bool has_normal = mat && mat->Map(MapType::Normal);
|
||||
const vk::Format format = traits.native;
|
||||
|
||||
u32 num_images = 0;
|
||||
std::array<vk::Image, 2> raw_images;
|
||||
boost::container::static_vector<vk::Image, 2> raw_images;
|
||||
|
||||
vk::ImageCreateFlags flags{};
|
||||
if (texture_type == VideoCore::TextureType::CubeMap) {
|
||||
@ -769,23 +767,23 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
|
||||
const std::string debug_name = DebugName(false, true);
|
||||
handles[0] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
|
||||
traits.usage, flags, traits.aspect, false, debug_name);
|
||||
raw_images[num_images++] = handles[0].image;
|
||||
raw_images.emplace_back(handles[0].image);
|
||||
|
||||
if (res_scale != 1) {
|
||||
handles[1] = MakeHandle(instance, mat->width, mat->height, levels, texture_type,
|
||||
vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect,
|
||||
false, debug_name);
|
||||
raw_images[num_images++] = handles[1].image;
|
||||
raw_images.emplace_back(handles[1].image);
|
||||
}
|
||||
if (has_normal) {
|
||||
handles[2] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
|
||||
traits.usage, flags, traits.aspect, false, debug_name);
|
||||
raw_images[num_images++] = handles[2].image;
|
||||
raw_images.emplace_back(handles[2].image);
|
||||
}
|
||||
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images, num_images);
|
||||
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images);
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
@ -825,11 +823,10 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||
|
||||
scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params,
|
||||
staging, upload](vk::CommandBuffer cmdbuf) {
|
||||
u32 num_copies = 1;
|
||||
std::array<vk::BufferImageCopy, 2> buffer_image_copies;
|
||||
boost::container::static_vector<vk::BufferImageCopy, 2> buffer_image_copies;
|
||||
|
||||
const auto rect = upload.texture_rect;
|
||||
buffer_image_copies[0] = vk::BufferImageCopy{
|
||||
buffer_image_copies.emplace_back(vk::BufferImageCopy{
|
||||
.bufferOffset = upload.buffer_offset,
|
||||
.bufferRowLength = rect.GetWidth(),
|
||||
.bufferImageHeight = rect.GetHeight(),
|
||||
@ -841,15 +838,16 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||
},
|
||||
.imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0},
|
||||
.imageExtent = {rect.GetWidth(), rect.GetHeight(), 1},
|
||||
};
|
||||
});
|
||||
|
||||
if (params.aspect & vk::ImageAspectFlagBits::eStencil) {
|
||||
buffer_image_copies[0].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth;
|
||||
vk::BufferImageCopy& stencil_copy = buffer_image_copies[1];
|
||||
|
||||
vk::BufferImageCopy& stencil_copy =
|
||||
buffer_image_copies.emplace_back(buffer_image_copies[0]);
|
||||
stencil_copy = buffer_image_copies[0];
|
||||
stencil_copy.bufferOffset += UnpackDepthStencil(staging, format);
|
||||
stencil_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil;
|
||||
num_copies++;
|
||||
}
|
||||
|
||||
const vk::ImageMemoryBarrier read_barrier = {
|
||||
@ -877,7 +875,7 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
|
||||
|
||||
cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal,
|
||||
num_copies, buffer_image_copies.data());
|
||||
buffer_image_copies);
|
||||
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier);
|
||||
@ -1085,7 +1083,7 @@ void Surface::ScaleUp(u32 new_scale) {
|
||||
runtime->renderpass_cache.EndRendering();
|
||||
scheduler->Record(
|
||||
[raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images, raw_images.size());
|
||||
const auto barriers = MakeInitBarriers(aspect, raw_images);
|
||||
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::PipelineStageFlagBits::eTopOfPipe,
|
||||
vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
|
||||
@ -1351,7 +1349,7 @@ vk::Framebuffer Surface::Framebuffer() noexcept {
|
||||
runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false);
|
||||
const auto attachments = std::array{ImageView()};
|
||||
framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
|
||||
GetScaledHeight(), attachments, 1);
|
||||
GetScaledHeight(), attachments);
|
||||
return framebuffers[index].get();
|
||||
}
|
||||
|
||||
@ -1481,17 +1479,16 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
|
||||
image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
|
||||
};
|
||||
|
||||
u32 num_attachments = 0;
|
||||
std::array<vk::ImageView, 2> attachments;
|
||||
boost::container::static_vector<vk::ImageView, 2> attachments;
|
||||
|
||||
if (color) {
|
||||
prepare(0, color);
|
||||
attachments[num_attachments++] = image_views[0];
|
||||
attachments.emplace_back(image_views[0]);
|
||||
}
|
||||
|
||||
if (depth) {
|
||||
prepare(1, depth);
|
||||
attachments[num_attachments++] = image_views[1];
|
||||
attachments.emplace_back(image_views[1]);
|
||||
}
|
||||
|
||||
const vk::Device device = runtime.GetInstance().GetDevice();
|
||||
@ -1499,11 +1496,10 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
|
||||
render_pass =
|
||||
renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
|
||||
framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
|
||||
color->GetScaledHeight(), {}, 0);
|
||||
color->GetScaledHeight(), {});
|
||||
} else {
|
||||
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
|
||||
framebuffer =
|
||||
MakeFramebuffer(device, render_pass, width, height, attachments, num_attachments);
|
||||
framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -143,7 +143,7 @@ vec4 secondary_fragment_color = vec4(0.0);
|
||||
|
||||
out += "vec4 combiner_buffer = vec4(0.0);\n"
|
||||
"vec4 next_combiner_buffer = tev_combiner_buffer_color;\n"
|
||||
"vec4 combiner_output = rounded_primary_color;\n";
|
||||
"vec4 combiner_output = vec4(0.0);\n";
|
||||
|
||||
out += "vec3 color_results_1 = vec3(0.0);\n"
|
||||
"vec3 color_results_2 = vec3(0.0);\n"
|
||||
@ -225,96 +225,75 @@ void FragmentModule::WriteScissor() {
|
||||
"gl_FragCoord.y < float(scissor_y2))) discard;\n";
|
||||
}
|
||||
|
||||
void FragmentModule::AppendSource(Pica::TexturingRegs::TevStageConfig::Source source,
|
||||
u32 tev_index) {
|
||||
std::string FragmentModule::GetSource(Pica::TexturingRegs::TevStageConfig::Source source,
|
||||
u32 tev_index) {
|
||||
using Source = Pica::TexturingRegs::TevStageConfig::Source;
|
||||
switch (source) {
|
||||
case Source::PrimaryColor:
|
||||
out += "rounded_primary_color";
|
||||
break;
|
||||
return "rounded_primary_color";
|
||||
case Source::PrimaryFragmentColor:
|
||||
out += "primary_fragment_color";
|
||||
break;
|
||||
return "primary_fragment_color";
|
||||
case Source::SecondaryFragmentColor:
|
||||
out += "secondary_fragment_color";
|
||||
break;
|
||||
return "secondary_fragment_color";
|
||||
case Source::Texture0:
|
||||
out += "sampleTexUnit0()";
|
||||
break;
|
||||
return "sampleTexUnit0()";
|
||||
case Source::Texture1:
|
||||
out += "sampleTexUnit1()";
|
||||
break;
|
||||
return "sampleTexUnit1()";
|
||||
case Source::Texture2:
|
||||
out += "sampleTexUnit2()";
|
||||
break;
|
||||
return "sampleTexUnit2()";
|
||||
case Source::Texture3:
|
||||
out += "sampleTexUnit3()";
|
||||
break;
|
||||
return "sampleTexUnit3()";
|
||||
case Source::PreviousBuffer:
|
||||
out += "combiner_buffer";
|
||||
break;
|
||||
return "combiner_buffer";
|
||||
case Source::Constant:
|
||||
out += fmt::format("const_color[{}]", tev_index);
|
||||
break;
|
||||
return fmt::format("const_color[{}]", tev_index);
|
||||
case Source::Previous:
|
||||
out += "combiner_output";
|
||||
break;
|
||||
return "combiner_output";
|
||||
default:
|
||||
out += "vec4(0.0)";
|
||||
LOG_CRITICAL(Render, "Unknown source op {}", source);
|
||||
break;
|
||||
return "vec4(0.0)";
|
||||
}
|
||||
}
|
||||
|
||||
void FragmentModule::AppendColorModifier(
|
||||
Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,
|
||||
Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index) {
|
||||
using Source = Pica::TexturingRegs::TevStageConfig::Source;
|
||||
using ColorModifier = Pica::TexturingRegs::TevStageConfig::ColorModifier;
|
||||
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[tev_index];
|
||||
const bool force_source3 = tev_index == 0 && source == Source::Previous;
|
||||
const auto color_source =
|
||||
GetSource(force_source3 ? stage.color_source3.Value() : source, tev_index);
|
||||
switch (modifier) {
|
||||
case ColorModifier::SourceColor:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".rgb";
|
||||
out += fmt::format("{}.rgb", color_source);
|
||||
break;
|
||||
case ColorModifier::OneMinusSourceColor:
|
||||
out += "vec3(1.0) - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".rgb";
|
||||
out += fmt::format("vec3(1.0) - {}.rgb", color_source);
|
||||
break;
|
||||
case ColorModifier::SourceAlpha:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".aaa";
|
||||
out += fmt::format("{}.aaa", color_source);
|
||||
break;
|
||||
case ColorModifier::OneMinusSourceAlpha:
|
||||
out += "vec3(1.0) - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".aaa";
|
||||
out += fmt::format("vec3(1.0) - {}.aaa", color_source);
|
||||
break;
|
||||
case ColorModifier::SourceRed:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".rrr";
|
||||
out += fmt::format("{}.rrr", color_source);
|
||||
break;
|
||||
case ColorModifier::OneMinusSourceRed:
|
||||
out += "vec3(1.0) - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".rrr";
|
||||
out += fmt::format("vec3(1.0) - {}.rrr", color_source);
|
||||
break;
|
||||
case ColorModifier::SourceGreen:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".ggg";
|
||||
out += fmt::format("{}.ggg", color_source);
|
||||
break;
|
||||
case ColorModifier::OneMinusSourceGreen:
|
||||
out += "vec3(1.0) - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".ggg";
|
||||
out += fmt::format("vec3(1.0) - {}.ggg", color_source);
|
||||
break;
|
||||
case ColorModifier::SourceBlue:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".bbb";
|
||||
out += fmt::format("{}.bbb", color_source);
|
||||
break;
|
||||
case ColorModifier::OneMinusSourceBlue:
|
||||
out += "vec3(1.0) - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".bbb";
|
||||
out += fmt::format("vec3(1.0) - {}.bbb", color_source);
|
||||
break;
|
||||
default:
|
||||
out += "vec3(0.0)";
|
||||
@ -326,43 +305,36 @@ void FragmentModule::AppendColorModifier(
|
||||
void FragmentModule::AppendAlphaModifier(
|
||||
Pica::TexturingRegs::TevStageConfig::AlphaModifier modifier,
|
||||
Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index) {
|
||||
using Source = Pica::TexturingRegs::TevStageConfig::Source;
|
||||
using AlphaModifier = Pica::TexturingRegs::TevStageConfig::AlphaModifier;
|
||||
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[tev_index];
|
||||
const bool force_source3 = tev_index == 0 && source == Source::Previous;
|
||||
const auto alpha_source =
|
||||
GetSource(force_source3 ? stage.alpha_source3.Value() : source, tev_index);
|
||||
switch (modifier) {
|
||||
case AlphaModifier::SourceAlpha:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".a";
|
||||
out += fmt::format("{}.a", alpha_source);
|
||||
break;
|
||||
case AlphaModifier::OneMinusSourceAlpha:
|
||||
out += "1.0 - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".a";
|
||||
out += fmt::format("1.0 - {}.a", alpha_source);
|
||||
break;
|
||||
case AlphaModifier::SourceRed:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".r";
|
||||
out += fmt::format("{}.r", alpha_source);
|
||||
break;
|
||||
case AlphaModifier::OneMinusSourceRed:
|
||||
out += "1.0 - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".r";
|
||||
out += fmt::format("1.0 - {}.r", alpha_source);
|
||||
break;
|
||||
case AlphaModifier::SourceGreen:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".g";
|
||||
out += fmt::format("{}.g", alpha_source);
|
||||
break;
|
||||
case AlphaModifier::OneMinusSourceGreen:
|
||||
out += "1.0 - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".g";
|
||||
out += fmt::format("1.0 - {}.g", alpha_source);
|
||||
break;
|
||||
case AlphaModifier::SourceBlue:
|
||||
AppendSource(source, tev_index);
|
||||
out += ".b";
|
||||
out += fmt::format("{}.b", alpha_source);
|
||||
break;
|
||||
case AlphaModifier::OneMinusSourceBlue:
|
||||
out += "1.0 - ";
|
||||
AppendSource(source, tev_index);
|
||||
out += ".b";
|
||||
out += fmt::format("1.0 - {}.b", alpha_source);
|
||||
break;
|
||||
default:
|
||||
out += "0.0";
|
||||
@ -384,12 +356,11 @@ void FragmentModule::AppendColorCombiner(Pica::TexturingRegs::TevStageConfig::Op
|
||||
case Operation::AddSigned:
|
||||
return "color_results_1 + color_results_2 - vec3(0.5)";
|
||||
case Operation::Lerp:
|
||||
return "color_results_1 * color_results_3 + color_results_2 * (vec3(1.0) - "
|
||||
"color_results_3)";
|
||||
return "mix(color_results_2, color_results_1, color_results_3)";
|
||||
case Operation::Subtract:
|
||||
return "color_results_1 - color_results_2";
|
||||
case Operation::MultiplyThenAdd:
|
||||
return "color_results_1 * color_results_2 + color_results_3";
|
||||
return "fma(color_results_1, color_results_2, color_results_3)";
|
||||
case Operation::AddThenMultiply:
|
||||
return "min(color_results_1 + color_results_2, vec3(1.0)) * color_results_3";
|
||||
case Operation::Dot3_RGB:
|
||||
@ -416,11 +387,11 @@ void FragmentModule::AppendAlphaCombiner(Pica::TexturingRegs::TevStageConfig::Op
|
||||
case Operation::AddSigned:
|
||||
return "alpha_results_1 + alpha_results_2 - 0.5";
|
||||
case Operation::Lerp:
|
||||
return "alpha_results_1 * alpha_results_3 + alpha_results_2 * (1.0 - alpha_results_3)";
|
||||
return "mix(alpha_results_2, alpha_results_1, alpha_results_3)";
|
||||
case Operation::Subtract:
|
||||
return "alpha_results_1 - alpha_results_2";
|
||||
case Operation::MultiplyThenAdd:
|
||||
return "alpha_results_1 * alpha_results_2 + alpha_results_3";
|
||||
return "fma(alpha_results_1, alpha_results_2, alpha_results_3)";
|
||||
case Operation::AddThenMultiply:
|
||||
return "min(alpha_results_1 + alpha_results_2, 1.0) * alpha_results_3";
|
||||
default:
|
||||
|
@ -41,8 +41,8 @@ private:
|
||||
/// Writes the code to emulate PICA min/max blending factors
|
||||
void WriteBlending();
|
||||
|
||||
/// Writes the specified TEV stage source component(s)
|
||||
void AppendSource(Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index);
|
||||
/// Returns the specified TEV stage source component(s)
|
||||
std::string GetSource(Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index);
|
||||
|
||||
/// Writes the color components to use for the specified TEV stage color modifier
|
||||
void AppendColorModifier(Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,
|
||||
|
@ -40,7 +40,7 @@ layout (binding = 1, std140) uniform vs_data {
|
||||
vec4 clip_coef;
|
||||
};
|
||||
|
||||
const vec2 EPSILON_Z = vec2(0.00000001f, -1.00001f);
|
||||
const vec2 EPSILON_Z = vec2(0.000001f, -1.00001f);
|
||||
|
||||
vec4 SanitizeVertex(vec4 vtx_pos) {
|
||||
float ndc_z = vtx_pos.z / vtx_pos.w;
|
||||
|
@ -86,7 +86,7 @@ struct PicaUniformsData {
|
||||
};
|
||||
|
||||
struct VSUniformData {
|
||||
bool enable_clip1;
|
||||
u32 enable_clip1;
|
||||
alignas(16) Common::Vec4f clip_coef;
|
||||
};
|
||||
static_assert(sizeof(VSUniformData) == 32,
|
||||
|
@ -55,7 +55,7 @@ void FragmentModule::Generate() {
|
||||
|
||||
combiner_buffer = ConstF32(0.f, 0.f, 0.f, 0.f);
|
||||
next_combiner_buffer = GetShaderDataMember(vec_ids.Get(4), ConstS32(26));
|
||||
last_tex_env_out = rounded_primary_color;
|
||||
combiner_output = ConstF32(0.f, 0.f, 0.f, 0.f);
|
||||
|
||||
// Write shader bytecode to emulate PICA TEV stages
|
||||
for (u32 index = 0; index < config.texture.tev_stages.size(); ++index) {
|
||||
@ -76,7 +76,7 @@ void FragmentModule::Generate() {
|
||||
break;
|
||||
}
|
||||
|
||||
Id color{Byteround(last_tex_env_out, 4)};
|
||||
Id color{Byteround(combiner_output, 4)};
|
||||
switch (config.framebuffer.logic_op) {
|
||||
case FramebufferRegs::LogicOp::Clear:
|
||||
color = ConstF32(0.f, 0.f, 0.f, 0.f);
|
||||
@ -184,12 +184,12 @@ void FragmentModule::WriteFog() {
|
||||
|
||||
// Blend the fog
|
||||
const Id tex_env_rgb{
|
||||
OpVectorShuffle(vec_ids.Get(3), last_tex_env_out, last_tex_env_out, 0, 1, 2)};
|
||||
OpVectorShuffle(vec_ids.Get(3), combiner_output, combiner_output, 0, 1, 2)};
|
||||
const Id fog_color{GetShaderDataMember(vec_ids.Get(3), ConstS32(19))};
|
||||
const Id fog_factor_rgb{
|
||||
OpCompositeConstruct(vec_ids.Get(3), fog_factor, fog_factor, fog_factor)};
|
||||
const Id fog_result{OpFMix(vec_ids.Get(3), fog_color, tex_env_rgb, fog_factor_rgb)};
|
||||
last_tex_env_out = OpVectorShuffle(vec_ids.Get(4), fog_result, last_tex_env_out, 0, 1, 2, 6);
|
||||
combiner_output = OpVectorShuffle(vec_ids.Get(4), fog_result, combiner_output, 0, 1, 2, 6);
|
||||
}
|
||||
|
||||
void FragmentModule::WriteGas() {
|
||||
@ -630,8 +630,7 @@ void FragmentModule::WriteLighting() {
|
||||
}
|
||||
|
||||
void FragmentModule::WriteTevStage(s32 index) {
|
||||
const TexturingRegs::TevStageConfig stage =
|
||||
static_cast<const TexturingRegs::TevStageConfig>(config.texture.tev_stages[index]);
|
||||
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[index];
|
||||
|
||||
// Detects if a TEV stage is configured to be skipped (to avoid generating unnecessary code)
|
||||
const auto is_passthrough_tev_stage = [](const TevStageConfig& stage) {
|
||||
@ -674,18 +673,18 @@ void FragmentModule::WriteTevStage(s32 index) {
|
||||
alpha_output =
|
||||
OpFMul(f32_id, alpha_output, ConstF32(static_cast<float>(stage.GetAlphaMultiplier())));
|
||||
alpha_output = OpFClamp(f32_id, alpha_output, ConstF32(0.f), ConstF32(1.f));
|
||||
last_tex_env_out = OpCompositeConstruct(vec_ids.Get(4), color_output, alpha_output);
|
||||
combiner_output = OpCompositeConstruct(vec_ids.Get(4), color_output, alpha_output);
|
||||
}
|
||||
|
||||
combiner_buffer = next_combiner_buffer;
|
||||
if (config.TevStageUpdatesCombinerBufferColor(index)) {
|
||||
next_combiner_buffer =
|
||||
OpVectorShuffle(vec_ids.Get(4), last_tex_env_out, next_combiner_buffer, 0, 1, 2, 7);
|
||||
OpVectorShuffle(vec_ids.Get(4), combiner_output, next_combiner_buffer, 0, 1, 2, 7);
|
||||
}
|
||||
|
||||
if (config.TevStageUpdatesCombinerBufferAlpha(index)) {
|
||||
next_combiner_buffer =
|
||||
OpVectorShuffle(vec_ids.Get(4), next_combiner_buffer, last_tex_env_out, 0, 1, 2, 7);
|
||||
OpVectorShuffle(vec_ids.Get(4), next_combiner_buffer, combiner_output, 0, 1, 2, 7);
|
||||
}
|
||||
}
|
||||
|
||||
@ -728,7 +727,7 @@ void FragmentModule::WriteAlphaTestCondition(FramebufferRegs::CompareFunc func)
|
||||
case CompareFunc::GreaterThan:
|
||||
case CompareFunc::GreaterThanOrEqual: {
|
||||
const Id alpha_scaled{
|
||||
OpFMul(f32_id, OpCompositeExtract(f32_id, last_tex_env_out, 3), ConstF32(255.f))};
|
||||
OpFMul(f32_id, OpCompositeExtract(f32_id, combiner_output, 3), ConstF32(255.f))};
|
||||
const Id alpha_int{OpConvertFToS(i32_id, alpha_scaled)};
|
||||
const Id alphatest_ref{GetShaderDataMember(i32_id, ConstS32(1))};
|
||||
const Id alpha_comp_ref{compare(alpha_int, alphatest_ref)};
|
||||
@ -1280,7 +1279,7 @@ Id FragmentModule::LookupLightingLUT(Id lut_index, Id index, Id delta) {
|
||||
return OpFma(f32_id, entry_g, delta, entry_r);
|
||||
}
|
||||
|
||||
Id FragmentModule::AppendSource(TevStageConfig::Source source, s32 index) {
|
||||
Id FragmentModule::GetSource(TevStageConfig::Source source, s32 index) {
|
||||
using Source = TevStageConfig::Source;
|
||||
switch (source) {
|
||||
case Source::PrimaryColor:
|
||||
@ -1302,7 +1301,7 @@ Id FragmentModule::AppendSource(TevStageConfig::Source source, s32 index) {
|
||||
case Source::Constant:
|
||||
return GetShaderDataMember(vec_ids.Get(4), ConstS32(25), ConstS32(index));
|
||||
case Source::Previous:
|
||||
return last_tex_env_out;
|
||||
return combiner_output;
|
||||
default:
|
||||
LOG_CRITICAL(Render, "Unknown source op {}", source);
|
||||
return ConstF32(0.f, 0.f, 0.f, 0.f);
|
||||
@ -1311,8 +1310,11 @@ Id FragmentModule::AppendSource(TevStageConfig::Source source, s32 index) {
|
||||
|
||||
Id FragmentModule::AppendColorModifier(TevStageConfig::ColorModifier modifier,
|
||||
TevStageConfig::Source source, s32 index) {
|
||||
using Source = TevStageConfig::Source;
|
||||
using ColorModifier = TevStageConfig::ColorModifier;
|
||||
const Id source_color{AppendSource(source, index)};
|
||||
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[index];
|
||||
const bool force_source3 = index == 0 && source == Source::Previous;
|
||||
const Id source_color{GetSource(force_source3 ? stage.color_source3.Value() : source, index)};
|
||||
const Id one_vec{ConstF32(1.f, 1.f, 1.f)};
|
||||
|
||||
const auto shuffle = [&](s32 r, s32 g, s32 b) -> Id {
|
||||
@ -1348,11 +1350,14 @@ Id FragmentModule::AppendColorModifier(TevStageConfig::ColorModifier modifier,
|
||||
|
||||
Id FragmentModule::AppendAlphaModifier(TevStageConfig::AlphaModifier modifier,
|
||||
TevStageConfig::Source source, s32 index) {
|
||||
using Source = TevStageConfig::Source;
|
||||
using AlphaModifier = TevStageConfig::AlphaModifier;
|
||||
const Id source_color{AppendSource(source, index)};
|
||||
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[index];
|
||||
const bool force_source3 = index == 0 && source == Source::Previous;
|
||||
const Id source_alpha{GetSource(force_source3 ? stage.alpha_source3.Value() : source, index)};
|
||||
const Id one_f32{ConstF32(1.f)};
|
||||
|
||||
const auto component = [&](s32 c) -> Id { return OpCompositeExtract(f32_id, source_color, c); };
|
||||
const auto component = [&](s32 c) -> Id { return OpCompositeExtract(f32_id, source_alpha, c); };
|
||||
|
||||
switch (modifier) {
|
||||
case AlphaModifier::SourceAlpha:
|
||||
|
@ -99,13 +99,13 @@ private:
|
||||
/// Lookups the lighting LUT at the provided lut_index
|
||||
[[nodiscard]] Id LookupLightingLUT(Id lut_index, Id index, Id delta);
|
||||
|
||||
/// Writes the specified TEV stage source component(s)
|
||||
[[nodiscard]] Id AppendSource(Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||
/// Returns the specified TEV stage source component(s)
|
||||
[[nodiscard]] Id GetSource(Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||
|
||||
/// Writes the color components to use for the specified TEV stage color modifier
|
||||
[[nodiscard]] Id AppendColorModifier(
|
||||
Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,
|
||||
Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
|
||||
Pica::TexturingRegs::TevStageConfig::Source source, s32 tev_index);
|
||||
|
||||
/// Writes the alpha component to use for the specified TEV stage alpha modifier
|
||||
[[nodiscard]] Id AppendAlphaModifier(
|
||||
@ -272,7 +272,7 @@ private:
|
||||
Id secondary_fragment_color{};
|
||||
Id combiner_buffer{};
|
||||
Id next_combiner_buffer{};
|
||||
Id last_tex_env_out{};
|
||||
Id combiner_output{};
|
||||
|
||||
Id color_results_1{};
|
||||
Id color_results_2{};
|
||||
|
Reference in New Issue
Block a user