Compare commits

...

10 Commits

Author SHA1 Message Date
bac61d012c service: Stub mcu::HWC 2024-02-09 18:41:39 +02:00
2766118e33 http: Implement various missing commands (#7415) 2024-02-08 11:01:46 -08:00
06b26691ba soc: Pass accurate sockaddr length to socket functions. (#7426) 2024-02-08 11:01:38 -08:00
d41ce64f7b Add ipv6 socket support (#7418)
* Add IPV6 socket support

* Suggestions
2024-02-07 19:22:44 -08:00
1165a708d5 .tx/config: Use language mappings for android "tx pull" (#7422)
The language names we are using in the android resources differ from those on Transifex.

We need to manually specify mappings for them, so Transifex is able to place the files in the correct folders.
2024-02-07 05:41:29 -08:00
19784355f9 build: Improve support for Windows cross-compilation. (#7389)
* build: Improve support for Windows cross-compilation.

* build: Move linuxdeploy download to bundle target execution time.
2024-02-05 10:09:50 -08:00
aa6a29d7e1 AudioCore/HLE/source: Partially implement last_buffer_id (#7397)
* AudioCore/HLE/source: Partially implement last_buffer_id

shared_memory.h: fix typo

* tests\audio_core\hle\source.cpp: Add test cases to verify last_buffer_id
2024-02-05 09:54:13 -08:00
106364e01e video_core: Use source3 when GPU_PREVIOUS is used in first stage (#7411) 2024-02-05 09:53:54 -08:00
d5a1bd07f3 glsl_shader_gen: Increase z=0 epsillon (#7408) 2024-02-05 09:53:41 -08:00
8afa27718c dumpkeys: Add seeddb.bin to output files. (#7417) 2024-02-05 09:14:14 -08:00
32 changed files with 2015 additions and 383 deletions

View File

@ -85,8 +85,6 @@ option(ENABLE_VULKAN "Enables the Vulkan renderer" ON)
option(USE_DISCORD_PRESENCE "Enables Discord Rich Presence" OFF)
CMAKE_DEPENDENT_OPTION(CITRA_ENABLE_BUNDLE_TARGET "Enable the distribution bundling target." ON "NOT ANDROID AND NOT IOS" OFF)
# Compile options
CMAKE_DEPENDENT_OPTION(COMPILE_WITH_DWARF "Add DWARF debugging information" ${IS_DEBUG_BUILD} "MINGW" OFF)
option(ENABLE_LTO "Enable link time optimization" ${DEFAULT_ENABLE_LTO})
@ -249,6 +247,26 @@ if (ENABLE_QT)
if (ENABLE_QT_TRANSLATION)
find_package(Qt6 REQUIRED COMPONENTS LinguistTools)
endif()
if (NOT DEFINED QT_TARGET_PATH)
# Determine the location of the compile target's Qt.
get_target_property(qtcore_path Qt6::Core LOCATION_Release)
string(FIND "${qtcore_path}" "/bin/" qtcore_path_bin_pos REVERSE)
string(FIND "${qtcore_path}" "/lib/" qtcore_path_lib_pos REVERSE)
if (qtcore_path_bin_pos GREATER qtcore_path_lib_pos)
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_bin_pos} QT_TARGET_PATH)
else()
string(SUBSTRING "${qtcore_path}" 0 ${qtcore_path_lib_pos} QT_TARGET_PATH)
endif()
endif()
if (NOT DEFINED QT_HOST_PATH)
# Use the same for host Qt if none is defined.
set(QT_HOST_PATH "${QT_TARGET_PATH}")
endif()
message(STATUS "Using target Qt at ${QT_TARGET_PATH}")
message(STATUS "Using host Qt at ${QT_HOST_PATH}")
endif()
# Use system tsl::robin_map if available (otherwise we fallback to version bundled with dynarmic)
@ -424,7 +442,8 @@ else()
endif()
# Create target for outputting distributable bundles.
if (CITRA_ENABLE_BUNDLE_TARGET)
# Not supported for mobile platforms as distributables are built differently.
if (NOT ANDROID AND NOT IOS)
include(BundleTarget)
if (ENABLE_SDL2_FRONTEND)
bundle_target(citra)

View File

@ -2,37 +2,104 @@
if (BUNDLE_TARGET_EXECUTE)
# --- Bundling method logic ---
function(symlink_safe_copy from to)
if (WIN32)
# Use cmake copy for maximum compatibility.
execute_process(COMMAND ${CMAKE_COMMAND} -E copy "${from}" "${to}"
RESULT_VARIABLE cp_result)
else()
# Use native copy to turn symlinks into normal files.
execute_process(COMMAND cp -L "${from}" "${to}"
RESULT_VARIABLE cp_result)
endif()
if (NOT cp_result EQUAL "0")
message(FATAL_ERROR "cp \"${from}\" \"${to}\" failed: ${cp_result}")
endif()
endfunction()
function(bundle_qt executable_path)
if (WIN32)
# Perform standalone bundling first to copy over all used libraries, as windeployqt does not do this.
bundle_standalone("${executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
get_filename_component(executable_parent_dir "${executable_path}" DIRECTORY)
find_program(windeployqt_executable windeployqt6)
# Create a qt.conf file pointing to the app directory.
# This ensures Qt can find its plugins.
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nprefix = .")
file(WRITE "${executable_parent_dir}/qt.conf" "[Paths]\nPrefix = .")
find_program(windeployqt_executable windeployqt6 PATHS "${QT_HOST_PATH}/bin")
find_program(qtpaths_executable qtpaths6 PATHS "${QT_HOST_PATH}/bin")
# TODO: Hack around windeployqt's poor cross-compilation support by
# TODO: making a local copy with a prefix pointing to the target Qt.
if (NOT "${QT_HOST_PATH}" STREQUAL "${QT_TARGET_PATH}")
set(windeployqt_dir "${BINARY_PATH}/windeployqt_copy")
file(MAKE_DIRECTORY "${windeployqt_dir}")
symlink_safe_copy("${windeployqt_executable}" "${windeployqt_dir}/windeployqt.exe")
symlink_safe_copy("${qtpaths_executable}" "${windeployqt_dir}/qtpaths.exe")
symlink_safe_copy("${QT_HOST_PATH}/bin/Qt6Core.dll" "${windeployqt_dir}")
if (EXISTS "${QT_TARGET_PATH}/share")
# Unix-style Qt; we need to wire up the paths manually.
file(WRITE "${windeployqt_dir}/qt.conf" "\
[Paths]\n
Prefix = ${QT_TARGET_PATH}\n \
ArchData = ${QT_TARGET_PATH}/share/qt6\n \
Binaries = ${QT_TARGET_PATH}/bin\n \
Data = ${QT_TARGET_PATH}/share/qt6\n \
Documentation = ${QT_TARGET_PATH}/share/qt6/doc\n \
Headers = ${QT_TARGET_PATH}/include/qt6\n \
Libraries = ${QT_TARGET_PATH}/lib\n \
LibraryExecutables = ${QT_TARGET_PATH}/share/qt6/bin\n \
Plugins = ${QT_TARGET_PATH}/share/qt6/plugins\n \
QmlImports = ${QT_TARGET_PATH}/share/qt6/qml\n \
Translations = ${QT_TARGET_PATH}/share/qt6/translations\n \
")
else()
# Windows-style Qt; the defaults should suffice.
file(WRITE "${windeployqt_dir}/qt.conf" "[Paths]\nPrefix = ${QT_TARGET_PATH}")
endif()
set(windeployqt_executable "${windeployqt_dir}/windeployqt.exe")
set(qtpaths_executable "${windeployqt_dir}/qtpaths.exe")
endif()
message(STATUS "Executing windeployqt for executable ${executable_path}")
execute_process(COMMAND "${windeployqt_executable}" "${executable_path}"
--qtpaths "${qtpaths_executable}"
--no-compiler-runtime --no-system-d3d-compiler --no-opengl-sw --no-translations
--plugindir "${executable_parent_dir}/plugins")
--plugindir "${executable_parent_dir}/plugins"
RESULT_VARIABLE windeployqt_result)
if (NOT windeployqt_result EQUAL "0")
message(FATAL_ERROR "windeployqt failed: ${windeployqt_result}")
endif()
# Remove the FFmpeg multimedia plugin as we don't include FFmpeg.
# We want to use the Windows media plugin instead, which is also included.
file(REMOVE "${executable_parent_dir}/plugins/multimedia/ffmpegmediaplugin.dll")
elseif (APPLE)
get_filename_component(executable_name "${executable_path}" NAME_WE)
find_program(MACDEPLOYQT_EXECUTABLE macdeployqt6)
find_program(macdeployqt_executable macdeployqt6 PATHS "${QT_HOST_PATH}/bin")
message(STATUS "Executing macdeployqt for executable ${executable_path}")
message(STATUS "Executing macdeployqt at \"${macdeployqt_executable}\" for executable \"${executable_path}\"")
execute_process(
COMMAND "${MACDEPLOYQT_EXECUTABLE}"
COMMAND "${macdeployqt_executable}"
"${executable_path}"
"-executable=${executable_path}/Contents/MacOS/${executable_name}"
-always-overwrite)
-always-overwrite
RESULT_VARIABLE macdeployqt_result)
if (NOT macdeployqt_result EQUAL "0")
message(FATAL_ERROR "macdeployqt failed: ${macdeployqt_result}")
endif()
# Bundling libraries can rewrite path information and break code signatures of system libraries.
# Perform an ad-hoc re-signing on the whole app bundle to fix this.
execute_process(COMMAND codesign --deep -fs - "${executable_path}")
execute_process(COMMAND codesign --deep -fs - "${executable_path}"
RESULT_VARIABLE codesign_result)
if (NOT codesign_result EQUAL "0")
message(FATAL_ERROR "codesign failed: ${codesign_result}")
endif()
else()
message(FATAL_ERROR "Unsupported OS for Qt bundling.")
endif()
@ -44,9 +111,9 @@ if (BUNDLE_TARGET_EXECUTE)
if (enable_qt)
# Find qmake to make sure the plugin uses the right version of Qt.
find_program(QMAKE_EXECUTABLE qmake6)
find_program(qmake_executable qmake6 PATHS "${QT_HOST_PATH}/bin")
set(extra_linuxdeploy_env "QMAKE=${QMAKE_EXECUTABLE}")
set(extra_linuxdeploy_env "QMAKE=${qmake_executable}")
set(extra_linuxdeploy_args --plugin qt)
endif()
@ -59,7 +126,11 @@ if (BUNDLE_TARGET_EXECUTE)
--executable "${executable_path}"
--icon-file "${source_path}/dist/citra.svg"
--desktop-file "${source_path}/dist/${executable_name}.desktop"
--appdir "${appdir_path}")
--appdir "${appdir_path}"
RESULT_VARIABLE linuxdeploy_appdir_result)
if (NOT linuxdeploy_appdir_result EQUAL "0")
message(FATAL_ERROR "linuxdeploy failed to create AppDir: ${linuxdeploy_appdir_result}")
endif()
if (enable_qt)
set(qt_hook_file "${appdir_path}/apprun-hooks/linuxdeploy-plugin-qt-hook.sh")
@ -82,7 +153,11 @@ if (BUNDLE_TARGET_EXECUTE)
"OUTPUT=${bundle_dir}/${executable_name}.AppImage"
"${linuxdeploy_executable}"
--output appimage
--appdir "${appdir_path}")
--appdir "${appdir_path}"
RESULT_VARIABLE linuxdeploy_appimage_result)
if (NOT linuxdeploy_appimage_result EQUAL "0")
message(FATAL_ERROR "linuxdeploy failed to create AppImage: ${linuxdeploy_appimage_result}")
endif()
endfunction()
function(bundle_standalone executable_path original_executable_path bundle_library_paths)
@ -109,16 +184,23 @@ if (BUNDLE_TARGET_EXECUTE)
file(MAKE_DIRECTORY ${lib_dir})
foreach (lib_file IN LISTS resolved_deps)
message(STATUS "Bundling library ${lib_file}")
# Use native copy to turn symlinks into normal files.
execute_process(COMMAND cp -L "${lib_file}" "${lib_dir}")
symlink_safe_copy("${lib_file}" "${lib_dir}")
endforeach()
endif()
# Add libs directory to executable rpath where applicable.
if (APPLE)
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}")
execute_process(COMMAND install_name_tool -add_rpath "@loader_path/libs" "${executable_path}"
RESULT_VARIABLE install_name_tool_result)
if (NOT install_name_tool_result EQUAL "0")
message(FATAL_ERROR "install_name_tool failed: ${install_name_tool_result}")
endif()
elseif (UNIX)
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}")
execute_process(COMMAND patchelf --set-rpath '$ORIGIN/../libs' "${executable_path}"
RESULT_VARIABLE patchelf_result)
if (NOT patchelf_result EQUAL "0")
message(FATAL_ERROR "patchelf failed: ${patchelf_result}")
endif()
endif()
endfunction()
@ -127,7 +209,7 @@ if (BUNDLE_TARGET_EXECUTE)
set(bundle_dir ${BINARY_PATH}/bundle)
# On Linux, always bundle an AppImage.
if (DEFINED LINUXDEPLOY)
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if (IN_PLACE)
message(FATAL_ERROR "Cannot bundle for Linux in-place.")
endif()
@ -146,14 +228,12 @@ if (BUNDLE_TARGET_EXECUTE)
if (BUNDLE_QT)
bundle_qt("${bundled_executable_path}")
endif()
if (WIN32 OR NOT BUNDLE_QT)
else()
bundle_standalone("${bundled_executable_path}" "${EXECUTABLE_PATH}" "${BUNDLE_LIBRARY_PATHS}")
endif()
endif()
else()
# --- Bundling target creation logic ---
elseif (BUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY)
# --- linuxdeploy download logic ---
# Downloads and extracts a linuxdeploy component.
function(download_linuxdeploy_component base_dir name executable_name)
@ -161,7 +241,7 @@ else()
if (NOT EXISTS "${executable_file}")
message(STATUS "Downloading ${executable_name}")
file(DOWNLOAD
"https://github.com/linuxdeploy/${name}/releases/download/continuous/${executable_name}"
"https://github.com/${name}/releases/download/continuous/${executable_name}"
"${executable_file}" SHOW_PROGRESS)
file(CHMOD "${executable_file}" PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE)
@ -170,7 +250,11 @@ else()
message(STATUS "Extracting ${executable_name}")
execute_process(
COMMAND "${executable_file}" --appimage-extract
WORKING_DIRECTORY "${base_dir}")
WORKING_DIRECTORY "${base_dir}"
RESULT_VARIABLE extract_result)
if (NOT extract_result EQUAL "0")
message(FATAL_ERROR "AppImage extract failed: ${extract_result}")
endif()
else()
message(STATUS "Copying ${executable_name}")
file(COPY "${executable_file}" DESTINATION "${base_dir}/squashfs-root/usr/bin/")
@ -178,89 +262,102 @@ else()
endif()
endfunction()
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-${LINUXDEPLOY_ARCH}.AppImage")
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "darealshinji/linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt.sh")
download_linuxdeploy_component("${LINUXDEPLOY_PATH}" "linuxdeploy/linuxdeploy" "linuxdeploy-${LINUXDEPLOY_ARCH}.AppImage")
else()
# --- Bundling target creation logic ---
# Creates the base bundle target with common files and pre-bundle steps.
function(create_base_bundle_target)
message(STATUS "Creating base bundle target")
add_custom_target(bundle)
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
# On Linux, add a command to prepare linuxdeploy and any required plugins before any bundling occurs.
if (CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND}
"-DBUNDLE_TARGET_DOWNLOAD_LINUXDEPLOY=1"
"-DLINUXDEPLOY_PATH=${CMAKE_BINARY_DIR}/externals/linuxdeploy"
"-DLINUXDEPLOY_ARCH=${CMAKE_HOST_SYSTEM_PROCESSOR}"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
endif()
endfunction()
# Adds a target to the bundle target, packing in required libraries.
# If in_place is true, the bundling will be done in-place as part of the specified target.
function(bundle_target_internal target_name in_place)
# Create base bundle target if it does not exist.
if (NOT in_place AND NOT TARGET bundle)
message(STATUS "Creating base bundle target")
add_custom_target(bundle)
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E make_directory "${CMAKE_BINARY_DIR}/bundle/dist/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/dist/icon.png" "${CMAKE_BINARY_DIR}/bundle/dist/citra.png")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/license.txt" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy "${CMAKE_SOURCE_DIR}/README.md" "${CMAKE_BINARY_DIR}/bundle/")
add_custom_command(
TARGET bundle
COMMAND ${CMAKE_COMMAND} -E copy_directory "${CMAKE_SOURCE_DIR}/dist/scripting" "${CMAKE_BINARY_DIR}/bundle/scripting")
create_base_bundle_target()
endif()
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_FILE:${target_name}>")
set(bundle_executable_path "$<TARGET_FILE:${target_name}>")
if (target_name MATCHES ".*qt")
set(BUNDLE_QT ON)
set(bundle_qt ON)
if (APPLE)
# For Qt targets on Apple, expect an app bundle.
set(BUNDLE_EXECUTABLE_PATH "$<TARGET_BUNDLE_DIR:${target_name}>")
set(bundle_executable_path "$<TARGET_BUNDLE_DIR:${target_name}>")
endif()
else()
set(BUNDLE_QT OFF)
set(bundle_qt OFF)
endif()
# Build a list of library search paths from prefix paths.
foreach(prefix_path IN LISTS CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
foreach(prefix_path IN LISTS CMAKE_FIND_ROOT_PATH CMAKE_PREFIX_PATH CMAKE_SYSTEM_PREFIX_PATH)
if (WIN32)
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/bin")
list(APPEND bundle_library_paths "${prefix_path}/bin")
endif()
list(APPEND BUNDLE_LIBRARY_PATHS "${prefix_path}/lib")
list(APPEND bundle_library_paths "${prefix_path}/lib")
endforeach()
foreach(library_path IN LISTS CMAKE_SYSTEM_LIBRARY_PATH)
list(APPEND BUNDLE_LIBRARY_PATHS "${library_path}")
list(APPEND bundle_library_paths "${library_path}")
endforeach()
# On Linux, prepare linuxdeploy and any required plugins.
if (CMAKE_SYSTEM_NAME STREQUAL "Linux")
set(LINUXDEPLOY_BASE "${CMAKE_BINARY_DIR}/externals/linuxdeploy")
# Download plugins first so they don't overwrite linuxdeploy's AppRun file.
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-qt" "linuxdeploy-plugin-qt-x86_64.AppImage")
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy-plugin-checkrt" "linuxdeploy-plugin-checkrt-x86_64.sh")
download_linuxdeploy_component("${LINUXDEPLOY_BASE}" "linuxdeploy" "linuxdeploy-x86_64.AppImage")
set(EXTRA_BUNDLE_ARGS "-DLINUXDEPLOY=${LINUXDEPLOY_BASE}/squashfs-root/AppRun")
endif()
if (in_place)
message(STATUS "Adding in-place bundling to ${target_name}")
set(DEST_TARGET ${target_name})
set(dest_target ${target_name})
else()
message(STATUS "Adding ${target_name} to bundle target")
set(DEST_TARGET bundle)
set(dest_target bundle)
add_dependencies(bundle ${target_name})
endif()
add_custom_command(TARGET ${DEST_TARGET} POST_BUILD
add_custom_command(TARGET ${dest_target} POST_BUILD
COMMAND ${CMAKE_COMMAND}
"-DCMAKE_PREFIX_PATH=\"${CMAKE_PREFIX_PATH}\""
"-DQT_HOST_PATH=\"${QT_HOST_PATH}\""
"-DQT_TARGET_PATH=\"${QT_TARGET_PATH}\""
"-DBUNDLE_TARGET_EXECUTE=1"
"-DTARGET=${target_name}"
"-DSOURCE_PATH=${CMAKE_SOURCE_DIR}"
"-DBINARY_PATH=${CMAKE_BINARY_DIR}"
"-DEXECUTABLE_PATH=${BUNDLE_EXECUTABLE_PATH}"
"-DBUNDLE_LIBRARY_PATHS=\"${BUNDLE_LIBRARY_PATHS}\""
"-DBUNDLE_QT=${BUNDLE_QT}"
"-DEXECUTABLE_PATH=${bundle_executable_path}"
"-DBUNDLE_LIBRARY_PATHS=\"${bundle_library_paths}\""
"-DBUNDLE_QT=${bundle_qt}"
"-DIN_PLACE=${in_place}"
${EXTRA_BUNDLE_ARGS}
"-DLINUXDEPLOY=${CMAKE_BINARY_DIR}/externals/linuxdeploy/squashfs-root/AppRun"
-P "${CMAKE_SOURCE_DIR}/CMakeModules/BundleTarget.cmake"
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
endfunction()

View File

@ -1,21 +1,20 @@
set(CURRENT_MODULE_DIR ${CMAKE_CURRENT_LIST_DIR})
# This function downloads Qt using aqt. The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
# Params:
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
function(download_qt target)
# Determines parameters based on the host and target for downloading the right Qt binaries.
function(determine_qt_parameters target host_out type_out arch_out arch_path_out host_type_out host_arch_out host_arch_path_out)
if (target MATCHES "tools_.*")
set(DOWNLOAD_QT_TOOL ON)
set(tool ON)
else()
set(DOWNLOAD_QT_TOOL OFF)
set(tool OFF)
endif()
# Determine installation parameters for OS, architecture, and compiler
if (WIN32)
set(host "windows")
set(type "desktop")
if (NOT DOWNLOAD_QT_TOOL)
if (NOT tool)
if (MINGW)
set(arch "win64_mingw")
set(arch_path "mingw_64")
@ -28,21 +27,35 @@ function(download_qt target)
message(FATAL_ERROR "Unsupported bundled Qt architecture. Enable USE_SYSTEM_QT and provide your own.")
endif()
set(arch "win64_${arch_path}")
# In case we're cross-compiling, prepare to also fetch the correct host Qt tools.
if (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "AMD64")
set(host_arch_path "msvc2019_64")
elseif (CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "ARM64")
# TODO: msvc2019_arm64 doesn't include some of the required tools for some reason,
# TODO: so until it does, just use msvc2019_64 under x86_64 emulation.
# set(host_arch_path "msvc2019_arm64")
set(host_arch_path "msvc2019_64")
endif()
set(host_arch "win64_${host_arch_path}")
else()
message(FATAL_ERROR "Unsupported bundled Qt toolchain. Enable USE_SYSTEM_QT and provide your own.")
endif()
endif()
elseif (APPLE)
set(host "mac")
if (IOS AND NOT DOWNLOAD_QT_TOOL)
set(type "desktop")
set(arch "clang_64")
set(arch_path "macos")
if (IOS AND NOT tool)
set(host_type "${type}")
set(host_arch "${arch}")
set(host_arch_path "${arch_path}")
set(type "ios")
set(arch "ios")
set(arch_path "ios")
set(host_arch_path "macos")
else()
set(type "desktop")
set(arch "clang_64")
set(arch_path "macos")
endif()
else()
set(host "linux")
@ -51,38 +64,64 @@ function(download_qt target)
set(arch_path "linux")
endif()
get_external_prefix(qt base_path)
file(MAKE_DIRECTORY "${base_path}")
set(${host_out} "${host}" PARENT_SCOPE)
set(${type_out} "${type}" PARENT_SCOPE)
set(${arch_out} "${arch}" PARENT_SCOPE)
set(${arch_path_out} "${arch_path}" PARENT_SCOPE)
if (DEFINED host_type)
set(${host_type_out} "${host_type}" PARENT_SCOPE)
else()
set(${host_type_out} "${type}" PARENT_SCOPE)
endif()
if (DEFINED host_arch)
set(${host_arch_out} "${host_arch}" PARENT_SCOPE)
else()
set(${host_arch_out} "${arch}" PARENT_SCOPE)
endif()
if (DEFINED host_arch_path)
set(${host_arch_path_out} "${host_arch_path}" PARENT_SCOPE)
else()
set(${host_arch_path_out} "${arch_path}" PARENT_SCOPE)
endif()
endfunction()
# Download Qt binaries for a specifc configuration.
function(download_qt_configuration prefix_out target host type arch arch_path base_path)
if (target MATCHES "tools_.*")
set(tool ON)
else()
set(tool OFF)
endif()
set(install_args -c "${CURRENT_MODULE_DIR}/aqt_config.ini")
if (DOWNLOAD_QT_TOOL)
if (tool)
set(prefix "${base_path}/Tools")
set(install_args ${install_args} install-tool --outputdir ${base_path} ${host} desktop ${target})
else()
set(prefix "${base_path}/${target}/${arch_path}")
if (host_arch_path)
set(host_flag "--autodesktop")
set(host_prefix "${base_path}/${target}/${host_arch_path}")
endif()
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch} ${host_flag}
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
set(install_args ${install_args} install-qt --outputdir ${base_path} ${host} ${type} ${target} ${arch}
-m qtmultimedia --archives qttranslations qttools qtsvg qtbase)
endif()
if (NOT EXISTS "${prefix}")
message(STATUS "Downloading binaries for Qt...")
message(STATUS "Downloading Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path}")
set(AQT_PREBUILD_BASE_URL "https://github.com/miurahr/aqtinstall/releases/download/v3.1.9")
if (WIN32)
set(aqt_path "${base_path}/aqt.exe")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt.exe
${aqt_path} SHOW_PROGRESS)
if (NOT EXISTS "${aqt_path}")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt.exe
${aqt_path} SHOW_PROGRESS)
endif()
execute_process(COMMAND ${aqt_path} ${install_args}
WORKING_DIRECTORY ${base_path})
elseif (APPLE)
set(aqt_path "${base_path}/aqt-macos")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt-macos
${aqt_path} SHOW_PROGRESS)
if (NOT EXISTS "${aqt_path}")
file(DOWNLOAD
${AQT_PREBUILD_BASE_URL}/aqt-macos
${aqt_path} SHOW_PROGRESS)
endif()
execute_process(COMMAND chmod +x ${aqt_path})
execute_process(COMMAND ${aqt_path} ${install_args}
WORKING_DIRECTORY ${base_path})
@ -96,18 +135,38 @@ function(download_qt target)
execute_process(COMMAND ${CMAKE_COMMAND} -E env PYTHONPATH=${aqt_install_path} python3 -m aqt ${install_args}
WORKING_DIRECTORY ${base_path})
endif()
message(STATUS "Downloaded Qt binaries for ${target}:${host}:${type}:${arch}:${arch_path} to ${prefix}")
endif()
message(STATUS "Using downloaded Qt binaries at ${prefix}")
set(${prefix_out} "${prefix}" PARENT_SCOPE)
endfunction()
# Add the Qt prefix path so CMake can locate it.
# This function downloads Qt using aqt.
# The path of the downloaded content will be added to the CMAKE_PREFIX_PATH.
# QT_TARGET_PATH is set to the Qt for the compile target platform.
# QT_HOST_PATH is set to a host-compatible Qt, for running tools.
# Params:
# target: Qt dependency to install. Specify a version number to download Qt, or "tools_(name)" for a specific build tool.
function(download_qt target)
determine_qt_parameters("${target}" host type arch arch_path host_type host_arch host_arch_path)
get_external_prefix(qt base_path)
file(MAKE_DIRECTORY "${base_path}")
download_qt_configuration(prefix "${target}" "${host}" "${type}" "${arch}" "${arch_path}" "${base_path}")
if (DEFINED host_arch_path AND NOT "${host_arch_path}" STREQUAL "${arch_path}")
download_qt_configuration(host_prefix "${target}" "${host}" "${host_type}" "${host_arch}" "${host_arch_path}" "${base_path}")
else()
set(host_prefix "${prefix}")
endif()
set(QT_TARGET_PATH "${prefix}" CACHE STRING "")
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
# Add the target Qt prefix path so CMake can locate it.
list(APPEND CMAKE_PREFIX_PATH "${prefix}")
set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} PARENT_SCOPE)
if (DEFINED host_prefix)
message(STATUS "Using downloaded host Qt binaries at ${host_prefix}")
set(QT_HOST_PATH "${host_prefix}" CACHE STRING "")
endif()
endfunction()
function(download_moltenvk)

View File

@ -287,5 +287,13 @@ dumptxt -p $[OUT] "nfcSecret1Seed=$[NFC_SEED_1]"
dumptxt -p $[OUT] "nfcSecret1HmacKey=$[NFC_HMAC_KEY_1]"
dumptxt -p $[OUT] "nfcIv=$[NFC_IV]"
# Dump seeddb.bin as well
set SEEDDB_IN "0:/gm9/out/seeddb.bin"
set SEEDDB_OUT "0:/gm9/seeddb.bin"
sdump -w seeddb.bin
cp -w $[SEEDDB_IN] $[SEEDDB_OUT]
@Exit

View File

@ -6,5 +6,5 @@ Usage:
1. Copy "DumpKeys.gm9" into the "gm9/scripts/" directory on your SD card.
2. Launch GodMode9, press the HOME button, select Scripts, and select "DumpKeys" from the list of scripts that appears.
3. Wait for the script to complete and return you to the GodMode9 main menu.
4. Power off your system and copy the "gm9/aes_keys.txt" file off of your SD card into "(Citra directory)/sysdata/".
4. Power off your system and copy the "gm9/aes_keys.txt" and "gm9/seeddb.bin" files off of your SD card into "(Citra directory)/sysdata/".

View File

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

View File

@ -57,6 +57,12 @@ if(USE_SYSTEM_CRYPTOPP)
add_library(cryptopp INTERFACE)
target_link_libraries(cryptopp INTERFACE cryptopp::cryptopp)
else()
if (WIN32 AND NOT MSVC AND "arm64" IN_LIST ARCHITECTURE)
# TODO: CryptoPP ARM64 ASM does not seem to support Windows unless compiled with MSVC.
# TODO: See https://github.com/weidai11/cryptopp/issues/1260
set(CRYPTOPP_DISABLE_ASM ON CACHE BOOL "")
endif()
set(CRYPTOPP_BUILD_DOCUMENTATION OFF CACHE BOOL "")
set(CRYPTOPP_BUILD_TESTING OFF CACHE BOOL "")
set(CRYPTOPP_INSTALL OFF CACHE BOOL "")
@ -235,6 +241,18 @@ endif()
# DiscordRPC
if (USE_DISCORD_PRESENCE)
# rapidjson used by discord-rpc is old and doesn't correctly detect endianness for some platforms.
include(TestBigEndian)
test_big_endian(RAPIDJSON_BIG_ENDIAN)
if(RAPIDJSON_BIG_ENDIAN)
add_compile_definitions(RAPIDJSON_ENDIAN=1)
else()
add_compile_definitions(RAPIDJSON_ENDIAN=0)
endif()
# Apply a dummy CLANG_FORMAT_SUFFIX to disable discord-rpc's unnecessary automatic clang-format.
set(CLANG_FORMAT_SUFFIX "dummy")
add_subdirectory(discord-rpc EXCLUDE_FROM_ALL)
target_include_directories(discord-rpc INTERFACE ./discord-rpc/include)
endif()

View File

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

View File

@ -324,6 +324,7 @@ void Source::GenerateFrame() {
if (state.current_buffer.empty() && !DequeueBuffer()) {
state.enabled = false;
state.buffer_update = true;
state.last_buffer_id = state.current_buffer_id;
state.current_buffer_id = 0;
return;
}
@ -411,6 +412,7 @@ bool Source::DequeueBuffer() {
state.next_sample_number = state.current_sample_number;
state.current_buffer_physical_address = buf.physical_address;
state.current_buffer_id = buf.buffer_id;
state.last_buffer_id = 0;
state.buffer_update = buf.from_queue && !buf.has_played;
if (buf.is_looping) {
@ -432,9 +434,10 @@ SourceStatus::Status Source::GetCurrentStatus() {
ret.is_enabled = state.enabled;
ret.current_buffer_id_dirty = state.buffer_update ? 1 : 0;
state.buffer_update = false;
ret.current_buffer_id = state.current_buffer_id;
ret.buffer_position = state.current_sample_number;
ret.sync_count = state.sync_count;
ret.buffer_position = state.current_sample_number;
ret.current_buffer_id = state.current_buffer_id;
ret.last_buffer_id = state.last_buffer_id;
return ret;
}

View File

@ -143,7 +143,8 @@ private:
// buffer_id state
bool buffer_update = false;
u32 current_buffer_id = 0;
u16 last_buffer_id = 0;
u16 current_buffer_id = 0;
// Decoding state

View File

@ -327,6 +327,10 @@ add_library(citra_core STATIC
hle/service/ldr_ro/cro_helper.h
hle/service/ldr_ro/ldr_ro.cpp
hle/service/ldr_ro/ldr_ro.h
hle/service/mcu/mcu_hwc.cpp
hle/service/mcu/mcu_hwc.h
hle/service/mcu/mcu.cpp
hle/service/mcu/mcu.h
hle/service/mic/mic_u.cpp
hle/service/mic/mic_u.h
hle/service/mvd/mvd.cpp

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@
#include <boost/serialization/vector.hpp>
#include <boost/serialization/weak_ptr.hpp>
#include <httplib.h>
#include "common/thread.h"
#include "core/hle/ipc_helpers.h"
#include "core/hle/kernel/shared_memory.h"
#include "core/hle/service/service.h"
@ -48,12 +49,25 @@ constexpr u32 TotalRequestMethods = 8;
enum class RequestState : u8 {
NotStarted = 0x1, // Request has not started yet.
InProgress = 0x5, // Request in progress, sending request over the network.
ReadyToDownloadContent = 0x7, // Ready to download the content. (needs verification)
ReadyToDownload = 0x8, // Ready to download?
ConnectingToServer = 0x5, // Request in progress, connecting to server.
SendingRequest = 0x6, // Request in progress, sending HTTP request.
ReceivingResponse = 0x7, // Request in progress, receiving HTTP response.
ReadyToDownloadContent = 0x8, // Ready to download the content.
TimedOut = 0xA, // Request timed out?
};
enum class PostDataEncoding : u8 {
Auto = 0x0,
AsciiForm = 0x1,
MultipartForm = 0x2,
};
enum class PostDataType : u8 {
AsciiForm = 0x0,
MultipartForm = 0x1,
Raw = 0x2,
};
enum class ClientCertID : u32 {
Default = 0x40, // Default client cert
};
@ -197,6 +211,41 @@ public:
friend class boost::serialization::access;
};
struct Param {
Param(const std::vector<u8>& value)
: name(value.begin(), value.end()), value(value.begin(), value.end()){};
Param(const std::string& name, const std::string& value) : name(name), value(value){};
Param(const std::string& name, const std::vector<u8>& value)
: name(name), value(value.begin(), value.end()), is_binary(true){};
std::string name;
std::string value;
bool is_binary = false;
httplib::MultipartFormData ToMultipartForm() const {
httplib::MultipartFormData form;
form.name = name;
form.content = value;
if (is_binary) {
form.content_type = "application/octet-stream";
// TODO(DaniElectra): httplib doesn't support setting Content-Transfer-Encoding,
// while the 3DS sets Content-Transfer-Encoding: binary if a binary value is set
}
return form;
}
private:
template <class Archive>
void serialize(Archive& ar, const unsigned int) {
ar& name;
ar& value;
ar& is_binary;
}
friend class boost::serialization::access;
};
using Params = std::multimap<std::string, Param>;
Handle handle;
u32 session_id;
std::string url;
@ -208,8 +257,14 @@ public:
u32 socket_buffer_size;
std::vector<RequestHeader> headers;
const ClCertAData* clcert_data;
httplib::Params post_data;
Params post_data;
std::string post_data_raw;
PostDataEncoding post_data_encoding = PostDataEncoding::Auto;
PostDataType post_data_type;
std::string multipart_boundary;
bool force_multipart = false;
bool chunked_request = false;
u32 chunked_content_length;
std::future<void> request_future;
std::atomic<u64> current_download_size_bytes;
@ -217,12 +272,19 @@ public:
std::size_t current_copied_data;
bool uses_default_client_cert{};
httplib::Response response;
Common::Event finish_post_data;
void ParseAsciiPostData();
std::string ParseMultipartFormData();
void MakeRequest();
void MakeRequestNonSSL(httplib::Request& request, const URLInfo& url_info,
std::vector<Context::RequestHeader>& pending_headers);
void MakeRequestSSL(httplib::Request& request, const URLInfo& url_info,
std::vector<Context::RequestHeader>& pending_headers);
bool ContentProvider(size_t offset, size_t length, httplib::DataSink& sink);
bool ChunkedContentProvider(size_t offset, httplib::DataSink& sink);
std::size_t HandleHeaderWrite(std::vector<Context::RequestHeader>& pending_headers,
httplib::Stream& strm, httplib::Headers& httplib_headers);
};
struct SessionData : public Kernel::SessionRequestHandler::SessionDataBase {
@ -308,6 +370,16 @@ private:
*/
void CancelConnection(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::GetRequestState service function
* Inputs:
* 1 : Context handle
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Request state
*/
void GetRequestState(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::GetDownloadSizeState service function
* Inputs:
@ -418,6 +490,21 @@ private:
*/
void AddPostDataAscii(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::AddPostDataBinary service function
* Inputs:
* 1 : Context handle
* 2 : Form name buffer size, including null-terminator.
* 3 : Form value buffer size
* 4 : (FormNameSize<<14) | 0xC02
* 5 : Form name data pointer
* 6 : (FormValueSize<<4) | 10
* 7 : Form value data pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void AddPostDataBinary(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::AddPostDataRaw service function
* Inputs:
@ -430,6 +517,140 @@ private:
*/
void AddPostDataRaw(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::SetPostDataType service function
* Inputs:
* 1 : Context handle
* 2 : Post data type
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetPostDataType(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::SendPostDataAscii service function
* Inputs:
* 1 : Context handle
* 2 : Form name buffer size, including null-terminator.
* 3 : Form value buffer size, including null-terminator.
* 4 : (FormNameSize<<14) | 0xC02
* 5 : Form name data pointer
* 6 : (FormValueSize<<4) | 10
* 7 : Form value data pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SendPostDataAscii(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::SendPostDataAsciiTimeout service function
* Inputs:
* 1 : Context handle
* 2 : Form name buffer size, including null-terminator.
* 3 : Form value buffer size, including null-terminator.
* 4-5 : u64 nanoseconds delay
* 6 : (FormNameSize<<14) | 0xC02
* 7 : Form name data pointer
* 8 : (FormValueSize<<4) | 10
* 9 : Form value data pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SendPostDataAsciiTimeout(Kernel::HLERequestContext& ctx);
/**
* SendPostDataAsciiImpl:
* Implements SendPostDataAscii and SendPostDataAsciiTimeout service functions
*/
void SendPostDataAsciiImpl(Kernel::HLERequestContext& ctx, bool timeout);
/**
* HTTP_C::SendPostDataBinary service function
* Inputs:
* 1 : Context handle
* 2 : Form name buffer size, including null-terminator.
* 3 : Form value buffer size
* 4 : (FormNameSize<<14) | 0xC02
* 5 : Form name data pointer
* 6 : (FormValueSize<<4) | 10
* 7 : Form value data pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SendPostDataBinary(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::SendPostDataBinaryTimeout service function
* Inputs:
* 1 : Context handle
* 2 : Form name buffer size, including null-terminator.
* 3 : Form value buffer size
* 4-5 : u64 nanoseconds delay
* 6 : (FormNameSize<<14) | 0xC02
* 7 : Form name data pointer
* 8 : (FormValueSize<<4) | 10
* 9 : Form value data pointer
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SendPostDataBinaryTimeout(Kernel::HLERequestContext& ctx);
/**
* SendPostDataBinaryImpl:
* Implements SendPostDataBinary and SendPostDataBinaryTimeout service functions
*/
void SendPostDataBinaryImpl(Kernel::HLERequestContext& ctx, bool timeout);
/**
* HTTP_C::SendPostDataRaw service function
* Inputs:
* 1 : Context handle
* 2 : Post data length
* 3-4: (Mapped buffer) Post data
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2-3: (Mapped buffer) Post data
*/
void SendPostDataRaw(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::SendPostDataRawTimeout service function
* Inputs:
* 1 : Context handle
* 2 : Post data length
* 3-4: u64 nanoseconds delay
* 5-6: (Mapped buffer) Post data
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2-3: (Mapped buffer) Post data
*/
void SendPostDataRawTimeout(Kernel::HLERequestContext& ctx);
/**
* SendPostDataRawImpl:
* Implements SendPostDataRaw and SendPostDataRawTimeout service functions
*/
void SendPostDataRawImpl(Kernel::HLERequestContext& ctx, bool timeout);
/**
* HTTP_C::NotifyFinishSendPostData service function
* Inputs:
* 1 : Context handle
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void NotifyFinishSendPostData(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::SetPostDataEncoding service function
* Inputs:
* 1 : Context handle
* 2 : Post data encoding
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetPostDataEncoding(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::GetResponseHeader service function
* Inputs:
@ -445,6 +666,28 @@ private:
*/
void GetResponseHeader(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::GetResponseHeaderTimeout service function
* Inputs:
* 1 : Context handle
* 2 : Header name length
* 3 : Return value length
* 4-5 : u64 nanoseconds delay
* 6-7 : (Static buffer) Header name
* 8-9 : (Mapped buffer) Header value
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
* 2 : Header value copied size
* 3-4: (Mapped buffer) Header value
*/
void GetResponseHeaderTimeout(Kernel::HLERequestContext& ctx);
/**
* GetResponseHeaderImpl:
* Implements GetResponseHeader and GetResponseHeaderTimeout service functions
*/
void GetResponseHeaderImpl(Kernel::HLERequestContext& ctx, bool timeout);
/**
* HTTP_C::GetResponseStatusCode service function
* Inputs:
@ -578,6 +821,17 @@ private:
*/
void SetKeepAlive(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::SetPostDataTypeSize service function
* Inputs:
* 1 : Context handle
* 2 : Post data type
* 3 : Content length size
* Outputs:
* 1 : Result of function, 0 on success, otherwise error code
*/
void SetPostDataTypeSize(Kernel::HLERequestContext& ctx);
/**
* HTTP_C::Finalize service function
* Outputs:

View File

@ -0,0 +1,16 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "core/core.h"
#include "core/hle/service/mcu/mcu.h"
#include "core/hle/service/mcu/mcu_hwc.h"
namespace Service::MCU {
void InstallInterfaces(Core::System& system) {
auto& service_manager = system.ServiceManager();
std::make_shared<HWC>()->InstallAsService(service_manager);
}
} // namespace Service::MCU

View File

@ -0,0 +1,15 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
namespace Core {
class System;
}
namespace Service::MCU {
void InstallInterfaces(Core::System& system);
} // namespace Service::MCU

View File

@ -0,0 +1,36 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#include "common/archives.h"
#include "core/hle/service/mcu/mcu_hwc.h"
SERIALIZE_EXPORT_IMPL(Service::MCU::HWC)
namespace Service::MCU {
HWC::HWC() : ServiceFramework("mcu::HWC", 1) {
static const FunctionInfo functions[] = {
// clang-format off
{0x0001, nullptr, "ReadRegister"},
{0x0002, nullptr, "WriteRegister"},
{0x0003, nullptr, "GetInfoRegisters"},
{0x0004, nullptr, "GetBatteryVoltage"},
{0x0005, nullptr, "GetBatteryLevel"},
{0x0006, nullptr, "SetPowerLEDPattern"},
{0x0007, nullptr, "SetWifiLEDState"},
{0x0008, nullptr, "SetCameraLEDPattern"},
{0x0009, nullptr, "Set3DLEDState"},
{0x000A, nullptr, "SetInfoLEDPattern"},
{0x000B, nullptr, "GetSoundVolume"},
{0x000C, nullptr, "SetTopScreenFlicker"},
{0x000D, nullptr, "SetBottomScreenFlicker"},
{0x000F, nullptr, "GetRtcTime"},
{0x0010, nullptr, "GetMcuFwVerHigh"},
{0x0011, nullptr, "GetMcuFwVerLow"},
// clang-format on
};
RegisterHandlers(functions);
}
} // namespace Service::MCU

View File

@ -0,0 +1,21 @@
// Copyright 2024 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
#include "core/hle/service/service.h"
namespace Service::MCU {
class HWC final : public ServiceFramework<HWC> {
public:
explicit HWC();
private:
SERVICE_SERIALIZATION_SIMPLE
};
} // namespace Service::MCU
BOOST_CLASS_EXPORT_KEY(Service::MCU::HWC)

View File

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

View File

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

View File

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

View File

@ -0,0 +1,379 @@
#include <cstdio>
#include <catch2/catch_template_test_macros.hpp>
#include "audio_core/hle/shared_memory.h"
#include "common/settings.h"
#include "tests/audio_core/merryhime_3ds_audio/merry_audio/merry_audio.h"
TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 1",
"[audio_core][hle]") {
// World's worst triangle wave generator.
// Generates PCM16.
auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) {
for (size_t i = 0; i < size; i++) {
u32 data = (i % freq) * 256;
audio_buffer[i] = (data << 16) | (data & 0xFFFF);
}
DSP_FlushDataCache(audio_buffer, size);
};
constexpr size_t NUM_SAMPLES = 160 * 1;
u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer, NUM_SAMPLES, 160);
u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer2, NUM_SAMPLES, 80);
u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer3, NUM_SAMPLES, 40);
MerryAudio::AudioState state;
{
std::vector<u8> dspfirm;
SECTION("HLE") {
// The test case assumes HLE AudioCore doesn't require a valid firmware
InitDspCore(Settings::AudioEmulation::HLE);
dspfirm = {0};
}
SECTION("LLE Sanity") {
InitDspCore(Settings::AudioEmulation::LLE);
dspfirm = loadDspFirmFromFile();
}
if (!dspfirm.size()) {
SKIP("Couldn't load firmware\n");
return;
}
auto ret = audioInit(dspfirm);
if (!ret) {
INFO("Couldn't init audio\n");
goto end;
}
state = *ret;
}
state.waitForSync();
initSharedMem(state);
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
{
u16 buffer_id = 0;
size_t next_queue_position = 0;
state.write().source_configurations->config[0].play_position = 0;
state.write().source_configurations->config[0].physical_address =
osConvertVirtToPhys(audio_buffer3);
state.write().source_configurations->config[0].length = NUM_SAMPLES;
state.write().source_configurations->config[0].mono_or_stereo.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo);
state.write().source_configurations->config[0].format.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::Format::PCM16);
state.write().source_configurations->config[0].fade_in.Assign(false);
state.write().source_configurations->config[0].adpcm_dirty.Assign(false);
state.write().source_configurations->config[0].is_looping.Assign(false);
state.write().source_configurations->config[0].buffer_id = ++buffer_id;
state.write().source_configurations->config[0].partial_reset_flag.Assign(true);
state.write().source_configurations->config[0].play_position_dirty.Assign(true);
state.write().source_configurations->config[0].embedded_buffer_dirty.Assign(true);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write().source_configurations->config[0].buffers[next_queue_position].length =
NUM_SAMPLES;
state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].is_looping =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id =
++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
state.notifyDsp();
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
if (!state.read().source_statuses->status[0].is_enabled) {
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
}
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
state.read().source_statuses->status[0].current_buffer_id == 0) {
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address =
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.length = NUM_SAMPLES;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.adpcm_dirty = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.is_looping = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.buffer_id = ++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |=
1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
}
}
state.notifyDsp();
}
// current_buffer_id should be 0 if the queue is not empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
// Let the queue finish playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
state.notifyDsp();
}
// TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue,
// that differs from the HLE implementation
// REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5);
// current_buffer_id should be equal to buffer_id once the queue is empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
}
end:
audioExit(state);
}
TEST_CASE_METHOD(MerryAudio::MerryAudioFixture, "Verify SourceStatus::Status::last_buffer_id 2",
"[audio_core][hle]") {
// World's worst triangle wave generator.
// Generates PCM16.
auto fillBuffer = [this](u32* audio_buffer, size_t size, unsigned freq) {
for (size_t i = 0; i < size; i++) {
u32 data = (i % freq) * 256;
audio_buffer[i] = (data << 16) | (data & 0xFFFF);
}
DSP_FlushDataCache(audio_buffer, size);
};
constexpr size_t NUM_SAMPLES = 160 * 1;
u32* audio_buffer = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer, NUM_SAMPLES, 160);
u32* audio_buffer2 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer2, NUM_SAMPLES, 80);
u32* audio_buffer3 = (u32*)linearAlloc(NUM_SAMPLES * sizeof(u32));
fillBuffer(audio_buffer3, NUM_SAMPLES, 40);
MerryAudio::AudioState state;
{
std::vector<u8> dspfirm;
SECTION("HLE") {
// The test case assumes HLE AudioCore doesn't require a valid firmware
InitDspCore(Settings::AudioEmulation::HLE);
dspfirm = {0};
}
SECTION("LLE Sanity") {
InitDspCore(Settings::AudioEmulation::LLE);
dspfirm = loadDspFirmFromFile();
}
if (!dspfirm.size()) {
SKIP("Couldn't load firmware\n");
return;
}
auto ret = audioInit(dspfirm);
if (!ret) {
INFO("Couldn't init audio\n");
goto end;
}
state = *ret;
}
state.waitForSync();
initSharedMem(state);
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
state.waitForSync();
state.notifyDsp();
{
u16 buffer_id = 0;
size_t next_queue_position = 0;
state.write().source_configurations->config[0].play_position = 0;
state.write().source_configurations->config[0].physical_address =
osConvertVirtToPhys(audio_buffer3);
state.write().source_configurations->config[0].length = NUM_SAMPLES;
state.write().source_configurations->config[0].mono_or_stereo.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::MonoOrStereo::Stereo);
state.write().source_configurations->config[0].format.Assign(
AudioCore::HLE::SourceConfiguration::Configuration::Format::PCM16);
state.write().source_configurations->config[0].fade_in.Assign(false);
state.write().source_configurations->config[0].adpcm_dirty.Assign(false);
state.write().source_configurations->config[0].is_looping.Assign(false);
state.write().source_configurations->config[0].buffer_id = ++buffer_id;
state.write().source_configurations->config[0].partial_reset_flag.Assign(true);
state.write().source_configurations->config[0].play_position_dirty.Assign(true);
state.write().source_configurations->config[0].embedded_buffer_dirty.Assign(true);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address = osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write().source_configurations->config[0].buffers[next_queue_position].length =
NUM_SAMPLES;
state.write().source_configurations->config[0].buffers[next_queue_position].adpcm_dirty =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].is_looping =
false;
state.write().source_configurations->config[0].buffers[next_queue_position].buffer_id =
++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |= 1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
state.notifyDsp();
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
if (!state.read().source_statuses->status[0].is_enabled) {
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
}
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
state.read().source_statuses->status[0].current_buffer_id == 0) {
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address =
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.length = NUM_SAMPLES;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.adpcm_dirty = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.is_looping = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.buffer_id = ++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |=
1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
}
}
state.notifyDsp();
}
// current_buffer_id should be 0 if the queue is not empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
// Let the queue finish playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
state.notifyDsp();
}
// TODO: There seems to be some nuances with how the LLE firmware runs the buffer queue,
// that differs from the HLE implementation
// REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 5);
// current_buffer_id should be equal to buffer_id once the queue is empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
// Restart Playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
if (!state.read().source_statuses->status[0].is_enabled) {
state.write().source_configurations->config[0].enable = true;
state.write().source_configurations->config[0].enable_dirty.Assign(true);
}
if (state.read().source_statuses->status[0].current_buffer_id_dirty) {
if (state.read().source_statuses->status[0].current_buffer_id == buffer_id ||
state.read().source_statuses->status[0].current_buffer_id == 0) {
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.physical_address =
osConvertVirtToPhys(buffer_id % 2 ? audio_buffer2 : audio_buffer);
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.length = NUM_SAMPLES;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.adpcm_dirty = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.is_looping = false;
state.write()
.source_configurations->config[0]
.buffers[next_queue_position]
.buffer_id = ++buffer_id;
state.write().source_configurations->config[0].buffers_dirty |=
1 << next_queue_position;
next_queue_position = (next_queue_position + 1) % 4;
state.write().source_configurations->config[0].buffer_queue_dirty.Assign(true);
}
}
state.notifyDsp();
}
// current_buffer_id should be 0 if the queue is not empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == 0);
// Let the queue finish playing
for (size_t frame_count = 0; frame_count < 10; frame_count++) {
state.waitForSync();
state.notifyDsp();
}
// current_buffer_id should be equal to buffer_id once the queue is empty
REQUIRE(state.read().source_statuses->status[0].last_buffer_id == buffer_id);
}
end:
audioExit(state);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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