Compare commits

..

No commits in common. "master" and "nightly-2089" have entirely different histories.

80 changed files with 686 additions and 2561 deletions

View file

@ -12,13 +12,13 @@ jobs:
if: ${{ !github.head_ref }} if: ${{ !github.head_ref }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Pack - name: Pack
run: ./.ci/source.sh run: ./.ci/source.sh
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: source name: source
path: artifacts/ path: artifacts/
@ -37,11 +37,11 @@ jobs:
OS: linux OS: linux
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -53,7 +53,7 @@ jobs:
run: ./.ci/pack.sh run: ./.ci/pack.sh
if: ${{ matrix.target == 'appimage' }} if: ${{ matrix.target == 'appimage' }}
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
if: ${{ matrix.target == 'appimage' }} if: ${{ matrix.target == 'appimage' }}
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
@ -70,24 +70,24 @@ jobs:
OS: macos OS: macos
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}-${{ matrix.target }}- ${{ runner.os }}-${{ matrix.target }}-
- name: Install tools - name: Install tools
run: brew install ccache ninja run: brew install ccache glslang ninja
- name: Build - name: Build
run: ./.ci/macos.sh run: ./.ci/macos.sh
- name: Prepare outputs for caching - name: Prepare outputs for caching
run: mv build/bundle $OS-$TARGET run: mv build/bundle $OS-$TARGET
- name: Cache outputs for universal build - name: Cache outputs for universal build
uses: actions/cache/save@v4 uses: actions/cache/save@v3
with: with:
path: ${{ env.OS }}-${{ env.TARGET }} path: ${{ env.OS }}-${{ env.TARGET }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
@ -98,15 +98,15 @@ jobs:
OS: macos OS: macos
TARGET: universal TARGET: universal
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
- name: Download x86_64 build from cache - name: Download x86_64 build from cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v3
with: with:
path: ${{ env.OS }}-x86_64 path: ${{ env.OS }}-x86_64
key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }} key: ${{ runner.os }}-x86_64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
fail-on-cache-miss: true fail-on-cache-miss: true
- name: Download ARM64 build from cache - name: Download ARM64 build from cache
uses: actions/cache/restore@v4 uses: actions/cache/restore@v3
with: with:
path: ${{ env.OS }}-arm64 path: ${{ env.OS }}-arm64
key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }} key: ${{ runner.os }}-arm64-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
@ -118,7 +118,7 @@ jobs:
- name: Pack - name: Pack
run: ./.ci/pack.sh run: ./.ci/pack.sh
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
@ -137,11 +137,11 @@ jobs:
OS: windows OS: windows
TARGET: ${{ matrix.target }} TARGET: ${{ matrix.target }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }} key: ${{ runner.os }}-${{ matrix.target }}-${{ github.sha }}
@ -153,6 +153,13 @@ jobs:
- name: Install extra tools (MSVC) - name: Install extra tools (MSVC)
run: choco install ccache ninja wget run: choco install ccache ninja wget
if: ${{ matrix.target == 'msvc' }} 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 - name: Set up MSYS2
uses: msys2/setup-msys2@v2 uses: msys2/setup-msys2@v2
if: ${{ matrix.target == 'msys2' }} if: ${{ matrix.target == 'msys2' }}
@ -161,8 +168,10 @@ jobs:
update: true update: true
install: git make p7zip install: git make p7zip
pacboy: >- pacboy: >-
toolchain:p ccache:p cmake:p ninja:p toolchain:p ccache:p cmake:p ninja:p glslang:p
qt6-base:p qt6-multimedia:p qt6-multimedia-wmf:p qt6-tools:p qt6-translations: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 - name: Disable line ending translation
run: git config --global core.autocrlf input run: git config --global core.autocrlf input
- name: Build - name: Build
@ -170,7 +179,7 @@ jobs:
- name: Pack - name: Pack
run: ./.ci/pack.sh run: ./.ci/pack.sh
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: artifacts/ path: artifacts/
@ -183,11 +192,11 @@ jobs:
OS: android OS: android
TARGET: universal TARGET: universal
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: | path: |
~/.gradle/caches ~/.gradle/caches
@ -206,7 +215,7 @@ jobs:
run: | run: |
sudo add-apt-repository -y ppa:theofficialgman/gpu-tools sudo add-apt-repository -y ppa:theofficialgman/gpu-tools
sudo apt-get update -y sudo apt-get update -y
sudo apt-get install ccache apksigner -y sudo apt-get install ccache glslang-dev glslang-tools apksigner -y
- name: Build - name: Build
run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh run: JAVA_HOME=$JAVA_HOME_17_X64 ./.ci/android.sh
env: env:
@ -219,7 +228,7 @@ jobs:
env: env:
UNPACKED: 1 UNPACKED: 1
- name: Upload - name: Upload
uses: actions/upload-artifact@v4 uses: actions/upload-artifact@v3
with: with:
name: ${{ env.OS }}-${{ env.TARGET }} name: ${{ env.OS }}-${{ env.TARGET }}
path: src/android/app/artifacts/ path: src/android/app/artifacts/
@ -233,18 +242,18 @@ jobs:
OS: ios OS: ios
TARGET: arm64 TARGET: arm64
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
- name: Set up cache - name: Set up cache
uses: actions/cache@v4 uses: actions/cache@v3
with: with:
path: ${{ env.CCACHE_DIR }} path: ${{ env.CCACHE_DIR }}
key: ${{ runner.os }}-ios-${{ github.sha }} key: ${{ runner.os }}-ios-${{ github.sha }}
restore-keys: | restore-keys: |
${{ runner.os }}-ios- ${{ runner.os }}-ios-
- name: Install tools - name: Install tools
run: brew install ccache ninja run: brew install ccache glslang ninja
- name: Build - name: Build
run: ./.ci/ios.sh run: ./.ci/ios.sh
release: release:
@ -252,7 +261,7 @@ jobs:
needs: [windows, linux, macos-universal, android, source] needs: [windows, linux, macos-universal, android, source]
if: ${{ startsWith(github.ref, 'refs/tags/') }} if: ${{ startsWith(github.ref, 'refs/tags/') }}
steps: steps:
- uses: actions/download-artifact@v4 - uses: actions/download-artifact@v3
- name: Create release - name: Create release
uses: actions/create-release@v1 uses: actions/create-release@v1
env: env:

View file

@ -13,7 +13,7 @@ jobs:
image: citraemu/build-environments:linux-fresh image: citraemu/build-environments:linux-fresh
options: -u 1001 options: -u 1001
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
fetch-depth: 0 fetch-depth: 0
- name: Build - name: Build

View file

@ -20,11 +20,11 @@ jobs:
if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }} if: ${{ github.event.inputs.nightly != 'false' && github.repository == 'citra-emu/citra' }}
steps: steps:
# this checkout is required to make sure the GitHub Actions scripts are available # this checkout is required to make sure the GitHub Actions scripts are available
- uses: actions/checkout@v4 - uses: actions/checkout@v3
name: Pre-checkout name: Pre-checkout
with: with:
submodules: false submodules: false
- uses: actions/github-script@v7 - uses: actions/github-script@v6
id: check-changes id: check-changes
name: 'Check for new changes' name: 'Check for new changes'
env: env:
@ -38,7 +38,7 @@ jobs:
return checkBaseChanges(github, context); return checkBaseChanges(github, context);
- run: npm install execa@5 - run: npm install execa@5
if: ${{ steps.check-changes.outputs.result == 'true' }} if: ${{ steps.check-changes.outputs.result == 'true' }}
- uses: actions/checkout@v4 - uses: actions/checkout@v3
name: Checkout name: Checkout
if: ${{ steps.check-changes.outputs.result == 'true' }} if: ${{ steps.check-changes.outputs.result == 'true' }}
with: with:
@ -46,7 +46,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
token: ${{ secrets.ALT_GITHUB_TOKEN }} token: ${{ secrets.ALT_GITHUB_TOKEN }}
- uses: actions/github-script@v7 - uses: actions/github-script@v6
name: 'Update and tag new commits' name: 'Update and tag new commits'
if: ${{ steps.check-changes.outputs.result == 'true' }} if: ${{ steps.check-changes.outputs.result == 'true' }}
env: env:
@ -62,11 +62,11 @@ jobs:
if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }} if: ${{ github.event.inputs.canary != 'false' && github.repository == 'citra-emu/citra' }}
steps: steps:
# this checkout is required to make sure the GitHub Actions scripts are available # this checkout is required to make sure the GitHub Actions scripts are available
- uses: actions/checkout@v4 - uses: actions/checkout@v3
name: Pre-checkout name: Pre-checkout
with: with:
submodules: false submodules: false
- uses: actions/github-script@v7 - uses: actions/github-script@v6
id: check-changes id: check-changes
name: 'Check for new changes' name: 'Check for new changes'
env: env:
@ -79,7 +79,7 @@ jobs:
return checkCanaryChanges(github, context); return checkCanaryChanges(github, context);
- run: npm install execa@5 - run: npm install execa@5
if: ${{ steps.check-changes.outputs.result == 'true' }} if: ${{ steps.check-changes.outputs.result == 'true' }}
- uses: actions/checkout@v4 - uses: actions/checkout@v3
name: Checkout name: Checkout
if: ${{ steps.check-changes.outputs.result == 'true' }} if: ${{ steps.check-changes.outputs.result == 'true' }}
with: with:
@ -87,7 +87,7 @@ jobs:
fetch-depth: 0 fetch-depth: 0
submodules: true submodules: true
token: ${{ secrets.ALT_GITHUB_TOKEN }} token: ${{ secrets.ALT_GITHUB_TOKEN }}
- uses: actions/github-script@v7 - uses: actions/github-script@v6
name: 'Check and merge canary changes' name: 'Check and merge canary changes'
if: ${{ steps.check-changes.outputs.result == 'true' }} if: ${{ steps.check-changes.outputs.result == 'true' }}
env: env:

View file

@ -10,7 +10,7 @@ jobs:
container: citraemu/build-environments:linux-fresh container: citraemu/build-environments:linux-fresh
if: ${{ github.repository == 'citra-emu/citra' }} if: ${{ github.repository == 'citra-emu/citra' }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v3
with: with:
submodules: recursive submodules: recursive
fetch-depth: 0 fetch-depth: 0

View file

@ -85,6 +85,8 @@ option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF) 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 # Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF) 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}) option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
@ -247,26 +249,6 @@ if (ENABLE_QT)
if (ENABLE_QT_TRANSLATION) if (ENABLE_QT_TRANSLATION)
find_package(Qt6 REQUIRED COMPONENTS LinguistTools) find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
endif() 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() endif()
# Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic) # Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic)
@ -442,8 +424,7 @@ else()
endif() endif()
# Create target for outputting distributable bundles. # Create target for outputting distributable bundles.
# Not supported for mobile platforms as distributables are built differently. if (CITRA_ENABLE_BUNDLE_TARGET)
if (NOT ANDROID AND NOT IOS)
include(BundleTarget) include(BundleTarget)
if (ENABLE_SDL2_FRONTEND) if (ENABLE_SDL2_FRONTEND)
bundle_target(citra) bundle_target(citra)

View file

@ -2,104 +2,37 @@
if (BUNDLE_TARGET_EXECUTE) if (BUNDLE_TARGET_EXECUTE)
# --- Bundling method logic --- # --- 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) function(bundle_qt executable_path)
if (WIN32) 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) get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY)
find_program(windeployqt_executable windeployqt6)
# Create a qt.conf file pointing to the app directory. # Create a qt.conf file pointing to the app directory.
# This ensures Qt can find its plugins. # 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}") message(STATUS "Executing windeployqt for executable ${executable_path}")
execute_process(COMMAND "${windeployqt_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 --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. # Remove the FFmpeg multimedia plugin as we don't include FFmpeg.
# We want to use the Windows media plugin instead, which is also included. # We want to use the Windows media plugin instead, which is also included.
file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll") file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll")
elseif (APPLE) elseif (APPLE)
get_filename_component(executable_name "${executable_path}" NAME_WE) get_filename_component(executable_name "${executable_path}" NAME_WE)
find_program(macdeployqt_executable macdeployqt6 PATHS "${QT_HOST_PATH}/bin") find_program(MACDEPLOYQT_EXECUTABLE macdeployqt6)
message(STATUS "Executing macdeployqt at \"${macdeployqt_executable}\" for executable \"${executable_path}\"") message(STATUS "Executing macdeployqt for executable ${executable_path}")
execute_process( execute_process(
COMMAND "${macdeployqt_executable}" COMMAND "${MACDEPLOYQT_EXECUTABLE}"
"${executable_path}" "${executable_path}"
"-executable=${executable_path}/Contents/MacOS/${executable_name}" "-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. # 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. # 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() else()
message(FATAL_ERROR "Unsupported OS for Qt bundling.") message(FATAL_ERROR "Unsupported OS for Qt bundling.")
endif() endif()
@ -111,9 +44,9 @@ if (BUNDLE_TARGET_EXECUTE)
if (enable_qt) if (enable_qt)
# Find qmake to make sure the plugin uses the right version of Qt. # Find qmake to make sure the plugin uses the right version of Qt.
find_program(qmake_executable qmake6 PATHS "${QT_HOST_PATH}/bin") find_program(QMAKE_EXECUTABLE qmake6)
set(extra_linuxdeploy_env "QMAKE=${qmake_executable}") set(extra_linuxdeploy_env "QMAKE=${QMAKE_EXECUTABLE}")
set(extra_linuxdeploy_args --plugin qt) set(extra_linuxdeploy_args --plugin qt)
endif() endif()
@ -126,11 +59,7 @@ if (BUNDLE_TARGET_EXECUTE)
--executable "${executable_path}" --executable "${executable_path}"
--icon-file "${source_path}/dist/citra.svg" --icon-file "${source_path}/dist/citra.svg"
--desktop-file "${source_path}/dist/${executable_name}.desktop" --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) if (enable_qt)
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh") set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
@ -153,11 +82,7 @@ if (BUNDLE_TARGET_EXECUTE)
"OUTPUT=${bundle_dir}/${executable_name}.AppImage" "OUTPUT=${bundle_dir}/${executable_name}.AppImage"
"${linuxdeploy_executable}" "${linuxdeploy_executable}"
--output appimage --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() endfunction()
function(bundle_standalone executable_path original_executable_path bundle_library_paths) function(bundle_standalone executable_path original_executable_path bundle_library_paths)
@ -184,23 +109,16 @@ if (BUNDLE_TARGET_EXECUTE)
file(MAKE_DIRECTORY ${lib_dir}) file(MAKE_DIRECTORY ${lib_dir})
foreach (lib_file IN LISTS resolved_deps) foreach (lib_file IN LISTS resolved_deps)
message(STATUS "Bundling library ${lib_file}") message(STATUS "Bundling library ${lib_file}")
symlink_safe_copy("${lib_file}" "${lib_dir}") # Use native copy to turn symlinks into normal files.
execute_process(COMMAND cp -L "${lib_file}" "${lib_dir}")
endforeach() endforeach()
endif() endif()
# Add libs directory to executable rpath where applicable. # Add libs directory to executable rpath where applicable.
if (APPLE) 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) 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() endif()
endfunction() endfunction()
@ -209,7 +127,7 @@ if (BUNDLE_TARGET_EXECUTE)
set(bundle_dir ${BINARY_PATH}/bundle) set(bundle_dir ${BINARY_PATH}/bundle)
# On Linux, always bundle an AppImage. # On Linux, always bundle an AppImage.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux") if (DEFINED LINUXDEPLOY)
if (IN_PLACE) if (IN_PLACE)
message(FATAL_ERROR "Cannot bundle for Linux in-place.") message(FATAL_ERROR "Cannot bundle for Linux in-place.")
endif() endif()
@ -228,12 +146,14 @@ if (BUNDLE_TARGET_EXECUTE)
if (BUNDLE_QT) if (BUNDLE_QT)
bundle_qt("${bundled_executable_path}") bundle_qt("${bundled_executable_path}")
else() endif()
if (WIN32 OR NOT BUNDLE_QT)
bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}") bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
endif() endif()
endif() endif()
elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY) else()
# --- linuxdeploy download logic --- # --- Bundling target creation logic ---
# Downloads and extracts a linuxdeploy component. # Downloads and extracts a linuxdeploy component.
function(download_linuxdeploy_component base_dir name executable_name) function(download_linuxdeploy_component base_dir name executable_name)
@ -241,7 +161,7 @@ elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
if (NOT EXISTS "${executable_file}") if (NOT EXISTS "${executable_file}")
message(STATUS "Downloading ${executable_name}") message(STATUS "Downloading ${executable_name}")
file(DOWNLOAD file(DOWNLOAD
"https://github.com/${name}/releases/download/continuous/${executable_name}" "https://github.com/linuxdeploy/${name}/releases/download/continuous/${executable_name}"
"${executable_file}" SHOW_PROGRESS) "${executable_file}" SHOW_PROGRESS)
file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
@ -250,11 +170,7 @@ elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
message(STATUS "Extracting ${executable_name}") message(STATUS "Extracting ${executable_name}")
execute_process( execute_process(
COMMAND "${executable_file}" --appimage-extract 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() else()
message(STATUS "Copying ${executable_name}") message(STATUS "Copying ${executable_name}")
file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/") file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/")
@ -262,102 +178,89 @@ elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
endif() endif()
endfunction() 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. # 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. # 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) function(bundle_target_internal target_name in_place)
# Create base bundle target if it does not exist. # Create base bundle target if it does not exist.
if (NOT in_place AND NOT TARGET bundle) if (NOT in_place AND NOT TARGET bundle)
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")
endif() endif()
set(bundle_executable_path "$<TARGET_FILE:${target_name}>") set(BUNDLE_EXECUTABLE_PATH "$<TARGET_FILE:${target_name}>")
if (target_name MATCHES ".*qt") if (target_name MATCHES ".*qt")
set(bundle_qt ON) set(BUNDLE_QT ON)
if (APPLE) if (APPLE)
# For Qt targets on Apple, expect an app bundle. # 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() endif()
else() else()
set(bundle_qt OFF) set(BUNDLE_QT OFF)
endif() endif()
# Build a list of library search paths from prefix paths. # Build a list of library search paths from prefix paths.
foreach(prefix_path IN LISTS CMAKE_FIND_ROOT_PATH CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH) foreach(prefix_path IN LISTS CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
if (WIN32) if (WIN32)
list(APPEND bundle_library_paths "${prefix_path}/bin") list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin")
endif() endif()
list(APPEND bundle_library_paths "${prefix_path}/lib") list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib")
endforeach() endforeach()
foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH) foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH)
list(APPEND bundle_library_paths "${library_path}") list(APPEND BUNDLE_LIBRARY_PATHS "${library_path}")
endforeach() 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) if (in_place)
message(STATUS "Adding in-place bundling to ${target_name}") message(STATUS "Adding in-place bundling to ${target_name}")
set(dest_target ${target_name}) set(DEST_TARGET ${target_name})
else() else()
message(STATUS "Adding ${target_name} to bundle target") message(STATUS "Adding ${target_name} to bundle target")
set(dest_target bundle) set(DEST_TARGET bundle)
add_dependencies(bundle ${target_name}) add_dependencies(bundle ${target_name})
endif() endif()
add_custom_command(TARGET ${dest_target} POST_BUILD add_custom_command(TARGET ${DEST_TARGET} POST_BUILD
COMMAND ${CMAKE_COMMAND} COMMAND ${CMAKE_COMMAND}
"-DQT_HOST_PATH=\"${QT_HOST_PATH}\"" "-DCMAKE_PREFIX_PATH=\"${CMAKE_PREFIX_PATH}\""
"-DQT_TARGET_PATH=\"${QT_TARGET_PATH}\""
"-DBUNDLE_TARGET_EXECUTE=1" "-DBUNDLE_TARGET_EXECUTE=1"
"-DTARGET=${target_name}" "-DTARGET=${target_name}"
"-DSOURCE_PATH=${CMAKE_SOURCE_DIR}" "-DSOURCE_PATH=${CMAKE_SOURCE_DIR}"
"-DBINARY_PATH=${CMAKE_BINARY_DIR}" "-DBINARY_PATH=${CMAKE_BINARY_DIR}"
"-DEXECUTABLE_PATH=${bundle_executable_path}" "-DEXECUTABLE_PATH=${BUNDLE_EXECUTABLE_PATH}"
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\"" "-DBUNDLE_LIBRARY_PATHS=\"${BUNDLE_LIBRARY_PATHS}\""
"-DBUNDLE_QT=${bundle_qt}" "-DBUNDLE_QT=${BUNDLE_QT}"
"-DIN_PLACE=${in_place}" "-DIN_PLACE=${in_place}"
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun" ${EXTRA_BUNDLE_ARGS}
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake" -P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
endfunction() endfunction()

View file

@ -1,20 +1,21 @@
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR}) set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
# Determines parameters based on the host and target for downloading the right Qt binaries. # This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out) # 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)
if (target MATCHES "tools_.*") if (target MATCHES "tools_.*")
set(tool ON) set(DOWNLOAD_QT_TOOL ON)
else() else()
set(tool OFF) set(DOWNLOAD_QT_TOOL OFF)
endif() endif()
# Determine installation parameters for OS, architecture, and compiler # Determine installation parameters for OS, architecture, and compiler
if (WIN32) if (WIN32)
set(host "windows") set(host "windows")
set(type "desktop") set(type "desktop")
if (NOT DOWNLOAD_QT_TOOL)
if (NOT tool)
if (MINGW) if (MINGW)
set(arch "win64_mingw") set(arch "win64_mingw")
set(arch_path "mingw_64") set(arch_path "mingw_64")
@ -27,35 +28,21 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.") message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
endif() endif()
set(arch "win64_${arch_path}") 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() else()
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.") message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
endif() endif()
endif() endif()
elseif (APPLE) elseif (APPLE)
set(host "mac") set(host "mac")
set(type "desktop") if (IOS AND NOT DOWNLOAD_QT_TOOL)
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(type "ios")
set(arch "ios") set(arch "ios")
set(arch_path "ios") set(arch_path "ios")
set(host_arch_path "macos")
else()
set(type "desktop")
set(arch "clang_64")
set(arch_path "macos")
endif() endif()
else() else()
set(host "linux") set(host "linux")
@ -64,64 +51,38 @@ function(determine_qt_parameters target host_out type_out arch_out arch_path_out
set(arch_path "linux") set(arch_path "linux")
endif() endif()
set(${host_out} "${host}" PARENT_SCOPE) get_external_prefix(qt base_path)
set(${type_out} "${type}" PARENT_SCOPE) file(MAKE_DIRECTORY "${base_path}")
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") set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
if (tool) if (DOWNLOAD_QT_TOOL)
set(prefix "${base_path}/Tools") set(prefix "${base_path}/Tools")
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target}) set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
else() else()
set(prefix "${base_path}/${target}/${arch_path}") set(prefix "${base_path}/${target}/${arch_path}")
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} if (host_arch_path)
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase) 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)
endif() endif()
if (NOT EXISTS "${prefix}") if (NOT EXISTS "${prefix}")
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}") message(STATUS "Downloading binaries for Qt...")
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9") set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9")
if (WIN32) if (WIN32)
set(aqt_path "${base_path}/aqt.exe") set(aqt_path "${base_path}/aqt.exe")
if (NOT EXISTS "${aqt_path}") file(DOWNLOAD
file(DOWNLOAD ${AQT_PREBUILD_BASE_URL}/aqt.exe
${AQT_PREBUILD_BASE_URL}/aqt.exe ${aqt_path} SHOW_PROGRESS)
${aqt_path} SHOW_PROGRESS)
endif()
execute_process(COMMAND ${aqt_path} ${install_args} execute_process(COMMAND ${aqt_path} ${install_args}
WORKING_DIRECTORY ${base_path}) WORKING_DIRECTORY ${base_path})
elseif (APPLE) elseif (APPLE)
set(aqt_path "${base_path}/aqt-macos") set(aqt_path "${base_path}/aqt-macos")
if (NOT EXISTS "${aqt_path}") file(DOWNLOAD
file(DOWNLOAD ${AQT_PREBUILD_BASE_URL}/aqt-macos
${AQT_PREBUILD_BASE_URL}/aqt-macos ${aqt_path} SHOW_PROGRESS)
${aqt_path} SHOW_PROGRESS)
endif()
execute_process(COMMAND chmod +x ${aqt_path}) execute_process(COMMAND chmod +x ${aqt_path})
execute_process(COMMAND ${aqt_path} ${install_args} execute_process(COMMAND ${aqt_path} ${install_args}
WORKING_DIRECTORY ${base_path}) WORKING_DIRECTORY ${base_path})
@ -135,38 +96,18 @@ function(download_qt_configuration prefix_out target host type arch arch_path ba
execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args} execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
WORKING_DIRECTORY ${base_path}) WORKING_DIRECTORY ${base_path})
endif() endif()
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
endif() endif()
set(${prefix_out} "${prefix}" PARENT_SCOPE) message(STATUS "Using downloaded Qt binaries at ${prefix}")
endfunction()
# This function downloads Qt using aqt. # Add the Qt prefix path so CMake can locate it.
# 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}") list(APPEND CMAKE_PREFIX_PATH "${prefix}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE) 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() endfunction()
function(download_moltenvk) function(download_moltenvk)

View file

@ -287,13 +287,5 @@ dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]" dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]" 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 @Exit

View file

@ -6,5 +6,5 @@ Usage:
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card. 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. 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. 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" and "gm9/seeddb.bin" files off of your SD card into "(Citra directory)/sysdata/". 4. Power off your system and copy the "gm9/aes_keys.txt" file off of your SD card into "(Citra directory)/sysdata/".

View file

@ -11,4 +11,3 @@ type = QT
file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml file_filter = ../../src/android/app/src/main/res/values-<lang>/strings.xml
source_file = ../../src/android/app/src/main/res/values/strings.xml source_file = ../../src/android/app/src/main/res/values/strings.xml
type = ANDROID type = ANDROID
lang_map = es_ES:es, hu_HU:hu, ru_RU:ru, pt_BR:pt, zh_CN:zh

View file

@ -57,12 +57,6 @@ if(USE_SYSTEM_CRYPTOPP)
add_library(cryptopp INTERFACE) add_library(cryptopp INTERFACE)
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp) target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
else() 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_DOCUMENTATION OFF CACHE BOOL "")
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "") set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "") set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
@ -241,18 +235,6 @@ endif()
# DiscordRPC # DiscordRPC
if (USE_DISCORD_PRESENCE) 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) add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include) target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif() endif()
@ -294,20 +276,11 @@ endif()
add_library(httplib INTERFACE) add_library(httplib INTERFACE)
if(USE_SYSTEM_CPP_HTTPLIB) if(USE_SYSTEM_CPP_HTTPLIB)
find_package(CppHttp 0.14.1) find_package(CppHttp 0.14.1)
# Detect if system cpphttplib is a shared library if(CppHttp_FOUND)
# this breaks building as Citra relies on functions that are moved target_link_libraries(httplib INTERFACE httplib::httplib)
# 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() else()
if(CppHttp_FOUND) message(STATUS "Cpp-httplib not found or not suitable version! Falling back to bundled...")
target_link_libraries(httplib INTERFACE httplib::httplib) target_include_directories(httplib SYSTEM INTERFACE ./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() endif()
else() else()
target_include_directories(httplib SYSTEM INTERFACE ./httplib) target_include_directories(httplib SYSTEM INTERFACE ./httplib)

@ -1 +1 @@
Subproject commit a99c80c26686e44eddf0432140ae397f3efbd0b3 Subproject commit 9327192b0095dc1f420b2082d37bd427b5750d48

2
externals/dynarmic vendored

@ -1 +1 @@
Subproject commit 30f1a3c6289075ef4af08f5ec502be2fc8627a0c Subproject commit ca0e264f4f962e29baa23a3282ce484625866b98

2
externals/oaknut vendored

@ -1 +1 @@
Subproject commit 6b1d57ea7ed4882d32a91eeaa6557b0ecb4da152 Subproject commit 9d091109deb445bc6e9289c6195a282b7c993d49

View file

@ -10,7 +10,7 @@ plugins {
id("org.jetbrains.kotlin.android") id("org.jetbrains.kotlin.android")
id("de.undercouch.download") version "5.5.0" id("de.undercouch.download") version "5.5.0"
id("kotlin-parcelize") id("kotlin-parcelize")
kotlin("plugin.serialization") version "1.9.22" kotlin("plugin.serialization") version "1.8.21"
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
} }
@ -173,23 +173,23 @@ android {
dependencies { dependencies {
implementation("androidx.recyclerview:recyclerview:1.3.2") implementation("androidx.recyclerview:recyclerview:1.3.2")
implementation("androidx.activity:activity-ktx:1.8.2") implementation("androidx.activity:activity-ktx:1.8.0")
implementation("androidx.fragment:fragment-ktx:1.6.2") implementation("androidx.fragment:fragment-ktx:1.6.2")
implementation("androidx.appcompat:appcompat:1.6.1") implementation("androidx.appcompat:appcompat:1.6.1")
implementation("androidx.documentfile:documentfile:1.0.1") implementation("androidx.documentfile:documentfile:1.0.1")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.7.0") implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0") implementation("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
implementation("com.google.android.material:material:1.9.0") implementation("com.google.android.material:material:1.9.0")
implementation("androidx.core:core-splashscreen:1.0.1") implementation("androidx.core:core-splashscreen:1.0.1")
implementation("androidx.work:work-runtime:2.9.0") implementation("androidx.work:work-runtime:2.8.1")
implementation("org.ini4j:ini4j:0.5.4") implementation("org.ini4j:ini4j:0.5.4")
implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0")
implementation("androidx.navigation:navigation-fragment-ktx:2.7.6") implementation("androidx.navigation:navigation-fragment-ktx:2.7.5")
implementation("androidx.navigation:navigation-ui-ktx:2.7.6") implementation("androidx.navigation:navigation-ui-ktx:2.7.5")
implementation("info.debatty:java-string-similarity:2.0.0") implementation("info.debatty:java-string-similarity:2.0.0")
implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.5.0")
implementation("androidx.preference:preference-ktx:1.2.1") implementation("androidx.preference:preference-ktx:1.2.1")
implementation("io.coil-kt:coil:2.5.0") implementation("io.coil-kt:coil:2.2.2")
} }
// Download Vulkan Validation Layers from the KhronosGroup GitHub. // Download Vulkan Validation Layers from the KhronosGroup GitHub.

View file

@ -42,9 +42,6 @@
android:banner="@mipmap/ic_launcher" android:banner="@mipmap/ic_launcher"
android:requestLegacyExternalStorage="true"> android:requestLegacyExternalStorage="true">
<meta-data android:name="android.game_mode_config"
android:resource="@xml/game_mode_config" />
<activity <activity
android:name="org.citra.citra_emu.ui.main.MainActivity" android:name="org.citra.citra_emu.ui.main.MainActivity"
android:theme="@style/Theme.Citra.Splash.Main" android:theme="@style/Theme.Citra.Splash.Main"

View file

@ -9,13 +9,10 @@ import android.app.Application
import android.app.NotificationChannel import android.app.NotificationChannel
import android.app.NotificationManager import android.app.NotificationManager
import android.content.Context import android.content.Context
import android.os.Build
import org.citra.citra_emu.utils.DirectoryInitialization import org.citra.citra_emu.utils.DirectoryInitialization
import org.citra.citra_emu.utils.DocumentsTree import org.citra.citra_emu.utils.DocumentsTree
import org.citra.citra_emu.utils.GpuDriverHelper import org.citra.citra_emu.utils.GpuDriverHelper
import org.citra.citra_emu.utils.PermissionsHandler import org.citra.citra_emu.utils.PermissionsHandler
import org.citra.citra_emu.utils.Log
import org.citra.citra_emu.utils.MemoryUtil
class CitraApplication : Application() { class CitraApplication : Application() {
private fun createNotificationChannel() { private fun createNotificationChannel() {
@ -56,20 +53,9 @@ class CitraApplication : Application() {
} }
NativeLibrary.logDeviceInfo() NativeLibrary.logDeviceInfo()
logDeviceInfo()
createNotificationChannel() 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 { companion object {
private var application: CitraApplication? = null private var application: CitraApplication? = null

View file

@ -413,12 +413,12 @@ object NativeLibrary {
} }
fun setEmulationActivity(emulationActivity: EmulationActivity?) { fun setEmulationActivity(emulationActivity: EmulationActivity?) {
Log.debug("[NativeLibrary] Registering EmulationActivity.") Log.verbose("[NativeLibrary] Registering EmulationActivity.")
sEmulationActivity = WeakReference(emulationActivity) sEmulationActivity = WeakReference(emulationActivity)
} }
fun clearEmulationActivity() { fun clearEmulationActivity() {
Log.debug("[NativeLibrary] Unregistering EmulationActivity.") Log.verbose("[NativeLibrary] Unregistering EmulationActivity.")
sEmulationActivity.clear() sEmulationActivity.clear()
} }

View file

@ -94,14 +94,14 @@ object DirectoryInitialization {
val dataPath = PermissionsHandler.citraDirectory val dataPath = PermissionsHandler.citraDirectory
if (dataPath.toString().isNotEmpty()) { if (dataPath.toString().isNotEmpty()) {
userPath = dataPath.toString() userPath = dataPath.toString()
android.util.Log.d("[Citra Frontend]", "[DirectoryInitialization] User Dir: $userPath") Log.debug("[DirectoryInitialization] User Dir: $userPath")
return true return true
} }
return false return false
} }
private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) { private fun copyAsset(asset: String, output: File, overwrite: Boolean, context: Context) {
Log.debug("[DirectoryInitialization] Copying File $asset to $output") Log.verbose("[DirectoryInitialization] Copying File $asset to $output")
try { try {
if (!output.exists() || overwrite) { if (!output.exists() || overwrite) {
val inputStream = context.assets.open(asset) val inputStream = context.assets.open(asset)
@ -121,7 +121,7 @@ object DirectoryInitialization {
overwrite: Boolean, overwrite: Boolean,
context: Context context: Context
) { ) {
Log.debug("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder") Log.verbose("[DirectoryInitialization] Copying Folder $assetFolder to $outputFolder")
try { try {
var createdFolder = false var createdFolder = false
for (file in context.assets.list(assetFolder)!!) { for (file in context.assets.list(assetFolder)!!) {

View file

@ -4,17 +4,34 @@
package org.citra.citra_emu.utils 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 { object Log {
// Tracks whether we should share the old log or the current log // Tracks whether we should share the old log or the current log
var gameLaunched = false var gameLaunched = false
private const val TAG = "Citra Frontend"
external fun debug(message: String) fun verbose(message: String?) {
if (BuildConfig.DEBUG) {
Log.v(TAG, message!!)
}
}
external fun warning(message: String) fun debug(message: String?) {
if (BuildConfig.DEBUG) {
Log.d(TAG, message!!)
}
}
external fun info(message: String) fun info(message: String?) = Log.i(TAG, message!!)
external fun error(message: String) fun warning(message: String?) = Log.w(TAG, message!!)
external fun critical(message: String) fun error(message: String?) = Log.e(TAG, message!!)
} }

View file

@ -1,108 +0,0 @@
// 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)
}

View file

@ -28,7 +28,6 @@ add_library(citra-android SHARED
ndk_motion.cpp ndk_motion.cpp
ndk_motion.h ndk_motion.h
system_save_game.cpp system_save_game.cpp
native_log.cpp
) )
target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network) target_link_libraries(citra-android PRIVATE audio_core citra_common citra_core input_common network)

View file

@ -1,30 +0,0 @@
// 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"

View file

@ -442,17 +442,6 @@
<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_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> <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 --> <!-- Theme Modes -->
<string name="change_theme_mode">Change Theme Mode</string> <string name="change_theme_mode">Change Theme Mode</string>
<string name="theme_mode_follow_system">Follow System</string> <string name="theme_mode_follow_system">Follow System</string>

View file

@ -1,7 +0,0 @@
<?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"/>

View file

@ -4,10 +4,10 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins { plugins {
id("com.android.application") version "8.2.1" apply false id("com.android.application") version "8.1.2" apply false
id("com.android.library") version "8.2.1" apply false id("com.android.library") version "8.1.2" apply false
id("org.jetbrains.kotlin.android") version "1.9.22" apply false id("org.jetbrains.kotlin.android") version "1.8.21" apply false
id("org.jetbrains.kotlin.plugin.serialization") version "1.9.22" id("org.jetbrains.kotlin.plugin.serialization") version "1.8.21"
} }
tasks.register("clean").configure { tasks.register("clean").configure {
@ -19,6 +19,6 @@ buildscript {
google() google()
} }
dependencies { dependencies {
classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.6") classpath("androidx.navigation:navigation-safe-args-gradle-plugin:2.7.5")
} }
} }

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip

View file

@ -316,7 +316,7 @@ struct SourceStatus {
u16_le sync_count; ///< Is set by the DSP to the value of SourceConfiguration::sync_count 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 u32_dsp buffer_position; ///< Number of samples into the current buffer
u16_le current_buffer_id; ///< Updated when a buffer finishes playing u16_le current_buffer_id; ///< Updated when a buffer finishes playing
u16_le last_buffer_id; ///< Updated when all buffers in the queue finish playing INSERT_PADDING_DSPWORDS(1);
}; };
Status status[num_sources]; Status status[num_sources];

View file

@ -298,9 +298,9 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
b.buffer_id, b.buffer_id,
state.mono_or_stereo, state.mono_or_stereo,
state.format, state.format,
true, // from_queue true,
0, // play_position {}, // 0 in u32_dsp
false, // has_played false,
}); });
} }
LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i, LOG_TRACE(Audio_DSP, "enqueuing queued {} addr={:#010x} len={} id={}", i,
@ -321,19 +321,16 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
void Source::GenerateFrame() { void Source::GenerateFrame() {
current_frame.fill({}); current_frame.fill({});
if (state.current_buffer.empty()) { if (state.current_buffer.empty() && !DequeueBuffer()) {
// TODO(SachinV): Should dequeue happen at the end of the frame generation?
if (DequeueBuffer()) {
return;
}
state.enabled = false; state.enabled = false;
state.buffer_update = true; state.buffer_update = true;
state.last_buffer_id = state.current_buffer_id;
state.current_buffer_id = 0; state.current_buffer_id = 0;
return; return;
} }
std::size_t frame_position = 0; std::size_t frame_position = 0;
state.current_sample_number = state.next_sample_number;
while (frame_position < current_frame.size()) { while (frame_position < current_frame.size()) {
if (state.current_buffer.empty() && !DequeueBuffer()) { if (state.current_buffer.empty() && !DequeueBuffer()) {
break; break;
@ -360,7 +357,7 @@ void Source::GenerateFrame() {
} }
// TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision // TODO(jroweboy): Keep track of frame_position independently so that it doesn't lose precision
// over time // over time
state.current_sample_number += static_cast<u32>(frame_position * state.rate_multiplier); state.next_sample_number += static_cast<u32>(frame_position * state.rate_multiplier);
state.filters.ProcessFrame(current_frame); state.filters.ProcessFrame(current_frame);
} }
@ -411,9 +408,9 @@ bool Source::DequeueBuffer() {
// the first playthrough starts at play_position, loops start at the beginning of the buffer // 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.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_physical_address = buf.physical_address;
state.current_buffer_id = buf.buffer_id; state.current_buffer_id = buf.buffer_id;
state.last_buffer_id = 0;
state.buffer_update = buf.from_queue && !buf.has_played; state.buffer_update = buf.from_queue && !buf.has_played;
if (buf.is_looping) { if (buf.is_looping) {
@ -421,17 +418,8 @@ bool Source::DequeueBuffer() {
state.input_queue.push(buf); state.input_queue.push(buf);
} }
// Because our interpolation consumes samples instead of using an index, LOG_TRACE(Audio_DSP, "source_id={} buffer_id={} from_queue={} current_buffer.size()={}",
// let's just consume the samples up to the current sample number. source_id, buf.buffer_id, buf.from_queue, state.current_buffer.size());
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; return true;
} }
@ -444,10 +432,9 @@ SourceStatus::Status Source::GetCurrentStatus() {
ret.is_enabled = state.enabled; ret.is_enabled = state.enabled;
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0; ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
state.buffer_update = false; state.buffer_update = false;
ret.sync_count = state.sync_count;
ret.buffer_position = state.current_sample_number;
ret.current_buffer_id = state.current_buffer_id; ret.current_buffer_id = state.current_buffer_id;
ret.last_buffer_id = state.last_buffer_id; ret.buffer_position = state.current_sample_number;
ret.sync_count = state.sync_count;
return ret; return ret;
} }

View file

@ -87,8 +87,8 @@ private:
Format format; Format format;
bool from_queue; bool from_queue;
u32 play_position; // = 0; u32_dsp play_position; // = 0;
bool has_played; // = false; bool has_played; // = false;
private: private:
template <class Archive> template <class Archive>
@ -136,14 +136,14 @@ private:
// Current buffer // Current buffer
u32 current_sample_number = 0; u32 current_sample_number = 0;
u32 next_sample_number = 0;
PAddr current_buffer_physical_address = 0; PAddr current_buffer_physical_address = 0;
AudioInterp::StereoBuffer16 current_buffer = {}; AudioInterp::StereoBuffer16 current_buffer = {};
// buffer_id state // buffer_id state
bool buffer_update = false; bool buffer_update = false;
u16 last_buffer_id = 0; u32 current_buffer_id = 0;
u16 current_buffer_id = 0;
// Decoding state // Decoding state
@ -170,6 +170,7 @@ private:
ar& mono_or_stereo; ar& mono_or_stereo;
ar& format; ar& format;
ar& current_sample_number; ar& current_sample_number;
ar& next_sample_number;
ar& current_buffer_physical_address; ar& current_buffer_physical_address;
ar& current_buffer; ar& current_buffer;
ar& buffer_update; ar& buffer_update;

View file

@ -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 // 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. // UISetting::values.shortcuts, which is alphabetically ordered.
// clang-format off // clang-format off
const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{ const std::array<UISettings::Shortcut, 30> Config::default_hotkeys {{
{QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}}, {QStringLiteral("Advance Frame"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::ApplicationShortcut}},
{QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}}, {QStringLiteral("Audio Mute/Unmute"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+M"), Qt::WindowShortcut}},
{QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}}, {QStringLiteral("Audio Volume Down"), QStringLiteral("Main Window"), {QStringLiteral(""), Qt::WindowShortcut}},
@ -71,11 +71,6 @@ const std::array<UISettings::Shortcut, 35> Config::default_hotkeys {{
{QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}}, {QStringLiteral("Load Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F2"), Qt::WidgetWithChildrenShortcut}},
{QStringLiteral("Load File"), QStringLiteral("Main Window"), {QStringLiteral("Ctrl+O"), 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("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("Remove Amiibo"), QStringLiteral("Main Window"), {QStringLiteral("F3"), Qt::ApplicationShortcut}},
{QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}}, {QStringLiteral("Restart Emulation"), QStringLiteral("Main Window"), {QStringLiteral("F6"), Qt::WindowShortcut}},
{QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}}, {QStringLiteral("Rotate Screens Upright"), QStringLiteral("Main Window"), {QStringLiteral("F8"), Qt::WindowShortcut}},
@ -562,15 +557,6 @@ void Config::ReadMultiplayerValues() {
UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong(); UISettings::values.game_id = ReadSetting(QStringLiteral("game_id"), 0).toULongLong();
UISettings::values.room_description = UISettings::values.room_description =
ReadSetting(QStringLiteral("room_description"), QString{}).toString(); 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 // Read ban list back
int size = qt_config->beginReadArray(QStringLiteral("username_ban_list")); int size = qt_config->beginReadArray(QStringLiteral("username_ban_list"));
UISettings::values.ban_list.first.resize(size); UISettings::values.ban_list.first.resize(size);
@ -1088,15 +1074,6 @@ void Config::SaveMultiplayerValues() {
WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0); WriteSetting(QStringLiteral("game_id"), UISettings::values.game_id, 0);
WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description, WriteSetting(QStringLiteral("room_description"), UISettings::values.room_description,
QString{}); 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 // Write ban list
qt_config->beginWriteArray(QStringLiteral("username_ban_list")); qt_config->beginWriteArray(QStringLiteral("username_ban_list"));
for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) { for (std::size_t i = 0; i < UISettings::values.ban_list.first.size(); ++i) {

View file

@ -26,7 +26,7 @@ public:
static const std::array<int, Settings::NativeButton::NumButtons> default_buttons; 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<std::array<int, 5>, Settings::NativeAnalog::NumAnalogs> default_analogs;
static const std::array<UISettings::Shortcut, 35> default_hotkeys; static const std::array<UISettings::Shortcut, 30> default_hotkeys;
private: private:
void Initialize(const std::string& config_name); void Initialize(const std::string& config_name);

View file

@ -647,13 +647,6 @@ void GMainWindow::InitializeHotkeys() {
link_action_shortcut(ui->action_Advance_Frame, QStringLiteral("Advance Frame")); 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_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_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) { const auto add_secondary_window_hotkey = [this](QKeySequence hotkey, const char* slot) {
// This action will fire specifically when secondary_window is in focus // This action will fire specifically when secondary_window is in focus
@ -3197,10 +3190,8 @@ int main(int argc, char* argv[]) {
QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy); QApplication::setHighDpiScaleFactorRoundingPolicy(rounding_policy);
#ifdef __APPLE__ #ifdef __APPLE__
auto bundle_dir = FileUtil::GetBundleDirectory(); std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
if (bundle_dir) { chdir(bin_path.c_str());
FileUtil::SetCurrentDir(bundle_dir.value() + "..");
}
#endif #endif
#ifdef ENABLE_OPENGL #ifdef ENABLE_OPENGL

View file

@ -80,8 +80,9 @@ void DirectConnectWindow::Connect() {
// Store settings // Store settings
UISettings::values.nickname = ui->nickname->text(); UISettings::values.nickname = ui->nickname->text();
UISettings::values.ip = ui->ip->text(); UISettings::values.ip = ui->ip->text();
UISettings::values.port = UISettings::values.port = (ui->port->isModified() && !ui->port->text().isEmpty())
!ui->port->text().isEmpty() ? ui->port->text() : UISettings::values.port; ? ui->port->text()
: UISettings::values.port;
// attempt to connect in a different thread // attempt to connect in a different thread
QFuture<void> f = QtConcurrent::run([&] { QFuture<void> f = QtConcurrent::run([&] {

View file

@ -63,10 +63,10 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
// UI Buttons // UI Buttons
connect(ui->refresh_list, &QPushButton::clicked, this, &Lobby::RefreshLobby); 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->games_owned, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterOwned);
connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty); connect(ui->hide_empty, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterEmpty);
connect(ui->hide_full, &QCheckBox::toggled, proxy, &LobbyFilterProxyModel::SetFilterFull); 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::doubleClicked, this, &Lobby::OnJoinRoom);
connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom); connect(ui->room_list, &QTreeView::clicked, this, &Lobby::OnExpandRoom);
@ -74,12 +74,6 @@ Lobby::Lobby(Core::System& system_, QWidget* parent, QStandardItemModel* list,
connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this, connect(&room_list_watcher, &QFutureWatcher<AnnounceMultiplayerRoom::RoomList>::finished, this,
&Lobby::OnRefreshLobby); &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 // 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 // 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 // part of the constructor, but offload the refresh until after the window shown. perhaps emit a
@ -186,10 +180,6 @@ void Lobby::OnJoinRoom(const QModelIndex& source) {
UISettings::values.nickname = ui->nickname->text(); UISettings::values.nickname = ui->nickname->text();
UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString(); UISettings::values.ip = proxy->data(connection_index, LobbyItemHost::HostIPRole).toString();
UISettings::values.port = proxy->data(connection_index, LobbyItemHost::HostPortRole).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() { void Lobby::ResetModel() {

View file

@ -188,37 +188,12 @@ public:
} }
QVariant data(int role) const override { QVariant data(int role) const override {
switch (role) { if (role != Qt::DisplayRole) {
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); 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 { bool operator<(const QStandardItem& other) const override {

View file

@ -138,11 +138,6 @@ struct Values {
QString room_description; QString room_description;
std::pair<std::vector<std::string>, std::vector<std::string>> ban_list; 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 // logging
Settings::Setting<bool> show_console{false, "showConsole"}; Settings::Setting<bool> show_console{false, "showConsole"};
}; };

View file

@ -13,6 +13,7 @@
#endif #endif
// The user data dir // The user data dir
#define ROOT_DIR "."
#define USERDATA_DIR "user" #define USERDATA_DIR "user"
#ifdef USER_DIR #ifdef USER_DIR
#define EMU_DATA_DIR USER_DIR #define EMU_DATA_DIR USER_DIR

View file

@ -634,10 +634,6 @@ std::optional<std::string> GetCurrentDir() {
std::string strDir = dir; std::string strDir = dir;
#endif #endif
free(dir); free(dir);
if (!strDir.ends_with(DIR_SEP)) {
strDir += DIR_SEP;
}
return strDir; return strDir;
} // namespace FileUtil } // namespace FileUtil
@ -650,36 +646,17 @@ bool SetCurrentDir(const std::string& directory) {
} }
#if defined(__APPLE__) #if defined(__APPLE__)
std::optional<std::string> GetBundleDirectory() { std::string GetBundleDirectory() {
CFURLRef BundleRef;
char AppBundlePath[MAXPATHLEN];
// Get the main bundle for the app // Get the main bundle for the app
CFBundleRef bundle_ref = CFBundleGetMainBundle(); BundleRef = CFBundleCopyBundleURL(CFBundleGetMainBundle());
if (!bundle_ref) { CFStringRef BundlePath = CFURLCopyFileSystemPath(BundleRef, kCFURLPOSIXPathStyle);
return {}; CFStringGetFileSystemRepresentation(BundlePath, AppBundlePath, sizeof(AppBundlePath));
} CFRelease(BundleRef);
CFRelease(BundlePath);
CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref); return AppBundlePath;
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 #endif
@ -755,6 +732,22 @@ static const std::string& GetHomeDirectory() {
} }
#endif #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 { namespace {
std::unordered_map<UserPath, std::string> g_paths; std::unordered_map<UserPath, std::string> g_paths;
std::unordered_map<UserPath, std::string> g_default_paths; std::unordered_map<UserPath, std::string> g_default_paths;
@ -784,10 +777,8 @@ void SetUserPath(const std::string& path) {
g_paths.emplace(UserPath::ConfigDir, user_path + CONFIG_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); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
#else #else
auto current_dir = FileUtil::GetCurrentDir(); if (FileUtil::Exists(ROOT_DIR DIR_SEP USERDATA_DIR)) {
if (current_dir.has_value() && user_path = ROOT_DIR DIR_SEP USERDATA_DIR DIR_SEP;
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::ConfigDir, user_path + CONFIG_DIR DIR_SEP);
g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP); g_paths.emplace(UserPath::CacheDir, user_path + CACHE_DIR DIR_SEP);
} else { } else {

View file

@ -193,8 +193,11 @@ void SetCurrentRomPath(const std::string& path);
// Update the Global Path with the new value // Update the Global Path with the new value
void UpdateUserPath(UserPath path, const std::string& filename); void UpdateUserPath(UserPath path, const std::string& filename);
// Returns the path to where the sys file are
[[nodiscard]] std::string GetSysDirectory();
#ifdef __APPLE__ #ifdef __APPLE__
[[nodiscard]] std::optional<std::string> GetBundleDirectory(); [[nodiscard]] std::string GetBundleDirectory();
#endif #endif
#ifdef _WIN32 #ifdef _WIN32

View file

@ -451,7 +451,7 @@ void SetColorConsoleBackendEnabled(bool enabled) {
} }
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, fmt::string_view format, unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args) { const fmt::format_args& args) {
if (!initialization_in_progress_suppress_logging) { if (!initialization_in_progress_suppress_logging) {
Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function, Impl::Instance().PushEntry(log_class, log_level, filename, line_num, function,

View file

@ -24,12 +24,12 @@ constexpr const char* TrimSourcePath(std::string_view source) {
/// Logs a message to the global logger, using fmt /// Logs a message to the global logger, using fmt
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, fmt::string_view format, unsigned int line_num, const char* function, const char* format,
const fmt::format_args& args); const fmt::format_args& args);
template <typename... Args> template <typename... Args>
void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num, void FmtLogMessage(Class log_class, Level log_level, const char* filename, unsigned int line_num,
const char* function, fmt::format_string<Args...> format, const Args&... args) { const char* function, const char* format, const Args&... args) {
FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format, FmtLogMessageImpl(log_class, log_level, filename, line_num, function, format,
fmt::make_format_args(args...)); fmt::make_format_args(args...));
} }

View file

@ -327,10 +327,6 @@ add_library(citra_core STATIC
hle/service/ldr_ro/cro_helper.h hle/service/ldr_ro/cro_helper.h
hle/service/ldr_ro/ldr_ro.cpp hle/service/ldr_ro/ldr_ro.cpp
hle/service/ldr_ro/ldr_ro.h 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.cpp
hle/service/mic/mic_u.h hle/service/mic/mic_u.h
hle/service/mvd/mvd.cpp hle/service/mvd/mvd.cpp

View file

@ -14,18 +14,18 @@ namespace ConfigMem {
Handler::Handler() { Handler::Handler() {
std::memset(&config_mem, 0, sizeof(config_mem)); std::memset(&config_mem, 0, sizeof(config_mem));
// Values extracted from firmware 11.17.0-50E // Values extracted from firmware 11.2.0-35E
config_mem.kernel_version_min = 0x3a; config_mem.kernel_version_min = 0x34;
config_mem.kernel_version_maj = 0x2; config_mem.kernel_version_maj = 0x2;
config_mem.ns_tid = 0x0004013000008002; config_mem.ns_tid = 0x0004013000008002;
config_mem.sys_core_ver = 0x2; config_mem.sys_core_ver = 0x2;
config_mem.unit_info = 0x1; // Bit 0 set for Retail config_mem.unit_info = 0x1; // Bit 0 set for Retail
config_mem.prev_firm = 0x1; config_mem.prev_firm = 0x1;
config_mem.ctr_sdk_ver = 0x0000F450; config_mem.ctr_sdk_ver = 0x0000F297;
config_mem.firm_version_min = 0x3a; config_mem.firm_version_min = 0x34;
config_mem.firm_version_maj = 0x2; config_mem.firm_version_maj = 0x2;
config_mem.firm_sys_core_ver = 0x2; config_mem.firm_sys_core_ver = 0x2;
config_mem.firm_ctr_sdk_ver = 0x0000F450; config_mem.firm_ctr_sdk_ver = 0x0000F297;
} }
ConfigMemDef& Handler::GetConfigMem() { ConfigMemDef& Handler::GetConfigMem() {

View file

@ -210,10 +210,10 @@ void Process::Set3dsxKernelCaps() {
}; };
// Similar to Rosalina, we set kernel version to a recent one. // Similar to Rosalina, we set kernel version to a recent one.
// This is 11.17.0, to be consistent with core/hle/kernel/config_mem.cpp // This is 11.2.0, to be consistent with core/hle/kernel/config_mem.cpp
// TODO: refactor kernel version out so it is configurable and consistent // TODO: refactor kernel version out so it is configurable and consistent
// among all relevant places. // among all relevant places.
kernel_version = 0x23a; kernel_version = 0x234;
} }
void Process::Run(s32 main_thread_priority, u32 stack_size) { void Process::Run(s32 main_thread_priority, u32 stack_size) {

View file

@ -373,10 +373,7 @@ ResultVal<AppletManager::InitializeResult> AppletManager::Initialize(AppletId ap
if (active_slot == AppletSlot::Error) { if (active_slot == AppletSlot::Error) {
active_slot = slot; active_slot = slot;
// APT automatically calls enable on the first registered applet. // Wake up the application.
Enable(attributes);
// Wake up the applet.
SendParameter({ SendParameter({
.sender_id = AppletId::None, .sender_id = AppletId::None,
.destination_id = app_id, .destination_id = app_id,
@ -401,8 +398,7 @@ Result AppletManager::Enable(AppletAttributes attributes) {
auto slot_data = GetAppletSlot(slot); auto slot_data = GetAppletSlot(slot);
slot_data->registered = true; slot_data->registered = true;
if (slot_data->applet_id != AppletId::None && if (slot_data->attributes.applet_pos == AppletPos::System &&
slot_data->attributes.applet_pos == AppletPos::System &&
slot_data->attributes.is_home_menu) { slot_data->attributes.is_home_menu) {
slot_data->attributes.raw |= attributes.raw; slot_data->attributes.raw |= attributes.raw;
LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.", LOG_DEBUG(Service_APT, "Updated home menu attributes to {:08X}.",
@ -790,23 +786,16 @@ Result AppletManager::PrepareToStartSystemApplet(AppletId applet_id) {
Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object, Result AppletManager::StartSystemApplet(AppletId applet_id, std::shared_ptr<Kernel::Object> object,
const std::vector<u8>& buffer) { const std::vector<u8>& buffer) {
auto source_applet_id = AppletId::Application; auto source_applet_id = AppletId::None;
if (last_system_launcher_slot != AppletSlot::Error) { if (last_system_launcher_slot != AppletSlot::Error) {
const auto launcher_slot_data = GetAppletSlot(last_system_launcher_slot); const auto slot_data = GetAppletSlot(last_system_launcher_slot);
source_applet_id = launcher_slot_data->applet_id; source_applet_id = slot_data->applet_id;
// APT generally clears and terminates the caller of StartSystemApplet. This helps in // If a system applet is launching another system applet, reset the slot to avoid conflicts.
// situations such as a system applet launching another system applet, which would // This is needed because system applets won't necessarily call CloseSystemApplet before
// otherwise deadlock. // exiting.
// TODO: In real APT, the check for AppletSlot::Application does not exist; there is if (last_system_launcher_slot == AppletSlot::SystemApplet) {
// TODO: something wrong with our implementation somewhere that makes this necessary. slot_data->Reset();
// 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.
} }
} }

View file

@ -152,7 +152,6 @@ union AppletAttributes {
u32 raw; u32 raw;
BitField<0, 3, AppletPos> applet_pos; BitField<0, 3, AppletPos> applet_pos;
BitField<28, 1, u32> no_exit_on_system_applet;
BitField<29, 1, u32> is_home_menu; BitField<29, 1, u32> is_home_menu;
AppletAttributes() : raw(0) {} AppletAttributes() : raw(0) {}

File diff suppressed because it is too large Load diff

View file

@ -18,7 +18,6 @@
#include <boost/serialization/vector.hpp> #include <boost/serialization/vector.hpp>
#include <boost/serialization/weak_ptr.hpp> #include <boost/serialization/weak_ptr.hpp>
#include <httplib.h> #include <httplib.h>
#include "common/thread.h"
#include "core/hle/ipc_helpers.h" #include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/shared_memory.h" #include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/service.h" #include "core/hle/service/service.h"
@ -49,25 +48,12 @@ constexpr u32 TotalRequestMethods = 8;
enum class RequestState : u8 { enum class RequestState : u8 {
NotStarted = 0x1, // Request has not started yet. NotStarted = 0x1, // Request has not started yet.
ConnectingToServer = 0x5, // Request in progress, connecting to server. InProgress = 0x5, // Request in progress, sending request over the network.
SendingRequest = 0x6, // Request in progress, sending HTTP request. ReadyToDownloadContent = 0x7, // Ready to download the content. (needs verification)
ReceivingResponse = 0x7, // Request in progress, receiving HTTP response. ReadyToDownload = 0x8, // Ready to download?
ReadyToDownloadContent = 0x8, // Ready to download the content.
TimedOut = 0xA, // Request timed out? 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 { enum class ClientCertID : u32 {
Default = 0x40, // Default client cert Default = 0x40, // Default client cert
}; };
@ -211,41 +197,6 @@ public:
friend class boost::serialization::access; 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; Handle handle;
u32 session_id; u32 session_id;
std::string url; std::string url;
@ -257,14 +208,8 @@ public:
u32 socket_buffer_size; u32 socket_buffer_size;
std::vector<RequestHeader> headers; std::vector<RequestHeader> headers;
const ClCertAData* clcert_data; const ClCertAData* clcert_data;
Params post_data; httplib::Params post_data;
std::string post_data_raw; 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::future<void> request_future;
std::atomic<u64> current_download_size_bytes; std::atomic<u64> current_download_size_bytes;
@ -272,19 +217,12 @@ public:
std::size_t current_copied_data; std::size_t current_copied_data;
bool uses_default_client_cert{}; bool uses_default_client_cert{};
httplib::Response response; httplib::Response response;
Common::Event finish_post_data;
void ParseAsciiPostData();
std::string ParseMultipartFormData();
void MakeRequest(); void MakeRequest();
void MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_info, void MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_info,
std::vector<Context::RequestHeader>& pending_headers); std::vector<Context::RequestHeader>& pending_headers);
void MakeRequestSSL(httplib::Request& request, const URLInfo& url_info, void MakeRequestSSL(httplib::Request& request, const URLInfo& url_info,
std::vector<Context::RequestHeader>& pending_headers); 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 { struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
@ -370,16 +308,6 @@ private:
*/ */
void CancelConnection(Kernel::HLERequestContext& ctx); 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 * HTTP_C::GetDownloadSizeState service function
* Inputs: * Inputs:
@ -490,21 +418,6 @@ private:
*/ */
void AddPostDataAscii(Kernel::HLERequestContext& ctx); 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 * HTTP_C::AddPostDataRaw service function
* Inputs: * Inputs:
@ -517,140 +430,6 @@ private:
*/ */
void AddPostDataRaw(Kernel::HLERequestContext& ctx); 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 * HTTP_C::GetResponseHeader service function
* Inputs: * Inputs:
@ -666,28 +445,6 @@ private:
*/ */
void GetResponseHeader(Kernel::HLERequestContext& ctx); 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 * HTTP_C::GetResponseStatusCode service function
* Inputs: * Inputs:
@ -821,17 +578,6 @@ private:
*/ */
void SetKeepAlive(Kernel::HLERequestContext& ctx); 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 * HTTP_C::Finalize service function
* Outputs: * Outputs:

View file

@ -1,16 +0,0 @@
// 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

View file

@ -1,15 +0,0 @@
// 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

View file

@ -1,36 +0,0 @@
// 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

View file

@ -1,21 +0,0 @@
// 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)

View file

@ -35,7 +35,6 @@
#include "core/hle/service/http/http_c.h" #include "core/hle/service/http/http_c.h"
#include "core/hle/service/ir/ir.h" #include "core/hle/service/ir/ir.h"
#include "core/hle/service/ldr_ro/ldr_ro.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/mic/mic_u.h"
#include "core/hle/service/mvd/mvd.h" #include "core/hle/service/mvd/mvd.h"
#include "core/hle/service/ndm/ndm_u.h" #include "core/hle/service/ndm/ndm_u.h"
@ -102,7 +101,7 @@ const std::array<ServiceModuleInfo, 41> service_module_map{
{"CDC", 0x00040130'00001802, nullptr}, {"CDC", 0x00040130'00001802, nullptr},
{"GPIO", 0x00040130'00001B02, nullptr}, {"GPIO", 0x00040130'00001B02, nullptr},
{"I2C", 0x00040130'00001E02, nullptr}, {"I2C", 0x00040130'00001E02, nullptr},
{"MCU", 0x00040130'00001F02, MCU::InstallInterfaces}, {"MCU", 0x00040130'00001F02, nullptr},
{"MP", 0x00040130'00002A02, nullptr}, {"MP", 0x00040130'00002A02, nullptr},
{"PDN", 0x00040130'00002102, nullptr}, {"PDN", 0x00040130'00002102, nullptr},
{"SPI", 0x00040130'00002302, nullptr}}}; {"SPI", 0x00040130'00002302, nullptr}}};

View file

@ -598,10 +598,9 @@ static_assert(std::is_trivially_copyable_v<CTRPollFD>,
union CTRSockAddr { union CTRSockAddr {
/// Structure to represent a raw sockaddr /// Structure to represent a raw sockaddr
struct { struct {
u8 len; ///< The length of the entire structure, only the set fields count u8 len; ///< The length of the entire structure, only the set fields count
u8 sa_family; ///< The address family of the sockaddr u8 sa_family; ///< The address family of the sockaddr
std::array<u8, 0x1A> u8 sa_data[0x1A]; ///< The extra data, this varies, depending on the address family
sa_data; ///< The extra data, this varies, depending on the address family
} raw; } raw;
/// Structure to represent the 3ds' sockaddr_in structure /// Structure to represent the 3ds' sockaddr_in structure
@ -613,57 +612,36 @@ union CTRSockAddr {
} in; } in;
static_assert(sizeof(CTRSockAddrIn) == 8, "Invalid CTRSockAddrIn size"); 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 /// Convert a 3DS CTRSockAddr to a platform-specific sockaddr
static std::pair<sockaddr_storage, socklen_t> ToPlatform(CTRSockAddr const& ctr_addr) { static sockaddr ToPlatform(CTRSockAddr const& ctr_addr) {
sockaddr_storage result{}; sockaddr result;
socklen_t result_len = sizeof(result.ss_family); ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn),
ASSERT_MSG(ctr_addr.raw.len == sizeof(CTRSockAddrIn) ||
ctr_addr.raw.len == sizeof(CTRSockAddrIn6),
"Unhandled address size (len) in CTRSockAddr::ToPlatform"); "Unhandled address size (len) in CTRSockAddr::ToPlatform");
result.ss_family = SocketDomainToPlatform(ctr_addr.raw.sa_family); result.sa_family = SocketDomainToPlatform(ctr_addr.raw.sa_family);
std::memset(result.sa_data, 0, sizeof(result.sa_data));
// We can not guarantee ABI compatibility between platforms so we copy the fields manually // We can not guarantee ABI compatibility between platforms so we copy the fields manually
switch (result.ss_family) { switch (result.sa_family) {
case AF_INET: { case AF_INET: {
sockaddr_in* result_in = reinterpret_cast<sockaddr_in*>(&result); sockaddr_in* result_in = reinterpret_cast<sockaddr_in*>(&result);
result_in->sin_port = ctr_addr.in.sin_port; result_in->sin_port = ctr_addr.in.sin_port;
result_in->sin_addr.s_addr = ctr_addr.in.sin_addr; result_in->sin_addr.s_addr = ctr_addr.in.sin_addr;
result_len = sizeof(sockaddr_in); std::memset(result_in->sin_zero, 0, sizeof(result_in->sin_zero));
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; break;
} }
default: default:
ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
break; break;
} }
return std::make_pair(result, result_len); return result;
} }
/// Convert a platform-specific sockaddr to a 3DS CTRSockAddr /// Convert a platform-specific sockaddr to a 3DS CTRSockAddr
static CTRSockAddr FromPlatform(sockaddr_storage const& addr) { static CTRSockAddr FromPlatform(sockaddr const& addr) {
CTRSockAddr result; CTRSockAddr result;
result.raw.sa_family = static_cast<u8>(SocketDomainFromPlatform(addr.ss_family)); result.raw.sa_family = static_cast<u8>(SocketDomainFromPlatform(addr.sa_family));
// We can not guarantee ABI compatibility between platforms so we copy the fields manually // We can not guarantee ABI compatibility between platforms so we copy the fields manually
switch (addr.ss_family) { switch (addr.sa_family) {
case AF_INET: { case AF_INET: {
sockaddr_in const* addr_in = reinterpret_cast<sockaddr_in const*>(&addr); sockaddr_in const* addr_in = reinterpret_cast<sockaddr_in const*>(&addr);
result.raw.len = sizeof(CTRSockAddrIn); result.raw.len = sizeof(CTRSockAddrIn);
@ -671,15 +649,6 @@ union CTRSockAddr {
result.in.sin_addr = addr_in->sin_addr.s_addr; result.in.sin_addr = addr_in->sin_addr.s_addr;
break; 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: default:
ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform"); ASSERT_MSG(false, "Unhandled address family (sa_family) in CTRSockAddr::ToPlatform");
break; break;
@ -738,8 +707,7 @@ struct CTRAddrInfo {
.ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)), .ai_family = static_cast<s32_le>(SocketDomainFromPlatform(addr.ai_family)),
.ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)), .ai_socktype = static_cast<s32_le>(SocketTypeFromPlatform(addr.ai_socktype)),
.ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)), .ai_protocol = static_cast<s32_le>(SocketProtocolFromPlatform(addr.ai_protocol)),
.ai_addr = .ai_addr = CTRSockAddr::FromPlatform(*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); ctr_addr.ai_addrlen = static_cast<s32_le>(ctr_addr.ai_addr.raw.len);
if (addr.ai_canonname) if (addr.ai_canonname)
@ -872,9 +840,9 @@ void SOC_U::Bind(Kernel::HLERequestContext& ctx) {
CTRSockAddr ctr_sock_addr; CTRSockAddr ctr_sock_addr;
std::memcpy(&ctr_sock_addr, sock_addr_buf.data(), std::min<size_t>(len, sizeof(ctr_sock_addr))); std::memcpy(&ctr_sock_addr, sock_addr_buf.data(), std::min<size_t>(len, sizeof(ctr_sock_addr)));
auto [sock_addr, sock_addr_len] = CTRSockAddr::ToPlatform(ctr_sock_addr); sockaddr sock_addr = CTRSockAddr::ToPlatform(ctr_sock_addr);
s32 ret = ::bind(holder.socket_fd, reinterpret_cast<sockaddr*>(&sock_addr), sock_addr_len); s32 ret = ::bind(holder.socket_fd, &sock_addr, sizeof(sock_addr));
if (ret != 0) if (ret != 0)
ret = TranslateError(GET_ERRNO); ret = TranslateError(GET_ERRNO);
@ -969,7 +937,7 @@ void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
// Output // Output
s32 ret{}; s32 ret{};
int accept_error; int accept_error;
sockaddr_storage addr; sockaddr addr;
}; };
auto async_data = std::make_shared<AsyncData>(); auto async_data = std::make_shared<AsyncData>();
@ -982,8 +950,7 @@ void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
[async_data](Kernel::HLERequestContext& ctx) { [async_data](Kernel::HLERequestContext& ctx) {
socklen_t addr_len = sizeof(async_data->addr); socklen_t addr_len = sizeof(async_data->addr);
async_data->ret = static_cast<u32>( async_data->ret = static_cast<u32>(
::accept(async_data->fd_info->socket_fd, ::accept(async_data->fd_info->socket_fd, &async_data->addr, &addr_len));
reinterpret_cast<sockaddr*>(&async_data->addr), &addr_len));
async_data->accept_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; async_data->accept_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
return 0; return 0;
}, },
@ -1014,8 +981,8 @@ void SOC_U::Accept(Kernel::HLERequestContext& ctx) {
ctr_addr_buf.resize(async_data->max_addr_len); ctr_addr_buf.resize(async_data->max_addr_len);
} }
LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->pid, LOG_DEBUG(Service_SOC, "called, pid={}, fd={}, ret={}", async_data->socket_handle,
async_data->socket_handle, static_cast<s32>(async_data->ret)); static_cast<s32>(async_data->ret));
IPC::RequestBuilder rb(ctx, 0x04, 2, 2); IPC::RequestBuilder rb(ctx, 0x04, 2, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1142,10 +1109,10 @@ void SOC_U::SendToOther(Kernel::HLERequestContext& ctx) {
CTRSockAddr ctr_dest_addr; CTRSockAddr ctr_dest_addr;
std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(), std::memcpy(&ctr_dest_addr, dest_addr_buffer.data(),
std::min<size_t>(addr_len, sizeof(ctr_dest_addr))); std::min<size_t>(addr_len, sizeof(ctr_dest_addr)));
auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr); sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
ret = static_cast<s32>( ret = static_cast<s32>(::sendto(holder.socket_fd,
::sendto(holder.socket_fd, reinterpret_cast<const char*>(input_buff.data()), len, flags, reinterpret_cast<const char*>(input_buff.data()), len,
reinterpret_cast<sockaddr*>(&dest_addr), dest_addr_len)); flags, &dest_addr, sizeof(dest_addr)));
} else { } else {
ret = static_cast<s32>(::sendto(holder.socket_fd, ret = static_cast<s32>(::sendto(holder.socket_fd,
reinterpret_cast<const char*>(input_buff.data()), len, reinterpret_cast<const char*>(input_buff.data()), len,
@ -1192,10 +1159,10 @@ s32 SOC_U::SendToImpl(SocketHolder& holder, u32 len, u32 flags, u32 addr_len,
CTRSockAddr ctr_dest_addr; CTRSockAddr ctr_dest_addr;
std::memcpy(&ctr_dest_addr, dest_addr_buff, std::memcpy(&ctr_dest_addr, dest_addr_buff,
std::min<size_t>(addr_len, sizeof(ctr_dest_addr))); std::min<size_t>(addr_len, sizeof(ctr_dest_addr)));
auto [dest_addr, dest_addr_len] = CTRSockAddr::ToPlatform(ctr_dest_addr); sockaddr dest_addr = CTRSockAddr::ToPlatform(ctr_dest_addr);
ret = static_cast<s32>( ret = static_cast<s32>(::sendto(holder.socket_fd,
::sendto(holder.socket_fd, reinterpret_cast<const char*>(input_buff.data()), len, flags, reinterpret_cast<const char*>(input_buff.data()), len,
reinterpret_cast<sockaddr*>(&dest_addr), dest_addr_len)); flags, &dest_addr, sizeof(dest_addr)));
} else { } else {
ret = static_cast<s32>(::sendto(holder.socket_fd, ret = static_cast<s32>(::sendto(holder.socket_fd,
reinterpret_cast<const char*>(input_buff.data()), len, reinterpret_cast<const char*>(input_buff.data()), len,
@ -1327,7 +1294,7 @@ void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
ctx.RunAsync( ctx.RunAsync(
[async_data](Kernel::HLERequestContext& ctx) { [async_data](Kernel::HLERequestContext& ctx) {
sockaddr_storage src_addr; sockaddr src_addr;
socklen_t src_addr_len = sizeof(src_addr); socklen_t src_addr_len = sizeof(src_addr);
CTRSockAddr ctr_src_addr; CTRSockAddr ctr_src_addr;
// Windows, why do you have to be so special... // Windows, why do you have to be so special...
@ -1335,10 +1302,10 @@ void SOC_U::RecvFromOther(Kernel::HLERequestContext& ctx) {
RecvBusyWaitForEvent(*async_data->fd_info); RecvBusyWaitForEvent(*async_data->fd_info);
} }
if (async_data->addr_len > 0) { if (async_data->addr_len > 0) {
async_data->ret = static_cast<s32>(::recvfrom( async_data->ret = static_cast<s32>(
async_data->fd_info->socket_fd, ::recvfrom(async_data->fd_info->socket_fd,
reinterpret_cast<char*>(async_data->output_buff.data()), async_data->len, reinterpret_cast<char*>(async_data->output_buff.data()),
async_data->flags, reinterpret_cast<sockaddr*>(&src_addr), &src_addr_len)); async_data->len, async_data->flags, &src_addr, &src_addr_len));
if (async_data->ret >= 0 && src_addr_len > 0) { if (async_data->ret >= 0 && src_addr_len > 0) {
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
std::memcpy(async_data->addr_buff.data(), &ctr_src_addr, std::memcpy(async_data->addr_buff.data(), &ctr_src_addr,
@ -1444,7 +1411,7 @@ void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) {
ctx.RunAsync( ctx.RunAsync(
[async_data](Kernel::HLERequestContext& ctx) { [async_data](Kernel::HLERequestContext& ctx) {
sockaddr_storage src_addr; sockaddr src_addr;
socklen_t src_addr_len = sizeof(src_addr); socklen_t src_addr_len = sizeof(src_addr);
CTRSockAddr ctr_src_addr; CTRSockAddr ctr_src_addr;
if (async_data->is_blocking) { if (async_data->is_blocking) {
@ -1452,10 +1419,10 @@ void SOC_U::RecvFrom(Kernel::HLERequestContext& ctx) {
} }
if (async_data->addr_len > 0) { if (async_data->addr_len > 0) {
// Only get src adr if input adr available // Only get src adr if input adr available
async_data->ret = static_cast<s32>(::recvfrom( async_data->ret = static_cast<s32>(
async_data->fd_info->socket_fd, ::recvfrom(async_data->fd_info->socket_fd,
reinterpret_cast<char*>(async_data->output_buff.data()), async_data->len, reinterpret_cast<char*>(async_data->output_buff.data()),
async_data->flags, reinterpret_cast<sockaddr*>(&src_addr), &src_addr_len)); async_data->len, async_data->flags, &src_addr, &src_addr_len));
if (async_data->ret >= 0 && src_addr_len > 0) { if (async_data->ret >= 0 && src_addr_len > 0) {
ctr_src_addr = CTRSockAddr::FromPlatform(src_addr); ctr_src_addr = CTRSockAddr::FromPlatform(src_addr);
std::memcpy(async_data->addr_buff.data(), &ctr_src_addr, std::memcpy(async_data->addr_buff.data(), &ctr_src_addr,
@ -1591,10 +1558,9 @@ void SOC_U::GetSockName(Kernel::HLERequestContext& ctx) {
} }
SocketHolder& holder = socket_holder_optional->get(); SocketHolder& holder = socket_holder_optional->get();
sockaddr_storage dest_addr; sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr); socklen_t dest_addr_len = sizeof(dest_addr);
s32 ret = s32 ret = ::getsockname(holder.socket_fd, &dest_addr, &dest_addr_len);
::getsockname(holder.socket_fd, reinterpret_cast<sockaddr*>(&dest_addr), &dest_addr_len);
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr)); std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
@ -1681,11 +1647,10 @@ void SOC_U::GetHostByAddr(Kernel::HLERequestContext& ctx) {
[[maybe_unused]] u32 out_buf_len = rp.Pop<u32>(); [[maybe_unused]] u32 out_buf_len = rp.Pop<u32>();
auto addr = rp.PopStaticBuffer(); auto addr = rp.PopStaticBuffer();
auto [platform_addr, platform_addr_len] = sockaddr platform_addr = CTRSockAddr::ToPlatform(*reinterpret_cast<CTRSockAddr*>(addr.data()));
CTRSockAddr::ToPlatform(*reinterpret_cast<CTRSockAddr*>(addr.data()));
struct hostent* result = struct hostent* result =
::gethostbyaddr(reinterpret_cast<char*>(&platform_addr), platform_addr_len, type); ::gethostbyaddr(reinterpret_cast<char*>(&platform_addr), sizeof(platform_addr), type);
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
rb.Push(ResultSuccess); rb.Push(ResultSuccess);
@ -1733,10 +1698,9 @@ void SOC_U::GetPeerName(Kernel::HLERequestContext& ctx) {
} }
SocketHolder& holder = socket_holder_optional->get(); SocketHolder& holder = socket_holder_optional->get();
sockaddr_storage dest_addr; sockaddr dest_addr;
socklen_t dest_addr_len = sizeof(dest_addr); socklen_t dest_addr_len = sizeof(dest_addr);
const int ret = const int ret = ::getpeername(holder.socket_fd, &dest_addr, &dest_addr_len);
::getpeername(holder.socket_fd, reinterpret_cast<sockaddr*>(&dest_addr), &dest_addr_len);
CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr); CTRSockAddr ctr_dest_addr = CTRSockAddr::FromPlatform(dest_addr);
std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr)); std::vector<u8> dest_addr_buff(sizeof(ctr_dest_addr));
@ -1777,7 +1741,7 @@ void SOC_U::Connect(Kernel::HLERequestContext& ctx) {
struct AsyncData { struct AsyncData {
// Input // Input
SocketHolder* fd_info; SocketHolder* fd_info;
std::pair<sockaddr_storage, socklen_t> input_addr; sockaddr input_addr;
u32 socket_handle; u32 socket_handle;
u32 pid; u32 pid;
@ -1799,9 +1763,8 @@ void SOC_U::Connect(Kernel::HLERequestContext& ctx) {
ctx.RunAsync( ctx.RunAsync(
[async_data](Kernel::HLERequestContext& ctx) { [async_data](Kernel::HLERequestContext& ctx) {
async_data->ret = ::connect(async_data->fd_info->socket_fd, async_data->ret = ::connect(async_data->fd_info->socket_fd, &async_data->input_addr,
reinterpret_cast<sockaddr*>(&async_data->input_addr.first), sizeof(async_data->input_addr));
async_data->input_addr.second);
async_data->connect_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0; async_data->connect_error = (async_data->ret == SOCKET_ERROR_VALUE) ? GET_ERRNO : 0;
return 0; return 0;
}, },
@ -2084,15 +2047,14 @@ void SOC_U::GetNameInfoImpl(Kernel::HLERequestContext& ctx) {
CTRSockAddr ctr_sa; CTRSockAddr ctr_sa;
std::memcpy(&ctr_sa, sa_buff.data(), socklen); std::memcpy(&ctr_sa, sa_buff.data(), socklen);
auto [sa, sa_len] = CTRSockAddr::ToPlatform(ctr_sa); sockaddr sa = CTRSockAddr::ToPlatform(ctr_sa);
std::vector<u8> host(hostlen); std::vector<u8> host(hostlen);
std::vector<u8> serv(servlen); std::vector<u8> serv(servlen);
char* host_data = hostlen > 0 ? reinterpret_cast<char*>(host.data()) : nullptr; char* host_data = hostlen > 0 ? reinterpret_cast<char*>(host.data()) : nullptr;
char* serv_data = servlen > 0 ? reinterpret_cast<char*>(serv.data()) : nullptr; char* serv_data = servlen > 0 ? reinterpret_cast<char*>(serv.data()) : nullptr;
s32 ret = getnameinfo(reinterpret_cast<sockaddr*>(&sa), sa_len, host_data, hostlen, serv_data, s32 ret = getnameinfo(&sa, sizeof(sa), host_data, hostlen, serv_data, servlen, flags);
servlen, flags);
if (ret == SOCKET_ERROR_VALUE) { if (ret == SOCKET_ERROR_VALUE) {
ret = TranslateError(GET_ERRNO); ret = TranslateError(GET_ERRNO);
} }

View file

@ -152,7 +152,6 @@ static void SaveBanList(const Network::Room::BanList& ban_list, const std::strin
static void InitializeLogging(const std::string& log_file) { static void InitializeLogging(const std::string& log_file) {
Common::Log::Initialize(log_file); Common::Log::Initialize(log_file);
Common::Log::SetColorConsoleBackendEnabled(true); Common::Log::SetColorConsoleBackendEnabled(true);
Common::Log::Start();
} }
/// Application entry point /// Application entry point

View file

@ -429,20 +429,18 @@ Common::ParamPackage SDLState::GetSDLControllerButtonBindByGUID(
#if SDL_VERSION_ATLEAST(2, 0, 6) #if SDL_VERSION_ATLEAST(2, 0, 6)
{ {
if (mapped_button != SDL_CONTROLLER_BUTTON_INVALID) { const SDL_ExtendedGameControllerBind extended_bind =
const SDL_ExtendedGameControllerBind extended_bind = controller->bindings[mapped_button];
controller->bindings[mapped_button]; if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) {
if (extended_bind.input.axis.axis_max < extended_bind.input.axis.axis_min) { params.Set("direction", "-");
params.Set("direction", "-"); } else {
} else { params.Set("direction", "+");
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 #else
params.Set("direction", "+"); // lacks extended_bind, so just a guess params.Set("direction", "+"); // lacks extended_bind, so just a guess

View file

@ -9,7 +9,6 @@ add_executable(tests
core/memory/vm_manager.cpp core/memory/vm_manager.cpp
precompiled_headers.h precompiled_headers.h
audio_core/hle/hle.cpp audio_core/hle/hle.cpp
audio_core/hle/source.cpp
audio_core/lle/lle.cpp audio_core/lle/lle.cpp
audio_core/audio_fixures.h audio_core/audio_fixures.h
audio_core/decoder_tests.cpp audio_core/decoder_tests.cpp

View file

@ -1,379 +0,0 @@
#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);
}

View file

@ -26,6 +26,17 @@ set(SHADER_FILES
vulkan_blit_depth_stencil.frag 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_INCLUDE ${CMAKE_CURRENT_BINARY_DIR}/include)
set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders) set(SHADER_DIR ${SHADER_INCLUDE}/video_core/host_shaders)
set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE) set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
@ -33,22 +44,57 @@ set(HOST_SHADERS_INCLUDE ${SHADER_INCLUDE} PARENT_SCOPE)
set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in) set(INPUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/source_shader.h.in)
set(HEADER_GENERATOR ${CMAKE_CURRENT_SOURCE_DIR}/StringShaderHeader.cmake) 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}) foreach(FILENAME IN ITEMS ${SHADER_FILES})
string(REPLACE "." "_" SHADER_NAME ${FILENAME}) string(REPLACE "." "_" SHADER_NAME ${FILENAME})
set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME}) set(SOURCE_FILE ${CMAKE_CURRENT_SOURCE_DIR}/${FILENAME})
set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h) # Skip generating source headers on Vulkan exclusive files
add_custom_command( if (NOT ${FILENAME} MATCHES "vulkan.*")
OUTPUT set(SOURCE_HEADER_FILE ${SHADER_DIR}/${SHADER_NAME}.h)
${SOURCE_HEADER_FILE} add_custom_command(
COMMAND OUTPUT
${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE} ${SOURCE_HEADER_FILE}
MAIN_DEPENDENCY COMMAND
${SOURCE_FILE} ${CMAKE_COMMAND} -P ${HEADER_GENERATOR} ${SOURCE_FILE} ${SOURCE_HEADER_FILE} ${INPUT_FILE}
DEPENDS MAIN_DEPENDENCY
${INPUT_FILE} ${SOURCE_FILE}
# HEADER_GENERATOR should be included here but msbuild seems to assume it's always modified DEPENDS
) ${INPUT_FILE}
set(SHADER_HEADERS ${SHADER_HEADERS} ${SOURCE_HEADER_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()
endforeach() endforeach()
set(SHADER_SOURCES ${SHADER_FILES}) set(SHADER_SOURCES ${SHADER_FILES})

View file

@ -8,10 +8,6 @@ layout(location = 0) out vec2 dst_coord;
layout(location = 0) uniform mediump ivec2 dst_size; layout(location = 0) uniform mediump ivec2 dst_size;
#ifdef VULKAN
#define gl_VertexID gl_VertexIndex
#endif
const vec2 vertices[4] = 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)); vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));

View file

@ -10,7 +10,6 @@ out gl_PerVertex {
layout(location = 0) out vec2 texcoord; layout(location = 0) out vec2 texcoord;
#ifdef VULKAN #ifdef VULKAN
#define gl_VertexID gl_VertexIndex
#define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants { #define BEGIN_PUSH_CONSTANTS layout(push_constant) uniform PushConstants {
#define END_PUSH_CONSTANTS }; #define END_PUSH_CONSTANTS };
#define UNIFORM(n) #define UNIFORM(n)

View file

@ -5,10 +5,6 @@
//? #version 430 core //? #version 430 core
layout(location = 0) out vec2 tex_coord; layout(location = 0) out vec2 tex_coord;
#ifdef VULKAN
#define gl_VertexID gl_VertexIndex
#endif
const vec2 vertices[4] = 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)); vec2[4](vec2(-1.0, -1.0), vec2(1.0, -1.0), vec2(-1.0, 1.0), vec2(1.0, 1.0));

View file

@ -846,7 +846,7 @@ void RasterizerAccelerated::SyncTextureBorderColor(int tex_index) {
} }
void RasterizerAccelerated::SyncClipPlane() { void RasterizerAccelerated::SyncClipPlane() {
const u32 enable_clip1 = regs.rasterizer.clip_enable != 0; const bool enable_clip1 = regs.rasterizer.clip_enable != 0;
const auto raw_clip_coef = regs.rasterizer.GetClipCoef(); const auto raw_clip_coef = regs.rasterizer.GetClipCoef();
const Common::Vec4f new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(), 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()}; raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()};

View file

@ -148,6 +148,8 @@ inline GLenum BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
// Range check table for input // Range check table for input
if (index >= blend_func_table.size()) { if (index >= blend_func_table.size()) {
LOG_CRITICAL(Render_OpenGL, "Unknown blend factor {}", index); LOG_CRITICAL(Render_OpenGL, "Unknown blend factor {}", index);
UNREACHABLE();
return GL_ONE; return GL_ONE;
} }

View file

@ -695,7 +695,7 @@ Common::Vec4<u8> RasterizerSoftware::WriteTevConfig(
* with some basic arithmetic. Alpha combiners can be configured separately but work * with some basic arithmetic. Alpha combiners can be configured separately but work
* analogously. * analogously.
**/ **/
Common::Vec4<u8> combiner_output = {0, 0, 0, 0}; Common::Vec4<u8> combiner_output = primary_color;
Common::Vec4<u8> combiner_buffer = {0, 0, 0, 0}; Common::Vec4<u8> combiner_buffer = {0, 0, 0, 0};
Common::Vec4<u8> next_combiner_buffer = Common::Vec4<u8> next_combiner_buffer =
Common::MakeVec(regs.texturing.tev_combiner_buffer_color.r.Value(), Common::MakeVec(regs.texturing.tev_combiner_buffer_color.r.Value(),
@ -746,15 +746,9 @@ Common::Vec4<u8> RasterizerSoftware::WriteTevConfig(
* combiner_output.rgb(), but instead store it in a temporary variable until * combiner_output.rgb(), but instead store it in a temporary variable until
* alpha combining has been done. * 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 = { const std::array<Common::Vec3<u8>, 3> color_result = {
GetColorModifier(tev_stage.color_modifier1, get_source(source1)), GetColorModifier(tev_stage.color_modifier1, get_source(tev_stage.color_source1)),
GetColorModifier(tev_stage.color_modifier2, get_source(source2)), GetColorModifier(tev_stage.color_modifier2, get_source(tev_stage.color_source2)),
GetColorModifier(tev_stage.color_modifier3, get_source(tev_stage.color_source3)), GetColorModifier(tev_stage.color_modifier3, get_source(tev_stage.color_source3)),
}; };
const Common::Vec3<u8> color_output = ColorCombine(tev_stage.color_op, color_result); const Common::Vec3<u8> color_output = ColorCombine(tev_stage.color_op, color_result);

View file

@ -96,10 +96,7 @@ inline vk::BlendFactor BlendFunc(Pica::FramebufferRegs::BlendFactor factor) {
}}; }};
const auto index = static_cast<std::size_t>(factor); const auto index = static_cast<std::size_t>(factor);
if (index >= blend_func_table.size()) { ASSERT_MSG(index < blend_func_table.size(), "Unknown blend factor {}", index);
LOG_CRITICAL(Render_Vulkan, "Unknown blend factor {}", index);
return vk::BlendFactor::eOne;
}
return blend_func_table[index]; return blend_func_table[index];
} }

View file

@ -15,10 +15,10 @@
#include "video_core/renderer_vulkan/vk_memory_util.h" #include "video_core/renderer_vulkan/vk_memory_util.h"
#include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/host_shaders/vulkan_present_anaglyph_frag.h" #include "video_core/host_shaders/vulkan_present_anaglyph_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_frag.h" #include "video_core/host_shaders/vulkan_present_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_interlaced_frag.h" #include "video_core/host_shaders/vulkan_present_interlaced_frag_spv.h"
#include "video_core/host_shaders/vulkan_present_vert.h" #include "video_core/host_shaders/vulkan_present_vert_spv.h"
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>
@ -225,14 +225,10 @@ void RendererVulkan::LoadFBToScreenInfo(const Pica::FramebufferConfig& framebuff
void RendererVulkan::CompileShaders() { void RendererVulkan::CompileShaders() {
vk::Device device = instance.GetDevice(); vk::Device device = instance.GetDevice();
present_vertex_shader = present_vertex_shader = CompileSPV(VULKAN_PRESENT_VERT_SPV, device);
Compile(HostShaders::VULKAN_PRESENT_VERT, vk::ShaderStageFlagBits::eVertex, device); present_shaders[0] = CompileSPV(VULKAN_PRESENT_FRAG_SPV, device);
present_shaders[0] = present_shaders[1] = CompileSPV(VULKAN_PRESENT_ANAGLYPH_FRAG_SPV, device);
Compile(HostShaders::VULKAN_PRESENT_FRAG, vk::ShaderStageFlagBits::eFragment, device); present_shaders[2] = CompileSPV(VULKAN_PRESENT_INTERLACED_FRAG_SPV, 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(); auto properties = instance.GetPhysicalDevice().getProperties();
for (std::size_t i = 0; i < present_samplers.size(); i++) { for (std::size_t i = 0; i < present_samplers.size(); i++) {

View file

@ -10,10 +10,10 @@
#include "video_core/renderer_vulkan/vk_shader_util.h" #include "video_core/renderer_vulkan/vk_shader_util.h"
#include "video_core/renderer_vulkan/vk_texture_runtime.h" #include "video_core/renderer_vulkan/vk_texture_runtime.h"
#include "video_core/host_shaders/format_reinterpreter/vulkan_d24s8_to_rgba8_comp.h" #include "video_core/host_shaders/format_reinterpreter/vulkan_d24s8_to_rgba8_comp_spv.h"
#include "video_core/host_shaders/full_screen_triangle_vert.h" #include "video_core/host_shaders/full_screen_triangle_vert_spv.h"
#include "video_core/host_shaders/vulkan_blit_depth_stencil_frag.h" #include "video_core/host_shaders/vulkan_blit_depth_stencil_frag_spv.h"
#include "video_core/host_shaders/vulkan_depth_to_buffer_comp.h" #include "video_core/host_shaders/vulkan_depth_to_buffer_comp_spv.h"
namespace Vulkan { namespace Vulkan {
@ -189,14 +189,10 @@ BlitHelper::BlitHelper(const Instance& instance_, Scheduler& scheduler_, Descrip
PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))}, PipelineLayoutCreateInfo(&compute_buffer_provider.Layout(), true))},
two_textures_pipeline_layout{ two_textures_pipeline_layout{
device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))}, device.createPipelineLayout(PipelineLayoutCreateInfo(&two_textures_provider.Layout()))},
full_screen_vert{Compile(HostShaders::FULL_SCREEN_TRIANGLE_VERT, full_screen_vert{CompileSPV(FULL_SCREEN_TRIANGLE_VERT_SPV, device)},
vk::ShaderStageFlagBits::eVertex, device)}, d24s8_to_rgba8_comp{CompileSPV(VULKAN_D24S8_TO_RGBA8_COMP_SPV, device)},
d24s8_to_rgba8_comp{Compile(HostShaders::VULKAN_D24S8_TO_RGBA8_COMP, depth_to_buffer_comp{CompileSPV(VULKAN_DEPTH_TO_BUFFER_COMP_SPV, device)},
vk::ShaderStageFlagBits::eCompute, device)}, blit_depth_stencil_frag{CompileSPV(VULKAN_BLIT_DEPTH_STENCIL_FRAG_SPV, 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)}, d24s8_to_rgba8_pipeline{MakeComputePipeline(d24s8_to_rgba8_comp, compute_pipeline_layout)},
depth_to_buffer_pipeline{ depth_to_buffer_pipeline{
MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)}, MakeComputePipeline(depth_to_buffer_comp, compute_buffer_pipeline_layout)},

View file

@ -4,7 +4,6 @@
#include <span> #include <span>
#include <boost/container/static_vector.hpp> #include <boost/container/static_vector.hpp>
#include <fmt/format.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/settings.h" #include "common/settings.h"
@ -154,12 +153,6 @@ Instance::Instance(Core::TelemetrySession& telemetry, Frontend::EmuWindow& windo
physical_device = physical_devices[physical_device_index]; physical_device = physical_devices[physical_device_index];
available_extensions = GetSupportedExtensions(physical_device); available_extensions = GetSupportedExtensions(physical_device);
properties = physical_device.getProperties(); 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); CollectTelemetryParameters(telemetry);
CreateDevice(); CreateDevice();
@ -636,7 +629,7 @@ void Instance::CreateAllocator() {
.device = *device, .device = *device,
.pVulkanFunctions = &functions, .pVulkanFunctions = &functions,
.instance = *instance, .instance = *instance,
.vulkanApiVersion = TargetVulkanApiVersion, .vulkanApiVersion = properties.apiVersion,
}; };
const VkResult result = vmaCreateAllocator(&allocator_info, &allocator); const VkResult result = vmaCreateAllocator(&allocator_info, &allocator);
@ -677,7 +670,7 @@ void Instance::CollectToolingInfo() {
if (!tooling_info) { if (!tooling_info) {
return; return;
} }
const auto tools = physical_device.getToolPropertiesEXT(); const auto tools = physical_device.getToolProperties();
for (const vk::PhysicalDeviceToolProperties& tool : tools) { for (const vk::PhysicalDeviceToolProperties& tool : tools) {
const std::string_view name = tool.name; const std::string_view name = tool.name;
LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name); LOG_INFO(Render_Vulkan, "Attached debugging tool: {}", name);

View file

@ -17,7 +17,6 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include <boost/container/static_vector.hpp> #include <boost/container/static_vector.hpp>
#include <fmt/format.h>
#include "common/assert.h" #include "common/assert.h"
#include "common/logging/log.h" #include "common/logging/log.h"
@ -291,14 +290,13 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library,
} }
VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr); VULKAN_HPP_DEFAULT_DISPATCHER.init(vkGetInstanceProcAddr);
const u32 available_version = VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion if (!VULKAN_HPP_DEFAULT_DISPATCHER.vkEnumerateInstanceVersion) {
? vk::enumerateInstanceVersion() throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!");
: VK_API_VERSION_1_0; }
if (available_version < TargetVulkanApiVersion) {
throw std::runtime_error(fmt::format( const u32 available_version = vk::enumerateInstanceVersion();
"Vulkan {}.{} is required, but only {}.{} is supported by instance!", if (available_version < VK_API_VERSION_1_1) {
VK_VERSION_MAJOR(TargetVulkanApiVersion), VK_VERSION_MINOR(TargetVulkanApiVersion), throw std::runtime_error("Vulkan 1.0 is not supported, 1.1 is required!");
VK_VERSION_MAJOR(available_version), VK_VERSION_MINOR(available_version)));
} }
const auto extensions = GetInstanceExtensions(window_type, enable_validation); const auto extensions = GetInstanceExtensions(window_type, enable_validation);
@ -308,7 +306,7 @@ vk::UniqueInstance CreateInstance(const Common::DynamicLibrary& library,
.applicationVersion = VK_MAKE_VERSION(1, 0, 0), .applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "Citra Vulkan", .pEngineName = "Citra Vulkan",
.engineVersion = VK_MAKE_VERSION(1, 0, 0), .engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = TargetVulkanApiVersion, .apiVersion = VK_API_VERSION_1_3,
}; };
boost::container::static_vector<const char*, 2> layers; boost::container::static_vector<const char*, 2> layers;

View file

@ -19,8 +19,6 @@ enum class WindowSystemType : u8;
namespace Vulkan { namespace Vulkan {
constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_1;
using DebugCallback = using DebugCallback =
std::variant<vk::UniqueDebugUtilsMessengerEXT, vk::UniqueDebugReportCallbackEXT>; std::variant<vk::UniqueDebugUtilsMessengerEXT, vk::UniqueDebugReportCallbackEXT>;

View file

@ -3,7 +3,6 @@
// Refer to the license.txt file included. // Refer to the license.txt file included.
#include <boost/container/small_vector.hpp> #include <boost/container/small_vector.hpp>
#include <boost/container/static_vector.hpp>
#include "common/literals.h" #include "common/literals.h"
#include "common/microprofile.h" #include "common/microprofile.h"
@ -120,9 +119,9 @@ u32 UnpackDepthStencil(const VideoCore::StagingData& data, vk::Format dest) {
} }
boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers( boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
vk::ImageAspectFlags aspect, std::span<const vk::Image> images) { vk::ImageAspectFlags aspect, std::span<const vk::Image> images, std::size_t num_images) {
boost::container::small_vector<vk::ImageMemoryBarrier, 3> barriers; boost::container::small_vector<vk::ImageMemoryBarrier, 3> barriers;
for (const vk::Image& image : images) { for (std::size_t i = 0; i < num_images; i++) {
barriers.push_back(vk::ImageMemoryBarrier{ barriers.push_back(vk::ImageMemoryBarrier{
.srcAccessMask = vk::AccessFlagBits::eNone, .srcAccessMask = vk::AccessFlagBits::eNone,
.dstAccessMask = vk::AccessFlagBits::eNone, .dstAccessMask = vk::AccessFlagBits::eNone,
@ -130,7 +129,7 @@ boost::container::small_vector<vk::ImageMemoryBarrier, 3> MakeInitBarriers(
.newLayout = vk::ImageLayout::eGeneral, .newLayout = vk::ImageLayout::eGeneral,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = image, .image = images[i],
.subresourceRange{ .subresourceRange{
.aspectMask = aspect, .aspectMask = aspect,
.baseMipLevel = 0, .baseMipLevel = 0,
@ -220,10 +219,11 @@ Handle MakeHandle(const Instance* instance, u32 width, u32 height, u32 levels, T
} }
vk::UniqueFramebuffer MakeFramebuffer(vk::Device device, vk::RenderPass render_pass, u32 width, vk::UniqueFramebuffer MakeFramebuffer(vk::Device device, vk::RenderPass render_pass, u32 width,
u32 height, std::span<const vk::ImageView> attachments) { u32 height, std::span<const vk::ImageView> attachments,
u32 num_attachments) {
const vk::FramebufferCreateInfo framebuffer_info = { const vk::FramebufferCreateInfo framebuffer_info = {
.renderPass = render_pass, .renderPass = render_pass,
.attachmentCount = static_cast<u32>(attachments.size()), .attachmentCount = num_attachments,
.pAttachments = attachments.data(), .pAttachments = attachments.data(),
.width = width, .width = width,
.height = height, .height = height,
@ -715,7 +715,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1, ASSERT_MSG(format != vk::Format::eUndefined && levels >= 1,
"Image allocation parameters are invalid"); "Image allocation parameters are invalid");
boost::container::static_vector<vk::Image, 3> raw_images; u32 num_images = 0;
std::array<vk::Image, 3> raw_images;
vk::ImageCreateFlags flags{}; vk::ImageCreateFlags flags{};
if (texture_type == VideoCore::TextureType::CubeMap) { if (texture_type == VideoCore::TextureType::CubeMap) {
@ -728,18 +729,18 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceParams& param
const bool need_format_list = is_mutable && instance->IsImageFormatListSupported(); const bool need_format_list = is_mutable && instance->IsImageFormatListSupported();
handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage, handles[0] = MakeHandle(instance, width, height, levels, texture_type, format, traits.usage,
flags, traits.aspect, need_format_list, DebugName(false)); flags, traits.aspect, need_format_list, DebugName(false));
raw_images.emplace_back(handles[0].image); raw_images[num_images++] = handles[0].image;
if (res_scale != 1) { if (res_scale != 1) {
handles[1] = handles[1] =
MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format, MakeHandle(instance, GetScaledWidth(), GetScaledHeight(), levels, texture_type, format,
traits.usage, flags, traits.aspect, need_format_list, DebugName(true)); traits.usage, flags, traits.aspect, need_format_list, DebugName(true));
raw_images.emplace_back(handles[1].image); raw_images[num_images++] = handles[1].image;
} }
runtime->renderpass_cache.EndRendering(); runtime->renderpass_cache.EndRendering();
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
const auto barriers = MakeInitBarriers(aspect, raw_images); const auto barriers = MakeInitBarriers(aspect, raw_images, num_images);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, barriers); vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
@ -757,7 +758,8 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
const bool has_normal = mat && mat->Map(MapType::Normal); const bool has_normal = mat && mat->Map(MapType::Normal);
const vk::Format format = traits.native; const vk::Format format = traits.native;
boost::container::static_vector<vk::Image, 2> raw_images; u32 num_images = 0;
std::array<vk::Image, 2> raw_images;
vk::ImageCreateFlags flags{}; vk::ImageCreateFlags flags{};
if (texture_type == VideoCore::TextureType::CubeMap) { if (texture_type == VideoCore::TextureType::CubeMap) {
@ -767,23 +769,23 @@ Surface::Surface(TextureRuntime& runtime_, const VideoCore::SurfaceBase& surface
const std::string debug_name = DebugName(false, true); const std::string debug_name = DebugName(false, true);
handles[0] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format, handles[0] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
traits.usage, flags, traits.aspect, false, debug_name); traits.usage, flags, traits.aspect, false, debug_name);
raw_images.emplace_back(handles[0].image); raw_images[num_images++] = handles[0].image;
if (res_scale != 1) { if (res_scale != 1) {
handles[1] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, handles[1] = MakeHandle(instance, mat->width, mat->height, levels, texture_type,
vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect, vk::Format::eR8G8B8A8Unorm, traits.usage, flags, traits.aspect,
false, debug_name); false, debug_name);
raw_images.emplace_back(handles[1].image); raw_images[num_images++] = handles[1].image;
} }
if (has_normal) { if (has_normal) {
handles[2] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format, handles[2] = MakeHandle(instance, mat->width, mat->height, levels, texture_type, format,
traits.usage, flags, traits.aspect, false, debug_name); traits.usage, flags, traits.aspect, false, debug_name);
raw_images.emplace_back(handles[2].image); raw_images[num_images++] = handles[2].image;
} }
runtime->renderpass_cache.EndRendering(); runtime->renderpass_cache.EndRendering();
scheduler->Record([raw_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { scheduler->Record([raw_images, num_images, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
const auto barriers = MakeInitBarriers(aspect, raw_images); const auto barriers = MakeInitBarriers(aspect, raw_images, num_images);
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, barriers); vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
@ -823,10 +825,11 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params, scheduler->Record([buffer = runtime->upload_buffer.Handle(), format = traits.native, params,
staging, upload](vk::CommandBuffer cmdbuf) { staging, upload](vk::CommandBuffer cmdbuf) {
boost::container::static_vector<vk::BufferImageCopy, 2> buffer_image_copies; u32 num_copies = 1;
std::array<vk::BufferImageCopy, 2> buffer_image_copies;
const auto rect = upload.texture_rect; const auto rect = upload.texture_rect;
buffer_image_copies.emplace_back(vk::BufferImageCopy{ buffer_image_copies[0] = vk::BufferImageCopy{
.bufferOffset = upload.buffer_offset, .bufferOffset = upload.buffer_offset,
.bufferRowLength = rect.GetWidth(), .bufferRowLength = rect.GetWidth(),
.bufferImageHeight = rect.GetHeight(), .bufferImageHeight = rect.GetHeight(),
@ -838,16 +841,15 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
}, },
.imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0}, .imageOffset = {static_cast<s32>(rect.left), static_cast<s32>(rect.bottom), 0},
.imageExtent = {rect.GetWidth(), rect.GetHeight(), 1}, .imageExtent = {rect.GetWidth(), rect.GetHeight(), 1},
}); };
if (params.aspect & vk::ImageAspectFlagBits::eStencil) { if (params.aspect & vk::ImageAspectFlagBits::eStencil) {
buffer_image_copies[0].imageSubresource.aspectMask = vk::ImageAspectFlagBits::eDepth; 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 = buffer_image_copies[0];
stencil_copy.bufferOffset += UnpackDepthStencil(staging, format); stencil_copy.bufferOffset += UnpackDepthStencil(staging, format);
stencil_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil; stencil_copy.imageSubresource.aspectMask = vk::ImageAspectFlagBits::eStencil;
num_copies++;
} }
const vk::ImageMemoryBarrier read_barrier = { const vk::ImageMemoryBarrier read_barrier = {
@ -875,7 +877,7 @@ void Surface::Upload(const VideoCore::BufferTextureCopy& upload,
vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier); vk::DependencyFlagBits::eByRegion, {}, {}, read_barrier);
cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal, cmdbuf.copyBufferToImage(buffer, params.src_image, vk::ImageLayout::eTransferDstOptimal,
buffer_image_copies); num_copies, buffer_image_copies.data());
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags, cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTransfer, params.pipeline_flags,
vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier); vk::DependencyFlagBits::eByRegion, {}, {}, write_barrier);
@ -1083,7 +1085,7 @@ void Surface::ScaleUp(u32 new_scale) {
runtime->renderpass_cache.EndRendering(); runtime->renderpass_cache.EndRendering();
scheduler->Record( scheduler->Record(
[raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) { [raw_images = std::array{Image()}, aspect = traits.aspect](vk::CommandBuffer cmdbuf) {
const auto barriers = MakeInitBarriers(aspect, raw_images); const auto barriers = MakeInitBarriers(aspect, raw_images, raw_images.size());
cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe, cmdbuf.pipelineBarrier(vk::PipelineStageFlagBits::eTopOfPipe,
vk::PipelineStageFlagBits::eTopOfPipe, vk::PipelineStageFlagBits::eTopOfPipe,
vk::DependencyFlagBits::eByRegion, {}, {}, barriers); vk::DependencyFlagBits::eByRegion, {}, {}, barriers);
@ -1349,7 +1351,7 @@ vk::Framebuffer Surface::Framebuffer() noexcept {
runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false); runtime->renderpass_cache.GetRenderpass(color_format, depth_format, false);
const auto attachments = std::array{ImageView()}; const auto attachments = std::array{ImageView()};
framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(), framebuffers[index] = MakeFramebuffer(instance->GetDevice(), render_pass, GetScaledWidth(),
GetScaledHeight(), attachments); GetScaledHeight(), attachments, 1);
return framebuffers[index].get(); return framebuffers[index].get();
} }
@ -1479,16 +1481,17 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView(); image_views[index] = shadow_rendering ? surface->StorageView() : surface->FramebufferView();
}; };
boost::container::static_vector<vk::ImageView, 2> attachments; u32 num_attachments = 0;
std::array<vk::ImageView, 2> attachments;
if (color) { if (color) {
prepare(0, color); prepare(0, color);
attachments.emplace_back(image_views[0]); attachments[num_attachments++] = image_views[0];
} }
if (depth) { if (depth) {
prepare(1, depth); prepare(1, depth);
attachments.emplace_back(image_views[1]); attachments[num_attachments++] = image_views[1];
} }
const vk::Device device = runtime.GetInstance().GetDevice(); const vk::Device device = runtime.GetInstance().GetDevice();
@ -1496,10 +1499,11 @@ Framebuffer::Framebuffer(TextureRuntime& runtime, const VideoCore::FramebufferPa
render_pass = render_pass =
renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false); renderpass_cache.GetRenderpass(PixelFormat::Invalid, PixelFormat::Invalid, false);
framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(), framebuffer = MakeFramebuffer(device, render_pass, color->GetScaledWidth(),
color->GetScaledHeight(), {}); color->GetScaledHeight(), {}, 0);
} else { } else {
render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false); render_pass = renderpass_cache.GetRenderpass(formats[0], formats[1], false);
framebuffer = MakeFramebuffer(device, render_pass, width, height, attachments); framebuffer =
MakeFramebuffer(device, render_pass, width, height, attachments, num_attachments);
} }
} }

View file

@ -143,7 +143,7 @@ vec4 secondary_fragment_color = vec4(0.0);
out += "vec4 combiner_buffer = vec4(0.0);\n" out += "vec4 combiner_buffer = vec4(0.0);\n"
"vec4 next_combiner_buffer = tev_combiner_buffer_color;\n" "vec4 next_combiner_buffer = tev_combiner_buffer_color;\n"
"vec4 combiner_output = vec4(0.0);\n"; "vec4 combiner_output = rounded_primary_color;\n";
out += "vec3 color_results_1 = vec3(0.0);\n" out += "vec3 color_results_1 = vec3(0.0);\n"
"vec3 color_results_2 = vec3(0.0);\n" "vec3 color_results_2 = vec3(0.0);\n"
@ -225,75 +225,96 @@ void FragmentModule::WriteScissor() {
"gl_FragCoord.y < float(scissor_y2))) discard;\n"; "gl_FragCoord.y < float(scissor_y2))) discard;\n";
} }
std::string FragmentModule::GetSource(Pica::TexturingRegs::TevStageConfig::Source source, void FragmentModule::AppendSource(Pica::TexturingRegs::TevStageConfig::Source source,
u32 tev_index) { u32 tev_index) {
using Source = Pica::TexturingRegs::TevStageConfig::Source; using Source = Pica::TexturingRegs::TevStageConfig::Source;
switch (source) { switch (source) {
case Source::PrimaryColor: case Source::PrimaryColor:
return "rounded_primary_color"; out += "rounded_primary_color";
break;
case Source::PrimaryFragmentColor: case Source::PrimaryFragmentColor:
return "primary_fragment_color"; out += "primary_fragment_color";
break;
case Source::SecondaryFragmentColor: case Source::SecondaryFragmentColor:
return "secondary_fragment_color"; out += "secondary_fragment_color";
break;
case Source::Texture0: case Source::Texture0:
return "sampleTexUnit0()"; out += "sampleTexUnit0()";
break;
case Source::Texture1: case Source::Texture1:
return "sampleTexUnit1()"; out += "sampleTexUnit1()";
break;
case Source::Texture2: case Source::Texture2:
return "sampleTexUnit2()"; out += "sampleTexUnit2()";
break;
case Source::Texture3: case Source::Texture3:
return "sampleTexUnit3()"; out += "sampleTexUnit3()";
break;
case Source::PreviousBuffer: case Source::PreviousBuffer:
return "combiner_buffer"; out += "combiner_buffer";
break;
case Source::Constant: case Source::Constant:
return fmt::format("const_color[{}]", tev_index); out += fmt::format("const_color[{}]", tev_index);
break;
case Source::Previous: case Source::Previous:
return "combiner_output"; out += "combiner_output";
break;
default: default:
out += "vec4(0.0)";
LOG_CRITICAL(Render, "Unknown source op {}", source); LOG_CRITICAL(Render, "Unknown source op {}", source);
return "vec4(0.0)"; break;
} }
} }
void FragmentModule::AppendColorModifier( void FragmentModule::AppendColorModifier(
Pica::TexturingRegs::TevStageConfig::ColorModifier modifier, Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,
Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index) { Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index) {
using Source = Pica::TexturingRegs::TevStageConfig::Source;
using ColorModifier = Pica::TexturingRegs::TevStageConfig::ColorModifier; 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) { switch (modifier) {
case ColorModifier::SourceColor: case ColorModifier::SourceColor:
out += fmt::format("{}.rgb", color_source); AppendSource(source, tev_index);
out += ".rgb";
break; break;
case ColorModifier::OneMinusSourceColor: case ColorModifier::OneMinusSourceColor:
out += fmt::format("vec3(1.0) - {}.rgb", color_source); out += "vec3(1.0) - ";
AppendSource(source, tev_index);
out += ".rgb";
break; break;
case ColorModifier::SourceAlpha: case ColorModifier::SourceAlpha:
out += fmt::format("{}.aaa", color_source); AppendSource(source, tev_index);
out += ".aaa";
break; break;
case ColorModifier::OneMinusSourceAlpha: case ColorModifier::OneMinusSourceAlpha:
out += fmt::format("vec3(1.0) - {}.aaa", color_source); out += "vec3(1.0) - ";
AppendSource(source, tev_index);
out += ".aaa";
break; break;
case ColorModifier::SourceRed: case ColorModifier::SourceRed:
out += fmt::format("{}.rrr", color_source); AppendSource(source, tev_index);
out += ".rrr";
break; break;
case ColorModifier::OneMinusSourceRed: case ColorModifier::OneMinusSourceRed:
out += fmt::format("vec3(1.0) - {}.rrr", color_source); out += "vec3(1.0) - ";
AppendSource(source, tev_index);
out += ".rrr";
break; break;
case ColorModifier::SourceGreen: case ColorModifier::SourceGreen:
out += fmt::format("{}.ggg", color_source); AppendSource(source, tev_index);
out += ".ggg";
break; break;
case ColorModifier::OneMinusSourceGreen: case ColorModifier::OneMinusSourceGreen:
out += fmt::format("vec3(1.0) - {}.ggg", color_source); out += "vec3(1.0) - ";
AppendSource(source, tev_index);
out += ".ggg";
break; break;
case ColorModifier::SourceBlue: case ColorModifier::SourceBlue:
out += fmt::format("{}.bbb", color_source); AppendSource(source, tev_index);
out += ".bbb";
break; break;
case ColorModifier::OneMinusSourceBlue: case ColorModifier::OneMinusSourceBlue:
out += fmt::format("vec3(1.0) - {}.bbb", color_source); out += "vec3(1.0) - ";
AppendSource(source, tev_index);
out += ".bbb";
break; break;
default: default:
out += "vec3(0.0)"; out += "vec3(0.0)";
@ -305,36 +326,43 @@ void FragmentModule::AppendColorModifier(
void FragmentModule::AppendAlphaModifier( void FragmentModule::AppendAlphaModifier(
Pica::TexturingRegs::TevStageConfig::AlphaModifier modifier, Pica::TexturingRegs::TevStageConfig::AlphaModifier modifier,
Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index) { Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index) {
using Source = Pica::TexturingRegs::TevStageConfig::Source;
using AlphaModifier = Pica::TexturingRegs::TevStageConfig::AlphaModifier; 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) { switch (modifier) {
case AlphaModifier::SourceAlpha: case AlphaModifier::SourceAlpha:
out += fmt::format("{}.a", alpha_source); AppendSource(source, tev_index);
out += ".a";
break; break;
case AlphaModifier::OneMinusSourceAlpha: case AlphaModifier::OneMinusSourceAlpha:
out += fmt::format("1.0 - {}.a", alpha_source); out += "1.0 - ";
AppendSource(source, tev_index);
out += ".a";
break; break;
case AlphaModifier::SourceRed: case AlphaModifier::SourceRed:
out += fmt::format("{}.r", alpha_source); AppendSource(source, tev_index);
out += ".r";
break; break;
case AlphaModifier::OneMinusSourceRed: case AlphaModifier::OneMinusSourceRed:
out += fmt::format("1.0 - {}.r", alpha_source); out += "1.0 - ";
AppendSource(source, tev_index);
out += ".r";
break; break;
case AlphaModifier::SourceGreen: case AlphaModifier::SourceGreen:
out += fmt::format("{}.g", alpha_source); AppendSource(source, tev_index);
out += ".g";
break; break;
case AlphaModifier::OneMinusSourceGreen: case AlphaModifier::OneMinusSourceGreen:
out += fmt::format("1.0 - {}.g", alpha_source); out += "1.0 - ";
AppendSource(source, tev_index);
out += ".g";
break; break;
case AlphaModifier::SourceBlue: case AlphaModifier::SourceBlue:
out += fmt::format("{}.b", alpha_source); AppendSource(source, tev_index);
out += ".b";
break; break;
case AlphaModifier::OneMinusSourceBlue: case AlphaModifier::OneMinusSourceBlue:
out += fmt::format("1.0 - {}.b", alpha_source); out += "1.0 - ";
AppendSource(source, tev_index);
out += ".b";
break; break;
default: default:
out += "0.0"; out += "0.0";
@ -356,11 +384,12 @@ void FragmentModule::AppendColorCombiner(Pica::TexturingRegs::TevStageConfig::Op
case Operation::AddSigned: case Operation::AddSigned:
return "color_results_1 + color_results_2 - vec3(0.5)"; return "color_results_1 + color_results_2 - vec3(0.5)";
case Operation::Lerp: case Operation::Lerp:
return "mix(color_results_2, color_results_1, color_results_3)"; return "color_results_1 * color_results_3 + color_results_2 * (vec3(1.0) - "
"color_results_3)";
case Operation::Subtract: case Operation::Subtract:
return "color_results_1 - color_results_2"; return "color_results_1 - color_results_2";
case Operation::MultiplyThenAdd: case Operation::MultiplyThenAdd:
return "fma(color_results_1, color_results_2, color_results_3)"; return "color_results_1 * color_results_2 + color_results_3";
case Operation::AddThenMultiply: case Operation::AddThenMultiply:
return "min(color_results_1 + color_results_2, vec3(1.0)) * color_results_3"; return "min(color_results_1 + color_results_2, vec3(1.0)) * color_results_3";
case Operation::Dot3_RGB: case Operation::Dot3_RGB:
@ -387,11 +416,11 @@ void FragmentModule::AppendAlphaCombiner(Pica::TexturingRegs::TevStageConfig::Op
case Operation::AddSigned: case Operation::AddSigned:
return "alpha_results_1 + alpha_results_2 - 0.5"; return "alpha_results_1 + alpha_results_2 - 0.5";
case Operation::Lerp: case Operation::Lerp:
return "mix(alpha_results_2, alpha_results_1, alpha_results_3)"; return "alpha_results_1 * alpha_results_3 + alpha_results_2 * (1.0 - alpha_results_3)";
case Operation::Subtract: case Operation::Subtract:
return "alpha_results_1 - alpha_results_2"; return "alpha_results_1 - alpha_results_2";
case Operation::MultiplyThenAdd: case Operation::MultiplyThenAdd:
return "fma(alpha_results_1, alpha_results_2, alpha_results_3)"; return "alpha_results_1 * alpha_results_2 + alpha_results_3";
case Operation::AddThenMultiply: case Operation::AddThenMultiply:
return "min(alpha_results_1 + alpha_results_2, 1.0) * alpha_results_3"; return "min(alpha_results_1 + alpha_results_2, 1.0) * alpha_results_3";
default: default:

View file

@ -41,8 +41,8 @@ private:
/// Writes the code to emulate PICA min/max blending factors /// Writes the code to emulate PICA min/max blending factors
void WriteBlending(); void WriteBlending();
/// Returns the specified TEV stage source component(s) /// Writes the specified TEV stage source component(s)
std::string GetSource(Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index); void AppendSource(Pica::TexturingRegs::TevStageConfig::Source source, u32 tev_index);
/// Writes the color components to use for the specified TEV stage color modifier /// Writes the color components to use for the specified TEV stage color modifier
void AppendColorModifier(Pica::TexturingRegs::TevStageConfig::ColorModifier modifier, void AppendColorModifier(Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,

View file

@ -40,7 +40,7 @@ layout (binding = 1, std140) uniform vs_data {
vec4 clip_coef; vec4 clip_coef;
}; };
const vec2 EPSILON_Z = vec2(0.000001f, -1.00001f); const vec2 EPSILON_Z = vec2(0.00000001f, -1.00001f);
vec4 SanitizeVertex(vec4 vtx_pos) { vec4 SanitizeVertex(vec4 vtx_pos) {
float ndc_z = vtx_pos.z / vtx_pos.w; float ndc_z = vtx_pos.z / vtx_pos.w;

View file

@ -86,7 +86,7 @@ struct PicaUniformsData {
}; };
struct VSUniformData { struct VSUniformData {
u32 enable_clip1; bool enable_clip1;
alignas(16) Common::Vec4f clip_coef; alignas(16) Common::Vec4f clip_coef;
}; };
static_assert(sizeof(VSUniformData) == 32, static_assert(sizeof(VSUniformData) == 32,

View file

@ -55,7 +55,7 @@ void FragmentModule::Generate() {
combiner_buffer = ConstF32(0.f, 0.f, 0.f, 0.f); combiner_buffer = ConstF32(0.f, 0.f, 0.f, 0.f);
next_combiner_buffer = GetShaderDataMember(vec_ids.Get(4), ConstS32(26)); next_combiner_buffer = GetShaderDataMember(vec_ids.Get(4), ConstS32(26));
combiner_output = ConstF32(0.f, 0.f, 0.f, 0.f); last_tex_env_out = rounded_primary_color;
// Write shader bytecode to emulate PICA TEV stages // Write shader bytecode to emulate PICA TEV stages
for (u32 index = 0; index < config.texture.tev_stages.size(); ++index) { for (u32 index = 0; index < config.texture.tev_stages.size(); ++index) {
@ -76,7 +76,7 @@ void FragmentModule::Generate() {
break; break;
} }
Id color{Byteround(combiner_output, 4)}; Id color{Byteround(last_tex_env_out, 4)};
switch (config.framebuffer.logic_op) { switch (config.framebuffer.logic_op) {
case FramebufferRegs::LogicOp::Clear: case FramebufferRegs::LogicOp::Clear:
color = ConstF32(0.f, 0.f, 0.f, 0.f); color = ConstF32(0.f, 0.f, 0.f, 0.f);
@ -184,12 +184,12 @@ void FragmentModule::WriteFog() {
// Blend the fog // Blend the fog
const Id tex_env_rgb{ const Id tex_env_rgb{
OpVectorShuffle(vec_ids.Get(3), combiner_output, combiner_output, 0, 1, 2)}; OpVectorShuffle(vec_ids.Get(3), last_tex_env_out, last_tex_env_out, 0, 1, 2)};
const Id fog_color{GetShaderDataMember(vec_ids.Get(3), ConstS32(19))}; const Id fog_color{GetShaderDataMember(vec_ids.Get(3), ConstS32(19))};
const Id fog_factor_rgb{ const Id fog_factor_rgb{
OpCompositeConstruct(vec_ids.Get(3), fog_factor, fog_factor, fog_factor)}; 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)}; const Id fog_result{OpFMix(vec_ids.Get(3), fog_color, tex_env_rgb, fog_factor_rgb)};
combiner_output = OpVectorShuffle(vec_ids.Get(4), fog_result, combiner_output, 0, 1, 2, 6); last_tex_env_out = OpVectorShuffle(vec_ids.Get(4), fog_result, last_tex_env_out, 0, 1, 2, 6);
} }
void FragmentModule::WriteGas() { void FragmentModule::WriteGas() {
@ -630,7 +630,8 @@ void FragmentModule::WriteLighting() {
} }
void FragmentModule::WriteTevStage(s32 index) { void FragmentModule::WriteTevStage(s32 index) {
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[index]; const TexturingRegs::TevStageConfig stage =
static_cast<const TexturingRegs::TevStageConfig>(config.texture.tev_stages[index]);
// Detects if a TEV stage is configured to be skipped (to avoid generating unnecessary code) // Detects if a TEV stage is configured to be skipped (to avoid generating unnecessary code)
const auto is_passthrough_tev_stage = [](const TevStageConfig& stage) { const auto is_passthrough_tev_stage = [](const TevStageConfig& stage) {
@ -673,18 +674,18 @@ void FragmentModule::WriteTevStage(s32 index) {
alpha_output = alpha_output =
OpFMul(f32_id, alpha_output, ConstF32(static_cast<float>(stage.GetAlphaMultiplier()))); OpFMul(f32_id, alpha_output, ConstF32(static_cast<float>(stage.GetAlphaMultiplier())));
alpha_output = OpFClamp(f32_id, alpha_output, ConstF32(0.f), ConstF32(1.f)); alpha_output = OpFClamp(f32_id, alpha_output, ConstF32(0.f), ConstF32(1.f));
combiner_output = OpCompositeConstruct(vec_ids.Get(4), color_output, alpha_output); last_tex_env_out = OpCompositeConstruct(vec_ids.Get(4), color_output, alpha_output);
} }
combiner_buffer = next_combiner_buffer; combiner_buffer = next_combiner_buffer;
if (config.TevStageUpdatesCombinerBufferColor(index)) { if (config.TevStageUpdatesCombinerBufferColor(index)) {
next_combiner_buffer = next_combiner_buffer =
OpVectorShuffle(vec_ids.Get(4), combiner_output, next_combiner_buffer, 0, 1, 2, 7); OpVectorShuffle(vec_ids.Get(4), last_tex_env_out, next_combiner_buffer, 0, 1, 2, 7);
} }
if (config.TevStageUpdatesCombinerBufferAlpha(index)) { if (config.TevStageUpdatesCombinerBufferAlpha(index)) {
next_combiner_buffer = next_combiner_buffer =
OpVectorShuffle(vec_ids.Get(4), next_combiner_buffer, combiner_output, 0, 1, 2, 7); OpVectorShuffle(vec_ids.Get(4), next_combiner_buffer, last_tex_env_out, 0, 1, 2, 7);
} }
} }
@ -727,7 +728,7 @@ void FragmentModule::WriteAlphaTestCondition(FramebufferRegs::CompareFunc func)
case CompareFunc::GreaterThan: case CompareFunc::GreaterThan:
case CompareFunc::GreaterThanOrEqual: { case CompareFunc::GreaterThanOrEqual: {
const Id alpha_scaled{ const Id alpha_scaled{
OpFMul(f32_id, OpCompositeExtract(f32_id, combiner_output, 3), ConstF32(255.f))}; OpFMul(f32_id, OpCompositeExtract(f32_id, last_tex_env_out, 3), ConstF32(255.f))};
const Id alpha_int{OpConvertFToS(i32_id, alpha_scaled)}; const Id alpha_int{OpConvertFToS(i32_id, alpha_scaled)};
const Id alphatest_ref{GetShaderDataMember(i32_id, ConstS32(1))}; const Id alphatest_ref{GetShaderDataMember(i32_id, ConstS32(1))};
const Id alpha_comp_ref{compare(alpha_int, alphatest_ref)}; const Id alpha_comp_ref{compare(alpha_int, alphatest_ref)};
@ -1279,7 +1280,7 @@ Id FragmentModule::LookupLightingLUT(Id lut_index, Id index, Id delta) {
return OpFma(f32_id, entry_g, delta, entry_r); return OpFma(f32_id, entry_g, delta, entry_r);
} }
Id FragmentModule::GetSource(TevStageConfig::Source source, s32 index) { Id FragmentModule::AppendSource(TevStageConfig::Source source, s32 index) {
using Source = TevStageConfig::Source; using Source = TevStageConfig::Source;
switch (source) { switch (source) {
case Source::PrimaryColor: case Source::PrimaryColor:
@ -1301,7 +1302,7 @@ Id FragmentModule::GetSource(TevStageConfig::Source source, s32 index) {
case Source::Constant: case Source::Constant:
return GetShaderDataMember(vec_ids.Get(4), ConstS32(25), ConstS32(index)); return GetShaderDataMember(vec_ids.Get(4), ConstS32(25), ConstS32(index));
case Source::Previous: case Source::Previous:
return combiner_output; return last_tex_env_out;
default: default:
LOG_CRITICAL(Render, "Unknown source op {}", source); LOG_CRITICAL(Render, "Unknown source op {}", source);
return ConstF32(0.f, 0.f, 0.f, 0.f); return ConstF32(0.f, 0.f, 0.f, 0.f);
@ -1310,11 +1311,8 @@ Id FragmentModule::GetSource(TevStageConfig::Source source, s32 index) {
Id FragmentModule::AppendColorModifier(TevStageConfig::ColorModifier modifier, Id FragmentModule::AppendColorModifier(TevStageConfig::ColorModifier modifier,
TevStageConfig::Source source, s32 index) { TevStageConfig::Source source, s32 index) {
using Source = TevStageConfig::Source;
using ColorModifier = TevStageConfig::ColorModifier; using ColorModifier = TevStageConfig::ColorModifier;
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[index]; const Id source_color{AppendSource(source, 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 Id one_vec{ConstF32(1.f, 1.f, 1.f)};
const auto shuffle = [&](s32 r, s32 g, s32 b) -> Id { const auto shuffle = [&](s32 r, s32 g, s32 b) -> Id {
@ -1350,14 +1348,11 @@ Id FragmentModule::AppendColorModifier(TevStageConfig::ColorModifier modifier,
Id FragmentModule::AppendAlphaModifier(TevStageConfig::AlphaModifier modifier, Id FragmentModule::AppendAlphaModifier(TevStageConfig::AlphaModifier modifier,
TevStageConfig::Source source, s32 index) { TevStageConfig::Source source, s32 index) {
using Source = TevStageConfig::Source;
using AlphaModifier = TevStageConfig::AlphaModifier; using AlphaModifier = TevStageConfig::AlphaModifier;
const TexturingRegs::TevStageConfig stage = config.texture.tev_stages[index]; const Id source_color{AppendSource(source, 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 Id one_f32{ConstF32(1.f)};
const auto component = [&](s32 c) -> Id { return OpCompositeExtract(f32_id, source_alpha, c); }; const auto component = [&](s32 c) -> Id { return OpCompositeExtract(f32_id, source_color, c); };
switch (modifier) { switch (modifier) {
case AlphaModifier::SourceAlpha: case AlphaModifier::SourceAlpha:

View file

@ -99,13 +99,13 @@ private:
/// Lookups the lighting LUT at the provided lut_index /// Lookups the lighting LUT at the provided lut_index
[[nodiscard]] Id LookupLightingLUT(Id lut_index, Id index, Id delta); [[nodiscard]] Id LookupLightingLUT(Id lut_index, Id index, Id delta);
/// Returns the specified TEV stage source component(s) /// Writes the specified TEV stage source component(s)
[[nodiscard]] Id GetSource(Pica::TexturingRegs::TevStageConfig::Source source, s32 index); [[nodiscard]] Id AppendSource(Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
/// Writes the color components to use for the specified TEV stage color modifier /// Writes the color components to use for the specified TEV stage color modifier
[[nodiscard]] Id AppendColorModifier( [[nodiscard]] Id AppendColorModifier(
Pica::TexturingRegs::TevStageConfig::ColorModifier modifier, Pica::TexturingRegs::TevStageConfig::ColorModifier modifier,
Pica::TexturingRegs::TevStageConfig::Source source, s32 tev_index); Pica::TexturingRegs::TevStageConfig::Source source, s32 index);
/// Writes the alpha component to use for the specified TEV stage alpha modifier /// Writes the alpha component to use for the specified TEV stage alpha modifier
[[nodiscard]] Id AppendAlphaModifier( [[nodiscard]] Id AppendAlphaModifier(
@ -272,7 +272,7 @@ private:
Id secondary_fragment_color{}; Id secondary_fragment_color{};
Id combiner_buffer{}; Id combiner_buffer{};
Id next_combiner_buffer{}; Id next_combiner_buffer{};
Id combiner_output{}; Id last_tex_env_out{};
Id color_results_1{}; Id color_results_1{};
Id color_results_2{}; Id color_results_2{};

View file

@ -1,8 +0,0 @@
#!/bin/bash -ex
# SPDX-FileCopyrightText: 2024 yuzu Emulator Project
# SPDX-License-Identifier: MIT
git submodule sync
git submodule foreach --recursive git reset --hard
git submodule update --init --recursive