summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/ISSUE_TEMPLATE.md4
-rw-r--r--.gitignore2
-rwxr-xr-x.travis-upload.sh10
-rw-r--r--.travis.yml23
-rw-r--r--CMakeLists.txt7
-rw-r--r--CONTRIBUTING.md3
-rw-r--r--README.md2
-rw-r--r--appveyor.yml79
m---------externals/dynarmic0
-rw-r--r--externals/microprofile/microprofile.h2
-rw-r--r--externals/microprofile/microprofileui.h2
-rw-r--r--externals/qhexedit/CMakeLists.txt21
-rw-r--r--externals/qhexedit/commands.cpp115
-rw-r--r--externals/qhexedit/commands.h70
-rw-r--r--externals/qhexedit/license.txt502
-rw-r--r--externals/qhexedit/qhexedit.cpp180
-rw-r--r--externals/qhexedit/qhexedit.h240
-rw-r--r--externals/qhexedit/qhexedit_p.cpp857
-rw-r--r--externals/qhexedit/qhexedit_p.h128
-rw-r--r--externals/qhexedit/xbytearray.cpp167
-rw-r--r--externals/qhexedit/xbytearray.h66
-rw-r--r--src/audio_core/audio_core.cpp16
-rw-r--r--src/audio_core/hle/source.cpp44
-rw-r--r--src/audio_core/hle/source.h2
-rw-r--r--src/audio_core/null_sink.h6
-rw-r--r--src/audio_core/sdl2_sink.cpp30
-rw-r--r--src/audio_core/sdl2_sink.h5
-rw-r--r--src/audio_core/sink.h9
-rw-r--r--src/audio_core/sink_details.cpp19
-rw-r--r--src/audio_core/sink_details.h2
-rw-r--r--src/citra/citra.cpp20
-rw-r--r--src/citra/config.cpp23
-rw-r--r--src/citra/default_ini.h41
-rw-r--r--src/citra/emu_window/emu_window_sdl2.cpp22
-rw-r--r--src/citra/emu_window/emu_window_sdl2.h5
-rw-r--r--src/citra_qt/CMakeLists.txt4
-rw-r--r--src/citra_qt/bootmanager.cpp12
-rw-r--r--src/citra_qt/bootmanager.h4
-rw-r--r--src/citra_qt/config.cpp43
-rw-r--r--src/citra_qt/configure_audio.cpp33
-rw-r--r--src/citra_qt/configure_audio.h3
-rw-r--r--src/citra_qt/configure_audio.ui15
-rw-r--r--src/citra_qt/configure_general.cpp6
-rw-r--r--src/citra_qt/configure_general.ui45
-rw-r--r--src/citra_qt/configure_graphics.cpp76
-rw-r--r--src/citra_qt/configure_graphics.ui90
-rw-r--r--src/citra_qt/configure_input.cpp1
-rw-r--r--src/citra_qt/configure_input.ui3
-rw-r--r--src/citra_qt/configure_system.ui3
-rw-r--r--src/citra_qt/debugger/callstack.cpp1
-rw-r--r--src/citra_qt/debugger/graphics/graphics_cmdlists.cpp15
-rw-r--r--src/citra_qt/debugger/graphics/graphics_surface.cpp20
-rw-r--r--src/citra_qt/debugger/graphics/graphics_tracing.cpp4
-rw-r--r--src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp15
-rw-r--r--src/citra_qt/debugger/graphics/graphics_vertex_shader.h3
-rw-r--r--src/citra_qt/debugger/ramview.cpp12
-rw-r--r--src/citra_qt/debugger/ramview.h17
-rw-r--r--src/citra_qt/debugger/wait_tree.cpp9
-rw-r--r--src/citra_qt/main.cpp3
-rw-r--r--src/citra_qt/util/spinbox.cpp7
-rw-r--r--src/common/CMakeLists.txt6
-rw-r--r--src/common/bit_set.h33
-rw-r--r--src/common/file_util.cpp4
-rw-r--r--src/common/hash.cpp8
-rw-r--r--src/common/hash.h5
-rw-r--r--src/common/logging/backend.cpp1
-rw-r--r--src/common/logging/log.h1
-rw-r--r--src/common/math_util.h2
-rw-r--r--src/common/quaternion.h44
-rw-r--r--src/common/thread.h10
-rw-r--r--src/common/vector_math.h19
-rw-r--r--src/common/x64/abi.cpp350
-rw-r--r--src/common/x64/abi.h58
-rw-r--r--src/common/x64/emitter.cpp2583
-rw-r--r--src/common/x64/emitter.h1206
-rw-r--r--src/core/CMakeLists.txt20
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp31
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.h2
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.cpp88
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_cp15.h32
-rw-r--r--src/core/core.cpp1
-rw-r--r--src/core/core.h2
-rw-r--r--src/core/core_timing.cpp2
-rw-r--r--src/core/core_timing.h1
-rw-r--r--src/core/file_sys/archive_extsavedata.cpp2
-rw-r--r--src/core/file_sys/archive_sdmc.cpp12
-rw-r--r--src/core/file_sys/savedata_archive.cpp12
-rw-r--r--src/core/frontend/camera/blank_camera.cpp31
-rw-r--r--src/core/frontend/camera/blank_camera.h28
-rw-r--r--src/core/frontend/camera/factory.cpp32
-rw-r--r--src/core/frontend/camera/factory.h41
-rw-r--r--src/core/frontend/camera/interface.cpp11
-rw-r--r--src/core/frontend/camera/interface.h61
-rw-r--r--src/core/frontend/emu_window.cpp25
-rw-r--r--src/core/frontend/emu_window.h52
-rw-r--r--src/core/frontend/motion_emu.cpp89
-rw-r--r--src/core/frontend/motion_emu.h52
-rw-r--r--src/core/gdbstub/gdbstub.cpp5
-rw-r--r--src/core/hle/applets/applet.cpp5
-rw-r--r--src/core/hle/applets/mint.cpp72
-rw-r--r--src/core/hle/applets/mint.h29
-rw-r--r--src/core/hle/config_mem.cpp13
-rw-r--r--src/core/hle/hle.cpp58
-rw-r--r--src/core/hle/kernel/event.cpp21
-rw-r--r--src/core/hle/kernel/event.h6
-rw-r--r--src/core/hle/kernel/kernel.cpp49
-rw-r--r--src/core/hle/kernel/kernel.h15
-rw-r--r--src/core/hle/kernel/mutex.cpp84
-rw-r--r--src/core/hle/kernel/mutex.h17
-rw-r--r--src/core/hle/kernel/resource_limit.cpp2
-rw-r--r--src/core/hle/kernel/semaphore.cpp7
-rw-r--r--src/core/hle/kernel/semaphore.h4
-rw-r--r--src/core/hle/kernel/server_port.cpp6
-rw-r--r--src/core/hle/kernel/server_port.h4
-rw-r--r--src/core/hle/kernel/server_session.cpp6
-rw-r--r--src/core/hle/kernel/server_session.h4
-rw-r--r--src/core/hle/kernel/thread.cpp128
-rw-r--r--src/core/hle/kernel/thread.h62
-rw-r--r--src/core/hle/kernel/timer.cpp18
-rw-r--r--src/core/hle/kernel/timer.h6
-rw-r--r--src/core/hle/service/ac/ac.cpp181
-rw-r--r--src/core/hle/service/ac/ac.h134
-rw-r--r--src/core/hle/service/ac/ac_i.cpp39
-rw-r--r--src/core/hle/service/ac/ac_i.h22
-rw-r--r--src/core/hle/service/ac/ac_u.cpp39
-rw-r--r--src/core/hle/service/ac/ac_u.h (renamed from src/core/hle/service/ac_u.h)1
-rw-r--r--src/core/hle/service/ac_u.cpp291
-rw-r--r--src/core/hle/service/boss/boss.cpp4
-rw-r--r--src/core/hle/service/cam/cam.cpp1024
-rw-r--r--src/core/hle/service/cam/cam.h358
-rw-r--r--src/core/hle/service/cam/cam_u.cpp32
-rw-r--r--src/core/hle/service/cfg/cfg.cpp60
-rw-r--r--src/core/hle/service/cfg/cfg.h7
-rw-r--r--src/core/hle/service/err_f.cpp2
-rw-r--r--src/core/hle/service/gsp_gpu.cpp31
-rw-r--r--src/core/hle/service/hid/hid.cpp150
-rw-r--r--src/core/hle/service/hid/hid.h3
-rw-r--r--src/core/hle/service/mic_u.cpp13
-rw-r--r--src/core/hle/service/nfc/nfc.cpp125
-rw-r--r--src/core/hle/service/nfc/nfc.h139
-rw-r--r--src/core/hle/service/nfc/nfc_m.cpp23
-rw-r--r--src/core/hle/service/nfc/nfc_u.cpp23
-rw-r--r--src/core/hle/service/service.cpp7
-rw-r--r--src/core/hle/service/soc_u.cpp4
-rw-r--r--src/core/hle/service/y2r_u.cpp4
-rw-r--r--src/core/hle/svc.cpp106
-rw-r--r--src/core/hw/gpu.cpp6
-rw-r--r--src/core/loader/3dsx.cpp34
-rw-r--r--src/core/loader/ncch.cpp24
-rw-r--r--src/core/loader/ncch.h5
-rw-r--r--src/core/settings.cpp3
-rw-r--r--src/core/settings.h12
-rw-r--r--src/video_core/CMakeLists.txt42
-rw-r--r--src/video_core/clipper.cpp24
-rw-r--r--src/video_core/command_processor.cpp43
-rw-r--r--src/video_core/debug_utils/debug_utils.cpp256
-rw-r--r--src/video_core/debug_utils/debug_utils.h25
-rw-r--r--src/video_core/pica.cpp2
-rw-r--r--src/video_core/pica.h62
-rw-r--r--src/video_core/pica_state.h4
-rw-r--r--src/video_core/primitive_assembly.cpp2
-rw-r--r--src/video_core/primitive_assembly.h5
-rw-r--r--src/video_core/rasterizer.cpp12
-rw-r--r--src/video_core/rasterizer.h40
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h4
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.cpp28
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer_cache.h7
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp19
-rw-r--r--src/video_core/shader/shader.cpp139
-rw-r--r--src/video_core/shader/shader.h120
-rw-r--r--src/video_core/shader/shader_interpreter.cpp51
-rw-r--r--src/video_core/shader/shader_interpreter.h25
-rw-r--r--src/video_core/shader/shader_jit_x64.cpp890
-rw-r--r--src/video_core/shader/shader_jit_x64.h115
-rw-r--r--src/video_core/shader/shader_jit_x64_compiler.cpp889
-rw-r--r--src/video_core/shader/shader_jit_x64_compiler.h124
-rw-r--r--src/video_core/texture/etc1.cpp124
-rw-r--r--src/video_core/texture/etc1.h16
-rw-r--r--src/video_core/texture/texture_decode.cpp229
-rw-r--r--src/video_core/texture/texture_decode.h60
-rw-r--r--src/video_core/vertex_loader.cpp5
-rw-r--r--src/video_core/vertex_loader.h4
-rw-r--r--src/video_core/video_core.cpp1
-rw-r--r--src/video_core/video_core.h1
185 files changed, 5728 insertions, 9299 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 4c9c7975a..9727c5712 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -4,10 +4,10 @@ Please read the FAQ:
https://citra-emu.org/wiki/FAQ
THIS IS NOT A SUPPORT FORUM, FOR SUPPORT GO TO:
-http://discuss.citra-emu.org/
+https://community.citra-emu.org/
If the FAQ does not answer your question, please go to:
-http://discuss.citra-emu.org/
+https://community.citra-emu.org/
====================================================
diff --git a/.gitignore b/.gitignore
index ad8aea5da..ec74b0fa4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,10 +4,12 @@ doc-build/
# Generated source files
src/common/scm_rev.cpp
+.travis.descriptor.json
# Project/editor files
*.swp
.idea/
+.vscode/
# *nix related
# Common convention for backup or temporary files
diff --git a/.travis-upload.sh b/.travis-upload.sh
index 2eeda4c50..9aed815d4 100755
--- a/.travis-upload.sh
+++ b/.travis-upload.sh
@@ -1,17 +1,16 @@
if [ "$TRAVIS_EVENT_TYPE" = "push" ]&&[ "$TRAVIS_BRANCH" = "master" ]; then
GITDATE="`git show -s --date=short --format='%ad' | sed 's/-//g'`"
GITREV="`git show -s --format='%h'`"
+ mkdir -p artifacts
if [ "$TRAVIS_OS_NAME" = "linux" -o -z "$TRAVIS_OS_NAME" ]; then
- REV_NAME="citra-${GITDATE}-${GITREV}-linux-amd64"
- UPLOAD_DIR="/citra/nightly/linux-amd64"
+ REV_NAME="citra-linux-${GITDATE}-${GITREV}"
mkdir "$REV_NAME"
cp build/src/citra/citra "$REV_NAME"
cp build/src/citra_qt/citra-qt "$REV_NAME"
elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
- REV_NAME="citra-${GITDATE}-${GITREV}-osx-amd64"
- UPLOAD_DIR="/citra/nightly/osx-amd64"
+ REV_NAME="citra-osx-${GITDATE}-${GITREV}"
mkdir "$REV_NAME"
cp build/src/citra/Release/citra "$REV_NAME"
@@ -121,4 +120,7 @@ EOL
ARCHIVE_NAME="${REV_NAME}.tar.xz"
tar -cJvf "$ARCHIVE_NAME" "$REV_NAME"
+
+ # move the compiled archive into the artifacts directory to be uploaded by travis releases
+ mv "$ARCHIVE_NAME" artifacts/
fi
diff --git a/.travis.yml b/.travis.yml
index cc34e039c..cf1e1e26c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,4 @@
language: cpp
-
matrix:
include:
- os: linux
@@ -9,10 +8,6 @@ matrix:
sudo: false
osx_image: xcode7.3
-env:
- global:
- - secure: "AXHFIafTmbGDsHD3mUVj5a4I397DQjti/WoqAJGUp2PglxTcc04BwxZ9Z+xLuf5N2Hs5r9ojAJLT8OGxJCLBDXzneQTNSqXbFuYSLbqrEAiIRlA9eRIotWCg+wYcO+5e8MKX+cHVKwiIWasUB21AtCdq6msh6Y3pUshZp212VPg="
-
addons:
apt:
sources:
@@ -27,8 +22,18 @@ addons:
cache:
directories:
- - $HOME/.local
+ - "$HOME/.local"
+
+install: "./.travis-deps.sh"
+script: "./.travis-build.sh"
+after_success: "./.travis-upload.sh"
-install: ./.travis-deps.sh
-script: ./.travis-build.sh
-after_success: ./.travis-upload.sh
+deploy:
+ provider: releases
+ api_key:
+ secure: Mck15DIWaJdxDiS3aYVlM9N3G6y8VKUI1rnwII7/iolfm1s94U+tgvbheZDmT7SSbFyaGaYO/E8HrV/uZR9Vvs7ev20sHsTN1u60OTWfDIIyHs9SqjhcGbtq95m9/dMFschOYqTOR+gAs5BsxjuoeAotHdhpQEwvkO2oo5oR0zhGy45gjFnVvtcxT/IfpZBIpVgcK3aLb9zT6ekcJbSiPmEB15iLq3xXd0nFUNtEZdX3D6Veye4n5jB6n72qN8JVoKvPZAwaC2K0pZxpcGJaXDchLsw1q+4eCvdz6UJfUemeQ/uMAmjfeQ3wrzYGXe3nCM3WmX5wosCsB0mw4zYatzl3si6CZ1W+0GkV4Rwlx03dfp7v3EeFhTsXYCaXqhwuLZnWOLUik8t9vaSoFUx4nUIRwfO9kAMUJQSpLuHNO2nT01s3GxvqxzczuLQ9he5nGSi0RRodUzDwek1qUp6I4uV3gRHKz4B07YIc1i2fK88NLXjyQ0uLVZ+7Oq1+kgDp6+N7vvXXZ5qZ17tdaysSbKEE0Y8zsoXw7Rk1tPN19vrCS+TSpomNMyQyne1k+I5iZ/qkxPTLAS5qI6Utc2dL3GJdxWRAEfGNO9AIX3GV/jmmKfdcvwGsCYP8hxqs5vLYfgacw3D8NLf1941lQUwavC17jm9EV9g5G3Pn1Cp516E=
+ file_glob: true
+ file: "artifacts/*.tar.xz"
+ skip_cleanup: true
+ on:
+ repo: citra-emu/citra-nightly \ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 52a1fd492..306959e24 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -72,8 +72,6 @@ if (NOT MSVC)
if (MINGW)
add_definitions(-DMINGW_HAS_SECURE_API)
- # Microprofile causes crashes when launching titles on MinGW
- add_definitions(-DMICROPROFILE_ENABLED=0)
if (MINGW_STATIC_BUILD)
add_definitions(-DQT_STATICPLUGIN)
@@ -271,11 +269,6 @@ if (MSVC)
endif()
# process subdirectories
-if(ENABLE_QT)
- include_directories(externals/qhexedit)
- add_subdirectory(externals/qhexedit)
-endif()
-
add_subdirectory(externals/soundtouch)
enable_testing()
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 7a21eebf8..55f520cb4 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,6 +1,7 @@
# Reporting Issues
-**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) and then either visit our IRC channel, [our forum](https://discuss.citra-emu.org/) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
+**The issue tracker is not a support forum.** Unless you can provide precise *technical information* regarding an issue, you *should not post in it*. If you need support, first read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) and then either visit our IRC channel, [our forum](https://community.citra-emu.org)
+.citra-emu.org/) or ask in a general emulation forum such as [/r/emulation](https://www.reddit.com/r/emulation/). If you post support questions, generic messages to the developers or vague reports without technical details, they will be closed and locked.
If you believe you have a valid issue report, please post text or a screenshot from the log (the console window that opens alongside Citra) and build version (hex string visible in the titlebar and zip filename), as well as your hardware and software information if applicable.
diff --git a/README.md b/README.md
index 7d1e1de0d..461d2e3a7 100644
--- a/README.md
+++ b/README.md
@@ -7,7 +7,7 @@ Citra Emulator
Citra is an experimental open-source Nintendo 3DS emulator/debugger written in C++. It is written with portability in mind, with builds actively maintained for Windows, Linux and macOS. Citra only emulates a subset of 3DS hardware, and therefore is generally only useful for running/debugging homebrew applications. At this time, Citra is even able to boot several commercial games! Most of these do not run to a playable state, but we are working every day to advance the project forward.
-Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://github.com/citra-emu/citra/wiki/FAQ) before getting started with the project.
+Citra is licensed under the GPLv2 (or any later version). Refer to the license.txt file included. Please read the [FAQ](https://citra-emu.org/wikis/faq) before getting started with the project.
Check out our [website](https://citra-emu.org/)!
diff --git a/appveyor.yml b/appveyor.yml
index 0ffb680ff..c07559479 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -1,9 +1,8 @@
# shallow clone
-clone_depth: 5
+clone_depth: 10
-environment:
- BUILD_PASSWORD:
- secure: EXGNlWKJsCtbeImEJ5EP9qrxZ+EqUFfNy+CP61nDOMA=
+# don't build on tag
+skip_tags: true
cache:
- C:\ProgramData\chocolatey\bin -> appveyor.yml
@@ -30,42 +29,48 @@ build:
project: build/citra.sln
parallel: true
-test_script:
- - cd build && ctest -VV -C Release && cd ..
+after_build:
+ - ps: |
+ $GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
+ $GITREV = $(git show -s --format='%h')
+ $GIT_LONG_HASH = $(git rev-parse HEAD)
+ # Where are these spaces coming from? Regardless, let's remove them
+ $MSVC_BUILD_NAME = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
+ $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
+ $BINTRAY_VERSION = "nightly-$GIT_LONG_HASH" -replace " ", ""
-on_success:
- # copying the needed QT Dlls is now done post build. See the CMakeLists.txt file in the citra-qt folder
- - ps: >
- if (!"$env:APPVEYOR_PULL_REQUEST_TITLE" -and ("$env:APPVEYOR_REPO_BRANCH" -eq "master"))
- {
- $GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
- $GITREV = $(git show -s --format='%h')
- # Where are these spaces coming from? Regardless, let's remove them
- $BUILD_NAME = "citra-${GITDATE}-${GITREV}-windows-amd64.7z" -replace " ",""
- $BUILD_NAME_PDB = "citra-${GITDATE}-${GITREV}-windows-amd64-debugsymbols.7z" -replace " ",""
- $BUILD_NAME_NOQT = "citra-noqt-${GITDATE}-${GITREV}-windows-amd64.7z" -replace " ",""
+ # set the build names as env vars so the artifacts can upload them
+ $env:MSVC_BUILD_NAME = $MSVC_BUILD_NAME
+ $env:MSVC_BUILD_PDB = $MSVC_BUILD_PDB
+ $env:GITREV = $GITREV
- # Remove unnecessary files
- rm .\build\bin\release\*tests*
+ 7z a -tzip $MSVC_BUILD_PDB .\build\bin\release\*.pdb
+ rm .\build\bin\release\*.pdb
+ 7z a -tzip $MSVC_BUILD_NAME .\build\bin\release\* .\license.txt .\README.md
- # Put the pdb files in a separate archive and remove them from the main download
- 7z a $BUILD_NAME_PDB .\build\bin\release\*.pdb
- rm .\build\bin\release\*.pdb
+test_script:
+ - cd build && ctest -VV -C Release && cd ..
- # Zip up the build folder and documentation
- 7z a $BUILD_NAME .\build\bin\release\* .\license.txt .\README.md
- # Do a second archive with only the binaries (excludes dlls) and documentation
- 7z a $BUILD_NAME_NOQT .\build\bin\release\*.exe .\license.txt .\README.md
+artifacts:
+ - path: $(MSVC_BUILD_NAME)
+ name: msvcbuild
+ type: zip
+ - path: $(MSVC_BUILD_PDB)
+ name: msvcdebug
+ type: zip
+deploy:
+ provider: GitHub
+ release: nightly-$(appveyor_build_number)
+ description: |
+ Citra nightly releases. Please choose the correct download for your operating system from the list below.
- # Download WinSCP and upload to server
- choco install winscp.portable
- WinSCP.exe /command `
- "option batch abort" `
- "option confirm off" `
- "open sftp://citra-builds:${env:BUILD_PASSWORD}@builds.citra-emu.org -hostkey=*" `
- "put $BUILD_NAME /citra/nightly/windows-amd64/" `
- "put $BUILD_NAME_NOQT /citra/nightly/windows-noqt-amd64/" `
- "put $BUILD_NAME_PDB /citra/nightly/windows-amd64-debugsymbols/" `
- "exit"
- }
+ Short Commit Hash $(GITREV)
+ auth_token:
+ secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1"
+ artifact: msvcbuild
+ draft: false
+ prerelease: false
+ on:
+ branch: master
+ appveyor_repo_name: citra-emu/citra-nightly
diff --git a/externals/dynarmic b/externals/dynarmic
-Subproject 36082087ded632079b16d24137fdd0c450ce82e
+Subproject 459d7d1bafcf85677c989b7cb260d3789aa813e
diff --git a/externals/microprofile/microprofile.h b/externals/microprofile/microprofile.h
index f45c9ba82..384863ccc 100644
--- a/externals/microprofile/microprofile.h
+++ b/externals/microprofile/microprofile.h
@@ -201,7 +201,7 @@ typedef uint64_t ThreadIdType;
int64_t MicroProfileGetTick();
#define MP_TICK() MicroProfileGetTick()
#define MP_BREAK() __debugbreak()
-#define MP_THREAD_LOCAL __declspec(thread)
+#define MP_THREAD_LOCAL thread_local
#define MP_STRCASECMP _stricmp
#define MP_GETCURRENTTHREADID() GetCurrentThreadId()
typedef uint32_t ThreadIdType;
diff --git a/externals/microprofile/microprofileui.h b/externals/microprofile/microprofileui.h
index 66a73abc5..09223b33f 100644
--- a/externals/microprofile/microprofileui.h
+++ b/externals/microprofile/microprofileui.h
@@ -1231,7 +1231,7 @@ void MicroProfileDrawDetailedBars(uint32_t nWidth, uint32_t nHeight, int nBaseY,
char ThreadName[MicroProfileThreadLog::THREAD_MAX_LEN + 16];
const char* cLocal = MicroProfileIsLocalThread(nThreadId) ? "*": " ";
-#if defined(WIN32)
+#if defined(_WIN32)
// nThreadId is 32-bit on Windows
int nStrLen = snprintf(ThreadName, sizeof(ThreadName)-1, "%04x: %s%s", nThreadId, cLocal, i < nNumThreadsBase ? &S.Pool[i]->ThreadName[0] : MICROPROFILE_THREAD_NAME_FROM_ID(nThreadId) );
#else
diff --git a/externals/qhexedit/CMakeLists.txt b/externals/qhexedit/CMakeLists.txt
deleted file mode 100644
index e7470dfe4..000000000
--- a/externals/qhexedit/CMakeLists.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-set(CMAKE_AUTOMOC ON)
-set(CMAKE_INCLUDE_CURRENT_DIR ON)
-
-set(SRCS
- commands.cpp
- qhexedit.cpp
- qhexedit_p.cpp
- xbytearray.cpp
- )
-
-set(HEADERS
- commands.h
- qhexedit.h
- qhexedit_p.h
- xbytearray.h
- )
-
-create_directory_groups(${SRCS} ${HEADERS})
-
-add_library(qhexedit STATIC ${SRCS} ${HEADERS})
-target_link_libraries(qhexedit ${CITRA_QT_LIBS})
diff --git a/externals/qhexedit/commands.cpp b/externals/qhexedit/commands.cpp
deleted file mode 100644
index 303091d1d..000000000
--- a/externals/qhexedit/commands.cpp
+++ /dev/null
@@ -1,115 +0,0 @@
-#include "commands.h"
-
-CharCommand::CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar, QUndoCommand *parent)
- : QUndoCommand(parent)
-{
- _xData = xData;
- _charPos = charPos;
- _newChar = newChar;
- _cmd = cmd;
-}
-
-bool CharCommand::mergeWith(const QUndoCommand *command)
-{
- const CharCommand *nextCommand = static_cast<const CharCommand *>(command);
- bool result = false;
-
- if (_cmd != remove)
- {
- if (nextCommand->_cmd == replace)
- if (nextCommand->_charPos == _charPos)
- {
- _newChar = nextCommand->_newChar;
- result = true;
- }
- }
- return result;
-}
-
-void CharCommand::undo()
-{
- switch (_cmd)
- {
- case insert:
- _xData->remove(_charPos, 1);
- break;
- case replace:
- _xData->replace(_charPos, _oldChar);
- _xData->setDataChanged(_charPos, _wasChanged);
- break;
- case remove:
- _xData->insert(_charPos, _oldChar);
- _xData->setDataChanged(_charPos, _wasChanged);
- break;
- }
-}
-
-void CharCommand::redo()
-{
- switch (_cmd)
- {
- case insert:
- _xData->insert(_charPos, _newChar);
- break;
- case replace:
- _oldChar = _xData->data()[_charPos];
- _wasChanged = _xData->dataChanged(_charPos);
- _xData->replace(_charPos, _newChar);
- break;
- case remove:
- _oldChar = _xData->data()[_charPos];
- _wasChanged = _xData->dataChanged(_charPos);
- _xData->remove(_charPos, 1);
- break;
- }
-}
-
-
-
-ArrayCommand::ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa, int len, QUndoCommand *parent)
- : QUndoCommand(parent)
-{
- _cmd = cmd;
- _xData = xData;
- _baPos = baPos;
- _newBa = newBa;
- _len = len;
-}
-
-void ArrayCommand::undo()
-{
- switch (_cmd)
- {
- case insert:
- _xData->remove(_baPos, _newBa.length());
- break;
- case replace:
- _xData->replace(_baPos, _oldBa);
- _xData->setDataChanged(_baPos, _wasChanged);
- break;
- case remove:
- _xData->insert(_baPos, _oldBa);
- _xData->setDataChanged(_baPos, _wasChanged);
- break;
- }
-}
-
-void ArrayCommand::redo()
-{
- switch (_cmd)
- {
- case insert:
- _xData->insert(_baPos, _newBa);
- break;
- case replace:
- _oldBa = _xData->data().mid(_baPos, _len);
- _wasChanged = _xData->dataChanged(_baPos, _len);
- _xData->replace(_baPos, _newBa);
- break;
- case remove:
- _oldBa = _xData->data().mid(_baPos, _len);
- _wasChanged = _xData->dataChanged(_baPos, _len);
- _xData->remove(_baPos, _len);
- break;
- }
-}
diff --git a/externals/qhexedit/commands.h b/externals/qhexedit/commands.h
deleted file mode 100644
index 9931b3fb5..000000000
--- a/externals/qhexedit/commands.h
+++ /dev/null
@@ -1,70 +0,0 @@
-#ifndef COMMANDS_H
-#define COMMANDS_H
-
-/** \cond docNever */
-
-#include <QUndoCommand>
-
-#include "xbytearray.h"
-
-/*! CharCommand is a class to prived undo/redo functionality in QHexEdit.
-A QUndoCommand represents a single editing action on a document. CharCommand
-is responsable for manipulations on single chars. It can insert. replace and
-remove characters. A manipulation stores allways to actions
-1. redo (or do) action
-2. undo action.
-
-CharCommand also supports command compression via mergeWidht(). This allows
-the user to execute a undo command contation e.g. 3 steps in a single command.
-If you for example insert a new byt "34" this means for the editor doing 3
-steps: insert a "00", replace it with "03" and the replace it with "34". These
-3 steps are combined into a single step, insert a "34".
-*/
-class CharCommand : public QUndoCommand
-{
-public:
- enum { Id = 1234 };
- enum Cmd {insert, remove, replace};
-
- CharCommand(XByteArray * xData, Cmd cmd, int charPos, char newChar,
- QUndoCommand *parent=0);
-
- void undo();
- void redo();
- bool mergeWith(const QUndoCommand *command);
- int id() const { return Id; }
-
-private:
- XByteArray * _xData;
- int _charPos;
- bool _wasChanged;
- char _newChar;
- char _oldChar;
- Cmd _cmd;
-};
-
-/*! ArrayCommand provides undo/redo functionality for handling binary strings. It
-can undo/redo insert, replace and remove binary strins (QByteArrays).
-*/
-class ArrayCommand : public QUndoCommand
-{
-public:
- enum Cmd {insert, remove, replace};
- ArrayCommand(XByteArray * xData, Cmd cmd, int baPos, QByteArray newBa=QByteArray(), int len=0,
- QUndoCommand *parent=0);
- void undo();
- void redo();
-
-private:
- Cmd _cmd;
- XByteArray * _xData;
- int _baPos;
- int _len;
- QByteArray _wasChanged;
- QByteArray _newBa;
- QByteArray _oldBa;
-};
-
-/** \endcond docNever */
-
-#endif // COMMANDS_H
diff --git a/externals/qhexedit/license.txt b/externals/qhexedit/license.txt
deleted file mode 100644
index f166cc57b..000000000
--- a/externals/qhexedit/license.txt
+++ /dev/null
@@ -1,502 +0,0 @@
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 2.1, February 1999
-
- Copyright (C) 1991, 1999 Free Software Foundation, Inc.
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-[This is the first released version of the Lesser GPL. It also counts
- as the successor of the GNU Library Public License, version 2, hence
- the version number 2.1.]
-
- Preamble
-
- The licenses for most software are designed to take away your
-freedom to share and change it. By contrast, the GNU General Public
-Licenses are intended to guarantee your freedom to share and change
-free software--to make sure the software is free for all its users.
-
- This license, the Lesser General Public License, applies to some
-specially designated software packages--typically libraries--of the
-Free Software Foundation and other authors who decide to use it. You
-can use it too, but we suggest you first think carefully about whether
-this license or the ordinary General Public License is the better
-strategy to use in any particular case, based on the explanations below.
-
- When we speak of free software, we are referring to freedom of use,
-not price. Our General Public Licenses are designed to make sure that
-you have the freedom to distribute copies of free software (and charge
-for this service if you wish); that you receive source code or can get
-it if you want it; that you can change the software and use pieces of
-it in new free programs; and that you are informed that you can do
-these things.
-
- To protect your rights, we need to make restrictions that forbid
-distributors to deny you these rights or to ask you to surrender these
-rights. These restrictions translate to certain responsibilities for
-you if you distribute copies of the library or if you modify it.
-
- For example, if you distribute copies of the library, whether gratis
-or for a fee, you must give the recipients all the rights that we gave
-you. You must make sure that they, too, receive or can get the source
-code. If you link other code with the library, you must provide
-complete object files to the recipients, so that they can relink them
-with the library after making changes to the library and recompiling
-it. And you must show them these terms so they know their rights.
-
- We protect your rights with a two-step method: (1) we copyright the
-library, and (2) we offer you this license, which gives you legal
-permission to copy, distribute and/or modify the library.
-
- To protect each distributor, we want to make it very clear that
-there is no warranty for the free library. Also, if the library is
-modified by someone else and passed on, the recipients should know
-that what they have is not the original version, so that the original
-author's reputation will not be affected by problems that might be
-introduced by others.
-
- Finally, software patents pose a constant threat to the existence of
-any free program. We wish to make sure that a company cannot
-effectively restrict the users of a free program by obtaining a
-restrictive license from a patent holder. Therefore, we insist that
-any patent license obtained for a version of the library must be
-consistent with the full freedom of use specified in this license.
-
- Most GNU software, including some libraries, is covered by the
-ordinary GNU General Public License. This license, the GNU Lesser
-General Public License, applies to certain designated libraries, and
-is quite different from the ordinary General Public License. We use
-this license for certain libraries in order to permit linking those
-libraries into non-free programs.
-
- When a program is linked with a library, whether statically or using
-a shared library, the combination of the two is legally speaking a
-combined work, a derivative of the original library. The ordinary
-General Public License therefore permits such linking only if the
-entire combination fits its criteria of freedom. The Lesser General
-Public License permits more lax criteria for linking other code with
-the library.
-
- We call this license the "Lesser" General Public License because it
-does Less to protect the user's freedom than the ordinary General
-Public License. It also provides other free software developers Less
-of an advantage over competing non-free programs. These disadvantages
-are the reason we use the ordinary General Public License for many
-libraries. However, the Lesser license provides advantages in certain
-special circumstances.
-
- For example, on rare occasions, there may be a special need to
-encourage the widest possible use of a certain library, so that it becomes
-a de-facto standard. To achieve this, non-free programs must be
-allowed to use the library. A more frequent case is that a free
-library does the same job as widely used non-free libraries. In this
-case, there is little to gain by limiting the free library to free
-software only, so we use the Lesser General Public License.
-
- In other cases, permission to use a particular library in non-free
-programs enables a greater number of people to use a large body of
-free software. For example, permission to use the GNU C Library in
-non-free programs enables many more people to use the whole GNU
-operating system, as well as its variant, the GNU/Linux operating
-system.
-
- Although the Lesser General Public License is Less protective of the
-users' freedom, it does ensure that the user of a program that is
-linked with the Library has the freedom and the wherewithal to run
-that program using a modified version of the Library.
-
- The precise terms and conditions for copying, distribution and
-modification follow. Pay close attention to the difference between a
-"work based on the library" and a "work that uses the library". The
-former contains code derived from the library, whereas the latter must
-be combined with the library in order to run.
-
- GNU LESSER GENERAL PUBLIC LICENSE
- TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
-
- 0. This License Agreement applies to any software library or other
-program which contains a notice placed by the copyright holder or
-other authorized party saying it may be distributed under the terms of
-this Lesser General Public License (also called "this License").
-Each licensee is addressed as "you".
-
- A "library" means a collection of software functions and/or data
-prepared so as to be conveniently linked with application programs
-(which use some of those functions and data) to form executables.
-
- The "Library", below, refers to any such software library or work
-which has been distributed under these terms. A "work based on the
-Library" means either the Library or any derivative work under
-copyright law: that is to say, a work containing the Library or a
-portion of it, either verbatim or with modifications and/or translated
-straightforwardly into another language. (Hereinafter, translation is
-included without limitation in the term "modification".)
-
- "Source code" for a work means the preferred form of the work for
-making modifications to it. For a library, complete source code means
-all the source code for all modules it contains, plus any associated
-interface definition files, plus the scripts used to control compilation
-and installation of the library.
-
- Activities other than copying, distribution and modification are not
-covered by this License; they are outside its scope. The act of
-running a program using the Library is not restricted, and output from
-such a program is covered only if its contents constitute a work based
-on the Library (independent of the use of the Library in a tool for
-writing it). Whether that is true depends on what the Library does
-and what the program that uses the Library does.
-
- 1. You may copy and distribute verbatim copies of the Library's
-complete source code as you receive it, in any medium, provided that
-you conspicuously and appropriately publish on each copy an
-appropriate copyright notice and disclaimer of warranty; keep intact
-all the notices that refer to this License and to the absence of any
-warranty; and distribute a copy of this License along with the
-Library.
-
- You may charge a fee for the physical act of transferring a copy,
-and you may at your option offer warranty protection in exchange for a
-fee.
-
- 2. You may modify your copy or copies of the Library or any portion
-of it, thus forming a work based on the Library, and copy and
-distribute such modifications or work under the terms of Section 1
-above, provided that you also meet all of these conditions:
-
- a) The modified work must itself be a software library.
-
- b) You must cause the files modified to carry prominent notices
- stating that you changed the files and the date of any change.
-
- c) You must cause the whole of the work to be licensed at no
- charge to all third parties under the terms of this License.
-
- d) If a facility in the modified Library refers to a function or a
- table of data to be supplied by an application program that uses
- the facility, other than as an argument passed when the facility
- is invoked, then you must make a good faith effort to ensure that,
- in the event an application does not supply such function or
- table, the facility still operates, and performs whatever part of
- its purpose remains meaningful.
-
- (For example, a function in a library to compute square roots has
- a purpose that is entirely well-defined independent of the
- application. Therefore, Subsection 2d requires that any
- application-supplied function or table used by this function must
- be optional: if the application does not supply it, the square
- root function must still compute square roots.)
-
-These requirements apply to the modified work as a whole. If
-identifiable sections of that work are not derived from the Library,
-and can be reasonably considered independent and separate works in
-themselves, then this License, and its terms, do not apply to those
-sections when you distribute them as separate works. But when you
-distribute the same sections as part of a whole which is a work based
-on the Library, the distribution of the whole must be on the terms of
-this License, whose permissions for other licensees extend to the
-entire whole, and thus to each and every part regardless of who wrote
-it.
-
-Thus, it is not the intent of this section to claim rights or contest
-your rights to work written entirely by you; rather, the intent is to
-exercise the right to control the distribution of derivative or
-collective works based on the Library.
-
-In addition, mere aggregation of another work not based on the Library
-with the Library (or with a work based on the Library) on a volume of
-a storage or distribution medium does not bring the other work under
-the scope of this License.
-
- 3. You may opt to apply the terms of the ordinary GNU General Public
-License instead of this License to a given copy of the Library. To do
-this, you must alter all the notices that refer to this License, so
-that they refer to the ordinary GNU General Public License, version 2,
-instead of to this License. (If a newer version than version 2 of the
-ordinary GNU General Public License has appeared, then you can specify
-that version instead if you wish.) Do not make any other change in
-these notices.
-
- Once this change is made in a given copy, it is irreversible for
-that copy, so the ordinary GNU General Public License applies to all
-subsequent copies and derivative works made from that copy.
-
- This option is useful when you wish to copy part of the code of
-the Library into a program that is not a library.
-
- 4. You may copy and distribute the Library (or a portion or
-derivative of it, under Section 2) in object code or executable form
-under the terms of Sections 1 and 2 above provided that you accompany
-it with the complete corresponding machine-readable source code, which
-must be distributed under the terms of Sections 1 and 2 above on a
-medium customarily used for software interchange.
-
- If distribution of object code is made by offering access to copy
-from a designated place, then offering equivalent access to copy the
-source code from the same place satisfies the requirement to
-distribute the source code, even though third parties are not
-compelled to copy the source along with the object code.
-
- 5. A program that contains no derivative of any portion of the
-Library, but is designed to work with the Library by being compiled or
-linked with it, is called a "work that uses the Library". Such a
-work, in isolation, is not a derivative work of the Library, and
-therefore falls outside the scope of this License.
-
- However, linking a "work that uses the Library" with the Library
-creates an executable that is a derivative of the Library (because it
-contains portions of the Library), rather than a "work that uses the
-library". The executable is therefore covered by this License.
-Section 6 states terms for distribution of such executables.
-
- When a "work that uses the Library" uses material from a header file
-that is part of the Library, the object code for the work may be a
-derivative work of the Library even though the source code is not.
-Whether this is true is especially significant if the work can be
-linked without the Library, or if the work is itself a library. The
-threshold for this to be true is not precisely defined by law.
-
- If such an object file uses only numerical parameters, data
-structure layouts and accessors, and small macros and small inline
-functions (ten lines or less in length), then the use of the object
-file is unrestricted, regardless of whether it is legally a derivative
-work. (Executables containing this object code plus portions of the
-Library will still fall under Section 6.)
-
- Otherwise, if the work is a derivative of the Library, you may
-distribute the object code for the work under the terms of Section 6.
-Any executables containing that work also fall under Section 6,
-whether or not they are linked directly with the Library itself.
-
- 6. As an exception to the Sections above, you may also combine or
-link a "work that uses the Library" with the Library to produce a
-work containing portions of the Library, and distribute that work
-under terms of your choice, provided that the terms permit
-modification of the work for the customer's own use and reverse
-engineering for debugging such modifications.
-
- You must give prominent notice with each copy of the work that the
-Library is used in it and that the Library and its use are covered by
-this License. You must supply a copy of this License. If the work
-during execution displays copyright notices, you must include the
-copyright notice for the Library among them, as well as a reference
-directing the user to the copy of this License. Also, you must do one
-of these things:
-
- a) Accompany the work with the complete corresponding
- machine-readable source code for the Library including whatever
- changes were used in the work (which must be distributed under
- Sections 1 and 2 above); and, if the work is an executable linked
- with the Library, with the complete machine-readable "work that
- uses the Library", as object code and/or source code, so that the
- user can modify the Library and then relink to produce a modified
- executable containing the modified Library. (It is understood
- that the user who changes the contents of definitions files in the
- Library will not necessarily be able to recompile the application
- to use the modified definitions.)
-
- b) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (1) uses at run time a
- copy of the library already present on the user's computer system,
- rather than copying library functions into the executable, and (2)
- will operate properly with a modified version of the library, if
- the user installs one, as long as the modified version is
- interface-compatible with the version that the work was made with.
-
- c) Accompany the work with a written offer, valid for at
- least three years, to give the same user the materials
- specified in Subsection 6a, above, for a charge no more
- than the cost of performing this distribution.
-
- d) If distribution of the work is made by offering access to copy
- from a designated place, offer equivalent access to copy the above
- specified materials from the same place.
-
- e) Verify that the user has already received a copy of these
- materials or that you have already sent this user a copy.
-
- For an executable, the required form of the "work that uses the
-Library" must include any data and utility programs needed for
-reproducing the executable from it. However, as a special exception,
-the materials to be distributed need not include anything that is
-normally distributed (in either source or binary form) with the major
-components (compiler, kernel, and so on) of the operating system on
-which the executable runs, unless that component itself accompanies
-the executable.
-
- It may happen that this requirement contradicts the license
-restrictions of other proprietary libraries that do not normally
-accompany the operating system. Such a contradiction means you cannot
-use both them and the Library together in an executable that you
-distribute.
-
- 7. You may place library facilities that are a work based on the
-Library side-by-side in a single library together with other library
-facilities not covered by this License, and distribute such a combined
-library, provided that the separate distribution of the work based on
-the Library and of the other library facilities is otherwise
-permitted, and provided that you do these two things:
-
- a) Accompany the combined library with a copy of the same work
- based on the Library, uncombined with any other library
- facilities. This must be distributed under the terms of the
- Sections above.
-
- b) Give prominent notice with the combined library of the fact
- that part of it is a work based on the Library, and explaining
- where to find the accompanying uncombined form of the same work.
-
- 8. You may not copy, modify, sublicense, link with, or distribute
-the Library except as expressly provided under this License. Any
-attempt otherwise to copy, modify, sublicense, link with, or
-distribute the Library is void, and will automatically terminate your
-rights under this License. However, parties who have received copies,
-or rights, from you under this License will not have their licenses
-terminated so long as such parties remain in full compliance.
-
- 9. You are not required to accept this License, since you have not
-signed it. However, nothing else grants you permission to modify or
-distribute the Library or its derivative works. These actions are
-prohibited by law if you do not accept this License. Therefore, by
-modifying or distributing the Library (or any work based on the
-Library), you indicate your acceptance of this License to do so, and
-all its terms and conditions for copying, distributing or modifying
-the Library or works based on it.
-
- 10. Each time you redistribute the Library (or any work based on the
-Library), the recipient automatically receives a license from the
-original licensor to copy, distribute, link with or modify the Library
-subject to these terms and conditions. You may not impose any further
-restrictions on the recipients' exercise of the rights granted herein.
-You are not responsible for enforcing compliance by third parties with
-this License.
-
- 11. If, as a consequence of a court judgment or allegation of patent
-infringement or for any other reason (not limited to patent issues),
-conditions are imposed on you (whether by court order, agreement or
-otherwise) that contradict the conditions of this License, they do not
-excuse you from the conditions of this License. If you cannot
-distribute so as to satisfy simultaneously your obligations under this
-License and any other pertinent obligations, then as a consequence you
-may not distribute the Library at all. For example, if a patent
-license would not permit royalty-free redistribution of the Library by
-all those who receive copies directly or indirectly through you, then
-the only way you could satisfy both it and this License would be to
-refrain entirely from distribution of the Library.
-
-If any portion of this section is held invalid or unenforceable under any
-particular circumstance, the balance of the section is intended to apply,
-and the section as a whole is intended to apply in other circumstances.
-
-It is not the purpose of this section to induce you to infringe any
-patents or other property right claims or to contest validity of any
-such claims; this section has the sole purpose of protecting the
-integrity of the free software distribution system which is
-implemented by public license practices. Many people have made
-generous contributions to the wide range of software distributed
-through that system in reliance on consistent application of that
-system; it is up to the author/donor to decide if he or she is willing
-to distribute software through any other system and a licensee cannot
-impose that choice.
-
-This section is intended to make thoroughly clear what is believed to
-be a consequence of the rest of this License.
-
- 12. If the distribution and/or use of the Library is restricted in
-certain countries either by patents or by copyrighted interfaces, the
-original copyright holder who places the Library under this License may add
-an explicit geographical distribution limitation excluding those countries,
-so that distribution is permitted only in or among countries not thus
-excluded. In such case, this License incorporates the limitation as if
-written in the body of this License.
-
- 13. The Free Software Foundation may publish revised and/or new
-versions of the Lesser General Public License from time to time.
-Such new versions will be similar in spirit to the present version,
-but may differ in detail to address new problems or concerns.
-
-Each version is given a distinguishing version number. If the Library
-specifies a version number of this License which applies to it and
-"any later version", you have the option of following the terms and
-conditions either of that version or of any later version published by
-the Free Software Foundation. If the Library does not specify a
-license version number, you may choose any version ever published by
-the Free Software Foundation.
-
- 14. If you wish to incorporate parts of the Library into other free
-programs whose distribution conditions are incompatible with these,
-write to the author to ask for permission. For software which is
-copyrighted by the Free Software Foundation, write to the Free
-Software Foundation; we sometimes make exceptions for this. Our
-decision will be guided by the two goals of preserving the free status
-of all derivatives of our free software and of promoting the sharing
-and reuse of software generally.
-
- NO WARRANTY
-
- 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
-WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
-EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
-OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY
-KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE
-IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
-PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE
-LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
-THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
-
- 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN
-WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY
-AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU
-FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR
-CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE
-LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING
-RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
-FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
-SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
-DAMAGES.
-
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Libraries
-
- If you develop a new library, and you want it to be of the greatest
-possible use to the public, we recommend making it free software that
-everyone can redistribute and change. You can do so by permitting
-redistribution under these terms (or, alternatively, under the terms of the
-ordinary General Public License).
-
- To apply these terms, attach the following notices to the library. It is
-safest to attach them to the start of each source file to most effectively
-convey the exclusion of warranty; and each file should have at least the
-"copyright" line and a pointer to where the full notice is found.
-
- <one line to give the library's name and a brief idea of what it does.>
- Copyright (C) <year> <name of author>
-
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
-
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
-
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-
-Also add information on how to contact you by electronic and paper mail.
-
-You should also get your employer (if you work as a programmer) or your
-school, if any, to sign a "copyright disclaimer" for the library, if
-necessary. Here is a sample; alter the names:
-
- Yoyodyne, Inc., hereby disclaims all copyright interest in the
- library `Frob' (a library for tweaking knobs) written by James Random Hacker.
-
- <signature of Ty Coon>, 1 April 1990
- Ty Coon, President of Vice
-
-That's all there is to it! \ No newline at end of file
diff --git a/externals/qhexedit/qhexedit.cpp b/externals/qhexedit/qhexedit.cpp
deleted file mode 100644
index b12624e08..000000000
--- a/externals/qhexedit/qhexedit.cpp
+++ /dev/null
@@ -1,180 +0,0 @@
-#include <QtGui>
-
-#include "qhexedit.h"
-
-
-QHexEdit::QHexEdit(QWidget *parent) : QScrollArea(parent)
-{
- qHexEdit_p = new QHexEditPrivate(this);
- setWidget(qHexEdit_p);
- setWidgetResizable(true);
-
- connect(qHexEdit_p, SIGNAL(currentAddressChanged(int)), this, SIGNAL(currentAddressChanged(int)));
- connect(qHexEdit_p, SIGNAL(currentSizeChanged(int)), this, SIGNAL(currentSizeChanged(int)));
- connect(qHexEdit_p, SIGNAL(dataChanged()), this, SIGNAL(dataChanged()));
- connect(qHexEdit_p, SIGNAL(overwriteModeChanged(bool)), this, SIGNAL(overwriteModeChanged(bool)));
- setFocusPolicy(Qt::NoFocus);
-}
-
-int QHexEdit::indexOf(const QByteArray & ba, int from) const
-{
- return qHexEdit_p->indexOf(ba, from);
-}
-
-void QHexEdit::insert(int i, const QByteArray & ba)
-{
- qHexEdit_p->insert(i, ba);
-}
-
-void QHexEdit::insert(int i, char ch)
-{
- qHexEdit_p->insert(i, ch);
-}
-
-int QHexEdit::lastIndexOf(const QByteArray & ba, int from) const
-{
- return qHexEdit_p->lastIndexOf(ba, from);
-}
-
-void QHexEdit::remove(int pos, int len)
-{
- qHexEdit_p->remove(pos, len);
-}
-
-void QHexEdit::replace( int pos, int len, const QByteArray & after)
-{
- qHexEdit_p->replace(pos, len, after);
-}
-
-QString QHexEdit::toReadableString()
-{
- return qHexEdit_p->toRedableString();
-}
-
-QString QHexEdit::selectionToReadableString()
-{
- return qHexEdit_p->selectionToReadableString();
-}
-
-void QHexEdit::setAddressArea(bool addressArea)
-{
- qHexEdit_p->setAddressArea(addressArea);
-}
-
-void QHexEdit::redo()
-{
- qHexEdit_p->redo();
-}
-
-void QHexEdit::undo()
-{
- qHexEdit_p->undo();
-}
-
-void QHexEdit::setAddressWidth(int addressWidth)
-{
- qHexEdit_p->setAddressWidth(addressWidth);
-}
-
-void QHexEdit::setAsciiArea(bool asciiArea)
-{
- qHexEdit_p->setAsciiArea(asciiArea);
-}
-
-void QHexEdit::setHighlighting(bool mode)
-{
- qHexEdit_p->setHighlighting(mode);
-}
-
-void QHexEdit::setAddressOffset(int offset)
-{
- qHexEdit_p->setAddressOffset(offset);
-}
-
-int QHexEdit::addressOffset()
-{
- return qHexEdit_p->addressOffset();
-}
-
-void QHexEdit::setCursorPosition(int cursorPos)
-{
- // cursorPos in QHexEditPrivate is the position of the textcoursor without
- // blanks, means bytePos*2
- qHexEdit_p->setCursorPos(cursorPos*2);
-}
-
-int QHexEdit::cursorPosition()
-{
- return qHexEdit_p->cursorPos() / 2;
-}
-
-
-void QHexEdit::setData(const QByteArray &data)
-{
- qHexEdit_p->setData(data);
-}
-
-QByteArray QHexEdit::data()
-{
- return qHexEdit_p->data();
-}
-
-void QHexEdit::setAddressAreaColor(const QColor &color)
-{
- qHexEdit_p->setAddressAreaColor(color);
-}
-
-QColor QHexEdit::addressAreaColor()
-{
- return qHexEdit_p->addressAreaColor();
-}
-
-void QHexEdit::setHighlightingColor(const QColor &color)
-{
- qHexEdit_p->setHighlightingColor(color);
-}
-
-QColor QHexEdit::highlightingColor()
-{
- return qHexEdit_p->highlightingColor();
-}
-
-void QHexEdit::setSelectionColor(const QColor &color)
-{
- qHexEdit_p->setSelectionColor(color);
-}
-
-QColor QHexEdit::selectionColor()
-{
- return qHexEdit_p->selectionColor();
-}
-
-void QHexEdit::setOverwriteMode(bool overwriteMode)
-{
- qHexEdit_p->setOverwriteMode(overwriteMode);
-}
-
-bool QHexEdit::overwriteMode()
-{
- return qHexEdit_p->overwriteMode();
-}
-
-void QHexEdit::setReadOnly(bool readOnly)
-{
- qHexEdit_p->setReadOnly(readOnly);
-}
-
-bool QHexEdit::isReadOnly()
-{
- return qHexEdit_p->isReadOnly();
-}
-
-void QHexEdit::setFont(const QFont &font)
-{
- qHexEdit_p->setFont(font);
-}
-
-const QFont & QHexEdit::font() const
-{
- return qHexEdit_p->font();
-}
diff --git a/externals/qhexedit/qhexedit.h b/externals/qhexedit/qhexedit.h
deleted file mode 100644
index 15b6d7603..000000000
--- a/externals/qhexedit/qhexedit.h
+++ /dev/null
@@ -1,240 +0,0 @@
-// Original author: Winfried Simon
-// See http://code.google.com/p/qhexedit2/
-// Huge thanks!
-
-#ifndef QHEXEDIT_H
-#define QHEXEDIT_H
-
-#include <QtGui>
-#include "qhexedit_p.h"
-
-/** \mainpage
-QHexEdit is a binary editor widget for Qt.
-
-\version Version 0.6.3
-\image html hexedit.png
-*/
-
-
-/*! QHexEdit is a hex editor widget written in C++ for the Qt (Qt4) framework.
-It is a simple editor for binary data, just like QPlainTextEdit is for text
-data. There are sip configuration files included, so it is easy to create
-bindings for PyQt and you can use this widget also in python.
-
-QHexEdit takes the data of a QByteArray (setData()) and shows it. You can use
-the mouse or the keyboard to navigate inside the widget. If you hit the keys
-(0..9, a..f) you will change the data. Changed data is highlighted and can be
-accessed via data().
-
-Normaly QHexEdit works in the overwrite Mode. You can set overwriteMode(false)
-and insert data. In this case the size of data() increases. It is also possible
-to delete bytes (del or backspace), here the size of data decreases.
-
-You can select data with keyboard hits or mouse movements. The copy-key will
-copy the selected data into the clipboard. The cut-key copies also but delets
-it afterwards. In overwrite mode, the paste function overwrites the content of
-the (does not change the length) data. In insert mode, clipboard data will be
-inserted. The clipboard content is expected in ASCII Hex notation. Unknown
-characters will be ignored.
-
-QHexEdit comes with undo/redo functionality. All changes can be undone, by
-pressing the undo-key (usually ctr-z). They can also be redone afterwards.
-The undo/redo framework is cleared, when setData() sets up a new
-content for the editor. You can search data inside the content with indexOf()
-and lastIndexOf(). The replace() function is to change located subdata. This
-'replaced' data can also be undone by the undo/redo framework.
-
-This widget can only handle small amounts of data. The size has to be below 10
-megabytes, otherwise the scroll sliders ard not shown and you can't scroll any
-more.
-*/
- class QHexEdit : public QScrollArea
-{
- Q_OBJECT
- /*! Property data holds the content of QHexEdit. Call setData() to set the
- content of QHexEdit, data() returns the actual content.
- */
- Q_PROPERTY(QByteArray data READ data WRITE setData)
-
- /*! Property addressOffset is added to the Numbers of the Address Area.
- A offset in the address area (left side) is sometimes usefull, whe you show
- only a segment of a complete memory picture. With setAddressOffset() you set
- this property - with addressOffset() you get the actual value.
- */
- Q_PROPERTY(int addressOffset READ addressOffset WRITE setAddressOffset)
-
- /*! Property address area color sets (setAddressAreaColor()) the backgorund
- color of address areas. You can also read the color (addressaAreaColor()).
- */
- Q_PROPERTY(QColor addressAreaColor READ addressAreaColor WRITE setAddressAreaColor)
-
- /*! Porperty cursorPosition sets or gets the position of the editor cursor
- in QHexEdit.
- */
- Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition)
-
- /*! Property highlighting color sets (setHighlightingColor()) the backgorund
- color of highlighted text areas. You can also read the color
- (highlightingColor()).
- */
- Q_PROPERTY(QColor highlightingColor READ highlightingColor WRITE setHighlightingColor)
-
- /*! Property selection color sets (setSelectionColor()) the backgorund
- color of selected text areas. You can also read the color
- (selectionColor()).
- */
- Q_PROPERTY(QColor selectionColor READ selectionColor WRITE setSelectionColor)
-
- /*! Porperty overwrite mode sets (setOverwriteMode()) or gets (overwriteMode()) the mode
- in which the editor works. In overwrite mode the user will overwrite existing data. The
- size of data will be constant. In insert mode the size will grow, when inserting
- new data.
- */
- Q_PROPERTY(bool overwriteMode READ overwriteMode WRITE setOverwriteMode)
-
- /*! Porperty readOnly sets (setReadOnly()) or gets (isReadOnly) the mode
- in which the editor works. In readonly mode the the user can only navigate
- through the data and select data; modifying is not possible. This
- property's default is false.
- */
- Q_PROPERTY(bool readOnly READ isReadOnly WRITE setReadOnly)
-
- /*! Set the font of the widget. Please use fixed width fonts like Mono or Courier.*/
- Q_PROPERTY(QFont font READ font WRITE setFont)
-
-
-public:
- /*! Creates an instance of QHexEdit.
- \param parent Parent widget of QHexEdit.
- */
- QHexEdit(QWidget *parent = 0);
-
- /*! Returns the index position of the first occurrence
- of the byte array ba in this byte array, searching forward from index position
- from. Returns -1 if ba could not be found. In addition to this functionality
- of QByteArray the cursorposition is set to the end of found bytearray and
- it will be selected.
-
- */
- int indexOf(const QByteArray & ba, int from = 0) const;
-
- /*! Inserts a byte array.
- \param i Index position, where to insert
- \param ba byte array, which is to insert
- In overwrite mode, the existing data will be overwritten, in insertmode ba will be
- inserted and size of data grows.
- */
- void insert(int i, const QByteArray & ba);
-
- /*! Inserts a char.
- \param i Index position, where to insert
- \param ch Char, which is to insert
- In overwrite mode, the existing data will be overwritten, in insertmode ba will be
- inserted and size of data grows.
- */
- void insert(int i, char ch);
-
- /*! Returns the index position of the last occurrence
- of the byte array ba in this byte array, searching backwards from index position
- from. Returns -1 if ba could not be found. In addition to this functionality
- of QByteArray the cursorposition is set to the beginning of found bytearray and
- it will be selected.
-
- */
- int lastIndexOf(const QByteArray & ba, int from = 0) const;
-
- /*! Removes len bytes from the content.
- \param pos Index position, where to remove
- \param len Amount of bytes to remove
- In overwrite mode, the existing bytes will be overwriten with 0x00.
- */
- void remove(int pos, int len=1);
-
- /*! Replaces len bytes from index position pos with the byte array after.
- */
- void replace( int pos, int len, const QByteArray & after);
-
- /*! Gives back a formatted image of the content of QHexEdit
- */
- QString toReadableString();
-
- /*! Gives back a formatted image of the selected content of QHexEdit
- */
- QString selectionToReadableString();
-
- /*! \cond docNever */
- void setAddressOffset(int offset);
- int addressOffset();
- void setCursorPosition(int cusorPos);
- int cursorPosition();
- void setData(QByteArray const &data);
- QByteArray data();
- void setAddressAreaColor(QColor const &color);
- QColor addressAreaColor();
- void setHighlightingColor(QColor const &color);
- QColor highlightingColor();
- void setSelectionColor(QColor const &color);
- QColor selectionColor();
- void setOverwriteMode(bool);
- bool overwriteMode();
- void setReadOnly(bool);
- bool isReadOnly();
- const QFont &font() const;
- void setFont(const QFont &);
- /*! \endcond docNever */
-
-public slots:
- /*! Redoes the last operation. If there is no operation to redo, i.e.
- there is no redo step in the undo/redo history, nothing happens.
- */
- void redo();
-
- /*! Set the minimum width of the address area.
- \param addressWidth Width in characters.
- */
- void setAddressWidth(int addressWidth);
-
- /*! Switch the address area on or off.
- \param addressArea true (show it), false (hide it).
- */
- void setAddressArea(bool addressArea);
-
- /*! Switch the ascii area on or off.
- \param asciiArea true (show it), false (hide it).
- */
- void setAsciiArea(bool asciiArea);
-
- /*! Switch the highlighting feature on or of.
- \param mode true (show it), false (hide it).
- */
- void setHighlighting(bool mode);
-
- /*! Undoes the last operation. If there is no operation to undo, i.e.
- there is no undo step in the undo/redo history, nothing happens.
- */
- void undo();
-
-signals:
-
- /*! Contains the address, where the cursor is located. */
- void currentAddressChanged(int address);
-
- /*! Contains the size of the data to edit. */
- void currentSizeChanged(int size);
-
- /*! The signal is emited every time, the data is changed. */
- void dataChanged();
-
- /*! The signal is emited every time, the overwrite mode is changed. */
- void overwriteModeChanged(bool state);
-
-private:
- /*! \cond docNever */
- QHexEditPrivate *qHexEdit_p;
- QHBoxLayout *layout;
- QScrollArea *scrollArea;
- /*! \endcond docNever */
-};
-
-#endif
-
diff --git a/externals/qhexedit/qhexedit_p.cpp b/externals/qhexedit/qhexedit_p.cpp
deleted file mode 100644
index 2a6885de8..000000000
--- a/externals/qhexedit/qhexedit_p.cpp
+++ /dev/null
@@ -1,857 +0,0 @@
-#include "qhexedit_p.h"
-#include "commands.h"
-
-const int HEXCHARS_IN_LINE = 47;
-const int GAP_ADR_HEX = 10;
-const int GAP_HEX_ASCII = 16;
-const int BYTES_PER_LINE = 16;
-
-QHexEditPrivate::QHexEditPrivate(QScrollArea *parent) : QWidget(parent)
-{
- _undoStack = new QUndoStack(this);
-
- _scrollArea = parent;
- setAddressWidth(4);
- setAddressOffset(0);
- setAddressArea(true);
- setAsciiArea(true);
- setHighlighting(true);
- setOverwriteMode(true);
- setReadOnly(false);
- setAddressAreaColor(QColor(0xd4, 0xd4, 0xd4, 0xff));
- setHighlightingColor(QColor(0xff, 0xff, 0x99, 0xff));
- setSelectionColor(QColor(0x6d, 0x9e, 0xff, 0xff));
- setFont(QFont("Courier", 10));
-
- _size = 0;
- resetSelection(0);
-
- setFocusPolicy(Qt::StrongFocus);
-
- connect(&_cursorTimer, SIGNAL(timeout()), this, SLOT(updateCursor()));
- _cursorTimer.setInterval(500);
- _cursorTimer.start();
-}
-
-void QHexEditPrivate::setAddressOffset(int offset)
-{
- _xData.setAddressOffset(offset);
- adjust();
-}
-
-int QHexEditPrivate::addressOffset()
-{
- return _xData.addressOffset();
-}
-
-void QHexEditPrivate::setData(const QByteArray &data)
-{
- _xData.setData(data);
- _undoStack->clear();
- adjust();
- setCursorPos(0);
-}
-
-QByteArray QHexEditPrivate::data()
-{
- return _xData.data();
-}
-
-void QHexEditPrivate::setAddressAreaColor(const QColor &color)
-{
- _addressAreaColor = color;
- update();
-}
-
-QColor QHexEditPrivate::addressAreaColor()
-{
- return _addressAreaColor;
-}
-
-void QHexEditPrivate::setHighlightingColor(const QColor &color)
-{
- _highlightingColor = color;
- update();
-}
-
-QColor QHexEditPrivate::highlightingColor()
-{
- return _highlightingColor;
-}
-
-void QHexEditPrivate::setSelectionColor(const QColor &color)
-{
- _selectionColor = color;
- update();
-}
-
-QColor QHexEditPrivate::selectionColor()
-{
- return _selectionColor;
-}
-
-void QHexEditPrivate::setReadOnly(bool readOnly)
-{
- _readOnly = readOnly;
-}
-
-bool QHexEditPrivate::isReadOnly()
-{
- return _readOnly;
-}
-
-XByteArray & QHexEditPrivate::xData()
-{
- return _xData;
-}
-
-int QHexEditPrivate::indexOf(const QByteArray & ba, int from)
-{
- if (from > (_xData.data().length() - 1))
- from = _xData.data().length() - 1;
- int idx = _xData.data().indexOf(ba, from);
- if (idx > -1)
- {
- int curPos = idx*2;
- setCursorPos(curPos + ba.length()*2);
- resetSelection(curPos);
- setSelection(curPos + ba.length()*2);
- ensureVisible();
- }
- return idx;
-}
-
-void QHexEditPrivate::insert(int index, const QByteArray & ba)
-{
- if (ba.length() > 0)
- {
- if (_overwriteMode)
- {
- QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
- _undoStack->push(arrayCommand);
- emit dataChanged();
- }
- else
- {
- QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::insert, index, ba, ba.length());
- _undoStack->push(arrayCommand);
- emit dataChanged();
- }
- }
-}
-
-void QHexEditPrivate::insert(int index, char ch)
-{
- QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::insert, index, ch);
- _undoStack->push(charCommand);
- emit dataChanged();
-}
-
-int QHexEditPrivate::lastIndexOf(const QByteArray & ba, int from)
-{
- from -= ba.length();
- if (from < 0)
- from = 0;
- int idx = _xData.data().lastIndexOf(ba, from);
- if (idx > -1)
- {
- int curPos = idx*2;
- setCursorPos(curPos);
- resetSelection(curPos);
- setSelection(curPos + ba.length()*2);
- ensureVisible();
- }
- return idx;
-}
-
-void QHexEditPrivate::remove(int index, int len)
-{
- if (len > 0)
- {
- if (len == 1)
- {
- if (_overwriteMode)
- {
- QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, char(0));
- _undoStack->push(charCommand);
- emit dataChanged();
- }
- else
- {
- QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::remove, index, char(0));
- _undoStack->push(charCommand);
- emit dataChanged();
- }
- }
- else
- {
- QByteArray ba = QByteArray(len, char(0));
- if (_overwriteMode)
- {
- QUndoCommand *arrayCommand = new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
- _undoStack->push(arrayCommand);
- emit dataChanged();
- }
- else
- {
- QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::remove, index, ba, len);
- _undoStack->push(arrayCommand);
- emit dataChanged();
- }
- }
- }
-}
-
-void QHexEditPrivate::replace(int index, char ch)
-{
- QUndoCommand *charCommand = new CharCommand(&_xData, CharCommand::replace, index, ch);
- _undoStack->push(charCommand);
- resetSelection();
- emit dataChanged();
-}
-
-void QHexEditPrivate::replace(int index, const QByteArray & ba)
-{
- QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, index, ba, ba.length());
- _undoStack->push(arrayCommand);
- resetSelection();
- emit dataChanged();
-}
-
-void QHexEditPrivate::replace(int pos, int len, const QByteArray &after)
-{
- QUndoCommand *arrayCommand= new ArrayCommand(&_xData, ArrayCommand::replace, pos, after, len);
- _undoStack->push(arrayCommand);
- resetSelection();
- emit dataChanged();
-}
-
-void QHexEditPrivate::setAddressArea(bool addressArea)
-{
- _addressArea = addressArea;
- adjust();
-
- setCursorPos(_cursorPosition);
-}
-
-void QHexEditPrivate::setAddressWidth(int addressWidth)
-{
- _xData.setAddressWidth(addressWidth);
-
- setCursorPos(_cursorPosition);
-}
-
-void QHexEditPrivate::setAsciiArea(bool asciiArea)
-{
- _asciiArea = asciiArea;
- adjust();
-}
-
-void QHexEditPrivate::setFont(const QFont &font)
-{
- QWidget::setFont(font);
- adjust();
-}
-
-void QHexEditPrivate::setHighlighting(bool mode)
-{
- _highlighting = mode;
- update();
-}
-
-void QHexEditPrivate::setOverwriteMode(bool overwriteMode)
-{
- _overwriteMode = overwriteMode;
-}
-
-bool QHexEditPrivate::overwriteMode()
-{
- return _overwriteMode;
-}
-
-void QHexEditPrivate::redo()
-{
- _undoStack->redo();
- emit dataChanged();
- setCursorPos(_cursorPosition);
- update();
-}
-
-void QHexEditPrivate::undo()
-{
- _undoStack->undo();
- emit dataChanged();
- setCursorPos(_cursorPosition);
- update();
-}
-
-QString QHexEditPrivate::toRedableString()
-{
- return _xData.toRedableString();
-}
-
-
-QString QHexEditPrivate::selectionToReadableString()
-{
- return _xData.toRedableString(getSelectionBegin(), getSelectionEnd());
-}
-
-void QHexEditPrivate::keyPressEvent(QKeyEvent *event)
-{
- int charX = (_cursorX - _xPosHex) / _charWidth;
- int posX = (charX / 3) * 2 + (charX % 3);
- int posBa = (_cursorY / _charHeight) * BYTES_PER_LINE + posX / 2;
-
-
-/*****************************************************************************/
-/* Cursor movements */
-/*****************************************************************************/
-
- if (event->matches(QKeySequence::MoveToNextChar))
- {
- setCursorPos(_cursorPosition + 1);
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToPreviousChar))
- {
- setCursorPos(_cursorPosition - 1);
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToEndOfLine))
- {
- setCursorPos(_cursorPosition | (2 * BYTES_PER_LINE -1));
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToStartOfLine))
- {
- setCursorPos(_cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)));
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToPreviousLine))
- {
- setCursorPos(_cursorPosition - (2 * BYTES_PER_LINE));
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToNextLine))
- {
- setCursorPos(_cursorPosition + (2 * BYTES_PER_LINE));
- resetSelection(_cursorPosition);
- }
-
- if (event->matches(QKeySequence::MoveToNextPage))
- {
- setCursorPos(_cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToPreviousPage))
- {
- setCursorPos(_cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE));
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToEndOfDocument))
- {
- setCursorPos(_xData.size() * 2);
- resetSelection(_cursorPosition);
- }
- if (event->matches(QKeySequence::MoveToStartOfDocument))
- {
- setCursorPos(0);
- resetSelection(_cursorPosition);
- }
-
-/*****************************************************************************/
-/* Select commands */
-/*****************************************************************************/
- if (event->matches(QKeySequence::SelectAll))
- {
- resetSelection(0);
- setSelection(2*_xData.size() + 1);
- }
- if (event->matches(QKeySequence::SelectNextChar))
- {
- int pos = _cursorPosition + 1;
- setCursorPos(pos);
- setSelection(pos);
- }
- if (event->matches(QKeySequence::SelectPreviousChar))
- {
- int pos = _cursorPosition - 1;
- setSelection(pos);
- setCursorPos(pos);
- }
- if (event->matches(QKeySequence::SelectEndOfLine))
- {
- int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE)) + (2 * BYTES_PER_LINE);
- setCursorPos(pos);
- setSelection(pos);
- }
- if (event->matches(QKeySequence::SelectStartOfLine))
- {
- int pos = _cursorPosition - (_cursorPosition % (2 * BYTES_PER_LINE));
- setCursorPos(pos);
- setSelection(pos);
- }
- if (event->matches(QKeySequence::SelectPreviousLine))
- {
- int pos = _cursorPosition - (2 * BYTES_PER_LINE);
- setCursorPos(pos);
- setSelection(pos);
- }
- if (event->matches(QKeySequence::SelectNextLine))
- {
- int pos = _cursorPosition + (2 * BYTES_PER_LINE);
- setCursorPos(pos);
- setSelection(pos);
- }
-
- if (event->matches(QKeySequence::SelectNextPage))
- {
- int pos = _cursorPosition + (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
- setCursorPos(pos);
- setSelection(pos);
- }
- if (event->matches(QKeySequence::SelectPreviousPage))
- {
- int pos = _cursorPosition - (((_scrollArea->viewport()->height() / _charHeight) - 1) * 2 * BYTES_PER_LINE);
- setCursorPos(pos);
- setSelection(pos);
- }
- if (event->matches(QKeySequence::SelectEndOfDocument))
- {
- int pos = _xData.size() * 2;
- setCursorPos(pos);
- setSelection(pos);
- }
- if (event->matches(QKeySequence::SelectStartOfDocument))
- {
- int pos = 0;
- setCursorPos(pos);
- setSelection(pos);
- }
-
-/*****************************************************************************/
-/* Edit Commands */
-/*****************************************************************************/
-if (!_readOnly)
-{
- /* Hex input */
- int key = int(event->text()[0].toLatin1());
- if ((key>='0' && key<='9') || (key>='a' && key <= 'f'))
- {
- if (getSelectionBegin() != getSelectionEnd())
- {
- posBa = getSelectionBegin();
- remove(posBa, getSelectionEnd() - posBa);
- setCursorPos(2*posBa);
- resetSelection(2*posBa);
- }
-
- // If insert mode, then insert a byte
- if (_overwriteMode == false)
- if ((charX % 3) == 0)
- {
- insert(posBa, char(0));
- }
-
- // Change content
- if (_xData.size() > 0)
- {
- QByteArray hexValue = _xData.data().mid(posBa, 1).toHex();
- if ((charX % 3) == 0)
- hexValue[0] = key;
- else
- hexValue[1] = key;
-
- replace(posBa, QByteArray().fromHex(hexValue)[0]);
-
- setCursorPos(_cursorPosition + 1);
- resetSelection(_cursorPosition);
- }
- }
-
- /* Cut & Paste */
- if (event->matches(QKeySequence::Cut))
- {
- QString result = QString();
- for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
- {
- result += _xData.data().mid(idx, 1).toHex() + " ";
- if ((idx % 16) == 15)
- result.append("\n");
- }
- remove(getSelectionBegin(), getSelectionEnd() - getSelectionBegin());
- QClipboard *clipboard = QApplication::clipboard();
- clipboard->setText(result);
- setCursorPos(getSelectionBegin());
- resetSelection(getSelectionBegin());
- }
-
- if (event->matches(QKeySequence::Paste))
- {
- QClipboard *clipboard = QApplication::clipboard();
- QByteArray ba = QByteArray().fromHex(clipboard->text().toLatin1());
- insert(_cursorPosition / 2, ba);
- setCursorPos(_cursorPosition + 2 * ba.length());
- resetSelection(getSelectionBegin());
- }
-
-
- /* Delete char */
- if (event->matches(QKeySequence::Delete))
- {
- if (getSelectionBegin() != getSelectionEnd())
- {
- posBa = getSelectionBegin();
- remove(posBa, getSelectionEnd() - posBa);
- setCursorPos(2*posBa);
- resetSelection(2*posBa);
- }
- else
- {
- if (_overwriteMode)
- replace(posBa, char(0));
- else
- remove(posBa, 1);
- }
- }
-
- /* Backspace */
- if ((event->key() == Qt::Key_Backspace) && (event->modifiers() == Qt::NoModifier))
- {
- if (getSelectionBegin() != getSelectionEnd())
- {
- posBa = getSelectionBegin();
- remove(posBa, getSelectionEnd() - posBa);
- setCursorPos(2*posBa);
- resetSelection(2*posBa);
- }
- else
- {
- if (posBa > 0)
- {
- if (_overwriteMode)
- replace(posBa - 1, char(0));
- else
- remove(posBa - 1, 1);
- setCursorPos(_cursorPosition - 2);
- }
- }
- }
-
- /* undo */
- if (event->matches(QKeySequence::Undo))
- {
- undo();
- }
-
- /* redo */
- if (event->matches(QKeySequence::Redo))
- {
- redo();
- }
-
- }
-
- if (event->matches(QKeySequence::Copy))
- {
- QString result = QString();
- for (int idx = getSelectionBegin(); idx < getSelectionEnd(); idx++)
- {
- result += _xData.data().mid(idx, 1).toHex() + " ";
- if ((idx % 16) == 15)
- result.append('\n');
- }
- QClipboard *clipboard = QApplication::clipboard();
- clipboard->setText(result);
- }
-
- // Switch between insert/overwrite mode
- if ((event->key() == Qt::Key_Insert) && (event->modifiers() == Qt::NoModifier))
- {
- _overwriteMode = !_overwriteMode;
- setCursorPos(_cursorPosition);
- overwriteModeChanged(_overwriteMode);
- }
-
- ensureVisible();
- update();
-}
-
-void QHexEditPrivate::mouseMoveEvent(QMouseEvent * event)
-{
- _blink = false;
- update();
- int actPos = cursorPos(event->pos());
- setCursorPos(actPos);
- setSelection(actPos);
-}
-
-void QHexEditPrivate::mousePressEvent(QMouseEvent * event)
-{
- _blink = false;
- update();
- int cPos = cursorPos(event->pos());
- resetSelection(cPos);
- setCursorPos(cPos);
-}
-
-void QHexEditPrivate::paintEvent(QPaintEvent *event)
-{
- QPainter painter(this);
-
- // draw some patterns if needed
- painter.fillRect(event->rect(), this->palette().color(QPalette::Base));
- if (_addressArea)
- painter.fillRect(QRect(_xPosAdr, event->rect().top(), _xPosHex - GAP_ADR_HEX + 2, height()), _addressAreaColor);
- if (_asciiArea)
- {
- int linePos = _xPosAscii - (GAP_HEX_ASCII / 2);
- painter.setPen(Qt::gray);
- painter.drawLine(linePos, event->rect().top(), linePos, height());
- }
-
- painter.setPen(this->palette().color(QPalette::WindowText));
-
- // calc position
- int firstLineIdx = ((event->rect().top()/ _charHeight) - _charHeight) * BYTES_PER_LINE;
- if (firstLineIdx < 0)
- firstLineIdx = 0;
- int lastLineIdx = ((event->rect().bottom() / _charHeight) + _charHeight) * BYTES_PER_LINE;
- if (lastLineIdx > _xData.size())
- lastLineIdx = _xData.size();
- int yPosStart = ((firstLineIdx) / BYTES_PER_LINE) * _charHeight + _charHeight;
-
- // paint address area
- if (_addressArea)
- {
- for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
- {
- QString address = QString("%1")
- .arg(lineIdx + _xData.addressOffset(), _xData.realAddressNumbers(), 16, QChar('0'));
- painter.drawText(_xPosAdr, yPos, address);
- }
- }
-
- // paint hex area
- QByteArray hexBa(_xData.data().mid(firstLineIdx, lastLineIdx - firstLineIdx + 1).toHex());
- QBrush highLighted = QBrush(_highlightingColor);
- QPen colHighlighted = QPen(this->palette().color(QPalette::WindowText));
- QBrush selected = QBrush(_selectionColor);
- QPen colSelected = QPen(Qt::white);
- QPen colStandard = QPen(this->palette().color(QPalette::WindowText));
-
- painter.setBackgroundMode(Qt::TransparentMode);
-
- for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
- {
- QByteArray hex;
- int xPos = _xPosHex;
- for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() && (colIdx < BYTES_PER_LINE)); colIdx++)
- {
- int posBa = lineIdx + colIdx;
- if ((getSelectionBegin() <= posBa) && (getSelectionEnd() > posBa))
- {
- painter.setBackground(selected);
- painter.setBackgroundMode(Qt::OpaqueMode);
- painter.setPen(colSelected);
- }
- else
- {
- if (_highlighting)
- {
- // hilight diff bytes
- painter.setBackground(highLighted);
- if (_xData.dataChanged(posBa))
- {
- painter.setPen(colHighlighted);
- painter.setBackgroundMode(Qt::OpaqueMode);
- }
- else
- {
- painter.setPen(colStandard);
- painter.setBackgroundMode(Qt::TransparentMode);
- }
- }
- }
-
- // render hex value
- if (colIdx == 0)
- {
- hex = hexBa.mid((lineIdx - firstLineIdx) * 2, 2);
- painter.drawText(xPos, yPos, hex);
- xPos += 2 * _charWidth;
- } else {
- hex = hexBa.mid((lineIdx + colIdx - firstLineIdx) * 2, 2).prepend(" ");
- painter.drawText(xPos, yPos, hex);
- xPos += 3 * _charWidth;
- }
-
- }
- }
- painter.setBackgroundMode(Qt::TransparentMode);
- painter.setPen(this->palette().color(QPalette::WindowText));
-
- // paint ascii area
- if (_asciiArea)
- {
- for (int lineIdx = firstLineIdx, yPos = yPosStart; lineIdx < lastLineIdx; lineIdx += BYTES_PER_LINE, yPos +=_charHeight)
- {
- int xPosAscii = _xPosAscii;
- for (int colIdx = 0; ((lineIdx + colIdx) < _xData.size() && (colIdx < BYTES_PER_LINE)); colIdx++)
- {
- painter.drawText(xPosAscii, yPos, _xData.asciiChar(lineIdx + colIdx));
- xPosAscii += _charWidth;
- }
- }
- }
-
- // paint cursor
- if (_blink && !_readOnly && hasFocus())
- {
- if (_overwriteMode)
- painter.fillRect(_cursorX, _cursorY + _charHeight - 2, _charWidth, 2, this->palette().color(QPalette::WindowText));
- else
- painter.fillRect(_cursorX, _cursorY, 2, _charHeight, this->palette().color(QPalette::WindowText));
- }
-
- if (_size != _xData.size())
- {
- _size = _xData.size();
- emit currentSizeChanged(_size);
- }
-}
-
-void QHexEditPrivate::setCursorPos(int position)
-{
- // delete cursor
- _blink = false;
- update();
-
- // cursor in range?
- if (_overwriteMode)
- {
- if (position > (_xData.size() * 2 - 1))
- position = _xData.size() * 2 - 1;
- } else {
- if (position > (_xData.size() * 2))
- position = _xData.size() * 2;
- }
-
- if (position < 0)
- position = 0;
-
- // calc position
- _cursorPosition = position;
- _cursorY = (position / (2 * BYTES_PER_LINE)) * _charHeight + 4;
- int x = (position % (2 * BYTES_PER_LINE));
- _cursorX = (((x / 2) * 3) + (x % 2)) * _charWidth + _xPosHex;
-
- // immiadately draw cursor
- _blink = true;
- update();
- emit currentAddressChanged(_cursorPosition/2);
-}
-
-int QHexEditPrivate::cursorPos(QPoint pos)
-{
- int result = -1;
- // find char under cursor
- if ((pos.x() >= _xPosHex) && (pos.x() < (_xPosHex + HEXCHARS_IN_LINE * _charWidth)))
- {
- int x = (pos.x() - _xPosHex) / _charWidth;
- if ((x % 3) == 0)
- x = (x / 3) * 2;
- else
- x = ((x / 3) * 2) + 1;
- int y = ((pos.y() - 3) / _charHeight) * 2 * BYTES_PER_LINE;
- result = x + y;
- }
- return result;
-}
-
-int QHexEditPrivate::cursorPos()
-{
- return _cursorPosition;
-}
-
-void QHexEditPrivate::resetSelection()
-{
- _selectionBegin = _selectionInit;
- _selectionEnd = _selectionInit;
-}
-
-void QHexEditPrivate::resetSelection(int pos)
-{
- if (pos < 0)
- pos = 0;
- pos = pos / 2;
- _selectionInit = pos;
- _selectionBegin = pos;
- _selectionEnd = pos;
-}
-
-void QHexEditPrivate::setSelection(int pos)
-{
- if (pos < 0)
- pos = 0;
- pos = pos / 2;
- if (pos >= _selectionInit)
- {
- _selectionEnd = pos;
- _selectionBegin = _selectionInit;
- }
- else
- {
- _selectionBegin = pos;
- _selectionEnd = _selectionInit;
- }
-}
-
-int QHexEditPrivate::getSelectionBegin()
-{
- return _selectionBegin;
-}
-
-int QHexEditPrivate::getSelectionEnd()
-{
- return _selectionEnd;
-}
-
-
-void QHexEditPrivate::updateCursor()
-{
- if (_blink)
- _blink = false;
- else
- _blink = true;
- update(_cursorX, _cursorY, _charWidth, _charHeight);
-}
-
-void QHexEditPrivate::adjust()
-{
- _charWidth = fontMetrics().width(QLatin1Char('9'));
- _charHeight = fontMetrics().height();
-
- _xPosAdr = 0;
- if (_addressArea)
- _xPosHex = _xData.realAddressNumbers()*_charWidth + GAP_ADR_HEX;
- else
- _xPosHex = 0;
- _xPosAscii = _xPosHex + HEXCHARS_IN_LINE * _charWidth + GAP_HEX_ASCII;
-
- // tell QAbstractScollbar, how big we are
- setMinimumHeight(((_xData.size()/16 + 1) * _charHeight) + 5);
- if(_asciiArea)
- setMinimumWidth(_xPosAscii + (BYTES_PER_LINE * _charWidth));
- else
- setMinimumWidth(_xPosHex + HEXCHARS_IN_LINE * _charWidth);
-
- update();
-}
-
-void QHexEditPrivate::ensureVisible()
-{
- // scrolls to cursorx, cusory (which are set by setCursorPos)
- // x-margin is 3 pixels, y-margin is half of charHeight
- _scrollArea->ensureVisible(_cursorX, _cursorY + _charHeight/2, 3, _charHeight/2 + 2);
-}
diff --git a/externals/qhexedit/qhexedit_p.h b/externals/qhexedit/qhexedit_p.h
deleted file mode 100644
index 1c2c11cc2..000000000
--- a/externals/qhexedit/qhexedit_p.h
+++ /dev/null
@@ -1,128 +0,0 @@
-#ifndef QHEXEDIT_P_H
-#define QHEXEDIT_P_H
-
-/** \cond docNever */
-
-
-#include <QtGui>
-#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
-#include <QtWidgets>
-#endif
-#include "xbytearray.h"
-
-class QHexEditPrivate : public QWidget
-{
-Q_OBJECT
-
-public:
- QHexEditPrivate(QScrollArea *parent);
-
- void setAddressAreaColor(QColor const &color);
- QColor addressAreaColor();
-
- void setAddressOffset(int offset);
- int addressOffset();
-
- void setCursorPos(int position);
- int cursorPos();
-
- void setData(QByteArray const &data);
- QByteArray data();
-
- void setHighlightingColor(QColor const &color);
- QColor highlightingColor();
-
- void setOverwriteMode(bool overwriteMode);
- bool overwriteMode();
-
- void setReadOnly(bool readOnly);
- bool isReadOnly();
-
- void setSelectionColor(QColor const &color);
- QColor selectionColor();
-
- XByteArray & xData();
-
- int indexOf(const QByteArray & ba, int from = 0);
- void insert(int index, const QByteArray & ba);
- void insert(int index, char ch);
- int lastIndexOf(const QByteArray & ba, int from = 0);
- void remove(int index, int len=1);
- void replace(int index, char ch);
- void replace(int index, const QByteArray & ba);
- void replace(int pos, int len, const QByteArray & after);
-
- void setAddressArea(bool addressArea);
- void setAddressWidth(int addressWidth);
- void setAsciiArea(bool asciiArea);
- void setHighlighting(bool mode);
- virtual void setFont(const QFont &font);
-
- void undo();
- void redo();
-
- QString toRedableString();
- QString selectionToReadableString();
-
-signals:
- void currentAddressChanged(int address);
- void currentSizeChanged(int size);
- void dataChanged();
- void overwriteModeChanged(bool state);
-
-protected:
- void keyPressEvent(QKeyEvent * event);
- void mouseMoveEvent(QMouseEvent * event);
- void mousePressEvent(QMouseEvent * event);
-
- void paintEvent(QPaintEvent *event);
-
- int cursorPos(QPoint pos); // calc cursorpos from graphics position. DOES NOT STORE POSITION
-
- void resetSelection(int pos); // set selectionStart and selectionEnd to pos
- void resetSelection(); // set selectionEnd to selectionStart
- void setSelection(int pos); // set min (if below init) or max (if greater init)
- int getSelectionBegin();
- int getSelectionEnd();
-
-
-private slots:
- void updateCursor();
-
-private:
- void adjust();
- void ensureVisible();
-
- QColor _addressAreaColor;
- QColor _highlightingColor;
- QColor _selectionColor;
- QScrollArea *_scrollArea;
- QTimer _cursorTimer;
- QUndoStack *_undoStack;
-
- XByteArray _xData; // Hält den Inhalt des Hex Editors
-
- bool _blink; // true: then cursor blinks
- bool _renderingRequired; // Flag to store that rendering is necessary
- bool _addressArea; // left area of QHexEdit
- bool _asciiArea; // medium area
- bool _highlighting; // highlighting of changed bytes
- bool _overwriteMode;
- bool _readOnly; // true: the user can only look and navigate
-
- int _charWidth, _charHeight; // char dimensions (dpendend on font)
- int _cursorX, _cursorY; // graphics position of the cursor
- int _cursorPosition; // character positioin in stream (on byte ends in to steps)
- int _xPosAdr, _xPosHex, _xPosAscii; // graphics x-position of the areas
-
- int _selectionBegin; // First selected char
- int _selectionEnd; // Last selected char
- int _selectionInit; // That's, where we pressed the mouse button
-
- int _size;
-};
-
-/** \endcond docNever */
-
-#endif
-
diff --git a/externals/qhexedit/xbytearray.cpp b/externals/qhexedit/xbytearray.cpp
deleted file mode 100644
index 09a04cfeb..000000000
--- a/externals/qhexedit/xbytearray.cpp
+++ /dev/null
@@ -1,167 +0,0 @@
-#include "xbytearray.h"
-
-XByteArray::XByteArray()
-{
- _oldSize = -99;
- _addressNumbers = 4;
- _addressOffset = 0;
-
-}
-
-int XByteArray::addressOffset()
-{
- return _addressOffset;
-}
-
-void XByteArray::setAddressOffset(int offset)
-{
- _addressOffset = offset;
-}
-
-int XByteArray::addressWidth()
-{
- return _addressNumbers;
-}
-
-void XByteArray::setAddressWidth(int width)
-{
- if ((width >= 0) && (width<=6))
- {
- _addressNumbers = width;
- }
-}
-
-QByteArray & XByteArray::data()
-{
- return _data;
-}
-
-void XByteArray::setData(QByteArray data)
-{
- _data = data;
- _changedData = QByteArray(data.length(), char(0));
-}
-
-bool XByteArray::dataChanged(int i)
-{
- return bool(_changedData[i]);
-}
-
-QByteArray XByteArray::dataChanged(int i, int len)
-{
- return _changedData.mid(i, len);
-}
-
-void XByteArray::setDataChanged(int i, bool state)
-{
- _changedData[i] = char(state);
-}
-
-void XByteArray::setDataChanged(int i, const QByteArray & state)
-{
- int length = state.length();
- int len;
- if ((i + length) > _changedData.length())
- len = _changedData.length() - i;
- else
- len = length;
- _changedData.replace(i, len, state);
-}
-
-int XByteArray::realAddressNumbers()
-{
- if (_oldSize != _data.size())
- {
- // is addressNumbers wide enought?
- QString test = QString("%1")
- .arg(_data.size() + _addressOffset, _addressNumbers, 16, QChar('0'));
- _realAddressNumbers = test.size();
- }
- return _realAddressNumbers;
-}
-
-int XByteArray::size()
-{
- return _data.size();
-}
-
-QByteArray & XByteArray::insert(int i, char ch)
-{
- _data.insert(i, ch);
- _changedData.insert(i, char(1));
- return _data;
-}
-
-QByteArray & XByteArray::insert(int i, const QByteArray & ba)
-{
- _data.insert(i, ba);
- _changedData.insert(i, QByteArray(ba.length(), char(1)));
- return _data;
-}
-
-QByteArray & XByteArray::remove(int i, int len)
-{
- _data.remove(i, len);
- _changedData.remove(i, len);
- return _data;
-}
-
-QByteArray & XByteArray::replace(int index, char ch)
-{
- _data[index] = ch;
- _changedData[index] = char(1);
- return _data;
-}
-
-QByteArray & XByteArray::replace(int index, const QByteArray & ba)
-{
- int len = ba.length();
- return replace(index, len, ba);
-}
-
-QByteArray & XByteArray::replace(int index, int length, const QByteArray & ba)
-{
- int len;
- if ((index + length) > _data.length())
- len = _data.length() - index;
- else
- len = length;
- _data.replace(index, len, ba.mid(0, len));
- _changedData.replace(index, len, QByteArray(len, char(1)));
- return _data;
-}
-
-QChar XByteArray::asciiChar(int index)
-{
- char ch = _data[index];
- if ((ch < 0x20) || (ch > 0x7e))
- ch = '.';
- return QChar(ch);
-}
-
-QString XByteArray::toRedableString(int start, int end)
-{
- int adrWidth = realAddressNumbers();
- if (_addressNumbers > adrWidth)
- adrWidth = _addressNumbers;
- if (end < 0)
- end = _data.size();
-
- QString result;
- for (int i=start; i < end; i += 16)
- {
- QString adrStr = QString("%1").arg(_addressOffset + i, adrWidth, 16, QChar('0'));
- QString hexStr;
- QString ascStr;
- for (int j=0; j<16; j++)
- {
- if ((i + j) < _data.size())
- {
- hexStr.append(" ").append(_data.mid(i+j, 1).toHex());
- ascStr.append(asciiChar(i+j));
- }
- }
- result += adrStr + " " + QString("%1").arg(hexStr, -48) + " " + QString("%1").arg(ascStr, -17) + "\n";
- }
- return result;
-}
diff --git a/externals/qhexedit/xbytearray.h b/externals/qhexedit/xbytearray.h
deleted file mode 100644
index 2b67c61b8..000000000
--- a/externals/qhexedit/xbytearray.h
+++ /dev/null
@@ -1,66 +0,0 @@
-#ifndef XBYTEARRAY_H
-#define XBYTEARRAY_H
-
-/** \cond docNever */
-
-#include <QtCore>
-
-/*! XByteArray represents the content of QHexEcit.
-XByteArray comprehend the data itself and informations to store if it was
-changed. The QHexEdit component uses these informations to perform nice
-rendering of the data
-
-XByteArray also provides some functionality to insert, replace and remove
-single chars and QByteArras. Additionally some functions support rendering
-and converting to readable strings.
-*/
-class XByteArray
-{
-public:
- explicit XByteArray();
-
- int addressOffset();
- void setAddressOffset(int offset);
-
- int addressWidth();
- void setAddressWidth(int width);
-
- QByteArray & data();
- void setData(QByteArray data);
-
- bool dataChanged(int i);
- QByteArray dataChanged(int i, int len);
- void setDataChanged(int i, bool state);
- void setDataChanged(int i, const QByteArray & state);
-
- int realAddressNumbers();
- int size();
-
- QByteArray & insert(int i, char ch);
- QByteArray & insert(int i, const QByteArray & ba);
-
- QByteArray & remove(int pos, int len);
-
- QByteArray & replace(int index, char ch);
- QByteArray & replace(int index, const QByteArray & ba);
- QByteArray & replace(int index, int length, const QByteArray & ba);
-
- QChar asciiChar(int index);
- QString toRedableString(int start=0, int end=-1);
-
-signals:
-
-public slots:
-
-private:
- QByteArray _data;
- QByteArray _changedData;
-
- int _addressNumbers; // wanted width of address area
- int _addressOffset; // will be added to the real addres inside bytearray
- int _realAddressNumbers; // real width of address area (can be greater then wanted width)
- int _oldSize; // size of data
-};
-
-/** \endcond docNever */
-#endif // XBYTEARRAY_H
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp
index ba6acf28e..84f9c03a7 100644
--- a/src/audio_core/audio_core.cpp
+++ b/src/audio_core/audio_core.cpp
@@ -56,20 +56,8 @@ void AddAddressSpace(Kernel::VMManager& address_space) {
}
void SelectSink(std::string sink_id) {
- auto iter =
- std::find_if(g_sink_details.begin(), g_sink_details.end(),
- [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
-
- if (sink_id == "auto" || iter == g_sink_details.end()) {
- if (sink_id != "auto") {
- LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id %s", sink_id.c_str());
- }
- // Auto-select.
- // g_sink_details is ordered in terms of desirability, with the best choice at the front.
- iter = g_sink_details.begin();
- }
-
- DSP::HLE::SetSink(iter->factory());
+ const SinkDetails& sink_details = GetSinkDetails(sink_id);
+ DSP::HLE::SetSink(sink_details.factory());
}
void EnableStretching(bool enable) {
diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp
index 2bbf7146e..92484c526 100644
--- a/src/audio_core/hle/source.cpp
+++ b/src/audio_core/hle/source.cpp
@@ -158,6 +158,14 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
static_cast<size_t>(state.mono_or_stereo));
}
+ u32_dsp play_position = {};
+ if (config.play_position_dirty && config.play_position != 0) {
+ config.play_position_dirty.Assign(0);
+ play_position = config.play_position;
+ // play_position applies only to the embedded buffer, and defaults to 0 w/o a dirty bit
+ // This will be the starting sample for the first time the buffer is played.
+ }
+
if (config.embedded_buffer_dirty) {
config.embedded_buffer_dirty.Assign(0);
state.input_queue.emplace(Buffer{
@@ -171,9 +179,18 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
state.mono_or_stereo,
state.format,
false,
+ play_position,
+ false,
});
- LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu",
- config.physical_address, config.length, config.buffer_id);
+ LOG_TRACE(Audio_DSP, "enqueuing embedded addr=0x%08x len=%u id=%hu start=%u",
+ config.physical_address, config.length, config.buffer_id,
+ static_cast<u32>(config.play_position));
+ }
+
+ if (config.loop_related_dirty && config.loop_related != 0) {
+ config.loop_related_dirty.Assign(0);
+ LOG_WARNING(Audio_DSP, "Unhandled complex loop with loop_related=0x%08x",
+ static_cast<u32>(config.loop_related));
}
if (config.buffer_queue_dirty) {
@@ -192,6 +209,8 @@ void Source::ParseConfig(SourceConfiguration::Configuration& config,
state.mono_or_stereo,
state.format,
true,
+ {}, // 0 in u32_dsp
+ false,
});
LOG_TRACE(Audio_DSP, "enqueuing queued %zu addr=0x%08x len=%u id=%hu", i,
b.physical_address, b.length, b.buffer_id);
@@ -247,18 +266,18 @@ bool Source::DequeueBuffer() {
if (state.input_queue.empty())
return false;
- const Buffer buf = state.input_queue.top();
- state.input_queue.pop();
+ Buffer buf = state.input_queue.top();
+
+ // if we're in a loop, the current sound keeps playing afterwards, so leave the queue alone
+ if (!buf.is_looping) {
+ state.input_queue.pop();
+ }
if (buf.adpcm_dirty) {
state.adpcm_state.yn1 = buf.adpcm_yn[0];
state.adpcm_state.yn2 = buf.adpcm_yn[1];
}
- if (buf.is_looping) {
- LOG_ERROR(Audio_DSP, "Looped buffers are unimplemented at the moment");
- }
-
const u8* const memory = Memory::GetPhysicalPointer(buf.physical_address);
if (memory) {
const unsigned num_channels = buf.mono_or_stereo == MonoOrStereo::Stereo ? 2 : 1;
@@ -305,10 +324,13 @@ bool Source::DequeueBuffer() {
break;
}
- state.current_sample_number = 0;
- state.next_sample_number = 0;
+ // the first playthrough starts at play_position, loops start at the beginning of the buffer
+ state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
+ state.next_sample_number = state.current_sample_number;
state.current_buffer_id = buf.buffer_id;
- state.buffer_update = buf.from_queue;
+ state.buffer_update = buf.from_queue && !buf.has_played;
+
+ buf.has_played = true;
LOG_TRACE(Audio_DSP, "source_id=%zu buffer_id=%hu from_queue=%s current_buffer.size()=%zu",
source_id, buf.buffer_id, buf.from_queue ? "true" : "false",
diff --git a/src/audio_core/hle/source.h b/src/audio_core/hle/source.h
index 3d725f2a3..ccb7f064f 100644
--- a/src/audio_core/hle/source.h
+++ b/src/audio_core/hle/source.h
@@ -76,6 +76,8 @@ private:
Format format;
bool from_queue;
+ u32_dsp play_position; // = 0;
+ bool has_played; // = false;
};
struct BufferOrder {
diff --git a/src/audio_core/null_sink.h b/src/audio_core/null_sink.h
index e7668438c..c732926a2 100644
--- a/src/audio_core/null_sink.h
+++ b/src/audio_core/null_sink.h
@@ -23,6 +23,12 @@ public:
size_t SamplesInQueue() const override {
return 0;
}
+
+ void SetDevice(int device_id) override {}
+
+ std::vector<std::string> GetDeviceList() const override {
+ return {};
+ }
};
} // namespace AudioCore
diff --git a/src/audio_core/sdl2_sink.cpp b/src/audio_core/sdl2_sink.cpp
index 4b66cd826..933c5f16d 100644
--- a/src/audio_core/sdl2_sink.cpp
+++ b/src/audio_core/sdl2_sink.cpp
@@ -4,12 +4,12 @@
#include <list>
#include <numeric>
-#include <vector>
#include <SDL.h>
#include "audio_core/audio_core.h"
#include "audio_core/sdl2_sink.h"
#include "common/assert.h"
#include "common/logging/log.h"
+#include "core/settings.h"
namespace AudioCore {
@@ -42,10 +42,24 @@ SDL2Sink::SDL2Sink() : impl(std::make_unique<Impl>()) {
SDL_AudioSpec obtained_audiospec;
SDL_zero(obtained_audiospec);
- impl->audio_device_id =
- SDL_OpenAudioDevice(nullptr, false, &desired_audiospec, &obtained_audiospec, 0);
+ int device_count = SDL_GetNumAudioDevices(0);
+ device_list.clear();
+ for (int i = 0; i < device_count; ++i) {
+ device_list.push_back(SDL_GetAudioDeviceName(i, 0));
+ }
+
+ const char* device = nullptr;
+
+ if (device_count >= 1 && Settings::values.audio_device_id != "auto" &&
+ !Settings::values.audio_device_id.empty()) {
+ device = Settings::values.audio_device_id.c_str();
+ }
+
+ impl->audio_device_id = SDL_OpenAudioDevice(device, false, &desired_audiospec,
+ &obtained_audiospec, SDL_AUDIO_ALLOW_ANY_CHANGE);
if (impl->audio_device_id <= 0) {
- LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with: %s", SDL_GetError());
+ LOG_CRITICAL(Audio_Sink, "SDL_OpenAudioDevice failed with code %d for device \"%s\"",
+ impl->audio_device_id, Settings::values.audio_device_id.c_str());
return;
}
@@ -69,6 +83,10 @@ unsigned int SDL2Sink::GetNativeSampleRate() const {
return impl->sample_rate;
}
+std::vector<std::string> SDL2Sink::GetDeviceList() const {
+ return device_list;
+}
+
void SDL2Sink::EnqueueSamples(const s16* samples, size_t sample_count) {
if (impl->audio_device_id <= 0)
return;
@@ -96,6 +114,10 @@ size_t SDL2Sink::SamplesInQueue() const {
return total_size;
}
+void SDL2Sink::SetDevice(int device_id) {
+ this->device_id = device_id;
+}
+
void SDL2Sink::Impl::Callback(void* impl_, u8* buffer, int buffer_size_in_bytes) {
Impl* impl = reinterpret_cast<Impl*>(impl_);
diff --git a/src/audio_core/sdl2_sink.h b/src/audio_core/sdl2_sink.h
index ccd0f7c7e..bcc725369 100644
--- a/src/audio_core/sdl2_sink.h
+++ b/src/audio_core/sdl2_sink.h
@@ -21,9 +21,14 @@ public:
size_t SamplesInQueue() const override;
+ std::vector<std::string> GetDeviceList() const override;
+ void SetDevice(int device_id) override;
+
private:
struct Impl;
std::unique_ptr<Impl> impl;
+ int device_id;
+ std::vector<std::string> device_list;
};
} // namespace AudioCore
diff --git a/src/audio_core/sink.h b/src/audio_core/sink.h
index 08f3bab5b..558c8c0fe 100644
--- a/src/audio_core/sink.h
+++ b/src/audio_core/sink.h
@@ -31,6 +31,15 @@ public:
/// Samples enqueued that have not been played yet.
virtual std::size_t SamplesInQueue() const = 0;
+
+ /**
+ * Sets the desired output device.
+ * @paran device_id Id of the desired device.
+ */
+ virtual void SetDevice(int device_id) = 0;
+
+ /// Returns the list of available devices.
+ virtual std::vector<std::string> GetDeviceList() const = 0;
};
} // namespace
diff --git a/src/audio_core/sink_details.cpp b/src/audio_core/sink_details.cpp
index 95ccc9e9d..6972395af 100644
--- a/src/audio_core/sink_details.cpp
+++ b/src/audio_core/sink_details.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
#include <memory>
#include <vector>
#include "audio_core/null_sink.h"
@@ -9,6 +10,7 @@
#ifdef HAVE_SDL2
#include "audio_core/sdl2_sink.h"
#endif
+#include "common/logging/log.h"
namespace AudioCore {
@@ -20,4 +22,21 @@ const std::vector<SinkDetails> g_sink_details = {
{"null", []() { return std::make_unique<NullSink>(); }},
};
+const SinkDetails& GetSinkDetails(std::string sink_id) {
+ auto iter =
+ std::find_if(g_sink_details.begin(), g_sink_details.end(),
+ [sink_id](const auto& sink_detail) { return sink_detail.id == sink_id; });
+
+ if (sink_id == "auto" || iter == g_sink_details.end()) {
+ if (sink_id != "auto") {
+ LOG_ERROR(Audio, "AudioCore::SelectSink given invalid sink_id %s", sink_id.c_str());
+ }
+ // Auto-select.
+ // g_sink_details is ordered in terms of desirability, with the best choice at the front.
+ iter = g_sink_details.begin();
+ }
+
+ return *iter;
+}
+
} // namespace AudioCore
diff --git a/src/audio_core/sink_details.h b/src/audio_core/sink_details.h
index 4b30cf835..9d3735171 100644
--- a/src/audio_core/sink_details.h
+++ b/src/audio_core/sink_details.h
@@ -24,4 +24,6 @@ struct SinkDetails {
extern const std::vector<SinkDetails> g_sink_details;
+const SinkDetails& GetSinkDetails(std::string sink_id);
+
} // namespace AudioCore
diff --git a/src/citra/citra.cpp b/src/citra/citra.cpp
index 99c096ac7..76f5caeb1 100644
--- a/src/citra/citra.cpp
+++ b/src/citra/citra.cpp
@@ -141,6 +141,26 @@ int main(int argc, char** argv) {
case Core::System::ResultStatus::ErrorLoader:
LOG_CRITICAL(Frontend, "Failed to load ROM!");
return -1;
+ case Core::System::ResultStatus::ErrorLoader_ErrorEncrypted:
+ LOG_CRITICAL(Frontend, "The game that you are trying to load must be decrypted before "
+ "being used with Citra. \n\n For more information on dumping and "
+ "decrypting games, please refer to: "
+ "https://citra-emu.org/wiki/Dumping-Game-Cartridges");
+ return -1;
+ case Core::System::ResultStatus::ErrorLoader_ErrorInvalidFormat:
+ LOG_CRITICAL(Frontend, "Error while loading ROM: The ROM format is not supported.");
+ return -1;
+ case Core::System::ResultStatus::ErrorNotInitialized:
+ LOG_CRITICAL(Frontend, "CPUCore not initialized");
+ return -1;
+ case Core::System::ResultStatus::ErrorSystemMode:
+ LOG_CRITICAL(Frontend, "Failed to determine system mode!");
+ return -1;
+ case Core::System::ResultStatus::ErrorVideoCore:
+ LOG_CRITICAL(Frontend, "VideoCore not initialized");
+ return -1;
+ case Core::System::ResultStatus::Success:
+ break; // Expected case
}
while (emu_window->IsOpen()) {
diff --git a/src/citra/config.cpp b/src/citra/config.cpp
index 29462c982..fac1c9a0e 100644
--- a/src/citra/config.cpp
+++ b/src/citra/config.cpp
@@ -63,8 +63,8 @@ void Config::ReadValues() {
// Renderer
Settings::values.use_hw_renderer = sdl2_config->GetBoolean("Renderer", "use_hw_renderer", true);
Settings::values.use_shader_jit = sdl2_config->GetBoolean("Renderer", "use_shader_jit", true);
- Settings::values.use_scaled_resolution =
- sdl2_config->GetBoolean("Renderer", "use_scaled_resolution", false);
+ Settings::values.resolution_factor =
+ (float)sdl2_config->GetReal("Renderer", "resolution_factor", 1.0);
Settings::values.use_vsync = sdl2_config->GetBoolean("Renderer", "use_vsync", false);
Settings::values.toggle_framelimit =
sdl2_config->GetBoolean("Renderer", "toggle_framelimit", true);
@@ -82,6 +82,7 @@ void Config::ReadValues() {
Settings::values.sink_id = sdl2_config->Get("Audio", "output_engine", "auto");
Settings::values.enable_audio_stretching =
sdl2_config->GetBoolean("Audio", "enable_audio_stretching", true);
+ Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto");
// Data Storage
Settings::values.use_virtual_sd =
@@ -89,7 +90,23 @@ void Config::ReadValues() {
// System
Settings::values.is_new_3ds = sdl2_config->GetBoolean("System", "is_new_3ds", false);
- Settings::values.region_value = sdl2_config->GetInteger("System", "region_value", 1);
+ Settings::values.region_value =
+ sdl2_config->GetInteger("System", "region_value", Settings::REGION_VALUE_AUTO_SELECT);
+
+ // Camera
+ using namespace Service::CAM;
+ Settings::values.camera_name[OuterRightCamera] =
+ sdl2_config->Get("Camera", "camera_outer_right_name", "blank");
+ Settings::values.camera_config[OuterRightCamera] =
+ sdl2_config->Get("Camera", "camera_outer_right_config", "");
+ Settings::values.camera_name[InnerCamera] =
+ sdl2_config->Get("Camera", "camera_inner_name", "blank");
+ Settings::values.camera_config[InnerCamera] =
+ sdl2_config->Get("Camera", "camera_inner_config", "");
+ Settings::values.camera_name[OuterLeftCamera] =
+ sdl2_config->Get("Camera", "camera_outer_left_name", "blank");
+ Settings::values.camera_config[OuterLeftCamera] =
+ sdl2_config->Get("Camera", "camera_outer_left_config", "");
// Miscellaneous
Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Info");
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h
index 001b18ac2..435ba6f00 100644
--- a/src/citra/default_ini.h
+++ b/src/citra/default_ini.h
@@ -51,14 +51,21 @@ use_hw_renderer =
# 0: Interpreter (slow), 1 (default): JIT (fast)
use_shader_jit =
-# Whether to use native 3DS screen resolution or to scale rendering resolution to the displayed screen size.
-# 0 (default): Native, 1: Scaled
-use_scaled_resolution =
+# Resolution scale factor
+# 0: Auto (scales resolution to window size), 1: Native 3DS screen resolution, Otherwise a scale
+# factor for the 3DS resolution
+resolution_factor =
# Whether to enable V-Sync (caps the framerate at 60FPS) or not.
# 0 (default): Off, 1: On
use_vsync =
+# The clear color for the renderer. What shows up on the sides of the bottom screen.
+# Must be in range of 0.0-1.0. Defaults to 1.0 for all.
+bg_red =
+bg_blue =
+bg_green =
+
[Layout]
# Layout for the screen inside the render window.
# 0 (default): Default Top Bottom Screen, 1: Single Screen Only, 2: Large Screen Small Screen
@@ -73,12 +80,6 @@ toggle_framelimit =
# 0 (default): Top Screen is prominent, 1: Bottom Screen is prominent
swap_screen =
-# The clear color for the renderer. What shows up on the sides of the bottom screen.
-# Must be in range of 0.0-1.0. Defaults to 1.0 for all.
-bg_red =
-bg_blue =
-bg_green =
-
[Audio]
# Which audio output engine to use.
# auto (default): Auto-select, null: No audio output, sdl2: SDL2 (if available)
@@ -90,6 +91,10 @@ output_engine =
# 0: No, 1 (default): Yes
enable_audio_stretching =
+# Which audio device to use.
+# auto (default): Auto-select
+output_device =
+
[Data Storage]
# Whether to create a virtual SD card.
# 1 (default): Yes, 0: No
@@ -101,9 +106,25 @@ use_virtual_sd =
is_new_3ds =
# The system region that Citra will use during emulation
-# 0: Japan, 1: USA (default), 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
+# -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan
region_value =
+[Camera]
+# Which camera engine to use for the right outer camera
+# blank (default): a dummy camera that always returns black image
+camera_outer_right_name =
+
+# A config string for the right outer camera. Its meaning is defined by the camera engine
+camera_outer_right_config =
+
+# ... for the left outer camera
+camera_outer_left_name =
+camera_outer_left_config =
+
+# ... for the inner camera
+camera_inner_name =
+camera_inner_config =
+
[Miscellaneous]
# A filter which removes logs below a certain logging level.
# Examples: *:Debug Kernel.SVC:Trace Service.*:Critical
diff --git a/src/citra/emu_window/emu_window_sdl2.cpp b/src/citra/emu_window/emu_window_sdl2.cpp
index b0d82b670..81a3abe3f 100644
--- a/src/citra/emu_window/emu_window_sdl2.cpp
+++ b/src/citra/emu_window/emu_window_sdl2.cpp
@@ -19,16 +19,22 @@
void EmuWindow_SDL2::OnMouseMotion(s32 x, s32 y) {
TouchMoved((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
+ motion_emu->Tilt(x, y);
}
void EmuWindow_SDL2::OnMouseButton(u32 button, u8 state, s32 x, s32 y) {
- if (button != SDL_BUTTON_LEFT)
- return;
-
- if (state == SDL_PRESSED) {
- TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
- } else {
- TouchReleased();
+ if (button == SDL_BUTTON_LEFT) {
+ if (state == SDL_PRESSED) {
+ TouchPressed((unsigned)std::max(x, 0), (unsigned)std::max(y, 0));
+ } else {
+ TouchReleased();
+ }
+ } else if (button == SDL_BUTTON_RIGHT) {
+ if (state == SDL_PRESSED) {
+ motion_emu->BeginTilt(x, y);
+ } else {
+ motion_emu->EndTilt();
+ }
}
}
@@ -54,6 +60,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
keyboard_id = KeyMap::NewDeviceId();
ReloadSetKeymaps();
+ motion_emu = std::make_unique<Motion::MotionEmu>(*this);
SDL_SetMainReady();
@@ -109,6 +116,7 @@ EmuWindow_SDL2::EmuWindow_SDL2() {
EmuWindow_SDL2::~EmuWindow_SDL2() {
SDL_GL_DeleteContext(gl_context);
SDL_Quit();
+ motion_emu = nullptr;
}
void EmuWindow_SDL2::SwapBuffers() {
diff --git a/src/citra/emu_window/emu_window_sdl2.h b/src/citra/emu_window/emu_window_sdl2.h
index c8cd919c6..b1cbf16d7 100644
--- a/src/citra/emu_window/emu_window_sdl2.h
+++ b/src/citra/emu_window/emu_window_sdl2.h
@@ -4,8 +4,10 @@
#pragma once
+#include <memory>
#include <utility>
#include "core/frontend/emu_window.h"
+#include "core/frontend/motion_emu.h"
struct SDL_Window;
@@ -61,4 +63,7 @@ private:
/// Device id of keyboard for use with KeyMap
int keyboard_id;
+
+ /// Motion sensors emulation
+ std::unique_ptr<Motion::MotionEmu> motion_emu;
};
diff --git a/src/citra_qt/CMakeLists.txt b/src/citra_qt/CMakeLists.txt
index 93f1c339d..d4460bf01 100644
--- a/src/citra_qt/CMakeLists.txt
+++ b/src/citra_qt/CMakeLists.txt
@@ -14,7 +14,6 @@ set(SRCS
debugger/graphics/graphics_tracing.cpp
debugger/graphics/graphics_vertex_shader.cpp
debugger/profiler.cpp
- debugger/ramview.cpp
debugger/registers.cpp
debugger/wait_tree.cpp
util/spinbox.cpp
@@ -48,7 +47,6 @@ set(HEADERS
debugger/graphics/graphics_tracing.h
debugger/graphics/graphics_vertex_shader.h
debugger/profiler.h
- debugger/ramview.h
debugger/registers.h
debugger/wait_tree.h
util/spinbox.h
@@ -100,7 +98,7 @@ if (APPLE)
else()
add_executable(citra-qt ${SRCS} ${HEADERS} ${UI_HDRS})
endif()
-target_link_libraries(citra-qt core video_core audio_core common qhexedit)
+target_link_libraries(citra-qt core video_core audio_core common)
target_link_libraries(citra-qt ${OPENGL_gl_LIBRARY} ${CITRA_QT_LIBS})
target_link_libraries(citra-qt ${PLATFORM_LIBRARIES} Threads::Threads)
diff --git a/src/citra_qt/bootmanager.cpp b/src/citra_qt/bootmanager.cpp
index 57fde6caa..948db384d 100644
--- a/src/citra_qt/bootmanager.cpp
+++ b/src/citra_qt/bootmanager.cpp
@@ -99,7 +99,7 @@ private:
};
GRenderWindow::GRenderWindow(QWidget* parent, EmuThread* emu_thread)
- : QWidget(parent), keyboard_id(0), emu_thread(emu_thread), child(nullptr) {
+ : QWidget(parent), child(nullptr), keyboard_id(0), emu_thread(emu_thread) {
std::string window_title =
Common::StringFromFormat("Citra | %s-%s", Common::g_scm_branch, Common::g_scm_desc);
@@ -191,6 +191,7 @@ qreal GRenderWindow::windowPixelRatio() {
}
void GRenderWindow::closeEvent(QCloseEvent* event) {
+ motion_emu = nullptr;
emit Closed();
QWidget::closeEvent(event);
}
@@ -204,11 +205,13 @@ void GRenderWindow::keyReleaseEvent(QKeyEvent* event) {
}
void GRenderWindow::mousePressEvent(QMouseEvent* event) {
+ auto pos = event->pos();
if (event->button() == Qt::LeftButton) {
- auto pos = event->pos();
qreal pixelRatio = windowPixelRatio();
this->TouchPressed(static_cast<unsigned>(pos.x() * pixelRatio),
static_cast<unsigned>(pos.y() * pixelRatio));
+ } else if (event->button() == Qt::RightButton) {
+ motion_emu->BeginTilt(pos.x(), pos.y());
}
}
@@ -217,11 +220,14 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
qreal pixelRatio = windowPixelRatio();
this->TouchMoved(std::max(static_cast<unsigned>(pos.x() * pixelRatio), 0u),
std::max(static_cast<unsigned>(pos.y() * pixelRatio), 0u));
+ motion_emu->Tilt(pos.x(), pos.y());
}
void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
if (event->button() == Qt::LeftButton)
this->TouchReleased();
+ else if (event->button() == Qt::RightButton)
+ motion_emu->EndTilt();
}
void GRenderWindow::ReloadSetKeymaps() {
@@ -279,11 +285,13 @@ void GRenderWindow::OnMinimalClientAreaChangeRequest(
}
void GRenderWindow::OnEmulationStarting(EmuThread* emu_thread) {
+ motion_emu = std::make_unique<Motion::MotionEmu>(*this);
this->emu_thread = emu_thread;
child->DisablePainting();
}
void GRenderWindow::OnEmulationStopping() {
+ motion_emu = nullptr;
emu_thread = nullptr;
child->EnablePainting();
}
diff --git a/src/citra_qt/bootmanager.h b/src/citra_qt/bootmanager.h
index 43015390b..7dac1c480 100644
--- a/src/citra_qt/bootmanager.h
+++ b/src/citra_qt/bootmanager.h
@@ -11,6 +11,7 @@
#include <QThread>
#include "common/thread.h"
#include "core/frontend/emu_window.h"
+#include "core/frontend/motion_emu.h"
class QKeyEvent;
class QScreen;
@@ -156,6 +157,9 @@ private:
EmuThread* emu_thread;
+ /// Motion sensors emulation
+ std::unique_ptr<Motion::MotionEmu> motion_emu;
+
protected:
void showEvent(QShowEvent* event) override;
};
diff --git a/src/citra_qt/config.cpp b/src/citra_qt/config.cpp
index 06a4e9d25..b65f57fdc 100644
--- a/src/citra_qt/config.cpp
+++ b/src/citra_qt/config.cpp
@@ -44,8 +44,7 @@ void Config::ReadValues() {
qt_config->beginGroup("Renderer");
Settings::values.use_hw_renderer = qt_config->value("use_hw_renderer", true).toBool();
Settings::values.use_shader_jit = qt_config->value("use_shader_jit", true).toBool();
- Settings::values.use_scaled_resolution =
- qt_config->value("use_scaled_resolution", false).toBool();
+ Settings::values.resolution_factor = qt_config->value("resolution_factor", 1.0).toFloat();
Settings::values.use_vsync = qt_config->value("use_vsync", false).toBool();
Settings::values.toggle_framelimit = qt_config->value("toggle_framelimit", true).toBool();
@@ -64,6 +63,24 @@ void Config::ReadValues() {
Settings::values.sink_id = qt_config->value("output_engine", "auto").toString().toStdString();
Settings::values.enable_audio_stretching =
qt_config->value("enable_audio_stretching", true).toBool();
+ Settings::values.audio_device_id =
+ qt_config->value("output_device", "auto").toString().toStdString();
+ qt_config->endGroup();
+
+ using namespace Service::CAM;
+ qt_config->beginGroup("Camera");
+ Settings::values.camera_name[OuterRightCamera] =
+ qt_config->value("camera_outer_right_name", "blank").toString().toStdString();
+ Settings::values.camera_config[OuterRightCamera] =
+ qt_config->value("camera_outer_right_config", "").toString().toStdString();
+ Settings::values.camera_name[InnerCamera] =
+ qt_config->value("camera_inner_name", "blank").toString().toStdString();
+ Settings::values.camera_config[InnerCamera] =
+ qt_config->value("camera_inner_config", "").toString().toStdString();
+ Settings::values.camera_name[OuterLeftCamera] =
+ qt_config->value("camera_outer_left_name", "blank").toString().toStdString();
+ Settings::values.camera_config[OuterLeftCamera] =
+ qt_config->value("camera_outer_left_config", "").toString().toStdString();
qt_config->endGroup();
qt_config->beginGroup("Data Storage");
@@ -72,7 +89,8 @@ void Config::ReadValues() {
qt_config->beginGroup("System");
Settings::values.is_new_3ds = qt_config->value("is_new_3ds", false).toBool();
- Settings::values.region_value = qt_config->value("region_value", 1).toInt();
+ Settings::values.region_value =
+ qt_config->value("region_value", Settings::REGION_VALUE_AUTO_SELECT).toInt();
qt_config->endGroup();
qt_config->beginGroup("Miscellaneous");
@@ -151,7 +169,7 @@ void Config::SaveValues() {
qt_config->beginGroup("Renderer");
qt_config->setValue("use_hw_renderer", Settings::values.use_hw_renderer);
qt_config->setValue("use_shader_jit", Settings::values.use_shader_jit);
- qt_config->setValue("use_scaled_resolution", Settings::values.use_scaled_resolution);
+ qt_config->setValue("resolution_factor", (double)Settings::values.resolution_factor);
qt_config->setValue("use_vsync", Settings::values.use_vsync);
qt_config->setValue("toggle_framelimit", Settings::values.toggle_framelimit);
@@ -169,6 +187,23 @@ void Config::SaveValues() {
qt_config->beginGroup("Audio");
qt_config->setValue("output_engine", QString::fromStdString(Settings::values.sink_id));
qt_config->setValue("enable_audio_stretching", Settings::values.enable_audio_stretching);
+ qt_config->setValue("output_device", QString::fromStdString(Settings::values.audio_device_id));
+ qt_config->endGroup();
+
+ using namespace Service::CAM;
+ qt_config->beginGroup("Camera");
+ qt_config->setValue("camera_outer_right_name",
+ QString::fromStdString(Settings::values.camera_name[OuterRightCamera]));
+ qt_config->setValue("camera_outer_right_config",
+ QString::fromStdString(Settings::values.camera_config[OuterRightCamera]));
+ qt_config->setValue("camera_inner_name",
+ QString::fromStdString(Settings::values.camera_name[InnerCamera]));
+ qt_config->setValue("camera_inner_config",
+ QString::fromStdString(Settings::values.camera_config[InnerCamera]));
+ qt_config->setValue("camera_outer_left_name",
+ QString::fromStdString(Settings::values.camera_name[OuterLeftCamera]));
+ qt_config->setValue("camera_outer_left_config",
+ QString::fromStdString(Settings::values.camera_config[OuterLeftCamera]));
qt_config->endGroup();
qt_config->beginGroup("Data Storage");
diff --git a/src/citra_qt/configure_audio.cpp b/src/citra_qt/configure_audio.cpp
index 3cdd4c780..3ddcf9232 100644
--- a/src/citra_qt/configure_audio.cpp
+++ b/src/citra_qt/configure_audio.cpp
@@ -2,6 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <memory>
+#include "audio_core/audio_core.h"
+#include "audio_core/sink.h"
#include "audio_core/sink_details.h"
#include "citra_qt/configure_audio.h"
#include "core/settings.h"
@@ -18,6 +21,8 @@ ConfigureAudio::ConfigureAudio(QWidget* parent)
}
this->setConfiguration();
+ connect(ui->output_sink_combo_box, SIGNAL(currentIndexChanged(int)), this,
+ SLOT(updateAudioDevices(int)));
}
ConfigureAudio::~ConfigureAudio() {}
@@ -33,6 +38,19 @@ void ConfigureAudio::setConfiguration() {
ui->output_sink_combo_box->setCurrentIndex(new_sink_index);
ui->toggle_audio_stretching->setChecked(Settings::values.enable_audio_stretching);
+
+ // The device list cannot be pre-populated (nor listed) until the output sink is known.
+ updateAudioDevices(new_sink_index);
+
+ int new_device_index = -1;
+ for (int index = 0; index < ui->audio_device_combo_box->count(); index++) {
+ if (ui->audio_device_combo_box->itemText(index).toStdString() ==
+ Settings::values.audio_device_id) {
+ new_device_index = index;
+ break;
+ }
+ }
+ ui->audio_device_combo_box->setCurrentIndex(new_device_index);
}
void ConfigureAudio::applyConfiguration() {
@@ -40,5 +58,20 @@ void ConfigureAudio::applyConfiguration() {
ui->output_sink_combo_box->itemText(ui->output_sink_combo_box->currentIndex())
.toStdString();
Settings::values.enable_audio_stretching = ui->toggle_audio_stretching->isChecked();
+ Settings::values.audio_device_id =
+ ui->audio_device_combo_box->itemText(ui->audio_device_combo_box->currentIndex())
+ .toStdString();
Settings::Apply();
}
+
+void ConfigureAudio::updateAudioDevices(int sink_index) {
+ ui->audio_device_combo_box->clear();
+ ui->audio_device_combo_box->addItem("auto");
+
+ std::string sink_id = ui->output_sink_combo_box->itemText(sink_index).toStdString();
+ std::vector<std::string> device_list =
+ AudioCore::GetSinkDetails(sink_id).factory()->GetDeviceList();
+ for (const auto& device : device_list) {
+ ui->audio_device_combo_box->addItem(device.c_str());
+ }
+}
diff --git a/src/citra_qt/configure_audio.h b/src/citra_qt/configure_audio.h
index 51df2e27b..8190e694f 100644
--- a/src/citra_qt/configure_audio.h
+++ b/src/citra_qt/configure_audio.h
@@ -20,6 +20,9 @@ public:
void applyConfiguration();
+public slots:
+ void updateAudioDevices(int sink_index);
+
private:
void setConfiguration();
diff --git a/src/citra_qt/configure_audio.ui b/src/citra_qt/configure_audio.ui
index 3e2b4635f..dd870eb61 100644
--- a/src/citra_qt/configure_audio.ui
+++ b/src/citra_qt/configure_audio.ui
@@ -35,6 +35,21 @@
</property>
</widget>
</item>
+ <item>
+ <layout class="QHBoxLayout">
+ <item>
+ <widget class="QLabel">
+ <property name="text">
+ <string>Audio Device:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="audio_device_combo_box">
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
diff --git a/src/citra_qt/configure_general.cpp b/src/citra_qt/configure_general.cpp
index 03cd8835b..ac90a6df4 100644
--- a/src/citra_qt/configure_general.cpp
+++ b/src/citra_qt/configure_general.cpp
@@ -23,13 +23,15 @@ void ConfigureGeneral::setConfiguration() {
ui->toggle_deepscan->setChecked(UISettings::values.gamedir_deepscan);
ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
ui->toggle_cpu_jit->setChecked(Settings::values.use_cpu_jit);
- ui->region_combobox->setCurrentIndex(Settings::values.region_value);
+
+ // The first item is "auto-select" with actual value -1, so plus one here will do the trick
+ ui->region_combobox->setCurrentIndex(Settings::values.region_value + 1);
}
void ConfigureGeneral::applyConfiguration() {
UISettings::values.gamedir_deepscan = ui->toggle_deepscan->isChecked();
UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
- Settings::values.region_value = ui->region_combobox->currentIndex();
+ Settings::values.region_value = ui->region_combobox->currentIndex() - 1;
Settings::values.use_cpu_jit = ui->toggle_cpu_jit->isChecked();
Settings::Apply();
}
diff --git a/src/citra_qt/configure_general.ui b/src/citra_qt/configure_general.ui
index 81688113f..0f3352a1d 100644
--- a/src/citra_qt/configure_general.ui
+++ b/src/citra_qt/configure_general.ui
@@ -43,26 +43,26 @@
</layout>
</widget>
</item>
- <item>
- <widget class="QGroupBox" name="groupBox_2">
- <property name="title">
- <string>Performance</string>
- </property>
- <layout class="QHBoxLayout" name="horizontalLayout_7">
- <item>
- <layout class="QVBoxLayout" name="verticalLayout_5">
- <item>
- <widget class="QCheckBox" name="toggle_cpu_jit">
- <property name="text">
- <string>Enable CPU JIT</string>
- </property>
- </widget>
- </item>
- </layout>
- </item>
- </layout>
- </widget>
- </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Performance</string>
+ </property>
+ <layout class="QHBoxLayout" name="horizontalLayout_7">
+ <item>
+ <layout class="QVBoxLayout" name="verticalLayout_5">
+ <item>
+ <widget class="QCheckBox" name="toggle_cpu_jit">
+ <property name="text">
+ <string>Enable CPU JIT</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ </item>
<item>
<widget class="QGroupBox" name="groupBox_4">
<property name="title">
@@ -84,6 +84,11 @@
<widget class="QComboBox" name="region_combobox">
<item>
<property name="text">
+ <string>Auto-select</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
<string notr="true">JPN</string>
</property>
</item>
diff --git a/src/citra_qt/configure_graphics.cpp b/src/citra_qt/configure_graphics.cpp
index cea7db388..54f799b47 100644
--- a/src/citra_qt/configure_graphics.cpp
+++ b/src/citra_qt/configure_graphics.cpp
@@ -18,10 +18,81 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
ConfigureGraphics::~ConfigureGraphics() {}
+enum class Resolution : int {
+ Auto,
+ Scale1x,
+ Scale2x,
+ Scale3x,
+ Scale4x,
+ Scale5x,
+ Scale6x,
+ Scale7x,
+ Scale8x,
+ Scale9x,
+ Scale10x,
+};
+
+float ToResolutionFactor(Resolution option) {
+ switch (option) {
+ case Resolution::Auto:
+ return 0.f;
+ case Resolution::Scale1x:
+ return 1.f;
+ case Resolution::Scale2x:
+ return 2.f;
+ case Resolution::Scale3x:
+ return 3.f;
+ case Resolution::Scale4x:
+ return 4.f;
+ case Resolution::Scale5x:
+ return 5.f;
+ case Resolution::Scale6x:
+ return 6.f;
+ case Resolution::Scale7x:
+ return 7.f;
+ case Resolution::Scale8x:
+ return 8.f;
+ case Resolution::Scale9x:
+ return 9.f;
+ case Resolution::Scale10x:
+ return 10.f;
+ }
+ return 0.f;
+}
+
+Resolution FromResolutionFactor(float factor) {
+ if (factor == 0.f) {
+ return Resolution::Auto;
+ } else if (factor == 1.f) {
+ return Resolution::Scale1x;
+ } else if (factor == 2.f) {
+ return Resolution::Scale2x;
+ } else if (factor == 3.f) {
+ return Resolution::Scale3x;
+ } else if (factor == 4.f) {
+ return Resolution::Scale4x;
+ } else if (factor == 5.f) {
+ return Resolution::Scale5x;
+ } else if (factor == 6.f) {
+ return Resolution::Scale6x;
+ } else if (factor == 7.f) {
+ return Resolution::Scale7x;
+ } else if (factor == 8.f) {
+ return Resolution::Scale8x;
+ } else if (factor == 9.f) {
+ return Resolution::Scale9x;
+ } else if (factor == 10.f) {
+ return Resolution::Scale10x;
+ }
+ return Resolution::Auto;
+}
+
void ConfigureGraphics::setConfiguration() {
ui->toggle_hw_renderer->setChecked(Settings::values.use_hw_renderer);
+ ui->resolution_factor_combobox->setEnabled(Settings::values.use_hw_renderer);
ui->toggle_shader_jit->setChecked(Settings::values.use_shader_jit);
- ui->toggle_scaled_resolution->setChecked(Settings::values.use_scaled_resolution);
+ ui->resolution_factor_combobox->setCurrentIndex(
+ static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
ui->toggle_vsync->setChecked(Settings::values.use_vsync);
ui->toggle_framelimit->setChecked(Settings::values.toggle_framelimit);
ui->layout_combobox->setCurrentIndex(static_cast<int>(Settings::values.layout_option));
@@ -31,7 +102,8 @@ void ConfigureGraphics::setConfiguration() {
void ConfigureGraphics::applyConfiguration() {
Settings::values.use_hw_renderer = ui->toggle_hw_renderer->isChecked();
Settings::values.use_shader_jit = ui->toggle_shader_jit->isChecked();
- Settings::values.use_scaled_resolution = ui->toggle_scaled_resolution->isChecked();
+ Settings::values.resolution_factor =
+ ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
Settings::values.use_vsync = ui->toggle_vsync->isChecked();
Settings::values.toggle_framelimit = ui->toggle_framelimit->isChecked();
Settings::values.layout_option =
diff --git a/src/citra_qt/configure_graphics.ui b/src/citra_qt/configure_graphics.ui
index 964aa0bbd..a091f4c60 100644
--- a/src/citra_qt/configure_graphics.ui
+++ b/src/citra_qt/configure_graphics.ui
@@ -37,13 +37,6 @@
</widget>
</item>
<item>
- <widget class="QCheckBox" name="toggle_scaled_resolution">
- <property name="text">
- <string>Enable scaled resolution</string>
- </property>
- </widget>
- </item>
- <item>
<widget class="QCheckBox" name="toggle_vsync">
<property name="text">
<string>Enable V-Sync</string>
@@ -57,6 +50,76 @@
</property>
</widget>
</item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QLabel" name="label">
+ <property name="text">
+ <string>Internal Resolution:</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QComboBox" name="resolution_factor_combobox">
+ <item>
+ <property name="text">
+ <string notr="true">Auto (Window Size)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">Native (400x240)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">2x Native (800x480)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">3x Native (1200x720)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">4x Native (1600x960)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">5x Native (2000x1200)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">6x Native (2400x1440)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">7x Native (2800x1680)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">8x Native (3200x1920)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">9x Native (3600x2160)</string>
+ </property>
+ </item>
+ <item>
+ <property name="text">
+ <string notr="true">10x Native (4000x2400)</string>
+ </property>
+ </item>
+ </widget>
+ </item>
+ </layout>
+ </item>
</layout>
</widget>
</item>
@@ -69,9 +132,9 @@
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QVBoxLayout" name="verticalLayout_4">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="label1">
<property name="text">
@@ -128,5 +191,12 @@
</layout>
</widget>
<resources/>
- <connections/>
+ <connections>
+ <connection>
+ <sender>toggle_hw_renderer</sender>
+ <signal>toggled(bool)</signal>
+ <receiver>resolution_factor_combobox</receiver>
+ <slot>setEnabled(bool)</slot>
+ </connection>
+ </connections>
</ui>
diff --git a/src/citra_qt/configure_input.cpp b/src/citra_qt/configure_input.cpp
index 3e6803b8a..c29652f32 100644
--- a/src/citra_qt/configure_input.cpp
+++ b/src/citra_qt/configure_input.cpp
@@ -17,7 +17,6 @@ static QString getKeyName(Qt::Key key_code) {
case Qt::Key_Alt:
return QObject::tr("Alt");
case Qt::Key_Meta:
- case -1:
return "";
default:
return QKeySequence(key_code).toString();
diff --git a/src/citra_qt/configure_input.ui b/src/citra_qt/configure_input.ui
index a040d4df4..2760787e5 100644
--- a/src/citra_qt/configure_input.ui
+++ b/src/citra_qt/configure_input.ui
@@ -275,7 +275,6 @@
</layout>
</item>
</layout>
- <zorder></zorder>
</widget>
</item>
<item row="1" column="1">
@@ -521,7 +520,7 @@
<item row="1" column="1">
<layout class="QVBoxLayout" name="verticalLayout_28">
<item>
- <widget class="QLabel" name="label_34">
+ <widget class="QLabel" name="label_36">
<property name="text">
<string>Circle Mod:</string>
</property>
diff --git a/src/citra_qt/configure_system.ui b/src/citra_qt/configure_system.ui
index 6a906b61b..cc54fa37f 100644
--- a/src/citra_qt/configure_system.ui
+++ b/src/citra_qt/configure_system.ui
@@ -129,6 +129,9 @@
</item>
<item row="2" column="1">
<widget class="QComboBox" name="combo_language">
+ <property name="toolTip">
+ <string>Note: this can be overridden when region setting is auto-select</string>
+ </property>
<item>
<property name="text">
<string>Japanese (日本語)</string>
diff --git a/src/citra_qt/debugger/callstack.cpp b/src/citra_qt/debugger/callstack.cpp
index c1db93583..08d2e7a22 100644
--- a/src/citra_qt/debugger/callstack.cpp
+++ b/src/citra_qt/debugger/callstack.cpp
@@ -45,7 +45,6 @@ void CallstackWidget::OnDebugModeEntered() {
if (ARM_Disasm::Decode(insn) == OP_BL) {
std::string name;
// ripped from disasm
- u8 cond = (insn >> 28) & 0xf;
u32 i_offset = insn & 0xffffff;
// Sign-extend the 24-bit offset
if ((i_offset >> 23) & 1)
diff --git a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
index dab529e3a..ee79f0edf 100644
--- a/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_cmdlists.cpp
@@ -20,13 +20,14 @@
#include "video_core/debug_utils/debug_utils.h"
#include "video_core/pica.h"
#include "video_core/pica_state.h"
+#include "video_core/texture/texture_decode.h"
namespace {
-QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) {
+QImage LoadTexture(const u8* src, const Pica::Texture::TextureInfo& info) {
QImage decoded_image(info.width, info.height, QImage::Format_ARGB32);
for (int y = 0; y < info.height; ++y) {
for (int x = 0; x < info.width; ++x) {
- Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(src, x, y, info, true);
+ Math::Vec4<u8> color = Pica::Texture::LookupTexture(src, x, y, info, true);
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
}
}
@@ -36,9 +37,10 @@ QImage LoadTexture(const u8* src, const Pica::DebugUtils::TextureInfo& info) {
class TextureInfoWidget : public QWidget {
public:
- TextureInfoWidget(const u8* src, const Pica::DebugUtils::TextureInfo& info,
+ TextureInfoWidget(const u8* src, const Pica::Texture::TextureInfo& info,
QWidget* parent = nullptr)
: QWidget(parent) {
+
QLabel* image_widget = new QLabel;
QPixmap image_pixmap = QPixmap::fromImage(LoadTexture(src, info));
image_pixmap = image_pixmap.scaled(200, 100, Qt::KeepAspectRatio, Qt::SmoothTransformation);
@@ -135,11 +137,6 @@ void GPUCommandListWidget::OnCommandDoubleClicked(const QModelIndex& index) {
UNREACHABLE_MSG("Unknown texture command");
}
- const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
- const auto config = texture.config;
- const auto format = texture.format;
- const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
-
// TODO: Open a surface debugger
}
}
@@ -165,7 +162,7 @@ void GPUCommandListWidget::SetCommandInfo(const QModelIndex& index) {
const auto config = texture.config;
const auto format = texture.format;
- const auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(config, format);
+ const auto info = Pica::Texture::TextureInfo::FromPicaRegister(config, format);
const u8* src = Memory::GetPhysicalPointer(config.GetPhysicalAddress());
new_info_widget = new TextureInfoWidget(src, info);
}
diff --git a/src/citra_qt/debugger/graphics/graphics_surface.cpp b/src/citra_qt/debugger/graphics/graphics_surface.cpp
index 4efd95d3c..bd82b00d4 100644
--- a/src/citra_qt/debugger/graphics/graphics_surface.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_surface.cpp
@@ -18,6 +18,7 @@
#include "core/memory.h"
#include "video_core/pica.h"
#include "video_core/pica_state.h"
+#include "video_core/texture/texture_decode.h"
#include "video_core/utils.h"
SurfacePicture::SurfacePicture(QWidget* parent, GraphicsSurfaceWidget* surface_widget_)
@@ -512,7 +513,7 @@ void GraphicsSurfaceWidget::OnUpdate() {
}
const auto texture = Pica::g_state.regs.GetTextures()[texture_index];
- auto info = Pica::DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format);
+ auto info = Pica::Texture::TextureInfo::FromPicaRegister(texture.config, texture.format);
surface_address = info.physical_address;
surface_width = info.width;
@@ -567,28 +568,27 @@ void GraphicsSurfaceWidget::OnUpdate() {
surface_picture_label->show();
- unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
- unsigned stride = nibbles_per_pixel * surface_width / 2;
-
- // We handle depth formats here because DebugUtils only supports TextureFormats
if (surface_format <= Format::MaxTextureFormat) {
-
// Generate a virtual texture
- Pica::DebugUtils::TextureInfo info;
+ Pica::Texture::TextureInfo info;
info.physical_address = surface_address;
info.width = surface_width;
info.height = surface_height;
info.format = static_cast<Pica::Regs::TextureFormat>(surface_format);
- info.stride = stride;
+ info.SetDefaultStride();
for (unsigned int y = 0; y < surface_height; ++y) {
for (unsigned int x = 0; x < surface_width; ++x) {
- Math::Vec4<u8> color = Pica::DebugUtils::LookupTexture(buffer, x, y, info, true);
+ Math::Vec4<u8> color = Pica::Texture::LookupTexture(buffer, x, y, info, true);
decoded_image.setPixel(x, y, qRgba(color.r(), color.g(), color.b(), color.a()));
}
}
-
} else {
+ // We handle depth formats here because DebugUtils only supports TextureFormats
+
+ // TODO(yuriks): Convert to newer tile-based addressing
+ unsigned nibbles_per_pixel = GraphicsSurfaceWidget::NibblesPerPixel(surface_format);
+ unsigned stride = nibbles_per_pixel * surface_width / 2;
ASSERT_MSG(nibbles_per_pixel >= 2,
"Depth decoder only supports formats with at least one byte per pixel");
diff --git a/src/citra_qt/debugger/graphics/graphics_tracing.cpp b/src/citra_qt/debugger/graphics/graphics_tracing.cpp
index 716ed50b8..17f1c5ce2 100644
--- a/src/citra_qt/debugger/graphics/graphics_tracing.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_tracing.cpp
@@ -71,8 +71,8 @@ void GraphicsTracingWidget::StartRecording() {
std::array<u32, 4 * 16> default_attributes;
for (unsigned i = 0; i < 16; ++i) {
for (unsigned comp = 0; comp < 3; ++comp) {
- default_attributes[4 * i + comp] =
- nihstro::to_float24(Pica::g_state.vs_default_attributes[i][comp].ToFloat32());
+ default_attributes[4 * i + comp] = nihstro::to_float24(
+ Pica::g_state.input_default_attributes.attr[i][comp].ToFloat32());
}
}
diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
index b75b94ef8..489ec5f21 100644
--- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
+++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.cpp
@@ -18,7 +18,9 @@
#include "citra_qt/util/util.h"
#include "video_core/pica.h"
#include "video_core/pica_state.h"
+#include "video_core/shader/debug_data.h"
#include "video_core/shader/shader.h"
+#include "video_core/shader/shader_interpreter.h"
using nihstro::OpCode;
using nihstro::Instruction;
@@ -276,9 +278,6 @@ QVariant GraphicsVertexShaderModel::data(const QModelIndex& index, int role) con
output << 'b' << instr.flow_control.bool_uniform_id << ' ';
}
- u32 target_addr = instr.flow_control.dest_offset;
- u32 target_addr_else = instr.flow_control.dest_offset;
-
if (opcode_info.subtype & OpCode::Info::HasAlternative) {
output << "else jump to 0x" << std::setw(4) << std::right
<< std::setfill('0') << std::hex
@@ -473,7 +472,6 @@ GraphicsVertexShaderWidget::GraphicsVertexShaderWidget(
}
void GraphicsVertexShaderWidget::OnBreakPointHit(Pica::DebugContext::Event event, void* data) {
- auto input = static_cast<Pica::Shader::InputVertex*>(data);
if (event == Pica::DebugContext::Event::VertexShaderInvocation) {
Reload(true, data);
} else {
@@ -513,7 +511,7 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
auto& shader_config = Pica::g_state.regs.vs;
for (auto instr : shader_setup.program_code)
info.code.push_back({instr});
- int num_attributes = Pica::g_state.regs.vertex_attributes.GetNumTotalAttributes();
+ int num_attributes = shader_config.max_input_attribute_index + 1;
for (auto pattern : shader_setup.swizzle_data)
info.swizzle_info.push_back({pattern});
@@ -522,12 +520,13 @@ void GraphicsVertexShaderWidget::Reload(bool replace_vertex_data, void* vertex_d
info.labels.insert({entry_point, "main"});
// Generate debug information
- debug_data = Pica::g_state.vs.ProduceDebugInfo(input_vertex, num_attributes, shader_config,
- shader_setup);
+ Pica::Shader::InterpreterEngine shader_engine;
+ shader_engine.SetupBatch(shader_setup, entry_point);
+ debug_data = shader_engine.ProduceDebugInfo(shader_setup, input_vertex, shader_config);
// Reload widget state
for (int attr = 0; attr < num_attributes; ++attr) {
- unsigned source_attr = shader_config.input_register_map.GetRegisterForAttribute(attr);
+ unsigned source_attr = shader_config.GetRegisterForAttribute(attr);
input_data_mapping[attr]->setText(QString("-> v%1").arg(source_attr));
input_data_container[attr]->setVisible(true);
}
diff --git a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h
index bedea0bed..c249a2ff8 100644
--- a/src/citra_qt/debugger/graphics/graphics_vertex_shader.h
+++ b/src/citra_qt/debugger/graphics/graphics_vertex_shader.h
@@ -8,6 +8,7 @@
#include <QTreeView>
#include "citra_qt/debugger/graphics/graphics_breakpoint_observer.h"
#include "nihstro/parser_shbin.h"
+#include "video_core/shader/debug_data.h"
#include "video_core/shader/shader.h"
class QLabel;
@@ -81,7 +82,7 @@ private:
nihstro::ShaderInfo info;
Pica::Shader::DebugData<true> debug_data;
- Pica::Shader::InputVertex input_vertex;
+ Pica::Shader::AttributeBuffer input_vertex;
friend class GraphicsVertexShaderModel;
};
diff --git a/src/citra_qt/debugger/ramview.cpp b/src/citra_qt/debugger/ramview.cpp
deleted file mode 100644
index 10a09dda8..000000000
--- a/src/citra_qt/debugger/ramview.cpp
+++ /dev/null
@@ -1,12 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "citra_qt/debugger/ramview.h"
-
-GRamView::GRamView(QWidget* parent) : QHexEdit(parent) {}
-
-void GRamView::OnCPUStepped() {
- // TODO: QHexEdit doesn't show vertical scroll bars for > 10MB data streams...
- // setData(QByteArray((const char*)Mem_RAM,sizeof(Mem_RAM)/8));
-}
diff --git a/src/citra_qt/debugger/ramview.h b/src/citra_qt/debugger/ramview.h
deleted file mode 100644
index d01cea93b..000000000
--- a/src/citra_qt/debugger/ramview.h
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "qhexedit.h"
-
-class GRamView : public QHexEdit {
- Q_OBJECT
-
-public:
- explicit GRamView(QWidget* parent = nullptr);
-
-public slots:
- void OnCPUStepped();
-};
diff --git a/src/citra_qt/debugger/wait_tree.cpp b/src/citra_qt/debugger/wait_tree.cpp
index 1d2de5185..b6ecf3819 100644
--- a/src/citra_qt/debugger/wait_tree.cpp
+++ b/src/citra_qt/debugger/wait_tree.cpp
@@ -153,7 +153,8 @@ QString WaitTreeThread::GetText() const {
case THREADSTATUS_WAIT_SLEEP:
status = tr("sleeping");
break;
- case THREADSTATUS_WAIT_SYNCH:
+ case THREADSTATUS_WAIT_SYNCH_ALL:
+ case THREADSTATUS_WAIT_SYNCH_ANY:
status = tr("waiting for objects");
break;
case THREADSTATUS_DORMANT:
@@ -180,7 +181,8 @@ QColor WaitTreeThread::GetColor() const {
return QColor(Qt::GlobalColor::darkRed);
case THREADSTATUS_WAIT_SLEEP:
return QColor(Qt::GlobalColor::darkYellow);
- case THREADSTATUS_WAIT_SYNCH:
+ case THREADSTATUS_WAIT_SYNCH_ALL:
+ case THREADSTATUS_WAIT_SYNCH_ANY:
return QColor(Qt::GlobalColor::red);
case THREADSTATUS_DORMANT:
return QColor(Qt::GlobalColor::darkCyan);
@@ -228,7 +230,8 @@ std::vector<std::unique_ptr<WaitTreeItem>> WaitTreeThread::GetChildren() const {
} else {
list.push_back(std::make_unique<WaitTreeMutexList>(thread.held_mutexes));
}
- if (thread.status == THREADSTATUS_WAIT_SYNCH) {
+ if (thread.status == THREADSTATUS_WAIT_SYNCH_ANY ||
+ thread.status == THREADSTATUS_WAIT_SYNCH_ALL) {
list.push_back(std::make_unique<WaitTreeObjectList>(thread.wait_objects,
thread.IsSleepingOnWaitAll()));
}
diff --git a/src/citra_qt/main.cpp b/src/citra_qt/main.cpp
index 6d59cf640..f765c0147 100644
--- a/src/citra_qt/main.cpp
+++ b/src/citra_qt/main.cpp
@@ -12,6 +12,7 @@
#include <QFileDialog>
#include <QMessageBox>
#include <QtGui>
+#include <QtWidgets>
#include "citra_qt/bootmanager.h"
#include "citra_qt/config.h"
#include "citra_qt/configure_dialog.h"
@@ -24,7 +25,6 @@
#include "citra_qt/debugger/graphics/graphics_tracing.h"
#include "citra_qt/debugger/graphics/graphics_vertex_shader.h"
#include "citra_qt/debugger/profiler.h"
-#include "citra_qt/debugger/ramview.h"
#include "citra_qt/debugger/registers.h"
#include "citra_qt/debugger/wait_tree.h"
#include "citra_qt/game_list.h"
@@ -46,7 +46,6 @@
#include "core/gdbstub/gdbstub.h"
#include "core/loader/loader.h"
#include "core/settings.h"
-#include "qhexedit.h"
#include "video_core/video_core.h"
#ifdef QT_STATICPLUGIN
diff --git a/src/citra_qt/util/spinbox.cpp b/src/citra_qt/util/spinbox.cpp
index feb0ea1b3..212709007 100644
--- a/src/citra_qt/util/spinbox.cpp
+++ b/src/citra_qt/util/spinbox.cpp
@@ -165,13 +165,6 @@ void CSpinBox::UpdateText() {
// Uppercase digits greater than 9.
mask += ">";
- // The greatest signed 64-bit number has 19 decimal digits.
- // TODO: Could probably make this more generic with some logarithms.
- // For reference, unsigned 64-bit can have up to 20 decimal digits.
- int digits = (num_digits != 0)
- ? num_digits
- : (base == 16) ? 16 : (base == 10) ? 19 : 0xFF; // fallback case...
-
// Match num_digits digits
// Digits irrelevant to the chosen number base are filtered in the validator
mask += QString("H").repeated(std::max(num_digits, 1));
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 5aecf6e6e..592911c2b 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -46,6 +46,7 @@ set(HEADERS
microprofileui.h
platform.h
profiler_reporting.h
+ quaternion.h
scm_rev.h
scope_exit.h
string_util.h
@@ -60,14 +61,11 @@ set(HEADERS
if(ARCHITECTURE_x86_64)
set(SRCS ${SRCS}
- x64/abi.cpp
x64/cpu_detect.cpp
- x64/emitter.cpp)
+ )
set(HEADERS ${HEADERS}
- x64/abi.h
x64/cpu_detect.h
- x64/emitter.h
x64/xbyak_abi.h
x64/xbyak_util.h
)
diff --git a/src/common/bit_set.h b/src/common/bit_set.h
index 3059d0cb0..9c2e6b28c 100644
--- a/src/common/bit_set.h
+++ b/src/common/bit_set.h
@@ -121,22 +121,19 @@ public:
class Iterator {
public:
Iterator(const Iterator& other) : m_val(other.m_val), m_bit(other.m_bit) {}
- Iterator(IntTy val, int bit) : m_val(val), m_bit(bit) {}
+ Iterator(IntTy val) : m_val(val), m_bit(0) {}
Iterator& operator=(Iterator other) {
new (this) Iterator(other);
return *this;
}
int operator*() {
- return m_bit;
+ return m_bit + ComputeLsb();
}
Iterator& operator++() {
- if (m_val == 0) {
- m_bit = -1;
- } else {
- int bit = LeastSignificantSetBit(m_val);
- m_val &= ~(1 << bit);
- m_bit = bit;
- }
+ int lsb = ComputeLsb();
+ m_val >>= lsb + 1;
+ m_bit += lsb + 1;
+ m_has_lsb = false;
return *this;
}
Iterator operator++(int _) {
@@ -145,15 +142,24 @@ public:
return other;
}
bool operator==(Iterator other) const {
- return m_bit == other.m_bit;
+ return m_val == other.m_val;
}
bool operator!=(Iterator other) const {
- return m_bit != other.m_bit;
+ return m_val != other.m_val;
}
private:
+ int ComputeLsb() {
+ if (!m_has_lsb) {
+ m_lsb = LeastSignificantSetBit(m_val);
+ m_has_lsb = true;
+ }
+ return m_lsb;
+ }
IntTy m_val;
int m_bit;
+ int m_lsb = -1;
+ bool m_has_lsb = false;
};
BitSet() : m_val(0) {}
@@ -221,11 +227,10 @@ public:
}
Iterator begin() const {
- Iterator it(m_val, 0);
- return ++it;
+ return Iterator(m_val);
}
Iterator end() const {
- return Iterator(m_val, -1);
+ return Iterator(0);
}
IntTy m_val;
diff --git a/src/common/file_util.cpp b/src/common/file_util.cpp
index 1a1f5d9b5..df234c225 100644
--- a/src/common/file_util.cpp
+++ b/src/common/file_util.cpp
@@ -303,7 +303,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
// copy loop
while (!feof(input)) {
// read input
- int rnum = fread(buffer, sizeof(char), BSIZE, input);
+ size_t rnum = fread(buffer, sizeof(char), BSIZE, input);
if (rnum != BSIZE) {
if (ferror(input) != 0) {
LOG_ERROR(Common_Filesystem, "failed reading from source, %s --> %s: %s",
@@ -313,7 +313,7 @@ bool Copy(const std::string& srcFilename, const std::string& destFilename) {
}
// write output
- int wnum = fwrite(buffer, sizeof(char), rnum, output);
+ size_t wnum = fwrite(buffer, sizeof(char), rnum, output);
if (wnum != rnum) {
LOG_ERROR(Common_Filesystem, "failed writing to output, %s --> %s: %s",
srcFilename.c_str(), destFilename.c_str(), GetLastErrorMsg());
diff --git a/src/common/hash.cpp b/src/common/hash.cpp
index 2309320bb..f3d390dc5 100644
--- a/src/common/hash.cpp
+++ b/src/common/hash.cpp
@@ -16,7 +16,7 @@ namespace Common {
// Block read - if your platform needs to do endian-swapping or can only handle aligned reads, do
// the conversion here
-static FORCE_INLINE u64 getblock64(const u64* p, int i) {
+static FORCE_INLINE u64 getblock64(const u64* p, size_t i) {
return p[i];
}
@@ -34,9 +34,9 @@ static FORCE_INLINE u64 fmix64(u64 k) {
// This is the 128-bit variant of the MurmurHash3 hash function that is targeted for 64-bit
// platforms (MurmurHash3_x64_128). It was taken from:
// https://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp
-void MurmurHash3_128(const void* key, int len, u32 seed, void* out) {
+void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out) {
const u8* data = (const u8*)key;
- const int nblocks = len / 16;
+ const size_t nblocks = len / 16;
u64 h1 = seed;
u64 h2 = seed;
@@ -48,7 +48,7 @@ void MurmurHash3_128(const void* key, int len, u32 seed, void* out) {
const u64* blocks = (const u64*)(data);
- for (int i = 0; i < nblocks; i++) {
+ for (size_t i = 0; i < nblocks; i++) {
u64 k1 = getblock64(blocks, i * 2 + 0);
u64 k2 = getblock64(blocks, i * 2 + 1);
diff --git a/src/common/hash.h b/src/common/hash.h
index a3850be68..ee2560dad 100644
--- a/src/common/hash.h
+++ b/src/common/hash.h
@@ -4,11 +4,12 @@
#pragma once
+#include <cstddef>
#include "common/common_types.h"
namespace Common {
-void MurmurHash3_128(const void* key, int len, u32 seed, void* out);
+void MurmurHash3_128(const void* key, size_t len, u32 seed, void* out);
/**
* Computes a 64-bit hash over the specified block of data
@@ -16,7 +17,7 @@ void MurmurHash3_128(const void* key, int len, u32 seed, void* out);
* @param len Length of data (in bytes) to compute hash over
* @returns 64-bit hash value that was computed over the data block
*/
-static inline u64 ComputeHash64(const void* data, int len) {
+static inline u64 ComputeHash64(const void* data, size_t len) {
u64 res[2];
MurmurHash3_128(data, len, 0, res);
return res[0];
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 3ea102229..2ef3e6b05 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -45,6 +45,7 @@ namespace Log {
SUB(Service, LDR) \
SUB(Service, MIC) \
SUB(Service, NDM) \
+ SUB(Service, NFC) \
SUB(Service, NIM) \
SUB(Service, NWM) \
SUB(Service, CAM) \
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 9d8c18d8e..4330ef879 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -62,6 +62,7 @@ enum class Class : ClassType {
Service_LDR, ///< The LDR (3ds dll loader) service
Service_MIC, ///< The MIC (Microphone) service
Service_NDM, ///< The NDM (Network daemon manager) service
+ Service_NFC, ///< The NFC service
Service_NIM, ///< The NIM (Network interface manager) service
Service_NWM, ///< The NWM (Network wlan manager) service
Service_CAM, ///< The CAM (Camera) service
diff --git a/src/common/math_util.h b/src/common/math_util.h
index cdeaeb733..45a1ed367 100644
--- a/src/common/math_util.h
+++ b/src/common/math_util.h
@@ -10,6 +10,8 @@
namespace MathUtil {
+static constexpr float PI = 3.14159265f;
+
inline bool IntervalsIntersect(unsigned start0, unsigned length0, unsigned start1,
unsigned length1) {
return (std::max(start0, start1) < std::min(start0 + length0, start1 + length1));
diff --git a/src/common/quaternion.h b/src/common/quaternion.h
new file mode 100644
index 000000000..84ac82ed3
--- /dev/null
+++ b/src/common/quaternion.h
@@ -0,0 +1,44 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/vector_math.h"
+
+namespace Math {
+
+template <typename T>
+class Quaternion {
+public:
+ Math::Vec3<T> xyz;
+ T w;
+
+ Quaternion<decltype(-T{})> Inverse() const {
+ return {-xyz, w};
+ }
+
+ Quaternion<decltype(T{} + T{})> operator+(const Quaternion& other) const {
+ return {xyz + other.xyz, w + other.w};
+ }
+
+ Quaternion<decltype(T{} - T{})> operator-(const Quaternion& other) const {
+ return {xyz - other.xyz, w - other.w};
+ }
+
+ Quaternion<decltype(T{} * T{} - T{} * T{})> operator*(const Quaternion& other) const {
+ return {xyz * other.w + other.xyz * w + Cross(xyz, other.xyz),
+ w * other.w - Dot(xyz, other.xyz)};
+ }
+};
+
+template <typename T>
+auto QuaternionRotate(const Quaternion<T>& q, const Math::Vec3<T>& v) {
+ return v + 2 * Cross(q.xyz, Cross(q.xyz, v) + v * q.w);
+}
+
+inline Quaternion<float> MakeQuaternion(const Math::Vec3<float>& axis, float angle) {
+ return {axis * std::sin(angle / 2), std::cos(angle / 2)};
+}
+
+} // namspace Math
diff --git a/src/common/thread.h b/src/common/thread.h
index 9c08be7e3..fa475ab51 100644
--- a/src/common/thread.h
+++ b/src/common/thread.h
@@ -4,6 +4,7 @@
#pragma once
+#include <chrono>
#include <condition_variable>
#include <cstddef>
#include <mutex>
@@ -54,6 +55,15 @@ public:
is_set = false;
}
+ template <class Clock, class Duration>
+ bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) {
+ std::unique_lock<std::mutex> lk(mutex);
+ if (!condvar.wait_until(lk, time, [this] { return is_set; }))
+ return false;
+ is_set = false;
+ return true;
+ }
+
void Reset() {
std::unique_lock<std::mutex> lk(mutex);
// no other action required, since wait loops on the predicate and any lingering signal will
diff --git a/src/common/vector_math.h b/src/common/vector_math.h
index a57d86d88..7ca8e15f5 100644
--- a/src/common/vector_math.h
+++ b/src/common/vector_math.h
@@ -186,6 +186,18 @@ Vec2<T> operator*(const V& f, const Vec2<T>& vec) {
typedef Vec2<float> Vec2f;
+template <>
+inline float Vec2<float>::Length() const {
+ return std::sqrt(x * x + y * y);
+}
+
+template <>
+inline float Vec2<float>::Normalize() {
+ float length = Length();
+ *this /= length;
+ return length;
+}
+
template <typename T>
class Vec3 {
public:
@@ -388,6 +400,13 @@ inline Vec3<float> Vec3<float>::Normalized() const {
return *this / Length();
}
+template <>
+inline float Vec3<float>::Normalize() {
+ float length = Length();
+ *this /= length;
+ return length;
+}
+
typedef Vec3<float> Vec3f;
template <typename T>
diff --git a/src/common/x64/abi.cpp b/src/common/x64/abi.cpp
deleted file mode 100644
index 504b9c940..000000000
--- a/src/common/x64/abi.cpp
+++ /dev/null
@@ -1,350 +0,0 @@
-// Copyright (C) 2003 Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0 or later versions.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
-
-// Official SVN repository and contact information can be found at
-// http://code.google.com/p/dolphin-emu/
-
-#include "abi.h"
-#include "emitter.h"
-
-using namespace Gen;
-
-// Shared code between Win64 and Unix64
-
-void XEmitter::ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size,
- size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp) {
- size_t shadow = 0;
-#if defined(_WIN32)
- shadow = 0x20;
-#endif
-
- int count = (mask & ABI_ALL_GPRS).Count();
- rsp_alignment -= count * 8;
- size_t subtraction = 0;
- int fpr_count = (mask & ABI_ALL_FPRS).Count();
- if (fpr_count) {
- // If we have any XMMs to save, we must align the stack here.
- subtraction = rsp_alignment & 0xf;
- }
- subtraction += 16 * fpr_count;
- size_t xmm_base_subtraction = subtraction;
- subtraction += needed_frame_size;
- subtraction += shadow;
- // Final alignment.
- rsp_alignment -= subtraction;
- subtraction += rsp_alignment & 0xf;
-
- *shadowp = shadow;
- *subtractionp = subtraction;
- *xmm_offsetp = subtraction - xmm_base_subtraction;
-}
-
-size_t XEmitter::ABI_PushRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment,
- size_t needed_frame_size) {
- size_t shadow, subtraction, xmm_offset;
- ABI_CalculateFrameSize(mask, rsp_alignment, needed_frame_size, &shadow, &subtraction,
- &xmm_offset);
-
- for (int r : mask& ABI_ALL_GPRS)
- PUSH((X64Reg)r);
-
- if (subtraction)
- SUB(64, R(RSP), subtraction >= 0x80 ? Imm32((u32)subtraction) : Imm8((u8)subtraction));
-
- for (int x : mask& ABI_ALL_FPRS) {
- MOVAPD(MDisp(RSP, (int)xmm_offset), (X64Reg)(x - 16));
- xmm_offset += 16;
- }
-
- return shadow;
-}
-
-void XEmitter::ABI_PopRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment,
- size_t needed_frame_size) {
- size_t shadow, subtraction, xmm_offset;
- ABI_CalculateFrameSize(mask, rsp_alignment, needed_frame_size, &shadow, &subtraction,
- &xmm_offset);
-
- for (int x : mask& ABI_ALL_FPRS) {
- MOVAPD((X64Reg)(x - 16), MDisp(RSP, (int)xmm_offset));
- xmm_offset += 16;
- }
-
- if (subtraction)
- ADD(64, R(RSP), subtraction >= 0x80 ? Imm32((u32)subtraction) : Imm8((u8)subtraction));
-
- for (int r = 15; r >= 0; r--) {
- if (mask[r])
- POP((X64Reg)r);
- }
-}
-
-// Common functions
-void XEmitter::ABI_CallFunction(const void* func) {
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionC16(const void* func, u16 param1) {
- MOV(32, R(ABI_PARAM1), Imm32((u32)param1));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionCC16(const void* func, u32 param1, u16 param2) {
- MOV(32, R(ABI_PARAM1), Imm32(param1));
- MOV(32, R(ABI_PARAM2), Imm32((u32)param2));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionC(const void* func, u32 param1) {
- MOV(32, R(ABI_PARAM1), Imm32(param1));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionCC(const void* func, u32 param1, u32 param2) {
- MOV(32, R(ABI_PARAM1), Imm32(param1));
- MOV(32, R(ABI_PARAM2), Imm32(param2));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionCCC(const void* func, u32 param1, u32 param2, u32 param3) {
- MOV(32, R(ABI_PARAM1), Imm32(param1));
- MOV(32, R(ABI_PARAM2), Imm32(param2));
- MOV(32, R(ABI_PARAM3), Imm32(param3));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionCCP(const void* func, u32 param1, u32 param2, void* param3) {
- MOV(32, R(ABI_PARAM1), Imm32(param1));
- MOV(32, R(ABI_PARAM2), Imm32(param2));
- MOV(64, R(ABI_PARAM3), ImmPtr(param3));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionCCCP(const void* func, u32 param1, u32 param2, u32 param3,
- void* param4) {
- MOV(32, R(ABI_PARAM1), Imm32(param1));
- MOV(32, R(ABI_PARAM2), Imm32(param2));
- MOV(32, R(ABI_PARAM3), Imm32(param3));
- MOV(64, R(ABI_PARAM4), ImmPtr(param4));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionP(const void* func, void* param1) {
- MOV(64, R(ABI_PARAM1), ImmPtr(param1));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionPA(const void* func, void* param1, const Gen::OpArg& arg2) {
- MOV(64, R(ABI_PARAM1), ImmPtr(param1));
- if (!arg2.IsSimpleReg(ABI_PARAM2))
- MOV(32, R(ABI_PARAM2), arg2);
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionPAA(const void* func, void* param1, const Gen::OpArg& arg2,
- const Gen::OpArg& arg3) {
- MOV(64, R(ABI_PARAM1), ImmPtr(param1));
- if (!arg2.IsSimpleReg(ABI_PARAM2))
- MOV(32, R(ABI_PARAM2), arg2);
- if (!arg3.IsSimpleReg(ABI_PARAM3))
- MOV(32, R(ABI_PARAM3), arg3);
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionPPC(const void* func, void* param1, void* param2, u32 param3) {
- MOV(64, R(ABI_PARAM1), ImmPtr(param1));
- MOV(64, R(ABI_PARAM2), ImmPtr(param2));
- MOV(32, R(ABI_PARAM3), Imm32(param3));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-// Pass a register as a parameter.
-void XEmitter::ABI_CallFunctionR(const void* func, X64Reg reg1) {
- if (reg1 != ABI_PARAM1)
- MOV(32, R(ABI_PARAM1), R(reg1));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-// Pass two registers as parameters.
-void XEmitter::ABI_CallFunctionRR(const void* func, X64Reg reg1, X64Reg reg2) {
- if (reg2 != ABI_PARAM1) {
- if (reg1 != ABI_PARAM1)
- MOV(64, R(ABI_PARAM1), R(reg1));
- if (reg2 != ABI_PARAM2)
- MOV(64, R(ABI_PARAM2), R(reg2));
- } else {
- if (reg2 != ABI_PARAM2)
- MOV(64, R(ABI_PARAM2), R(reg2));
- if (reg1 != ABI_PARAM1)
- MOV(64, R(ABI_PARAM1), R(reg1));
- }
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionAC(const void* func, const Gen::OpArg& arg1, u32 param2) {
- if (!arg1.IsSimpleReg(ABI_PARAM1))
- MOV(32, R(ABI_PARAM1), arg1);
- MOV(32, R(ABI_PARAM2), Imm32(param2));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionACC(const void* func, const Gen::OpArg& arg1, u32 param2,
- u32 param3) {
- if (!arg1.IsSimpleReg(ABI_PARAM1))
- MOV(32, R(ABI_PARAM1), arg1);
- MOV(32, R(ABI_PARAM2), Imm32(param2));
- MOV(64, R(ABI_PARAM3), Imm64(param3));
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionA(const void* func, const Gen::OpArg& arg1) {
- if (!arg1.IsSimpleReg(ABI_PARAM1))
- MOV(32, R(ABI_PARAM1), arg1);
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-}
-
-void XEmitter::ABI_CallFunctionAA(const void* func, const Gen::OpArg& arg1,
- const Gen::OpArg& arg2) {
- if (!arg1.IsSimpleReg(ABI_PARAM1))
- MOV(32, R(ABI_PARAM1), arg1);
- if (!arg2.IsSimpleReg(ABI_PARAM2))
- MOV(32, R(ABI_PARAM2), arg2);
- u64 distance = u64(func) - (u64(code) + 5);
- if (distance >= 0x0000000080000000ULL && distance < 0xFFFFFFFF80000000ULL) {
- // Far call
- MOV(64, R(RAX), ImmPtr(func));
- CALLptr(R(RAX));
- } else {
- CALL(func);
- }
-} \ No newline at end of file
diff --git a/src/common/x64/abi.h b/src/common/x64/abi.h
deleted file mode 100644
index eaaf81d89..000000000
--- a/src/common/x64/abi.h
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2008 Dolphin Emulator Project
-// Licensed under GPLv2+
-// Refer to the license.txt file included.
-
-#pragma once
-
-#include "common/bit_set.h"
-#include "emitter.h"
-
-// x64 ABI:s, and helpers to help follow them when JIT-ing code.
-// All convensions return values in EAX (+ possibly EDX).
-
-// Windows 64-bit
-// * 4-reg "fastcall" variant, very new-skool stack handling
-// * Callee moves stack pointer, to make room for shadow regs for the biggest function _it itself
-// calls_
-// * Parameters passed in RCX, RDX, ... further parameters are MOVed into the allocated stack space.
-// Scratch: RAX RCX RDX R8 R9 R10 R11
-// Callee-save: RBX RSI RDI RBP R12 R13 R14 R15
-// Parameters: RCX RDX R8 R9, further MOV-ed
-
-// Linux 64-bit
-// * 6-reg "fastcall" variant, old skool stack handling (parameters are pushed)
-// Scratch: RAX RCX RDX RSI RDI R8 R9 R10 R11
-// Callee-save: RBX RBP R12 R13 R14 R15
-// Parameters: RDI RSI RDX RCX R8 R9
-
-#define ABI_ALL_FPRS BitSet32(0xffff0000)
-#define ABI_ALL_GPRS BitSet32(0x0000ffff)
-
-#ifdef _WIN32 // 64-bit Windows - the really exotic calling convention
-
-#define ABI_PARAM1 RCX
-#define ABI_PARAM2 RDX
-#define ABI_PARAM3 R8
-#define ABI_PARAM4 R9
-
-// xmm0-xmm15 use the upper 16 bits in the functions that push/pop registers.
-#define ABI_ALL_CALLER_SAVED \
- (BitSet32{RAX, RCX, RDX, R8, R9, R10, R11, XMM0 + 16, XMM1 + 16, XMM2 + 16, XMM3 + 16, \
- XMM4 + 16, XMM5 + 16})
-#else // 64-bit Unix / OS X
-
-#define ABI_PARAM1 RDI
-#define ABI_PARAM2 RSI
-#define ABI_PARAM3 RDX
-#define ABI_PARAM4 RCX
-#define ABI_PARAM5 R8
-#define ABI_PARAM6 R9
-
-// TODO: Avoid pushing all 16 XMM registers when possible. Most functions we call probably
-// don't actually clobber them.
-#define ABI_ALL_CALLER_SAVED (BitSet32{RAX, RCX, RDX, RDI, RSI, R8, R9, R10, R11} | ABI_ALL_FPRS)
-#endif // WIN32
-
-#define ABI_ALL_CALLEE_SAVED (~ABI_ALL_CALLER_SAVED)
-
-#define ABI_RETURN RAX
diff --git a/src/common/x64/emitter.cpp b/src/common/x64/emitter.cpp
deleted file mode 100644
index f5930abec..000000000
--- a/src/common/x64/emitter.cpp
+++ /dev/null
@@ -1,2583 +0,0 @@
-// Copyright (C) 2003 Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0 or later versions.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
-
-// Official SVN repository and contact information can be found at
-// http://code.google.com/p/dolphin-emu/
-
-#include <cinttypes>
-#include <cstring>
-#include "abi.h"
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/memory_util.h"
-#include "cpu_detect.h"
-#include "emitter.h"
-
-namespace Gen {
-
-struct NormalOpDef {
- u8 toRm8, toRm32, fromRm8, fromRm32, imm8, imm32, simm8, eaximm8, eaximm32, ext;
-};
-
-// 0xCC is code for invalid combination of immediates
-static const NormalOpDef normalops[11] = {
- {0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x83, 0x04, 0x05, 0}, // ADD
- {0x10, 0x11, 0x12, 0x13, 0x80, 0x81, 0x83, 0x14, 0x15, 2}, // ADC
-
- {0x28, 0x29, 0x2A, 0x2B, 0x80, 0x81, 0x83, 0x2C, 0x2D, 5}, // SUB
- {0x18, 0x19, 0x1A, 0x1B, 0x80, 0x81, 0x83, 0x1C, 0x1D, 3}, // SBB
-
- {0x20, 0x21, 0x22, 0x23, 0x80, 0x81, 0x83, 0x24, 0x25, 4}, // AND
- {0x08, 0x09, 0x0A, 0x0B, 0x80, 0x81, 0x83, 0x0C, 0x0D, 1}, // OR
-
- {0x30, 0x31, 0x32, 0x33, 0x80, 0x81, 0x83, 0x34, 0x35, 6}, // XOR
- {0x88, 0x89, 0x8A, 0x8B, 0xC6, 0xC7, 0xCC, 0xCC, 0xCC, 0}, // MOV
-
- {0x84, 0x85, 0x84, 0x85, 0xF6, 0xF7, 0xCC, 0xA8, 0xA9, 0}, // TEST (to == from)
- {0x38, 0x39, 0x3A, 0x3B, 0x80, 0x81, 0x83, 0x3C, 0x3D, 7}, // CMP
-
- {0x86, 0x87, 0x86, 0x87, 0xCC, 0xCC, 0xCC, 0xCC, 0xCC, 7}, // XCHG
-};
-
-enum NormalSSEOps {
- sseCMP = 0xC2,
- sseADD = 0x58, // ADD
- sseSUB = 0x5C, // SUB
- sseAND = 0x54, // AND
- sseANDN = 0x55, // ANDN
- sseOR = 0x56,
- sseXOR = 0x57,
- sseMUL = 0x59, // MUL
- sseDIV = 0x5E, // DIV
- sseMIN = 0x5D, // MIN
- sseMAX = 0x5F, // MAX
- sseCOMIS = 0x2F, // COMIS
- sseUCOMIS = 0x2E, // UCOMIS
- sseSQRT = 0x51, // SQRT
- sseRSQRT = 0x52, // RSQRT (NO DOUBLE PRECISION!!!)
- sseRCP = 0x53, // RCP
- sseMOVAPfromRM = 0x28, // MOVAP from RM
- sseMOVAPtoRM = 0x29, // MOVAP to RM
- sseMOVUPfromRM = 0x10, // MOVUP from RM
- sseMOVUPtoRM = 0x11, // MOVUP to RM
- sseMOVLPfromRM = 0x12,
- sseMOVLPtoRM = 0x13,
- sseMOVHPfromRM = 0x16,
- sseMOVHPtoRM = 0x17,
- sseMOVHLPS = 0x12,
- sseMOVLHPS = 0x16,
- sseMOVDQfromRM = 0x6F,
- sseMOVDQtoRM = 0x7F,
- sseMASKMOVDQU = 0xF7,
- sseLDDQU = 0xF0,
- sseSHUF = 0xC6,
- sseMOVNTDQ = 0xE7,
- sseMOVNTP = 0x2B,
- sseHADD = 0x7C,
-};
-
-void XEmitter::SetCodePtr(u8* ptr) {
- code = ptr;
-}
-
-const u8* XEmitter::GetCodePtr() const {
- return code;
-}
-
-u8* XEmitter::GetWritableCodePtr() {
- return code;
-}
-
-void XEmitter::Write8(u8 value) {
- *code++ = value;
-}
-
-void XEmitter::Write16(u16 value) {
- std::memcpy(code, &value, sizeof(u16));
- code += sizeof(u16);
-}
-
-void XEmitter::Write32(u32 value) {
- std::memcpy(code, &value, sizeof(u32));
- code += sizeof(u32);
-}
-
-void XEmitter::Write64(u64 value) {
- std::memcpy(code, &value, sizeof(u64));
- code += sizeof(u64);
-}
-
-void XEmitter::ReserveCodeSpace(int bytes) {
- for (int i = 0; i < bytes; i++)
- *code++ = 0xCC;
-}
-
-const u8* XEmitter::AlignCode4() {
- int c = int((u64)code & 3);
- if (c)
- ReserveCodeSpace(4 - c);
- return code;
-}
-
-const u8* XEmitter::AlignCode16() {
- int c = int((u64)code & 15);
- if (c)
- ReserveCodeSpace(16 - c);
- return code;
-}
-
-const u8* XEmitter::AlignCodePage() {
- int c = int((u64)code & 4095);
- if (c)
- ReserveCodeSpace(4096 - c);
- return code;
-}
-
-// This operation modifies flags; check to see the flags are locked.
-// If the flags are locked, we should immediately and loudly fail before
-// causing a subtle JIT bug.
-void XEmitter::CheckFlags() {
- ASSERT_MSG(!flags_locked, "Attempt to modify flags while flags locked!");
-}
-
-void XEmitter::WriteModRM(int mod, int reg, int rm) {
- Write8((u8)((mod << 6) | ((reg & 7) << 3) | (rm & 7)));
-}
-
-void XEmitter::WriteSIB(int scale, int index, int base) {
- Write8((u8)((scale << 6) | ((index & 7) << 3) | (base & 7)));
-}
-
-void OpArg::WriteRex(XEmitter* emit, int opBits, int bits, int customOp) const {
- if (customOp == -1)
- customOp = operandReg;
-#ifdef ARCHITECTURE_x86_64
- u8 op = 0x40;
- // REX.W (whether operation is a 64-bit operation)
- if (opBits == 64)
- op |= 8;
- // REX.R (whether ModR/M reg field refers to R8-R15.
- if (customOp & 8)
- op |= 4;
- // REX.X (whether ModR/M SIB index field refers to R8-R15)
- if (indexReg & 8)
- op |= 2;
- // REX.B (whether ModR/M rm or SIB base or opcode reg field refers to R8-R15)
- if (offsetOrBaseReg & 8)
- op |= 1;
- // Write REX if wr have REX bits to write, or if the operation accesses
- // SIL, DIL, BPL, or SPL.
- if (op != 0x40 || (scale == SCALE_NONE && bits == 8 && (offsetOrBaseReg & 0x10c) == 4) ||
- (opBits == 8 && (customOp & 0x10c) == 4)) {
- emit->Write8(op);
- // Check the operation doesn't access AH, BH, CH, or DH.
- DEBUG_ASSERT((offsetOrBaseReg & 0x100) == 0);
- DEBUG_ASSERT((customOp & 0x100) == 0);
- }
-#else
- DEBUG_ASSERT(opBits != 64);
- DEBUG_ASSERT((customOp & 8) == 0 || customOp == -1);
- DEBUG_ASSERT((indexReg & 8) == 0);
- DEBUG_ASSERT((offsetOrBaseReg & 8) == 0);
- DEBUG_ASSERT(opBits != 8 || (customOp & 0x10c) != 4 || customOp == -1);
- DEBUG_ASSERT(scale == SCALE_ATREG || bits != 8 || (offsetOrBaseReg & 0x10c) != 4);
-#endif
-}
-
-void OpArg::WriteVex(XEmitter* emit, X64Reg regOp1, X64Reg regOp2, int L, int pp, int mmmmm,
- int W) const {
- int R = !(regOp1 & 8);
- int X = !(indexReg & 8);
- int B = !(offsetOrBaseReg & 8);
-
- int vvvv = (regOp2 == X64Reg::INVALID_REG) ? 0xf : (regOp2 ^ 0xf);
-
- // do we need any VEX fields that only appear in the three-byte form?
- if (X == 1 && B == 1 && W == 0 && mmmmm == 1) {
- u8 RvvvvLpp = (R << 7) | (vvvv << 3) | (L << 2) | pp;
- emit->Write8(0xC5);
- emit->Write8(RvvvvLpp);
- } else {
- u8 RXBmmmmm = (R << 7) | (X << 6) | (B << 5) | mmmmm;
- u8 WvvvvLpp = (W << 7) | (vvvv << 3) | (L << 2) | pp;
- emit->Write8(0xC4);
- emit->Write8(RXBmmmmm);
- emit->Write8(WvvvvLpp);
- }
-}
-
-void OpArg::WriteRest(XEmitter* emit, int extraBytes, X64Reg _operandReg,
- bool warn_64bit_offset) const {
- if (_operandReg == INVALID_REG)
- _operandReg = (X64Reg)this->operandReg;
- int mod = 0;
- int ireg = indexReg;
- bool SIB = false;
- int _offsetOrBaseReg = this->offsetOrBaseReg;
-
- if (scale == SCALE_RIP) // Also, on 32-bit, just an immediate address
- {
- // Oh, RIP addressing.
- _offsetOrBaseReg = 5;
- emit->WriteModRM(0, _operandReg, _offsetOrBaseReg);
-// TODO : add some checks
-#ifdef ARCHITECTURE_x86_64
- u64 ripAddr = (u64)emit->GetCodePtr() + 4 + extraBytes;
- s64 distance = (s64)offset - (s64)ripAddr;
- ASSERT_MSG((distance < 0x80000000LL && distance >= -0x80000000LL) || !warn_64bit_offset,
- "WriteRest: op out of range (0x%" PRIx64 " uses 0x%" PRIx64 ")", ripAddr,
- offset);
- s32 offs = (s32)distance;
- emit->Write32((u32)offs);
-#else
- emit->Write32((u32)offset);
-#endif
- return;
- }
-
- if (scale == 0) {
- // Oh, no memory, Just a reg.
- mod = 3; // 11
- } else if (scale >= 1) {
- // Ah good, no scaling.
- if (scale == SCALE_ATREG && !((_offsetOrBaseReg & 7) == 4 || (_offsetOrBaseReg & 7) == 5)) {
- // Okay, we're good. No SIB necessary.
- int ioff = (int)offset;
- if (ioff == 0) {
- mod = 0;
- } else if (ioff < -128 || ioff > 127) {
- mod = 2; // 32-bit displacement
- } else {
- mod = 1; // 8-bit displacement
- }
- } else if (scale >= SCALE_NOBASE_2 && scale <= SCALE_NOBASE_8) {
- SIB = true;
- mod = 0;
- _offsetOrBaseReg = 5;
- } else // if (scale != SCALE_ATREG)
- {
- if ((_offsetOrBaseReg & 7) == 4) // this would occupy the SIB encoding :(
- {
- // So we have to fake it with SIB encoding :(
- SIB = true;
- }
-
- if (scale >= SCALE_1 && scale < SCALE_ATREG) {
- SIB = true;
- }
-
- if (scale == SCALE_ATREG && ((_offsetOrBaseReg & 7) == 4)) {
- SIB = true;
- ireg = _offsetOrBaseReg;
- }
-
- // Okay, we're fine. Just disp encoding.
- // We need displacement. Which size?
- int ioff = (int)(s64)offset;
- if (ioff < -128 || ioff > 127) {
- mod = 2; // 32-bit displacement
- } else {
- mod = 1; // 8-bit displacement
- }
- }
- }
-
- // Okay. Time to do the actual writing
- // ModRM byte:
- int oreg = _offsetOrBaseReg;
- if (SIB)
- oreg = 4;
-
- // TODO(ector): WTF is this if about? I don't remember writing it :-)
- // if (RIP)
- // oreg = 5;
-
- emit->WriteModRM(mod, _operandReg & 7, oreg & 7);
-
- if (SIB) {
- // SIB byte
- int ss;
- switch (scale) {
- case SCALE_NONE:
- _offsetOrBaseReg = 4;
- ss = 0;
- break; // RSP
- case SCALE_1:
- ss = 0;
- break;
- case SCALE_2:
- ss = 1;
- break;
- case SCALE_4:
- ss = 2;
- break;
- case SCALE_8:
- ss = 3;
- break;
- case SCALE_NOBASE_2:
- ss = 1;
- break;
- case SCALE_NOBASE_4:
- ss = 2;
- break;
- case SCALE_NOBASE_8:
- ss = 3;
- break;
- case SCALE_ATREG:
- ss = 0;
- break;
- default:
- ASSERT_MSG(0, "Invalid scale for SIB byte");
- ss = 0;
- break;
- }
- emit->Write8((u8)((ss << 6) | ((ireg & 7) << 3) | (_offsetOrBaseReg & 7)));
- }
-
- if (mod == 1) // 8-bit disp
- {
- emit->Write8((u8)(s8)(s32)offset);
- } else if (mod == 2 || (scale >= SCALE_NOBASE_2 && scale <= SCALE_NOBASE_8)) // 32-bit disp
- {
- emit->Write32((u32)offset);
- }
-}
-
-// W = operand extended width (1 if 64-bit)
-// R = register# upper bit
-// X = scale amnt upper bit
-// B = base register# upper bit
-void XEmitter::Rex(int w, int r, int x, int b) {
- w = w ? 1 : 0;
- r = r ? 1 : 0;
- x = x ? 1 : 0;
- b = b ? 1 : 0;
- u8 rx = (u8)(0x40 | (w << 3) | (r << 2) | (x << 1) | (b));
- if (rx != 0x40)
- Write8(rx);
-}
-
-void XEmitter::JMP(const u8* addr, bool force5Bytes) {
- u64 fn = (u64)addr;
- if (!force5Bytes) {
- s64 distance = (s64)(fn - ((u64)code + 2));
- ASSERT_MSG(distance >= -0x80 && distance < 0x80,
- "Jump target too far away, needs force5Bytes = true");
- // 8 bits will do
- Write8(0xEB);
- Write8((u8)(s8)distance);
- } else {
- s64 distance = (s64)(fn - ((u64)code + 5));
-
- ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL,
- "Jump target too far away, needs indirect register");
- Write8(0xE9);
- Write32((u32)(s32)distance);
- }
-}
-
-void XEmitter::JMPptr(const OpArg& arg2) {
- OpArg arg = arg2;
- if (arg.IsImm())
- ASSERT_MSG(0, "JMPptr - Imm argument");
- arg.operandReg = 4;
- arg.WriteRex(this, 0, 0);
- Write8(0xFF);
- arg.WriteRest(this);
-}
-
-// Can be used to trap other processors, before overwriting their code
-// not used in dolphin
-void XEmitter::JMPself() {
- Write8(0xEB);
- Write8(0xFE);
-}
-
-void XEmitter::CALLptr(OpArg arg) {
- if (arg.IsImm())
- ASSERT_MSG(0, "CALLptr - Imm argument");
- arg.operandReg = 2;
- arg.WriteRex(this, 0, 0);
- Write8(0xFF);
- arg.WriteRest(this);
-}
-
-void XEmitter::CALL(const void* fnptr) {
- u64 distance = u64(fnptr) - (u64(code) + 5);
- ASSERT_MSG(distance < 0x0000000080000000ULL || distance >= 0xFFFFFFFF80000000ULL,
- "CALL out of range (%p calls %p)", code, fnptr);
- Write8(0xE8);
- Write32(u32(distance));
-}
-
-FixupBranch XEmitter::CALL() {
- FixupBranch branch;
- branch.type = 1;
- branch.ptr = code + 5;
-
- Write8(0xE8);
- Write32(0);
-
- return branch;
-}
-
-FixupBranch XEmitter::J(bool force5bytes) {
- FixupBranch branch;
- branch.type = force5bytes ? 1 : 0;
- branch.ptr = code + (force5bytes ? 5 : 2);
- if (!force5bytes) {
- // 8 bits will do
- Write8(0xEB);
- Write8(0);
- } else {
- Write8(0xE9);
- Write32(0);
- }
- return branch;
-}
-
-FixupBranch XEmitter::J_CC(CCFlags conditionCode, bool force5bytes) {
- FixupBranch branch;
- branch.type = force5bytes ? 1 : 0;
- branch.ptr = code + (force5bytes ? 6 : 2);
- if (!force5bytes) {
- // 8 bits will do
- Write8(0x70 + conditionCode);
- Write8(0);
- } else {
- Write8(0x0F);
- Write8(0x80 + conditionCode);
- Write32(0);
- }
- return branch;
-}
-
-void XEmitter::J_CC(CCFlags conditionCode, const u8* addr, bool force5bytes) {
- u64 fn = (u64)addr;
- s64 distance = (s64)(fn - ((u64)code + 2));
- if (distance < -0x80 || distance >= 0x80 || force5bytes) {
- distance = (s64)(fn - ((u64)code + 6));
- ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL,
- "Jump target too far away, needs indirect register");
- Write8(0x0F);
- Write8(0x80 + conditionCode);
- Write32((u32)(s32)distance);
- } else {
- Write8(0x70 + conditionCode);
- Write8((u8)(s8)distance);
- }
-}
-
-void XEmitter::SetJumpTarget(const FixupBranch& branch) {
- if (branch.type == 0) {
- s64 distance = (s64)(code - branch.ptr);
- ASSERT_MSG(distance >= -0x80 && distance < 0x80,
- "Jump target too far away, needs force5Bytes = true");
- branch.ptr[-1] = (u8)(s8)distance;
- } else if (branch.type == 1) {
- s64 distance = (s64)(code - branch.ptr);
- ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL,
- "Jump target too far away, needs indirect register");
- ((s32*)branch.ptr)[-1] = (s32)distance;
- }
-}
-
-void XEmitter::SetJumpTarget(const FixupBranch& branch, const u8* target) {
- if (branch.type == 0) {
- s64 distance = (s64)(target - branch.ptr);
- ASSERT_MSG(distance >= -0x80 && distance < 0x80,
- "Jump target too far away, needs force5Bytes = true");
- branch.ptr[-1] = (u8)(s8)distance;
- } else if (branch.type == 1) {
- s64 distance = (s64)(target - branch.ptr);
- ASSERT_MSG(distance >= -0x80000000LL && distance < 0x80000000LL,
- "Jump target too far away, needs indirect register");
- ((s32*)branch.ptr)[-1] = (s32)distance;
- }
-}
-
-// Single byte opcodes
-// There is no PUSHAD/POPAD in 64-bit mode.
-void XEmitter::INT3() {
- Write8(0xCC);
-}
-void XEmitter::RET() {
- Write8(0xC3);
-}
-void XEmitter::RET_FAST() {
- Write8(0xF3);
- Write8(0xC3);
-} // two-byte return (rep ret) - recommended by AMD optimization manual for the case of jumping to a
- // ret
-
-// The first sign of decadence: optimized NOPs.
-void XEmitter::NOP(size_t size) {
- DEBUG_ASSERT((int)size > 0);
- while (true) {
- switch (size) {
- case 0:
- return;
- case 1:
- Write8(0x90);
- return;
- case 2:
- Write8(0x66);
- Write8(0x90);
- return;
- case 3:
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x00);
- return;
- case 4:
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x40);
- Write8(0x00);
- return;
- case 5:
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x44);
- Write8(0x00);
- Write8(0x00);
- return;
- case 6:
- Write8(0x66);
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x44);
- Write8(0x00);
- Write8(0x00);
- return;
- case 7:
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x80);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- return;
- case 8:
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x84);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- return;
- case 9:
- Write8(0x66);
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x84);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- return;
- case 10:
- Write8(0x66);
- Write8(0x66);
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x84);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- return;
- default:
- // Even though x86 instructions are allowed to be up to 15 bytes long,
- // AMD advises against using NOPs longer than 11 bytes because they
- // carry a performance penalty on CPUs older than AMD family 16h.
- Write8(0x66);
- Write8(0x66);
- Write8(0x66);
- Write8(0x0F);
- Write8(0x1F);
- Write8(0x84);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- Write8(0x00);
- size -= 11;
- continue;
- }
- }
-}
-
-void XEmitter::PAUSE() {
- Write8(0xF3);
- NOP();
-} // use in tight spinloops for energy saving on some cpu
-void XEmitter::CLC() {
- CheckFlags();
- Write8(0xF8);
-} // clear carry
-void XEmitter::CMC() {
- CheckFlags();
- Write8(0xF5);
-} // flip carry
-void XEmitter::STC() {
- CheckFlags();
- Write8(0xF9);
-} // set carry
-
-// TODO: xchg ah, al ???
-void XEmitter::XCHG_AHAL() {
- Write8(0x86);
- Write8(0xe0);
- // alt. 86 c4
-}
-
-// These two can not be executed on early Intel 64-bit CPU:s, only on AMD!
-void XEmitter::LAHF() {
- Write8(0x9F);
-}
-void XEmitter::SAHF() {
- CheckFlags();
- Write8(0x9E);
-}
-
-void XEmitter::PUSHF() {
- Write8(0x9C);
-}
-void XEmitter::POPF() {
- CheckFlags();
- Write8(0x9D);
-}
-
-void XEmitter::LFENCE() {
- Write8(0x0F);
- Write8(0xAE);
- Write8(0xE8);
-}
-void XEmitter::MFENCE() {
- Write8(0x0F);
- Write8(0xAE);
- Write8(0xF0);
-}
-void XEmitter::SFENCE() {
- Write8(0x0F);
- Write8(0xAE);
- Write8(0xF8);
-}
-
-void XEmitter::WriteSimple1Byte(int bits, u8 byte, X64Reg reg) {
- if (bits == 16)
- Write8(0x66);
- Rex(bits == 64, 0, 0, (int)reg >> 3);
- Write8(byte + ((int)reg & 7));
-}
-
-void XEmitter::WriteSimple2Byte(int bits, u8 byte1, u8 byte2, X64Reg reg) {
- if (bits == 16)
- Write8(0x66);
- Rex(bits == 64, 0, 0, (int)reg >> 3);
- Write8(byte1);
- Write8(byte2 + ((int)reg & 7));
-}
-
-void XEmitter::CWD(int bits) {
- if (bits == 16)
- Write8(0x66);
- Rex(bits == 64, 0, 0, 0);
- Write8(0x99);
-}
-
-void XEmitter::CBW(int bits) {
- if (bits == 8)
- Write8(0x66);
- Rex(bits == 32, 0, 0, 0);
- Write8(0x98);
-}
-
-// Simple opcodes
-
-// push/pop do not need wide to be 64-bit
-void XEmitter::PUSH(X64Reg reg) {
- WriteSimple1Byte(32, 0x50, reg);
-}
-void XEmitter::POP(X64Reg reg) {
- WriteSimple1Byte(32, 0x58, reg);
-}
-
-void XEmitter::PUSH(int bits, const OpArg& reg) {
- if (reg.IsSimpleReg())
- PUSH(reg.GetSimpleReg());
- else if (reg.IsImm()) {
- switch (reg.GetImmBits()) {
- case 8:
- Write8(0x6A);
- Write8((u8)(s8)reg.offset);
- break;
- case 16:
- Write8(0x66);
- Write8(0x68);
- Write16((u16)(s16)(s32)reg.offset);
- break;
- case 32:
- Write8(0x68);
- Write32((u32)reg.offset);
- break;
- default:
- ASSERT_MSG(0, "PUSH - Bad imm bits");
- break;
- }
- } else {
- if (bits == 16)
- Write8(0x66);
- reg.WriteRex(this, bits, bits);
- Write8(0xFF);
- reg.WriteRest(this, 0, (X64Reg)6);
- }
-}
-
-void XEmitter::POP(int /*bits*/, const OpArg& reg) {
- if (reg.IsSimpleReg())
- POP(reg.GetSimpleReg());
- else
- ASSERT_MSG(0, "POP - Unsupported encoding");
-}
-
-void XEmitter::BSWAP(int bits, X64Reg reg) {
- if (bits >= 32) {
- WriteSimple2Byte(bits, 0x0F, 0xC8, reg);
- } else if (bits == 16) {
- ROL(16, R(reg), Imm8(8));
- } else if (bits == 8) {
- // Do nothing - can't bswap a single byte...
- } else {
- ASSERT_MSG(0, "BSWAP - Wrong number of bits");
- }
-}
-
-// Undefined opcode - reserved
-// If we ever need a way to always cause a non-breakpoint hard exception...
-void XEmitter::UD2() {
- Write8(0x0F);
- Write8(0x0B);
-}
-
-void XEmitter::PREFETCH(PrefetchLevel level, OpArg arg) {
- ASSERT_MSG(!arg.IsImm(), "PREFETCH - Imm argument");
- arg.operandReg = (u8)level;
- arg.WriteRex(this, 0, 0);
- Write8(0x0F);
- Write8(0x18);
- arg.WriteRest(this);
-}
-
-void XEmitter::SETcc(CCFlags flag, OpArg dest) {
- ASSERT_MSG(!dest.IsImm(), "SETcc - Imm argument");
- dest.operandReg = 0;
- dest.WriteRex(this, 0, 8);
- Write8(0x0F);
- Write8(0x90 + (u8)flag);
- dest.WriteRest(this);
-}
-
-void XEmitter::CMOVcc(int bits, X64Reg dest, OpArg src, CCFlags flag) {
- ASSERT_MSG(!src.IsImm(), "CMOVcc - Imm argument");
- ASSERT_MSG(bits != 8, "CMOVcc - 8 bits unsupported");
- if (bits == 16)
- Write8(0x66);
- src.operandReg = dest;
- src.WriteRex(this, bits, bits);
- Write8(0x0F);
- Write8(0x40 + (u8)flag);
- src.WriteRest(this);
-}
-
-void XEmitter::WriteMulDivType(int bits, OpArg src, int ext) {
- ASSERT_MSG(!src.IsImm(), "WriteMulDivType - Imm argument");
- CheckFlags();
- src.operandReg = ext;
- if (bits == 16)
- Write8(0x66);
- src.WriteRex(this, bits, bits, 0);
- if (bits == 8) {
- Write8(0xF6);
- } else {
- Write8(0xF7);
- }
- src.WriteRest(this);
-}
-
-void XEmitter::MUL(int bits, const OpArg& src) {
- WriteMulDivType(bits, src, 4);
-}
-void XEmitter::DIV(int bits, const OpArg& src) {
- WriteMulDivType(bits, src, 6);
-}
-void XEmitter::IMUL(int bits, const OpArg& src) {
- WriteMulDivType(bits, src, 5);
-}
-void XEmitter::IDIV(int bits, const OpArg& src) {
- WriteMulDivType(bits, src, 7);
-}
-void XEmitter::NEG(int bits, const OpArg& src) {
- WriteMulDivType(bits, src, 3);
-}
-void XEmitter::NOT(int bits, const OpArg& src) {
- WriteMulDivType(bits, src, 2);
-}
-
-void XEmitter::WriteBitSearchType(int bits, X64Reg dest, OpArg src, u8 byte2, bool rep) {
- ASSERT_MSG(!src.IsImm(), "WriteBitSearchType - Imm argument");
- CheckFlags();
- src.operandReg = (u8)dest;
- if (bits == 16)
- Write8(0x66);
- if (rep)
- Write8(0xF3);
- src.WriteRex(this, bits, bits);
- Write8(0x0F);
- Write8(byte2);
- src.WriteRest(this);
-}
-
-void XEmitter::MOVNTI(int bits, const OpArg& dest, X64Reg src) {
- if (bits <= 16)
- ASSERT_MSG(0, "MOVNTI - bits<=16");
- WriteBitSearchType(bits, src, dest, 0xC3);
-}
-
-void XEmitter::BSF(int bits, X64Reg dest, const OpArg& src) {
- WriteBitSearchType(bits, dest, src, 0xBC);
-} // Bottom bit to top bit
-void XEmitter::BSR(int bits, X64Reg dest, const OpArg& src) {
- WriteBitSearchType(bits, dest, src, 0xBD);
-} // Top bit to bottom bit
-
-void XEmitter::TZCNT(int bits, X64Reg dest, const OpArg& src) {
- CheckFlags();
- if (!Common::GetCPUCaps().bmi1)
- ASSERT_MSG(0, "Trying to use BMI1 on a system that doesn't support it. Bad programmer.");
- WriteBitSearchType(bits, dest, src, 0xBC, true);
-}
-void XEmitter::LZCNT(int bits, X64Reg dest, const OpArg& src) {
- CheckFlags();
- if (!Common::GetCPUCaps().lzcnt)
- ASSERT_MSG(0, "Trying to use LZCNT on a system that doesn't support it. Bad programmer.");
- WriteBitSearchType(bits, dest, src, 0xBD, true);
-}
-
-void XEmitter::MOVSX(int dbits, int sbits, X64Reg dest, OpArg src) {
- ASSERT_MSG(!src.IsImm(), "MOVSX - Imm argument");
- if (dbits == sbits) {
- MOV(dbits, R(dest), src);
- return;
- }
- src.operandReg = (u8)dest;
- if (dbits == 16)
- Write8(0x66);
- src.WriteRex(this, dbits, sbits);
- if (sbits == 8) {
- Write8(0x0F);
- Write8(0xBE);
- } else if (sbits == 16) {
- Write8(0x0F);
- Write8(0xBF);
- } else if (sbits == 32 && dbits == 64) {
- Write8(0x63);
- } else {
- Crash();
- }
- src.WriteRest(this);
-}
-
-void XEmitter::MOVZX(int dbits, int sbits, X64Reg dest, OpArg src) {
- ASSERT_MSG(!src.IsImm(), "MOVZX - Imm argument");
- if (dbits == sbits) {
- MOV(dbits, R(dest), src);
- return;
- }
- src.operandReg = (u8)dest;
- if (dbits == 16)
- Write8(0x66);
- // the 32bit result is automatically zero extended to 64bit
- src.WriteRex(this, dbits == 64 ? 32 : dbits, sbits);
- if (sbits == 8) {
- Write8(0x0F);
- Write8(0xB6);
- } else if (sbits == 16) {
- Write8(0x0F);
- Write8(0xB7);
- } else if (sbits == 32 && dbits == 64) {
- Write8(0x8B);
- } else {
- ASSERT_MSG(0, "MOVZX - Invalid size");
- }
- src.WriteRest(this);
-}
-
-void XEmitter::MOVBE(int bits, const OpArg& dest, const OpArg& src) {
- ASSERT_MSG(Common::GetCPUCaps().movbe,
- "Generating MOVBE on a system that does not support it.");
- if (bits == 8) {
- MOV(bits, dest, src);
- return;
- }
-
- if (bits == 16)
- Write8(0x66);
-
- if (dest.IsSimpleReg()) {
- ASSERT_MSG(!src.IsSimpleReg() && !src.IsImm(), "MOVBE: Loading from !mem");
- src.WriteRex(this, bits, bits, dest.GetSimpleReg());
- Write8(0x0F);
- Write8(0x38);
- Write8(0xF0);
- src.WriteRest(this, 0, dest.GetSimpleReg());
- } else if (src.IsSimpleReg()) {
- ASSERT_MSG(!dest.IsSimpleReg() && !dest.IsImm(), "MOVBE: Storing to !mem");
- dest.WriteRex(this, bits, bits, src.GetSimpleReg());
- Write8(0x0F);
- Write8(0x38);
- Write8(0xF1);
- dest.WriteRest(this, 0, src.GetSimpleReg());
- } else {
- ASSERT_MSG(0, "MOVBE: Not loading or storing to mem");
- }
-}
-
-void XEmitter::LEA(int bits, X64Reg dest, OpArg src) {
- ASSERT_MSG(!src.IsImm(), "LEA - Imm argument");
- src.operandReg = (u8)dest;
- if (bits == 16)
- Write8(0x66); // TODO: performance warning
- src.WriteRex(this, bits, bits);
- Write8(0x8D);
- src.WriteRest(this, 0, INVALID_REG, bits == 64);
-}
-
-// shift can be either imm8 or cl
-void XEmitter::WriteShift(int bits, OpArg dest, const OpArg& shift, int ext) {
- CheckFlags();
- bool writeImm = false;
- if (dest.IsImm()) {
- ASSERT_MSG(0, "WriteShift - can't shift imms");
- }
- if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) ||
- (shift.IsImm() && shift.GetImmBits() != 8)) {
- ASSERT_MSG(0, "WriteShift - illegal argument");
- }
- dest.operandReg = ext;
- if (bits == 16)
- Write8(0x66);
- dest.WriteRex(this, bits, bits, 0);
- if (shift.GetImmBits() == 8) {
- // ok an imm
- u8 imm = (u8)shift.offset;
- if (imm == 1) {
- Write8(bits == 8 ? 0xD0 : 0xD1);
- } else {
- writeImm = true;
- Write8(bits == 8 ? 0xC0 : 0xC1);
- }
- } else {
- Write8(bits == 8 ? 0xD2 : 0xD3);
- }
- dest.WriteRest(this, writeImm ? 1 : 0);
- if (writeImm)
- Write8((u8)shift.offset);
-}
-
-// large rotates and shift are slower on intel than amd
-// intel likes to rotate by 1, and the op is smaller too
-void XEmitter::ROL(int bits, const OpArg& dest, const OpArg& shift) {
- WriteShift(bits, dest, shift, 0);
-}
-void XEmitter::ROR(int bits, const OpArg& dest, const OpArg& shift) {
- WriteShift(bits, dest, shift, 1);
-}
-void XEmitter::RCL(int bits, const OpArg& dest, const OpArg& shift) {
- WriteShift(bits, dest, shift, 2);
-}
-void XEmitter::RCR(int bits, const OpArg& dest, const OpArg& shift) {
- WriteShift(bits, dest, shift, 3);
-}
-void XEmitter::SHL(int bits, const OpArg& dest, const OpArg& shift) {
- WriteShift(bits, dest, shift, 4);
-}
-void XEmitter::SHR(int bits, const OpArg& dest, const OpArg& shift) {
- WriteShift(bits, dest, shift, 5);
-}
-void XEmitter::SAR(int bits, const OpArg& dest, const OpArg& shift) {
- WriteShift(bits, dest, shift, 7);
-}
-
-// index can be either imm8 or register, don't use memory destination because it's slow
-void XEmitter::WriteBitTest(int bits, const OpArg& dest, const OpArg& index, int ext) {
- CheckFlags();
- if (dest.IsImm()) {
- ASSERT_MSG(0, "WriteBitTest - can't test imms");
- }
- if ((index.IsImm() && index.GetImmBits() != 8)) {
- ASSERT_MSG(0, "WriteBitTest - illegal argument");
- }
- if (bits == 16)
- Write8(0x66);
- if (index.IsImm()) {
- dest.WriteRex(this, bits, bits);
- Write8(0x0F);
- Write8(0xBA);
- dest.WriteRest(this, 1, (X64Reg)ext);
- Write8((u8)index.offset);
- } else {
- X64Reg operand = index.GetSimpleReg();
- dest.WriteRex(this, bits, bits, operand);
- Write8(0x0F);
- Write8(0x83 + 8 * ext);
- dest.WriteRest(this, 1, operand);
- }
-}
-
-void XEmitter::BT(int bits, const OpArg& dest, const OpArg& index) {
- WriteBitTest(bits, dest, index, 4);
-}
-void XEmitter::BTS(int bits, const OpArg& dest, const OpArg& index) {
- WriteBitTest(bits, dest, index, 5);
-}
-void XEmitter::BTR(int bits, const OpArg& dest, const OpArg& index) {
- WriteBitTest(bits, dest, index, 6);
-}
-void XEmitter::BTC(int bits, const OpArg& dest, const OpArg& index) {
- WriteBitTest(bits, dest, index, 7);
-}
-
-// shift can be either imm8 or cl
-void XEmitter::SHRD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift) {
- CheckFlags();
- if (dest.IsImm()) {
- ASSERT_MSG(0, "SHRD - can't use imms as destination");
- }
- if (!src.IsSimpleReg()) {
- ASSERT_MSG(0, "SHRD - must use simple register as source");
- }
- if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) ||
- (shift.IsImm() && shift.GetImmBits() != 8)) {
- ASSERT_MSG(0, "SHRD - illegal shift");
- }
- if (bits == 16)
- Write8(0x66);
- X64Reg operand = src.GetSimpleReg();
- dest.WriteRex(this, bits, bits, operand);
- if (shift.GetImmBits() == 8) {
- Write8(0x0F);
- Write8(0xAC);
- dest.WriteRest(this, 1, operand);
- Write8((u8)shift.offset);
- } else {
- Write8(0x0F);
- Write8(0xAD);
- dest.WriteRest(this, 0, operand);
- }
-}
-
-void XEmitter::SHLD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift) {
- CheckFlags();
- if (dest.IsImm()) {
- ASSERT_MSG(0, "SHLD - can't use imms as destination");
- }
- if (!src.IsSimpleReg()) {
- ASSERT_MSG(0, "SHLD - must use simple register as source");
- }
- if ((shift.IsSimpleReg() && shift.GetSimpleReg() != ECX) ||
- (shift.IsImm() && shift.GetImmBits() != 8)) {
- ASSERT_MSG(0, "SHLD - illegal shift");
- }
- if (bits == 16)
- Write8(0x66);
- X64Reg operand = src.GetSimpleReg();
- dest.WriteRex(this, bits, bits, operand);
- if (shift.GetImmBits() == 8) {
- Write8(0x0F);
- Write8(0xA4);
- dest.WriteRest(this, 1, operand);
- Write8((u8)shift.offset);
- } else {
- Write8(0x0F);
- Write8(0xA5);
- dest.WriteRest(this, 0, operand);
- }
-}
-
-void OpArg::WriteSingleByteOp(XEmitter* emit, u8 op, X64Reg _operandReg, int bits) {
- if (bits == 16)
- emit->Write8(0x66);
-
- this->operandReg = (u8)_operandReg;
- WriteRex(emit, bits, bits);
- emit->Write8(op);
- WriteRest(emit);
-}
-
-// operand can either be immediate or register
-void OpArg::WriteNormalOp(XEmitter* emit, bool toRM, NormalOp op, const OpArg& operand,
- int bits) const {
- X64Reg _operandReg;
- if (IsImm()) {
- ASSERT_MSG(0, "WriteNormalOp - Imm argument, wrong order");
- }
-
- if (bits == 16)
- emit->Write8(0x66);
-
- int immToWrite = 0;
-
- if (operand.IsImm()) {
- WriteRex(emit, bits, bits);
-
- if (!toRM) {
- ASSERT_MSG(0, "WriteNormalOp - Writing to Imm (!toRM)");
- }
-
- if (operand.scale == SCALE_IMM8 && bits == 8) {
- // op al, imm8
- if (!scale && offsetOrBaseReg == AL && normalops[op].eaximm8 != 0xCC) {
- emit->Write8(normalops[op].eaximm8);
- emit->Write8((u8)operand.offset);
- return;
- }
- // mov reg, imm8
- if (!scale && op == nrmMOV) {
- emit->Write8(0xB0 + (offsetOrBaseReg & 7));
- emit->Write8((u8)operand.offset);
- return;
- }
- // op r/m8, imm8
- emit->Write8(normalops[op].imm8);
- immToWrite = 8;
- } else if ((operand.scale == SCALE_IMM16 && bits == 16) ||
- (operand.scale == SCALE_IMM32 && bits == 32) ||
- (operand.scale == SCALE_IMM32 && bits == 64)) {
- // Try to save immediate size if we can, but first check to see
- // if the instruction supports simm8.
- // op r/m, imm8
- if (normalops[op].simm8 != 0xCC &&
- ((operand.scale == SCALE_IMM16 && (s16)operand.offset == (s8)operand.offset) ||
- (operand.scale == SCALE_IMM32 && (s32)operand.offset == (s8)operand.offset))) {
- emit->Write8(normalops[op].simm8);
- immToWrite = 8;
- } else {
- // mov reg, imm
- if (!scale && op == nrmMOV && bits != 64) {
- emit->Write8(0xB8 + (offsetOrBaseReg & 7));
- if (bits == 16)
- emit->Write16((u16)operand.offset);
- else
- emit->Write32((u32)operand.offset);
- return;
- }
- // op eax, imm
- if (!scale && offsetOrBaseReg == EAX && normalops[op].eaximm32 != 0xCC) {
- emit->Write8(normalops[op].eaximm32);
- if (bits == 16)
- emit->Write16((u16)operand.offset);
- else
- emit->Write32((u32)operand.offset);
- return;
- }
- // op r/m, imm
- emit->Write8(normalops[op].imm32);
- immToWrite = bits == 16 ? 16 : 32;
- }
- } else if ((operand.scale == SCALE_IMM8 && bits == 16) ||
- (operand.scale == SCALE_IMM8 && bits == 32) ||
- (operand.scale == SCALE_IMM8 && bits == 64)) {
- // op r/m, imm8
- emit->Write8(normalops[op].simm8);
- immToWrite = 8;
- } else if (operand.scale == SCALE_IMM64 && bits == 64) {
- if (scale) {
- ASSERT_MSG(0, "WriteNormalOp - MOV with 64-bit imm requres register destination");
- }
- // mov reg64, imm64
- else if (op == nrmMOV) {
- emit->Write8(0xB8 + (offsetOrBaseReg & 7));
- emit->Write64((u64)operand.offset);
- return;
- }
- ASSERT_MSG(0, "WriteNormalOp - Only MOV can take 64-bit imm");
- } else {
- ASSERT_MSG(0, "WriteNormalOp - Unhandled case");
- }
- _operandReg = (X64Reg)normalops[op].ext; // pass extension in REG of ModRM
- } else {
- _operandReg = (X64Reg)operand.offsetOrBaseReg;
- WriteRex(emit, bits, bits, _operandReg);
- // op r/m, reg
- if (toRM) {
- emit->Write8(bits == 8 ? normalops[op].toRm8 : normalops[op].toRm32);
- }
- // op reg, r/m
- else {
- emit->Write8(bits == 8 ? normalops[op].fromRm8 : normalops[op].fromRm32);
- }
- }
- WriteRest(emit, immToWrite >> 3, _operandReg);
- switch (immToWrite) {
- case 0:
- break;
- case 8:
- emit->Write8((u8)operand.offset);
- break;
- case 16:
- emit->Write16((u16)operand.offset);
- break;
- case 32:
- emit->Write32((u32)operand.offset);
- break;
- default:
- ASSERT_MSG(0, "WriteNormalOp - Unhandled case");
- }
-}
-
-void XEmitter::WriteNormalOp(XEmitter* emit, int bits, NormalOp op, const OpArg& a1,
- const OpArg& a2) {
- if (a1.IsImm()) {
- // Booh! Can't write to an imm
- ASSERT_MSG(0, "WriteNormalOp - a1 cannot be imm");
- return;
- }
- if (a2.IsImm()) {
- a1.WriteNormalOp(emit, true, op, a2, bits);
- } else {
- if (a1.IsSimpleReg()) {
- a2.WriteNormalOp(emit, false, op, a1, bits);
- } else {
- ASSERT_MSG(a2.IsSimpleReg() || a2.IsImm(),
- "WriteNormalOp - a1 and a2 cannot both be memory");
- a1.WriteNormalOp(emit, true, op, a2, bits);
- }
- }
-}
-
-void XEmitter::ADD(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmADD, a1, a2);
-}
-void XEmitter::ADC(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmADC, a1, a2);
-}
-void XEmitter::SUB(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmSUB, a1, a2);
-}
-void XEmitter::SBB(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmSBB, a1, a2);
-}
-void XEmitter::AND(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmAND, a1, a2);
-}
-void XEmitter::OR(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmOR, a1, a2);
-}
-void XEmitter::XOR(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmXOR, a1, a2);
-}
-void XEmitter::MOV(int bits, const OpArg& a1, const OpArg& a2) {
- if (a1.IsSimpleReg() && a2.IsSimpleReg() && a1.GetSimpleReg() == a2.GetSimpleReg())
- LOG_ERROR(Common, "Redundant MOV @ %p - bug in JIT?", code);
- WriteNormalOp(this, bits, nrmMOV, a1, a2);
-}
-void XEmitter::TEST(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmTEST, a1, a2);
-}
-void XEmitter::CMP(int bits, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- WriteNormalOp(this, bits, nrmCMP, a1, a2);
-}
-void XEmitter::XCHG(int bits, const OpArg& a1, const OpArg& a2) {
- WriteNormalOp(this, bits, nrmXCHG, a1, a2);
-}
-
-void XEmitter::IMUL(int bits, X64Reg regOp, const OpArg& a1, const OpArg& a2) {
- CheckFlags();
- if (bits == 8) {
- ASSERT_MSG(0, "IMUL - illegal bit size!");
- return;
- }
-
- if (a1.IsImm()) {
- ASSERT_MSG(0, "IMUL - second arg cannot be imm!");
- return;
- }
-
- if (!a2.IsImm()) {
- ASSERT_MSG(0, "IMUL - third arg must be imm!");
- return;
- }
-
- if (bits == 16)
- Write8(0x66);
- a1.WriteRex(this, bits, bits, regOp);
-
- if (a2.GetImmBits() == 8 || (a2.GetImmBits() == 16 && (s8)a2.offset == (s16)a2.offset) ||
- (a2.GetImmBits() == 32 && (s8)a2.offset == (s32)a2.offset)) {
- Write8(0x6B);
- a1.WriteRest(this, 1, regOp);
- Write8((u8)a2.offset);
- } else {
- Write8(0x69);
- if (a2.GetImmBits() == 16 && bits == 16) {
- a1.WriteRest(this, 2, regOp);
- Write16((u16)a2.offset);
- } else if (a2.GetImmBits() == 32 && (bits == 32 || bits == 64)) {
- a1.WriteRest(this, 4, regOp);
- Write32((u32)a2.offset);
- } else {
- ASSERT_MSG(0, "IMUL - unhandled case!");
- }
- }
-}
-
-void XEmitter::IMUL(int bits, X64Reg regOp, const OpArg& a) {
- CheckFlags();
- if (bits == 8) {
- ASSERT_MSG(0, "IMUL - illegal bit size!");
- return;
- }
-
- if (a.IsImm()) {
- IMUL(bits, regOp, R(regOp), a);
- return;
- }
-
- if (bits == 16)
- Write8(0x66);
- a.WriteRex(this, bits, bits, regOp);
- Write8(0x0F);
- Write8(0xAF);
- a.WriteRest(this, 0, regOp);
-}
-
-void XEmitter::WriteSSEOp(u8 opPrefix, u16 op, X64Reg regOp, OpArg arg, int extrabytes) {
- if (opPrefix)
- Write8(opPrefix);
- arg.operandReg = regOp;
- arg.WriteRex(this, 0, 0);
- Write8(0x0F);
- if (op > 0xFF)
- Write8((op >> 8) & 0xFF);
- Write8(op & 0xFF);
- arg.WriteRest(this, extrabytes);
-}
-
-void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) {
- WriteAVXOp(opPrefix, op, regOp, INVALID_REG, arg, extrabytes);
-}
-
-static int GetVEXmmmmm(u16 op) {
- // Currently, only 0x38 and 0x3A are used as secondary escape byte.
- if ((op >> 8) == 0x3A)
- return 3;
- if ((op >> 8) == 0x38)
- return 2;
-
- return 1;
-}
-
-static int GetVEXpp(u8 opPrefix) {
- if (opPrefix == 0x66)
- return 1;
- if (opPrefix == 0xF3)
- return 2;
- if (opPrefix == 0xF2)
- return 3;
-
- return 0;
-}
-
-void XEmitter::WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg,
- int extrabytes) {
- if (!Common::GetCPUCaps().avx)
- ASSERT_MSG(0, "Trying to use AVX on a system that doesn't support it. Bad programmer.");
- int mmmmm = GetVEXmmmmm(op);
- int pp = GetVEXpp(opPrefix);
- // FIXME: we currently don't support 256-bit instructions, and "size" is not the vector size
- // here
- arg.WriteVex(this, regOp1, regOp2, 0, pp, mmmmm);
- Write8(op & 0xFF);
- arg.WriteRest(this, extrabytes, regOp1);
-}
-
-// Like the above, but more general; covers GPR-based VEX operations, like BMI1/2
-void XEmitter::WriteVEXOp(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2,
- const OpArg& arg, int extrabytes) {
- if (size != 32 && size != 64)
- ASSERT_MSG(0, "VEX GPR instructions only support 32-bit and 64-bit modes!");
- int mmmmm = GetVEXmmmmm(op);
- int pp = GetVEXpp(opPrefix);
- arg.WriteVex(this, regOp1, regOp2, 0, pp, mmmmm, size == 64);
- Write8(op & 0xFF);
- arg.WriteRest(this, extrabytes, regOp1);
-}
-
-void XEmitter::WriteBMI1Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2,
- const OpArg& arg, int extrabytes) {
- CheckFlags();
- if (!Common::GetCPUCaps().bmi1)
- ASSERT_MSG(0, "Trying to use BMI1 on a system that doesn't support it. Bad programmer.");
- WriteVEXOp(size, opPrefix, op, regOp1, regOp2, arg, extrabytes);
-}
-
-void XEmitter::WriteBMI2Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2,
- const OpArg& arg, int extrabytes) {
- CheckFlags();
- if (!Common::GetCPUCaps().bmi2)
- ASSERT_MSG(0, "Trying to use BMI2 on a system that doesn't support it. Bad programmer.");
- WriteVEXOp(size, opPrefix, op, regOp1, regOp2, arg, extrabytes);
-}
-
-void XEmitter::MOVD_xmm(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x6E, dest, arg, 0);
-}
-void XEmitter::MOVD_xmm(const OpArg& arg, X64Reg src) {
- WriteSSEOp(0x66, 0x7E, src, arg, 0);
-}
-
-void XEmitter::MOVQ_xmm(X64Reg dest, OpArg arg) {
-#ifdef ARCHITECTURE_x86_64
- // Alternate encoding
- // This does not display correctly in MSVC's debugger, it thinks it's a MOVD
- arg.operandReg = dest;
- Write8(0x66);
- arg.WriteRex(this, 64, 0);
- Write8(0x0f);
- Write8(0x6E);
- arg.WriteRest(this, 0);
-#else
- arg.operandReg = dest;
- Write8(0xF3);
- Write8(0x0f);
- Write8(0x7E);
- arg.WriteRest(this, 0);
-#endif
-}
-
-void XEmitter::MOVQ_xmm(OpArg arg, X64Reg src) {
- if (src > 7 || arg.IsSimpleReg()) {
- // Alternate encoding
- // This does not display correctly in MSVC's debugger, it thinks it's a MOVD
- arg.operandReg = src;
- Write8(0x66);
- arg.WriteRex(this, 64, 0);
- Write8(0x0f);
- Write8(0x7E);
- arg.WriteRest(this, 0);
- } else {
- arg.operandReg = src;
- arg.WriteRex(this, 0, 0);
- Write8(0x66);
- Write8(0x0f);
- Write8(0xD6);
- arg.WriteRest(this, 0);
- }
-}
-
-void XEmitter::WriteMXCSR(OpArg arg, int ext) {
- if (arg.IsImm() || arg.IsSimpleReg())
- ASSERT_MSG(0, "MXCSR - invalid operand");
-
- arg.operandReg = ext;
- arg.WriteRex(this, 0, 0);
- Write8(0x0F);
- Write8(0xAE);
- arg.WriteRest(this);
-}
-
-void XEmitter::STMXCSR(const OpArg& memloc) {
- WriteMXCSR(memloc, 3);
-}
-void XEmitter::LDMXCSR(const OpArg& memloc) {
- WriteMXCSR(memloc, 2);
-}
-
-void XEmitter::MOVNTDQ(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x66, sseMOVNTDQ, regOp, arg);
-}
-void XEmitter::MOVNTPS(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x00, sseMOVNTP, regOp, arg);
-}
-void XEmitter::MOVNTPD(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x66, sseMOVNTP, regOp, arg);
-}
-
-void XEmitter::ADDSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseADD, regOp, arg);
-}
-void XEmitter::ADDSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseADD, regOp, arg);
-}
-void XEmitter::SUBSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseSUB, regOp, arg);
-}
-void XEmitter::SUBSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseSUB, regOp, arg);
-}
-void XEmitter::CMPSS(X64Reg regOp, const OpArg& arg, u8 compare) {
- WriteSSEOp(0xF3, sseCMP, regOp, arg, 1);
- Write8(compare);
-}
-void XEmitter::CMPSD(X64Reg regOp, const OpArg& arg, u8 compare) {
- WriteSSEOp(0xF2, sseCMP, regOp, arg, 1);
- Write8(compare);
-}
-void XEmitter::MULSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseMUL, regOp, arg);
-}
-void XEmitter::MULSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseMUL, regOp, arg);
-}
-void XEmitter::DIVSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseDIV, regOp, arg);
-}
-void XEmitter::DIVSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseDIV, regOp, arg);
-}
-void XEmitter::MINSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseMIN, regOp, arg);
-}
-void XEmitter::MINSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseMIN, regOp, arg);
-}
-void XEmitter::MAXSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseMAX, regOp, arg);
-}
-void XEmitter::MAXSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseMAX, regOp, arg);
-}
-void XEmitter::SQRTSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseSQRT, regOp, arg);
-}
-void XEmitter::SQRTSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseSQRT, regOp, arg);
-}
-void XEmitter::RCPSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseRCP, regOp, arg);
-}
-void XEmitter::RSQRTSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseRSQRT, regOp, arg);
-}
-
-void XEmitter::ADDPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseADD, regOp, arg);
-}
-void XEmitter::ADDPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseADD, regOp, arg);
-}
-void XEmitter::SUBPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseSUB, regOp, arg);
-}
-void XEmitter::SUBPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseSUB, regOp, arg);
-}
-void XEmitter::CMPPS(X64Reg regOp, const OpArg& arg, u8 compare) {
- WriteSSEOp(0x00, sseCMP, regOp, arg, 1);
- Write8(compare);
-}
-void XEmitter::CMPPD(X64Reg regOp, const OpArg& arg, u8 compare) {
- WriteSSEOp(0x66, sseCMP, regOp, arg, 1);
- Write8(compare);
-}
-void XEmitter::ANDPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseAND, regOp, arg);
-}
-void XEmitter::ANDPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseAND, regOp, arg);
-}
-void XEmitter::ANDNPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseANDN, regOp, arg);
-}
-void XEmitter::ANDNPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseANDN, regOp, arg);
-}
-void XEmitter::ORPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseOR, regOp, arg);
-}
-void XEmitter::ORPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseOR, regOp, arg);
-}
-void XEmitter::XORPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseXOR, regOp, arg);
-}
-void XEmitter::XORPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseXOR, regOp, arg);
-}
-void XEmitter::MULPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseMUL, regOp, arg);
-}
-void XEmitter::MULPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMUL, regOp, arg);
-}
-void XEmitter::DIVPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseDIV, regOp, arg);
-}
-void XEmitter::DIVPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseDIV, regOp, arg);
-}
-void XEmitter::MINPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseMIN, regOp, arg);
-}
-void XEmitter::MINPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMIN, regOp, arg);
-}
-void XEmitter::MAXPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseMAX, regOp, arg);
-}
-void XEmitter::MAXPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMAX, regOp, arg);
-}
-void XEmitter::SQRTPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseSQRT, regOp, arg);
-}
-void XEmitter::SQRTPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseSQRT, regOp, arg);
-}
-void XEmitter::RCPPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseRCP, regOp, arg);
-}
-void XEmitter::RSQRTPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseRSQRT, regOp, arg);
-}
-void XEmitter::SHUFPS(X64Reg regOp, const OpArg& arg, u8 shuffle) {
- WriteSSEOp(0x00, sseSHUF, regOp, arg, 1);
- Write8(shuffle);
-}
-void XEmitter::SHUFPD(X64Reg regOp, const OpArg& arg, u8 shuffle) {
- WriteSSEOp(0x66, sseSHUF, regOp, arg, 1);
- Write8(shuffle);
-}
-
-void XEmitter::HADDPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseHADD, regOp, arg);
-}
-
-void XEmitter::COMISS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseCOMIS, regOp, arg);
-} // weird that these should be packed
-void XEmitter::COMISD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseCOMIS, regOp, arg);
-} // ordered
-void XEmitter::UCOMISS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseUCOMIS, regOp, arg);
-} // unordered
-void XEmitter::UCOMISD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseUCOMIS, regOp, arg);
-}
-
-void XEmitter::MOVAPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseMOVAPfromRM, regOp, arg);
-}
-void XEmitter::MOVAPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMOVAPfromRM, regOp, arg);
-}
-void XEmitter::MOVAPS(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x00, sseMOVAPtoRM, regOp, arg);
-}
-void XEmitter::MOVAPD(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x66, sseMOVAPtoRM, regOp, arg);
-}
-
-void XEmitter::MOVUPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseMOVUPfromRM, regOp, arg);
-}
-void XEmitter::MOVUPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMOVUPfromRM, regOp, arg);
-}
-void XEmitter::MOVUPS(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x00, sseMOVUPtoRM, regOp, arg);
-}
-void XEmitter::MOVUPD(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x66, sseMOVUPtoRM, regOp, arg);
-}
-
-void XEmitter::MOVDQA(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMOVDQfromRM, regOp, arg);
-}
-void XEmitter::MOVDQA(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x66, sseMOVDQtoRM, regOp, arg);
-}
-void XEmitter::MOVDQU(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseMOVDQfromRM, regOp, arg);
-}
-void XEmitter::MOVDQU(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0xF3, sseMOVDQtoRM, regOp, arg);
-}
-
-void XEmitter::MOVSS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, sseMOVUPfromRM, regOp, arg);
-}
-void XEmitter::MOVSD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, sseMOVUPfromRM, regOp, arg);
-}
-void XEmitter::MOVSS(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0xF3, sseMOVUPtoRM, regOp, arg);
-}
-void XEmitter::MOVSD(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0xF2, sseMOVUPtoRM, regOp, arg);
-}
-
-void XEmitter::MOVLPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseMOVLPfromRM, regOp, arg);
-}
-void XEmitter::MOVLPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMOVLPfromRM, regOp, arg);
-}
-void XEmitter::MOVLPS(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x00, sseMOVLPtoRM, regOp, arg);
-}
-void XEmitter::MOVLPD(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x66, sseMOVLPtoRM, regOp, arg);
-}
-
-void XEmitter::MOVHPS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, sseMOVHPfromRM, regOp, arg);
-}
-void XEmitter::MOVHPD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, sseMOVHPfromRM, regOp, arg);
-}
-void XEmitter::MOVHPS(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x00, sseMOVHPtoRM, regOp, arg);
-}
-void XEmitter::MOVHPD(const OpArg& arg, X64Reg regOp) {
- WriteSSEOp(0x66, sseMOVHPtoRM, regOp, arg);
-}
-
-void XEmitter::MOVHLPS(X64Reg regOp1, X64Reg regOp2) {
- WriteSSEOp(0x00, sseMOVHLPS, regOp1, R(regOp2));
-}
-void XEmitter::MOVLHPS(X64Reg regOp1, X64Reg regOp2) {
- WriteSSEOp(0x00, sseMOVLHPS, regOp1, R(regOp2));
-}
-
-void XEmitter::CVTPS2PD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, 0x5A, regOp, arg);
-}
-void XEmitter::CVTPD2PS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, 0x5A, regOp, arg);
-}
-
-void XEmitter::CVTSD2SS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, 0x5A, regOp, arg);
-}
-void XEmitter::CVTSS2SD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, 0x5A, regOp, arg);
-}
-void XEmitter::CVTSD2SI(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, 0x2D, regOp, arg);
-}
-void XEmitter::CVTSS2SI(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, 0x2D, regOp, arg);
-}
-void XEmitter::CVTSI2SD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, 0x2A, regOp, arg);
-}
-void XEmitter::CVTSI2SS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, 0x2A, regOp, arg);
-}
-
-void XEmitter::CVTDQ2PD(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, 0xE6, regOp, arg);
-}
-void XEmitter::CVTDQ2PS(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x00, 0x5B, regOp, arg);
-}
-void XEmitter::CVTPD2DQ(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, 0xE6, regOp, arg);
-}
-void XEmitter::CVTPS2DQ(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, 0x5B, regOp, arg);
-}
-
-void XEmitter::CVTTSD2SI(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF2, 0x2C, regOp, arg);
-}
-void XEmitter::CVTTSS2SI(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, 0x2C, regOp, arg);
-}
-void XEmitter::CVTTPS2DQ(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0xF3, 0x5B, regOp, arg);
-}
-void XEmitter::CVTTPD2DQ(X64Reg regOp, const OpArg& arg) {
- WriteSSEOp(0x66, 0xE6, regOp, arg);
-}
-
-void XEmitter::MASKMOVDQU(X64Reg dest, X64Reg src) {
- WriteSSEOp(0x66, sseMASKMOVDQU, dest, R(src));
-}
-
-void XEmitter::MOVMSKPS(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x00, 0x50, dest, arg);
-}
-void XEmitter::MOVMSKPD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x50, dest, arg);
-}
-
-void XEmitter::LDDQU(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0xF2, sseLDDQU, dest, arg);
-} // For integer data only
-
-// THESE TWO ARE UNTESTED.
-void XEmitter::UNPCKLPS(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x00, 0x14, dest, arg);
-}
-void XEmitter::UNPCKHPS(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x00, 0x15, dest, arg);
-}
-
-void XEmitter::UNPCKLPD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x14, dest, arg);
-}
-void XEmitter::UNPCKHPD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x15, dest, arg);
-}
-
-void XEmitter::MOVDDUP(X64Reg regOp, const OpArg& arg) {
- if (Common::GetCPUCaps().sse3) {
- WriteSSEOp(0xF2, 0x12, regOp, arg); // SSE3 movddup
- } else {
- // Simulate this instruction with SSE2 instructions
- if (!arg.IsSimpleReg(regOp))
- MOVSD(regOp, arg);
- UNPCKLPD(regOp, R(regOp));
- }
-}
-
-// There are a few more left
-
-// Also some integer instructions are missing
-void XEmitter::PACKSSDW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x6B, dest, arg);
-}
-void XEmitter::PACKSSWB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x63, dest, arg);
-}
-void XEmitter::PACKUSWB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x67, dest, arg);
-}
-
-void XEmitter::PUNPCKLBW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x60, dest, arg);
-}
-void XEmitter::PUNPCKLWD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x61, dest, arg);
-}
-void XEmitter::PUNPCKLDQ(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x62, dest, arg);
-}
-void XEmitter::PUNPCKLQDQ(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x6C, dest, arg);
-}
-
-void XEmitter::PSRLW(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x71, (X64Reg)2, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSRLD(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x72, (X64Reg)2, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSRLQ(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x73, (X64Reg)2, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSRLQ(X64Reg reg, const OpArg& arg) {
- WriteSSEOp(0x66, 0xd3, reg, arg);
-}
-
-void XEmitter::PSRLDQ(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x73, (X64Reg)3, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSLLW(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x71, (X64Reg)6, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSLLD(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x72, (X64Reg)6, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSLLQ(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x73, (X64Reg)6, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSLLDQ(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x73, (X64Reg)7, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSRAW(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x71, (X64Reg)4, R(reg));
- Write8(shift);
-}
-
-void XEmitter::PSRAD(X64Reg reg, int shift) {
- WriteSSEOp(0x66, 0x72, (X64Reg)4, R(reg));
- Write8(shift);
-}
-
-void XEmitter::WriteSSSE3Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) {
- if (!Common::GetCPUCaps().ssse3)
- ASSERT_MSG(0, "Trying to use SSSE3 on a system that doesn't support it. Bad programmer.");
- WriteSSEOp(opPrefix, op, regOp, arg, extrabytes);
-}
-
-void XEmitter::WriteSSE41Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes) {
- if (!Common::GetCPUCaps().sse4_1)
- ASSERT_MSG(0, "Trying to use SSE4.1 on a system that doesn't support it. Bad programmer.");
- WriteSSEOp(opPrefix, op, regOp, arg, extrabytes);
-}
-
-void XEmitter::PSHUFB(X64Reg dest, const OpArg& arg) {
- WriteSSSE3Op(0x66, 0x3800, dest, arg);
-}
-void XEmitter::PTEST(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3817, dest, arg);
-}
-void XEmitter::PACKUSDW(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x382b, dest, arg);
-}
-void XEmitter::DPPS(X64Reg dest, const OpArg& arg, u8 mask) {
- WriteSSE41Op(0x66, 0x3A40, dest, arg, 1);
- Write8(mask);
-}
-
-void XEmitter::PMINSB(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3838, dest, arg);
-}
-void XEmitter::PMINSD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3839, dest, arg);
-}
-void XEmitter::PMINUW(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x383a, dest, arg);
-}
-void XEmitter::PMINUD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x383b, dest, arg);
-}
-void XEmitter::PMAXSB(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x383c, dest, arg);
-}
-void XEmitter::PMAXSD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x383d, dest, arg);
-}
-void XEmitter::PMAXUW(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x383e, dest, arg);
-}
-void XEmitter::PMAXUD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x383f, dest, arg);
-}
-
-void XEmitter::PMOVSXBW(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3820, dest, arg);
-}
-void XEmitter::PMOVSXBD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3821, dest, arg);
-}
-void XEmitter::PMOVSXBQ(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3822, dest, arg);
-}
-void XEmitter::PMOVSXWD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3823, dest, arg);
-}
-void XEmitter::PMOVSXWQ(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3824, dest, arg);
-}
-void XEmitter::PMOVSXDQ(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3825, dest, arg);
-}
-void XEmitter::PMOVZXBW(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3830, dest, arg);
-}
-void XEmitter::PMOVZXBD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3831, dest, arg);
-}
-void XEmitter::PMOVZXBQ(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3832, dest, arg);
-}
-void XEmitter::PMOVZXWD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3833, dest, arg);
-}
-void XEmitter::PMOVZXWQ(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3834, dest, arg);
-}
-void XEmitter::PMOVZXDQ(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3835, dest, arg);
-}
-
-void XEmitter::PBLENDVB(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3810, dest, arg);
-}
-void XEmitter::BLENDVPS(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3814, dest, arg);
-}
-void XEmitter::BLENDVPD(X64Reg dest, const OpArg& arg) {
- WriteSSE41Op(0x66, 0x3815, dest, arg);
-}
-void XEmitter::BLENDPS(X64Reg dest, const OpArg& arg, u8 blend) {
- WriteSSE41Op(0x66, 0x3A0C, dest, arg, 1);
- Write8(blend);
-}
-void XEmitter::BLENDPD(X64Reg dest, const OpArg& arg, u8 blend) {
- WriteSSE41Op(0x66, 0x3A0D, dest, arg, 1);
- Write8(blend);
-}
-
-void XEmitter::ROUNDSS(X64Reg dest, const OpArg& arg, u8 mode) {
- WriteSSE41Op(0x66, 0x3A0A, dest, arg, 1);
- Write8(mode);
-}
-void XEmitter::ROUNDSD(X64Reg dest, const OpArg& arg, u8 mode) {
- WriteSSE41Op(0x66, 0x3A0B, dest, arg, 1);
- Write8(mode);
-}
-void XEmitter::ROUNDPS(X64Reg dest, const OpArg& arg, u8 mode) {
- WriteSSE41Op(0x66, 0x3A08, dest, arg, 1);
- Write8(mode);
-}
-void XEmitter::ROUNDPD(X64Reg dest, const OpArg& arg, u8 mode) {
- WriteSSE41Op(0x66, 0x3A09, dest, arg, 1);
- Write8(mode);
-}
-
-void XEmitter::PAND(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xDB, dest, arg);
-}
-void XEmitter::PANDN(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xDF, dest, arg);
-}
-void XEmitter::PXOR(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xEF, dest, arg);
-}
-void XEmitter::POR(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xEB, dest, arg);
-}
-
-void XEmitter::PADDB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xFC, dest, arg);
-}
-void XEmitter::PADDW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xFD, dest, arg);
-}
-void XEmitter::PADDD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xFE, dest, arg);
-}
-void XEmitter::PADDQ(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xD4, dest, arg);
-}
-
-void XEmitter::PADDSB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xEC, dest, arg);
-}
-void XEmitter::PADDSW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xED, dest, arg);
-}
-void XEmitter::PADDUSB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xDC, dest, arg);
-}
-void XEmitter::PADDUSW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xDD, dest, arg);
-}
-
-void XEmitter::PSUBB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xF8, dest, arg);
-}
-void XEmitter::PSUBW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xF9, dest, arg);
-}
-void XEmitter::PSUBD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xFA, dest, arg);
-}
-void XEmitter::PSUBQ(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xFB, dest, arg);
-}
-
-void XEmitter::PSUBSB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xE8, dest, arg);
-}
-void XEmitter::PSUBSW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xE9, dest, arg);
-}
-void XEmitter::PSUBUSB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xD8, dest, arg);
-}
-void XEmitter::PSUBUSW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xD9, dest, arg);
-}
-
-void XEmitter::PAVGB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xE0, dest, arg);
-}
-void XEmitter::PAVGW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xE3, dest, arg);
-}
-
-void XEmitter::PCMPEQB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x74, dest, arg);
-}
-void XEmitter::PCMPEQW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x75, dest, arg);
-}
-void XEmitter::PCMPEQD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x76, dest, arg);
-}
-
-void XEmitter::PCMPGTB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x64, dest, arg);
-}
-void XEmitter::PCMPGTW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x65, dest, arg);
-}
-void XEmitter::PCMPGTD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0x66, dest, arg);
-}
-
-void XEmitter::PEXTRW(X64Reg dest, const OpArg& arg, u8 subreg) {
- WriteSSEOp(0x66, 0xC5, dest, arg, 1);
- Write8(subreg);
-}
-void XEmitter::PINSRW(X64Reg dest, const OpArg& arg, u8 subreg) {
- WriteSSEOp(0x66, 0xC4, dest, arg, 1);
- Write8(subreg);
-}
-
-void XEmitter::PMADDWD(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xF5, dest, arg);
-}
-void XEmitter::PSADBW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xF6, dest, arg);
-}
-
-void XEmitter::PMAXSW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xEE, dest, arg);
-}
-void XEmitter::PMAXUB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xDE, dest, arg);
-}
-void XEmitter::PMINSW(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xEA, dest, arg);
-}
-void XEmitter::PMINUB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xDA, dest, arg);
-}
-
-void XEmitter::PMOVMSKB(X64Reg dest, const OpArg& arg) {
- WriteSSEOp(0x66, 0xD7, dest, arg);
-}
-void XEmitter::PSHUFD(X64Reg regOp, const OpArg& arg, u8 shuffle) {
- WriteSSEOp(0x66, 0x70, regOp, arg, 1);
- Write8(shuffle);
-}
-void XEmitter::PSHUFLW(X64Reg regOp, const OpArg& arg, u8 shuffle) {
- WriteSSEOp(0xF2, 0x70, regOp, arg, 1);
- Write8(shuffle);
-}
-void XEmitter::PSHUFHW(X64Reg regOp, const OpArg& arg, u8 shuffle) {
- WriteSSEOp(0xF3, 0x70, regOp, arg, 1);
- Write8(shuffle);
-}
-
-// VEX
-void XEmitter::VADDSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0xF2, sseADD, regOp1, regOp2, arg);
-}
-void XEmitter::VSUBSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0xF2, sseSUB, regOp1, regOp2, arg);
-}
-void XEmitter::VMULSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0xF2, sseMUL, regOp1, regOp2, arg);
-}
-void XEmitter::VDIVSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0xF2, sseDIV, regOp1, regOp2, arg);
-}
-void XEmitter::VADDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseADD, regOp1, regOp2, arg);
-}
-void XEmitter::VSUBPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseSUB, regOp1, regOp2, arg);
-}
-void XEmitter::VMULPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseMUL, regOp1, regOp2, arg);
-}
-void XEmitter::VDIVPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseDIV, regOp1, regOp2, arg);
-}
-void XEmitter::VSQRTSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0xF2, sseSQRT, regOp1, regOp2, arg);
-}
-void XEmitter::VSHUFPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg, u8 shuffle) {
- WriteAVXOp(0x66, sseSHUF, regOp1, regOp2, arg, 1);
- Write8(shuffle);
-}
-void XEmitter::VUNPCKLPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x14, regOp1, regOp2, arg);
-}
-void XEmitter::VUNPCKHPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x15, regOp1, regOp2, arg);
-}
-
-void XEmitter::VANDPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x00, sseAND, regOp1, regOp2, arg);
-}
-void XEmitter::VANDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseAND, regOp1, regOp2, arg);
-}
-void XEmitter::VANDNPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x00, sseANDN, regOp1, regOp2, arg);
-}
-void XEmitter::VANDNPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseANDN, regOp1, regOp2, arg);
-}
-void XEmitter::VORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x00, sseOR, regOp1, regOp2, arg);
-}
-void XEmitter::VORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseOR, regOp1, regOp2, arg);
-}
-void XEmitter::VXORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x00, sseXOR, regOp1, regOp2, arg);
-}
-void XEmitter::VXORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, sseXOR, regOp1, regOp2, arg);
-}
-
-void XEmitter::VPAND(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0xDB, regOp1, regOp2, arg);
-}
-void XEmitter::VPANDN(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0xDF, regOp1, regOp2, arg);
-}
-void XEmitter::VPOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0xEB, regOp1, regOp2, arg);
-}
-void XEmitter::VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0xEF, regOp1, regOp2, arg);
-}
-
-void XEmitter::VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3898, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A8, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B8, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3898, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A8, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B8, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3899, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A9, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B9, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3899, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A9, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B9, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389A, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AA, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BA, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389A, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AA, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BA, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389B, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AB, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BB, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389B, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AB, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BB, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389C, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AC, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BC, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389C, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AC, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BC, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389D, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AD, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BD, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389D, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AD, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BD, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389E, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AE, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BE, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389E, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AE, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BE, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389F, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AF, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BF, regOp1, regOp2, arg);
-}
-void XEmitter::VFNMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x389F, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38AF, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFNMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38BF, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADDSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3896, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADDSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A6, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADDSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B6, regOp1, regOp2, arg);
-}
-void XEmitter::VFMADDSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3896, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADDSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A6, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMADDSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B6, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUBADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3897, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUBADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A7, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUBADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B7, regOp1, regOp2, arg);
-}
-void XEmitter::VFMSUBADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x3897, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUBADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38A7, regOp1, regOp2, arg, 1);
-}
-void XEmitter::VFMSUBADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteAVXOp(0x66, 0x38B7, regOp1, regOp2, arg, 1);
-}
-
-void XEmitter::SARX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) {
- WriteBMI2Op(bits, 0xF3, 0x38F7, regOp1, regOp2, arg);
-}
-void XEmitter::SHLX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) {
- WriteBMI2Op(bits, 0x66, 0x38F7, regOp1, regOp2, arg);
-}
-void XEmitter::SHRX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) {
- WriteBMI2Op(bits, 0xF2, 0x38F7, regOp1, regOp2, arg);
-}
-void XEmitter::RORX(int bits, X64Reg regOp, const OpArg& arg, u8 rotate) {
- WriteBMI2Op(bits, 0xF2, 0x3AF0, regOp, INVALID_REG, arg, 1);
- Write8(rotate);
-}
-void XEmitter::PEXT(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteBMI2Op(bits, 0xF3, 0x38F5, regOp1, regOp2, arg);
-}
-void XEmitter::PDEP(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteBMI2Op(bits, 0xF2, 0x38F5, regOp1, regOp2, arg);
-}
-void XEmitter::MULX(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteBMI2Op(bits, 0xF2, 0x38F6, regOp2, regOp1, arg);
-}
-void XEmitter::BZHI(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) {
- WriteBMI2Op(bits, 0x00, 0x38F5, regOp1, regOp2, arg);
-}
-void XEmitter::BLSR(int bits, X64Reg regOp, const OpArg& arg) {
- WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x1, regOp, arg);
-}
-void XEmitter::BLSMSK(int bits, X64Reg regOp, const OpArg& arg) {
- WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x2, regOp, arg);
-}
-void XEmitter::BLSI(int bits, X64Reg regOp, const OpArg& arg) {
- WriteBMI1Op(bits, 0x00, 0x38F3, (X64Reg)0x3, regOp, arg);
-}
-void XEmitter::BEXTR(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2) {
- WriteBMI1Op(bits, 0x00, 0x38F7, regOp1, regOp2, arg);
-}
-void XEmitter::ANDN(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg) {
- WriteBMI1Op(bits, 0x00, 0x38F2, regOp1, regOp2, arg);
-}
-
-// Prefixes
-
-void XEmitter::LOCK() {
- Write8(0xF0);
-}
-void XEmitter::REP() {
- Write8(0xF3);
-}
-void XEmitter::REPNE() {
- Write8(0xF2);
-}
-void XEmitter::FSOverride() {
- Write8(0x64);
-}
-void XEmitter::GSOverride() {
- Write8(0x65);
-}
-
-void XEmitter::FWAIT() {
- Write8(0x9B);
-}
-
-// TODO: make this more generic
-void XEmitter::WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, const OpArg& arg) {
- int mf = 0;
- ASSERT_MSG(!(bits == 80 && op_80b == floatINVALID),
- "WriteFloatLoadStore: 80 bits not supported for this instruction");
- switch (bits) {
- case 32:
- mf = 0;
- break;
- case 64:
- mf = 4;
- break;
- case 80:
- mf = 2;
- break;
- default:
- ASSERT_MSG(0, "WriteFloatLoadStore: invalid bits (should be 32/64/80)");
- }
- Write8(0xd9 | mf);
- // x87 instructions use the reg field of the ModR/M byte as opcode:
- if (bits == 80)
- op = op_80b;
- arg.WriteRest(this, 0, (X64Reg)op);
-}
-
-void XEmitter::FLD(int bits, const OpArg& src) {
- WriteFloatLoadStore(bits, floatLD, floatLD80, src);
-}
-void XEmitter::FST(int bits, const OpArg& dest) {
- WriteFloatLoadStore(bits, floatST, floatINVALID, dest);
-}
-void XEmitter::FSTP(int bits, const OpArg& dest) {
- WriteFloatLoadStore(bits, floatSTP, floatSTP80, dest);
-}
-void XEmitter::FNSTSW_AX() {
- Write8(0xDF);
- Write8(0xE0);
-}
-
-void XEmitter::RDTSC() {
- Write8(0x0F);
- Write8(0x31);
-}
-
-void XCodeBlock::PoisonMemory() {
- // x86/64: 0xCC = breakpoint
- memset(region, 0xCC, region_size);
-}
-}
diff --git a/src/common/x64/emitter.h b/src/common/x64/emitter.h
deleted file mode 100644
index 7d7cdde16..000000000
--- a/src/common/x64/emitter.h
+++ /dev/null
@@ -1,1206 +0,0 @@
-// Copyright (C) 2003 Dolphin Project.
-
-// This program is free software: you can redistribute it and/or modify
-// it under the terms of the GNU General Public License as published by
-// the Free Software Foundation, version 2.0 or later versions.
-
-// This program is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU General Public License 2.0 for more details.
-
-// A copy of the GPL 2.0 should have been included with the program.
-// If not, see http://www.gnu.org/licenses/
-
-// Official SVN repository and contact information can be found at
-// http://code.google.com/p/dolphin-emu/
-
-#pragma once
-
-#include <cstddef>
-#include "common/assert.h"
-#include "common/bit_set.h"
-#include "common/code_block.h"
-#include "common/common_types.h"
-
-#if defined(ARCHITECTURE_x86_64) && !defined(_ARCH_64)
-#define _ARCH_64
-#endif
-
-#ifdef _ARCH_64
-#define PTRBITS 64
-#else
-#define PTRBITS 32
-#endif
-
-namespace Gen {
-
-enum X64Reg {
- EAX = 0,
- EBX = 3,
- ECX = 1,
- EDX = 2,
- ESI = 6,
- EDI = 7,
- EBP = 5,
- ESP = 4,
-
- RAX = 0,
- RBX = 3,
- RCX = 1,
- RDX = 2,
- RSI = 6,
- RDI = 7,
- RBP = 5,
- RSP = 4,
- R8 = 8,
- R9 = 9,
- R10 = 10,
- R11 = 11,
- R12 = 12,
- R13 = 13,
- R14 = 14,
- R15 = 15,
-
- AL = 0,
- BL = 3,
- CL = 1,
- DL = 2,
- SIL = 6,
- DIL = 7,
- BPL = 5,
- SPL = 4,
- AH = 0x104,
- BH = 0x107,
- CH = 0x105,
- DH = 0x106,
-
- AX = 0,
- BX = 3,
- CX = 1,
- DX = 2,
- SI = 6,
- DI = 7,
- BP = 5,
- SP = 4,
-
- XMM0 = 0,
- XMM1,
- XMM2,
- XMM3,
- XMM4,
- XMM5,
- XMM6,
- XMM7,
- XMM8,
- XMM9,
- XMM10,
- XMM11,
- XMM12,
- XMM13,
- XMM14,
- XMM15,
-
- YMM0 = 0,
- YMM1,
- YMM2,
- YMM3,
- YMM4,
- YMM5,
- YMM6,
- YMM7,
- YMM8,
- YMM9,
- YMM10,
- YMM11,
- YMM12,
- YMM13,
- YMM14,
- YMM15,
-
- INVALID_REG = 0xFFFFFFFF
-};
-
-enum CCFlags {
- CC_O = 0,
- CC_NO = 1,
- CC_B = 2,
- CC_C = 2,
- CC_NAE = 2,
- CC_NB = 3,
- CC_NC = 3,
- CC_AE = 3,
- CC_Z = 4,
- CC_E = 4,
- CC_NZ = 5,
- CC_NE = 5,
- CC_BE = 6,
- CC_NA = 6,
- CC_NBE = 7,
- CC_A = 7,
- CC_S = 8,
- CC_NS = 9,
- CC_P = 0xA,
- CC_PE = 0xA,
- CC_NP = 0xB,
- CC_PO = 0xB,
- CC_L = 0xC,
- CC_NGE = 0xC,
- CC_NL = 0xD,
- CC_GE = 0xD,
- CC_LE = 0xE,
- CC_NG = 0xE,
- CC_NLE = 0xF,
- CC_G = 0xF
-};
-
-enum {
- NUMGPRs = 16,
- NUMXMMs = 16,
-};
-
-enum {
- SCALE_NONE = 0,
- SCALE_1 = 1,
- SCALE_2 = 2,
- SCALE_4 = 4,
- SCALE_8 = 8,
- SCALE_ATREG = 16,
- // SCALE_NOBASE_1 is not supported and can be replaced with SCALE_ATREG
- SCALE_NOBASE_2 = 34,
- SCALE_NOBASE_4 = 36,
- SCALE_NOBASE_8 = 40,
- SCALE_RIP = 0xFF,
- SCALE_IMM8 = 0xF0,
- SCALE_IMM16 = 0xF1,
- SCALE_IMM32 = 0xF2,
- SCALE_IMM64 = 0xF3,
-};
-
-enum NormalOp {
- nrmADD,
- nrmADC,
- nrmSUB,
- nrmSBB,
- nrmAND,
- nrmOR,
- nrmXOR,
- nrmMOV,
- nrmTEST,
- nrmCMP,
- nrmXCHG,
-};
-
-enum {
- CMP_EQ = 0,
- CMP_LT = 1,
- CMP_LE = 2,
- CMP_UNORD = 3,
- CMP_NEQ = 4,
- CMP_NLT = 5,
- CMP_NLE = 6,
- CMP_ORD = 7,
-};
-
-enum FloatOp {
- floatLD = 0,
- floatST = 2,
- floatSTP = 3,
- floatLD80 = 5,
- floatSTP80 = 7,
-
- floatINVALID = -1,
-};
-
-enum FloatRound {
- FROUND_NEAREST = 0,
- FROUND_FLOOR = 1,
- FROUND_CEIL = 2,
- FROUND_ZERO = 3,
- FROUND_MXCSR = 4,
-
- FROUND_RAISE_PRECISION = 0,
- FROUND_IGNORE_PRECISION = 8,
-};
-
-class XEmitter;
-
-// RIP addressing does not benefit from micro op fusion on Core arch
-struct OpArg {
- friend class XEmitter;
-
- constexpr OpArg() = default; // dummy op arg, used for storage
- constexpr OpArg(u64 offset_, int scale_, X64Reg rmReg = RAX, X64Reg scaledReg = RAX)
- : scale(static_cast<u8>(scale_)), offsetOrBaseReg(static_cast<u16>(rmReg)),
- indexReg(static_cast<u16>(scaledReg)), offset(offset_) {}
-
- constexpr bool operator==(const OpArg& b) const {
- return operandReg == b.operandReg && scale == b.scale &&
- offsetOrBaseReg == b.offsetOrBaseReg && indexReg == b.indexReg && offset == b.offset;
- }
-
- void WriteRex(XEmitter* emit, int opBits, int bits, int customOp = -1) const;
- void WriteVex(XEmitter* emit, X64Reg regOp1, X64Reg regOp2, int L, int pp, int mmmmm,
- int W = 0) const;
- void WriteRest(XEmitter* emit, int extraBytes = 0, X64Reg operandReg = INVALID_REG,
- bool warn_64bit_offset = true) const;
- void WriteSingleByteOp(XEmitter* emit, u8 op, X64Reg operandReg, int bits);
- void WriteNormalOp(XEmitter* emit, bool toRM, NormalOp op, const OpArg& operand,
- int bits) const;
-
- constexpr bool IsImm() const {
- return scale == SCALE_IMM8 || scale == SCALE_IMM16 || scale == SCALE_IMM32 ||
- scale == SCALE_IMM64;
- }
- constexpr bool IsSimpleReg() const {
- return scale == SCALE_NONE;
- }
- constexpr bool IsSimpleReg(X64Reg reg) const {
- return IsSimpleReg() && GetSimpleReg() == reg;
- }
-
- int GetImmBits() const {
- switch (scale) {
- case SCALE_IMM8:
- return 8;
- case SCALE_IMM16:
- return 16;
- case SCALE_IMM32:
- return 32;
- case SCALE_IMM64:
- return 64;
- default:
- return -1;
- }
- }
-
- void SetImmBits(int bits) {
- switch (bits) {
- case 8:
- scale = SCALE_IMM8;
- break;
- case 16:
- scale = SCALE_IMM16;
- break;
- case 32:
- scale = SCALE_IMM32;
- break;
- case 64:
- scale = SCALE_IMM64;
- break;
- }
- }
-
- constexpr X64Reg GetSimpleReg() const {
- return scale == SCALE_NONE ? static_cast<X64Reg>(offsetOrBaseReg) : INVALID_REG;
- }
-
- constexpr u32 GetImmValue() const {
- return static_cast<u32>(offset);
- }
-
- // For loops.
- void IncreaseOffset(int sz) {
- offset += sz;
- }
-
-private:
- u8 scale = 0;
- u16 offsetOrBaseReg = 0;
- u16 indexReg = 0;
- u64 offset = 0; // use RIP-relative as much as possible - 64-bit immediates are not available.
- u16 operandReg = 0;
-};
-
-template <typename T>
-inline OpArg M(const T* ptr) {
- return OpArg(reinterpret_cast<u64>(ptr), static_cast<int>(SCALE_RIP));
-}
-constexpr OpArg R(X64Reg value) {
- return OpArg(0, SCALE_NONE, value);
-}
-constexpr OpArg MatR(X64Reg value) {
- return OpArg(0, SCALE_ATREG, value);
-}
-
-constexpr OpArg MDisp(X64Reg value, int offset) {
- return OpArg(static_cast<u32>(offset), SCALE_ATREG, value);
-}
-
-constexpr OpArg MComplex(X64Reg base, X64Reg scaled, int scale, int offset) {
- return OpArg(offset, scale, base, scaled);
-}
-
-constexpr OpArg MScaled(X64Reg scaled, int scale, int offset) {
- return scale == SCALE_1 ? OpArg(offset, SCALE_ATREG, scaled)
- : OpArg(offset, scale | 0x20, RAX, scaled);
-}
-
-constexpr OpArg MRegSum(X64Reg base, X64Reg offset) {
- return MComplex(base, offset, 1, 0);
-}
-
-constexpr OpArg Imm8(u8 imm) {
- return OpArg(imm, SCALE_IMM8);
-}
-constexpr OpArg Imm16(u16 imm) {
- return OpArg(imm, SCALE_IMM16);
-} // rarely used
-constexpr OpArg Imm32(u32 imm) {
- return OpArg(imm, SCALE_IMM32);
-}
-constexpr OpArg Imm64(u64 imm) {
- return OpArg(imm, SCALE_IMM64);
-}
-constexpr OpArg UImmAuto(u32 imm) {
- return OpArg(imm, imm >= 128 ? SCALE_IMM32 : SCALE_IMM8);
-}
-constexpr OpArg SImmAuto(s32 imm) {
- return OpArg(imm, (imm >= 128 || imm < -128) ? SCALE_IMM32 : SCALE_IMM8);
-}
-
-template <typename T>
-OpArg ImmPtr(const T* imm) {
-#ifdef _ARCH_64
- return Imm64(reinterpret_cast<u64>(imm));
-#else
- return Imm32(reinterpret_cast<u32>(imm));
-#endif
-}
-
-inline u32 PtrOffset(const void* ptr, const void* base) {
-#ifdef _ARCH_64
- s64 distance = (s64)ptr - (s64)base;
- if (distance >= 0x80000000LL || distance < -0x80000000LL) {
- ASSERT_MSG(0, "pointer offset out of range");
- return 0;
- }
-
- return (u32)distance;
-#else
- return (u32)ptr - (u32)base;
-#endif
-}
-
-// usage: int a[]; ARRAY_OFFSET(a,10)
-#define ARRAY_OFFSET(array, index) ((u32)((u64) & (array)[index] - (u64) & (array)[0]))
-// usage: struct {int e;} s; STRUCT_OFFSET(s,e)
-#define STRUCT_OFFSET(str, elem) ((u32)((u64) & (str).elem - (u64) & (str)))
-
-struct FixupBranch {
- u8* ptr;
- int type; // 0 = 8bit 1 = 32bit
-};
-
-enum SSECompare {
- EQ = 0,
- LT,
- LE,
- UNORD,
- NEQ,
- NLT,
- NLE,
- ORD,
-};
-
-class XEmitter {
- friend struct OpArg; // for Write8 etc
-private:
- u8* code;
- bool flags_locked;
-
- void CheckFlags();
-
- void Rex(int w, int r, int x, int b);
- void WriteSimple1Byte(int bits, u8 byte, X64Reg reg);
- void WriteSimple2Byte(int bits, u8 byte1, u8 byte2, X64Reg reg);
- void WriteMulDivType(int bits, OpArg src, int ext);
- void WriteBitSearchType(int bits, X64Reg dest, OpArg src, u8 byte2, bool rep = false);
- void WriteShift(int bits, OpArg dest, const OpArg& shift, int ext);
- void WriteBitTest(int bits, const OpArg& dest, const OpArg& index, int ext);
- void WriteMXCSR(OpArg arg, int ext);
- void WriteSSEOp(u8 opPrefix, u16 op, X64Reg regOp, OpArg arg, int extrabytes = 0);
- void WriteSSSE3Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0);
- void WriteSSE41Op(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0);
- void WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp, const OpArg& arg, int extrabytes = 0);
- void WriteAVXOp(u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg,
- int extrabytes = 0);
- void WriteVEXOp(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg,
- int extrabytes = 0);
- void WriteBMI1Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg,
- int extrabytes = 0);
- void WriteBMI2Op(int size, u8 opPrefix, u16 op, X64Reg regOp1, X64Reg regOp2, const OpArg& arg,
- int extrabytes = 0);
- void WriteFloatLoadStore(int bits, FloatOp op, FloatOp op_80b, const OpArg& arg);
- void WriteNormalOp(XEmitter* emit, int bits, NormalOp op, const OpArg& a1, const OpArg& a2);
-
- void ABI_CalculateFrameSize(BitSet32 mask, size_t rsp_alignment, size_t needed_frame_size,
- size_t* shadowp, size_t* subtractionp, size_t* xmm_offsetp);
-
-protected:
- void Write8(u8 value);
- void Write16(u16 value);
- void Write32(u32 value);
- void Write64(u64 value);
-
-public:
- XEmitter() {
- code = nullptr;
- flags_locked = false;
- }
- XEmitter(u8* code_ptr) {
- code = code_ptr;
- flags_locked = false;
- }
- virtual ~XEmitter() {}
-
- void WriteModRM(int mod, int rm, int reg);
- void WriteSIB(int scale, int index, int base);
-
- void SetCodePtr(u8* ptr);
- void ReserveCodeSpace(int bytes);
- const u8* AlignCode4();
- const u8* AlignCode16();
- const u8* AlignCodePage();
- const u8* GetCodePtr() const;
- u8* GetWritableCodePtr();
-
- void LockFlags() {
- flags_locked = true;
- }
- void UnlockFlags() {
- flags_locked = false;
- }
-
- // Looking for one of these? It's BANNED!! Some instructions are slow on modern CPU
- // INC, DEC, LOOP, LOOPNE, LOOPE, ENTER, LEAVE, XCHG, XLAT, REP MOVSB/MOVSD, REP SCASD + other
- // string instr.,
- // INC and DEC are slow on Intel Core, but not on AMD. They create a
- // false flag dependency because they only update a subset of the flags.
- // XCHG is SLOW and should be avoided.
-
- // Debug breakpoint
- void INT3();
-
- // Do nothing
- void NOP(size_t count = 1);
-
- // Save energy in wait-loops on P4 only. Probably not too useful.
- void PAUSE();
-
- // Flag control
- void STC();
- void CLC();
- void CMC();
-
- // These two can not be executed in 64-bit mode on early Intel 64-bit CPU:s, only on Core2 and
- // AMD!
- void LAHF(); // 3 cycle vector path
- void SAHF(); // direct path fast
-
- // Stack control
- void PUSH(X64Reg reg);
- void POP(X64Reg reg);
- void PUSH(int bits, const OpArg& reg);
- void POP(int bits, const OpArg& reg);
- void PUSHF();
- void POPF();
-
- // Flow control
- void RET();
- void RET_FAST();
- void UD2();
- FixupBranch J(bool force5bytes = false);
-
- void JMP(const u8* addr, bool force5Bytes = false);
- void JMPptr(const OpArg& arg);
- void JMPself(); // infinite loop!
-#ifdef CALL
-#undef CALL
-#endif
- void CALL(const void* fnptr);
- FixupBranch CALL();
- void CALLptr(OpArg arg);
-
- FixupBranch J_CC(CCFlags conditionCode, bool force5bytes = false);
- void J_CC(CCFlags conditionCode, const u8* addr, bool force5Bytes = false);
-
- void SetJumpTarget(const FixupBranch& branch);
- void SetJumpTarget(const FixupBranch& branch, const u8* target);
-
- void SETcc(CCFlags flag, OpArg dest);
- // Note: CMOV brings small if any benefit on current cpus.
- void CMOVcc(int bits, X64Reg dest, OpArg src, CCFlags flag);
-
- // Fences
- void LFENCE();
- void MFENCE();
- void SFENCE();
-
- // Bit scan
- void BSF(int bits, X64Reg dest, const OpArg& src); // Bottom bit to top bit
- void BSR(int bits, X64Reg dest, const OpArg& src); // Top bit to bottom bit
-
- // Cache control
- enum PrefetchLevel {
- PF_NTA, // Non-temporal (data used once and only once)
- PF_T0, // All cache levels
- PF_T1, // Levels 2+ (aliased to T0 on AMD)
- PF_T2, // Levels 3+ (aliased to T0 on AMD)
- };
- void PREFETCH(PrefetchLevel level, OpArg arg);
- void MOVNTI(int bits, const OpArg& dest, X64Reg src);
- void MOVNTDQ(const OpArg& arg, X64Reg regOp);
- void MOVNTPS(const OpArg& arg, X64Reg regOp);
- void MOVNTPD(const OpArg& arg, X64Reg regOp);
-
- // Multiplication / division
- void MUL(int bits, const OpArg& src); // UNSIGNED
- void IMUL(int bits, const OpArg& src); // SIGNED
- void IMUL(int bits, X64Reg regOp, const OpArg& src);
- void IMUL(int bits, X64Reg regOp, const OpArg& src, const OpArg& imm);
- void DIV(int bits, const OpArg& src);
- void IDIV(int bits, const OpArg& src);
-
- // Shift
- void ROL(int bits, const OpArg& dest, const OpArg& shift);
- void ROR(int bits, const OpArg& dest, const OpArg& shift);
- void RCL(int bits, const OpArg& dest, const OpArg& shift);
- void RCR(int bits, const OpArg& dest, const OpArg& shift);
- void SHL(int bits, const OpArg& dest, const OpArg& shift);
- void SHR(int bits, const OpArg& dest, const OpArg& shift);
- void SAR(int bits, const OpArg& dest, const OpArg& shift);
-
- // Bit Test
- void BT(int bits, const OpArg& dest, const OpArg& index);
- void BTS(int bits, const OpArg& dest, const OpArg& index);
- void BTR(int bits, const OpArg& dest, const OpArg& index);
- void BTC(int bits, const OpArg& dest, const OpArg& index);
-
- // Double-Precision Shift
- void SHRD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift);
- void SHLD(int bits, const OpArg& dest, const OpArg& src, const OpArg& shift);
-
- // Extend EAX into EDX in various ways
- void CWD(int bits = 16);
- void CDQ() {
- CWD(32);
- }
- void CQO() {
- CWD(64);
- }
- void CBW(int bits = 8);
- void CWDE() {
- CBW(16);
- }
- void CDQE() {
- CBW(32);
- }
-
- // Load effective address
- void LEA(int bits, X64Reg dest, OpArg src);
-
- // Integer arithmetic
- void NEG(int bits, const OpArg& src);
- void ADD(int bits, const OpArg& a1, const OpArg& a2);
- void ADC(int bits, const OpArg& a1, const OpArg& a2);
- void SUB(int bits, const OpArg& a1, const OpArg& a2);
- void SBB(int bits, const OpArg& a1, const OpArg& a2);
- void AND(int bits, const OpArg& a1, const OpArg& a2);
- void CMP(int bits, const OpArg& a1, const OpArg& a2);
-
- // Bit operations
- void NOT(int bits, const OpArg& src);
- void OR(int bits, const OpArg& a1, const OpArg& a2);
- void XOR(int bits, const OpArg& a1, const OpArg& a2);
- void MOV(int bits, const OpArg& a1, const OpArg& a2);
- void TEST(int bits, const OpArg& a1, const OpArg& a2);
-
- // Are these useful at all? Consider removing.
- void XCHG(int bits, const OpArg& a1, const OpArg& a2);
- void XCHG_AHAL();
-
- // Byte swapping (32 and 64-bit only).
- void BSWAP(int bits, X64Reg reg);
-
- // Sign/zero extension
- void MOVSX(int dbits, int sbits, X64Reg dest,
- OpArg src); // automatically uses MOVSXD if necessary
- void MOVZX(int dbits, int sbits, X64Reg dest, OpArg src);
-
- // Available only on Atom or >= Haswell so far. Test with GetCPUCaps().movbe.
- void MOVBE(int dbits, const OpArg& dest, const OpArg& src);
-
- // Available only on AMD >= Phenom or Intel >= Haswell
- void LZCNT(int bits, X64Reg dest, const OpArg& src);
- // Note: this one is actually part of BMI1
- void TZCNT(int bits, X64Reg dest, const OpArg& src);
-
- // WARNING - These two take 11-13 cycles and are VectorPath! (AMD64)
- void STMXCSR(const OpArg& memloc);
- void LDMXCSR(const OpArg& memloc);
-
- // Prefixes
- void LOCK();
- void REP();
- void REPNE();
- void FSOverride();
- void GSOverride();
-
- // x87
- enum x87StatusWordBits {
- x87_InvalidOperation = 0x1,
- x87_DenormalizedOperand = 0x2,
- x87_DivisionByZero = 0x4,
- x87_Overflow = 0x8,
- x87_Underflow = 0x10,
- x87_Precision = 0x20,
- x87_StackFault = 0x40,
- x87_ErrorSummary = 0x80,
- x87_C0 = 0x100,
- x87_C1 = 0x200,
- x87_C2 = 0x400,
- x87_TopOfStack = 0x2000 | 0x1000 | 0x800,
- x87_C3 = 0x4000,
- x87_FPUBusy = 0x8000,
- };
-
- void FLD(int bits, const OpArg& src);
- void FST(int bits, const OpArg& dest);
- void FSTP(int bits, const OpArg& dest);
- void FNSTSW_AX();
- void FWAIT();
-
- // SSE/SSE2: Floating point arithmetic
- void ADDSS(X64Reg regOp, const OpArg& arg);
- void ADDSD(X64Reg regOp, const OpArg& arg);
- void SUBSS(X64Reg regOp, const OpArg& arg);
- void SUBSD(X64Reg regOp, const OpArg& arg);
- void MULSS(X64Reg regOp, const OpArg& arg);
- void MULSD(X64Reg regOp, const OpArg& arg);
- void DIVSS(X64Reg regOp, const OpArg& arg);
- void DIVSD(X64Reg regOp, const OpArg& arg);
- void MINSS(X64Reg regOp, const OpArg& arg);
- void MINSD(X64Reg regOp, const OpArg& arg);
- void MAXSS(X64Reg regOp, const OpArg& arg);
- void MAXSD(X64Reg regOp, const OpArg& arg);
- void SQRTSS(X64Reg regOp, const OpArg& arg);
- void SQRTSD(X64Reg regOp, const OpArg& arg);
- void RCPSS(X64Reg regOp, const OpArg& arg);
- void RSQRTSS(X64Reg regOp, const OpArg& arg);
-
- // SSE/SSE2: Floating point bitwise (yes)
- void CMPSS(X64Reg regOp, const OpArg& arg, u8 compare);
- void CMPSD(X64Reg regOp, const OpArg& arg, u8 compare);
-
- void CMPEQSS(X64Reg regOp, const OpArg& arg) {
- CMPSS(regOp, arg, CMP_EQ);
- }
- void CMPLTSS(X64Reg regOp, const OpArg& arg) {
- CMPSS(regOp, arg, CMP_LT);
- }
- void CMPLESS(X64Reg regOp, const OpArg& arg) {
- CMPSS(regOp, arg, CMP_LE);
- }
- void CMPUNORDSS(X64Reg regOp, const OpArg& arg) {
- CMPSS(regOp, arg, CMP_UNORD);
- }
- void CMPNEQSS(X64Reg regOp, const OpArg& arg) {
- CMPSS(regOp, arg, CMP_NEQ);
- }
- void CMPNLTSS(X64Reg regOp, const OpArg& arg) {
- CMPSS(regOp, arg, CMP_NLT);
- }
- void CMPORDSS(X64Reg regOp, const OpArg& arg) {
- CMPSS(regOp, arg, CMP_ORD);
- }
-
- // SSE/SSE2: Floating point packed arithmetic (x4 for float, x2 for double)
- void ADDPS(X64Reg regOp, const OpArg& arg);
- void ADDPD(X64Reg regOp, const OpArg& arg);
- void SUBPS(X64Reg regOp, const OpArg& arg);
- void SUBPD(X64Reg regOp, const OpArg& arg);
- void CMPPS(X64Reg regOp, const OpArg& arg, u8 compare);
- void CMPPD(X64Reg regOp, const OpArg& arg, u8 compare);
- void MULPS(X64Reg regOp, const OpArg& arg);
- void MULPD(X64Reg regOp, const OpArg& arg);
- void DIVPS(X64Reg regOp, const OpArg& arg);
- void DIVPD(X64Reg regOp, const OpArg& arg);
- void MINPS(X64Reg regOp, const OpArg& arg);
- void MINPD(X64Reg regOp, const OpArg& arg);
- void MAXPS(X64Reg regOp, const OpArg& arg);
- void MAXPD(X64Reg regOp, const OpArg& arg);
- void SQRTPS(X64Reg regOp, const OpArg& arg);
- void SQRTPD(X64Reg regOp, const OpArg& arg);
- void RCPPS(X64Reg regOp, const OpArg& arg);
- void RSQRTPS(X64Reg regOp, const OpArg& arg);
-
- // SSE/SSE2: Floating point packed bitwise (x4 for float, x2 for double)
- void ANDPS(X64Reg regOp, const OpArg& arg);
- void ANDPD(X64Reg regOp, const OpArg& arg);
- void ANDNPS(X64Reg regOp, const OpArg& arg);
- void ANDNPD(X64Reg regOp, const OpArg& arg);
- void ORPS(X64Reg regOp, const OpArg& arg);
- void ORPD(X64Reg regOp, const OpArg& arg);
- void XORPS(X64Reg regOp, const OpArg& arg);
- void XORPD(X64Reg regOp, const OpArg& arg);
-
- // SSE/SSE2: Shuffle components. These are tricky - see Intel documentation.
- void SHUFPS(X64Reg regOp, const OpArg& arg, u8 shuffle);
- void SHUFPD(X64Reg regOp, const OpArg& arg, u8 shuffle);
-
- // SSE/SSE2: Useful alternative to shuffle in some cases.
- void MOVDDUP(X64Reg regOp, const OpArg& arg);
-
- // SSE3: Horizontal operations in SIMD registers. Very slow! shufps-based code beats it handily
- // on Ivy.
- void HADDPS(X64Reg dest, const OpArg& src);
-
- // SSE4: Further horizontal operations - dot products. These are weirdly flexible, the arg
- // contains both a read mask and a write "mask".
- void DPPS(X64Reg dest, const OpArg& src, u8 arg);
-
- void UNPCKLPS(X64Reg dest, const OpArg& src);
- void UNPCKHPS(X64Reg dest, const OpArg& src);
- void UNPCKLPD(X64Reg dest, const OpArg& src);
- void UNPCKHPD(X64Reg dest, const OpArg& src);
-
- // SSE/SSE2: Compares.
- void COMISS(X64Reg regOp, const OpArg& arg);
- void COMISD(X64Reg regOp, const OpArg& arg);
- void UCOMISS(X64Reg regOp, const OpArg& arg);
- void UCOMISD(X64Reg regOp, const OpArg& arg);
-
- // SSE/SSE2: Moves. Use the right data type for your data, in most cases.
- void MOVAPS(X64Reg regOp, const OpArg& arg);
- void MOVAPD(X64Reg regOp, const OpArg& arg);
- void MOVAPS(const OpArg& arg, X64Reg regOp);
- void MOVAPD(const OpArg& arg, X64Reg regOp);
-
- void MOVUPS(X64Reg regOp, const OpArg& arg);
- void MOVUPD(X64Reg regOp, const OpArg& arg);
- void MOVUPS(const OpArg& arg, X64Reg regOp);
- void MOVUPD(const OpArg& arg, X64Reg regOp);
-
- void MOVDQA(X64Reg regOp, const OpArg& arg);
- void MOVDQA(const OpArg& arg, X64Reg regOp);
- void MOVDQU(X64Reg regOp, const OpArg& arg);
- void MOVDQU(const OpArg& arg, X64Reg regOp);
-
- void MOVSS(X64Reg regOp, const OpArg& arg);
- void MOVSD(X64Reg regOp, const OpArg& arg);
- void MOVSS(const OpArg& arg, X64Reg regOp);
- void MOVSD(const OpArg& arg, X64Reg regOp);
-
- void MOVLPS(X64Reg regOp, const OpArg& arg);
- void MOVLPD(X64Reg regOp, const OpArg& arg);
- void MOVLPS(const OpArg& arg, X64Reg regOp);
- void MOVLPD(const OpArg& arg, X64Reg regOp);
-
- void MOVHPS(X64Reg regOp, const OpArg& arg);
- void MOVHPD(X64Reg regOp, const OpArg& arg);
- void MOVHPS(const OpArg& arg, X64Reg regOp);
- void MOVHPD(const OpArg& arg, X64Reg regOp);
-
- void MOVHLPS(X64Reg regOp1, X64Reg regOp2);
- void MOVLHPS(X64Reg regOp1, X64Reg regOp2);
-
- void MOVD_xmm(X64Reg dest, const OpArg& arg);
- void MOVQ_xmm(X64Reg dest, OpArg arg);
- void MOVD_xmm(const OpArg& arg, X64Reg src);
- void MOVQ_xmm(OpArg arg, X64Reg src);
-
- // SSE/SSE2: Generates a mask from the high bits of the components of the packed register in
- // question.
- void MOVMSKPS(X64Reg dest, const OpArg& arg);
- void MOVMSKPD(X64Reg dest, const OpArg& arg);
-
- // SSE2: Selective byte store, mask in src register. EDI/RDI specifies store address. This is a
- // weird one.
- void MASKMOVDQU(X64Reg dest, X64Reg src);
- void LDDQU(X64Reg dest, const OpArg& src);
-
- // SSE/SSE2: Data type conversions.
- void CVTPS2PD(X64Reg dest, const OpArg& src);
- void CVTPD2PS(X64Reg dest, const OpArg& src);
- void CVTSS2SD(X64Reg dest, const OpArg& src);
- void CVTSI2SS(X64Reg dest, const OpArg& src);
- void CVTSD2SS(X64Reg dest, const OpArg& src);
- void CVTSI2SD(X64Reg dest, const OpArg& src);
- void CVTDQ2PD(X64Reg regOp, const OpArg& arg);
- void CVTPD2DQ(X64Reg regOp, const OpArg& arg);
- void CVTDQ2PS(X64Reg regOp, const OpArg& arg);
- void CVTPS2DQ(X64Reg regOp, const OpArg& arg);
-
- void CVTTPS2DQ(X64Reg regOp, const OpArg& arg);
- void CVTTPD2DQ(X64Reg regOp, const OpArg& arg);
-
- // Destinations are X64 regs (rax, rbx, ...) for these instructions.
- void CVTSS2SI(X64Reg xregdest, const OpArg& src);
- void CVTSD2SI(X64Reg xregdest, const OpArg& src);
- void CVTTSS2SI(X64Reg xregdest, const OpArg& arg);
- void CVTTSD2SI(X64Reg xregdest, const OpArg& arg);
-
- // SSE2: Packed integer instructions
- void PACKSSDW(X64Reg dest, const OpArg& arg);
- void PACKSSWB(X64Reg dest, const OpArg& arg);
- void PACKUSDW(X64Reg dest, const OpArg& arg);
- void PACKUSWB(X64Reg dest, const OpArg& arg);
-
- void PUNPCKLBW(X64Reg dest, const OpArg& arg);
- void PUNPCKLWD(X64Reg dest, const OpArg& arg);
- void PUNPCKLDQ(X64Reg dest, const OpArg& arg);
- void PUNPCKLQDQ(X64Reg dest, const OpArg& arg);
-
- void PTEST(X64Reg dest, const OpArg& arg);
- void PAND(X64Reg dest, const OpArg& arg);
- void PANDN(X64Reg dest, const OpArg& arg);
- void PXOR(X64Reg dest, const OpArg& arg);
- void POR(X64Reg dest, const OpArg& arg);
-
- void PADDB(X64Reg dest, const OpArg& arg);
- void PADDW(X64Reg dest, const OpArg& arg);
- void PADDD(X64Reg dest, const OpArg& arg);
- void PADDQ(X64Reg dest, const OpArg& arg);
-
- void PADDSB(X64Reg dest, const OpArg& arg);
- void PADDSW(X64Reg dest, const OpArg& arg);
- void PADDUSB(X64Reg dest, const OpArg& arg);
- void PADDUSW(X64Reg dest, const OpArg& arg);
-
- void PSUBB(X64Reg dest, const OpArg& arg);
- void PSUBW(X64Reg dest, const OpArg& arg);
- void PSUBD(X64Reg dest, const OpArg& arg);
- void PSUBQ(X64Reg dest, const OpArg& arg);
-
- void PSUBSB(X64Reg dest, const OpArg& arg);
- void PSUBSW(X64Reg dest, const OpArg& arg);
- void PSUBUSB(X64Reg dest, const OpArg& arg);
- void PSUBUSW(X64Reg dest, const OpArg& arg);
-
- void PAVGB(X64Reg dest, const OpArg& arg);
- void PAVGW(X64Reg dest, const OpArg& arg);
-
- void PCMPEQB(X64Reg dest, const OpArg& arg);
- void PCMPEQW(X64Reg dest, const OpArg& arg);
- void PCMPEQD(X64Reg dest, const OpArg& arg);
-
- void PCMPGTB(X64Reg dest, const OpArg& arg);
- void PCMPGTW(X64Reg dest, const OpArg& arg);
- void PCMPGTD(X64Reg dest, const OpArg& arg);
-
- void PEXTRW(X64Reg dest, const OpArg& arg, u8 subreg);
- void PINSRW(X64Reg dest, const OpArg& arg, u8 subreg);
-
- void PMADDWD(X64Reg dest, const OpArg& arg);
- void PSADBW(X64Reg dest, const OpArg& arg);
-
- void PMAXSW(X64Reg dest, const OpArg& arg);
- void PMAXUB(X64Reg dest, const OpArg& arg);
- void PMINSW(X64Reg dest, const OpArg& arg);
- void PMINUB(X64Reg dest, const OpArg& arg);
- // SSE4: More MAX/MIN instructions.
- void PMINSB(X64Reg dest, const OpArg& arg);
- void PMINSD(X64Reg dest, const OpArg& arg);
- void PMINUW(X64Reg dest, const OpArg& arg);
- void PMINUD(X64Reg dest, const OpArg& arg);
- void PMAXSB(X64Reg dest, const OpArg& arg);
- void PMAXSD(X64Reg dest, const OpArg& arg);
- void PMAXUW(X64Reg dest, const OpArg& arg);
- void PMAXUD(X64Reg dest, const OpArg& arg);
-
- void PMOVMSKB(X64Reg dest, const OpArg& arg);
- void PSHUFD(X64Reg dest, const OpArg& arg, u8 shuffle);
- void PSHUFB(X64Reg dest, const OpArg& arg);
-
- void PSHUFLW(X64Reg dest, const OpArg& arg, u8 shuffle);
- void PSHUFHW(X64Reg dest, const OpArg& arg, u8 shuffle);
-
- void PSRLW(X64Reg reg, int shift);
- void PSRLD(X64Reg reg, int shift);
- void PSRLQ(X64Reg reg, int shift);
- void PSRLQ(X64Reg reg, const OpArg& arg);
- void PSRLDQ(X64Reg reg, int shift);
-
- void PSLLW(X64Reg reg, int shift);
- void PSLLD(X64Reg reg, int shift);
- void PSLLQ(X64Reg reg, int shift);
- void PSLLDQ(X64Reg reg, int shift);
-
- void PSRAW(X64Reg reg, int shift);
- void PSRAD(X64Reg reg, int shift);
-
- // SSE4: data type conversions
- void PMOVSXBW(X64Reg dest, const OpArg& arg);
- void PMOVSXBD(X64Reg dest, const OpArg& arg);
- void PMOVSXBQ(X64Reg dest, const OpArg& arg);
- void PMOVSXWD(X64Reg dest, const OpArg& arg);
- void PMOVSXWQ(X64Reg dest, const OpArg& arg);
- void PMOVSXDQ(X64Reg dest, const OpArg& arg);
- void PMOVZXBW(X64Reg dest, const OpArg& arg);
- void PMOVZXBD(X64Reg dest, const OpArg& arg);
- void PMOVZXBQ(X64Reg dest, const OpArg& arg);
- void PMOVZXWD(X64Reg dest, const OpArg& arg);
- void PMOVZXWQ(X64Reg dest, const OpArg& arg);
- void PMOVZXDQ(X64Reg dest, const OpArg& arg);
-
- // SSE4: variable blend instructions (xmm0 implicit argument)
- void PBLENDVB(X64Reg dest, const OpArg& arg);
- void BLENDVPS(X64Reg dest, const OpArg& arg);
- void BLENDVPD(X64Reg dest, const OpArg& arg);
- void BLENDPS(X64Reg dest, const OpArg& arg, u8 blend);
- void BLENDPD(X64Reg dest, const OpArg& arg, u8 blend);
-
- // SSE4: rounding (see FloatRound for mode or use ROUNDNEARSS, etc. helpers.)
- void ROUNDSS(X64Reg dest, const OpArg& arg, u8 mode);
- void ROUNDSD(X64Reg dest, const OpArg& arg, u8 mode);
- void ROUNDPS(X64Reg dest, const OpArg& arg, u8 mode);
- void ROUNDPD(X64Reg dest, const OpArg& arg, u8 mode);
-
- void ROUNDNEARSS(X64Reg dest, const OpArg& arg) {
- ROUNDSS(dest, arg, FROUND_NEAREST);
- }
- void ROUNDFLOORSS(X64Reg dest, const OpArg& arg) {
- ROUNDSS(dest, arg, FROUND_FLOOR);
- }
- void ROUNDCEILSS(X64Reg dest, const OpArg& arg) {
- ROUNDSS(dest, arg, FROUND_CEIL);
- }
- void ROUNDZEROSS(X64Reg dest, const OpArg& arg) {
- ROUNDSS(dest, arg, FROUND_ZERO);
- }
-
- void ROUNDNEARSD(X64Reg dest, const OpArg& arg) {
- ROUNDSD(dest, arg, FROUND_NEAREST);
- }
- void ROUNDFLOORSD(X64Reg dest, const OpArg& arg) {
- ROUNDSD(dest, arg, FROUND_FLOOR);
- }
- void ROUNDCEILSD(X64Reg dest, const OpArg& arg) {
- ROUNDSD(dest, arg, FROUND_CEIL);
- }
- void ROUNDZEROSD(X64Reg dest, const OpArg& arg) {
- ROUNDSD(dest, arg, FROUND_ZERO);
- }
-
- void ROUNDNEARPS(X64Reg dest, const OpArg& arg) {
- ROUNDPS(dest, arg, FROUND_NEAREST);
- }
- void ROUNDFLOORPS(X64Reg dest, const OpArg& arg) {
- ROUNDPS(dest, arg, FROUND_FLOOR);
- }
- void ROUNDCEILPS(X64Reg dest, const OpArg& arg) {
- ROUNDPS(dest, arg, FROUND_CEIL);
- }
- void ROUNDZEROPS(X64Reg dest, const OpArg& arg) {
- ROUNDPS(dest, arg, FROUND_ZERO);
- }
-
- void ROUNDNEARPD(X64Reg dest, const OpArg& arg) {
- ROUNDPD(dest, arg, FROUND_NEAREST);
- }
- void ROUNDFLOORPD(X64Reg dest, const OpArg& arg) {
- ROUNDPD(dest, arg, FROUND_FLOOR);
- }
- void ROUNDCEILPD(X64Reg dest, const OpArg& arg) {
- ROUNDPD(dest, arg, FROUND_CEIL);
- }
- void ROUNDZEROPD(X64Reg dest, const OpArg& arg) {
- ROUNDPD(dest, arg, FROUND_ZERO);
- }
-
- // AVX
- void VADDSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VSUBSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VMULSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VDIVSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VADDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VSUBPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VMULPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VDIVPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VSQRTSD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VSHUFPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg, u8 shuffle);
- void VUNPCKLPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VUNPCKHPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
-
- void VANDPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VANDPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VANDNPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VANDNPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VXORPS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VXORPD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
-
- void VPAND(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VPANDN(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VPOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VPXOR(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
-
- // FMA3
- void VFMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMADD231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB132SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB213SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB231SS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB132SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB213SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFNMSUB231SD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADDSUB132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADDSUB213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADDSUB231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADDSUB132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADDSUB213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMADDSUB231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUBADD132PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUBADD213PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUBADD231PS(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUBADD132PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUBADD213PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void VFMSUBADD231PD(X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
-
- // VEX GPR instructions
- void SARX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2);
- void SHLX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2);
- void SHRX(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2);
- void RORX(int bits, X64Reg regOp, const OpArg& arg, u8 rotate);
- void PEXT(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void PDEP(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void MULX(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
- void BZHI(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2);
- void BLSR(int bits, X64Reg regOp, const OpArg& arg);
- void BLSMSK(int bits, X64Reg regOp, const OpArg& arg);
- void BLSI(int bits, X64Reg regOp, const OpArg& arg);
- void BEXTR(int bits, X64Reg regOp1, const OpArg& arg, X64Reg regOp2);
- void ANDN(int bits, X64Reg regOp1, X64Reg regOp2, const OpArg& arg);
-
- void RDTSC();
-
- // Utility functions
- // The difference between this and CALL is that this aligns the stack
- // where appropriate.
- void ABI_CallFunction(const void* func);
- template <typename T>
- void ABI_CallFunction(T (*func)()) {
- ABI_CallFunction((const void*)func);
- }
-
- void ABI_CallFunction(const u8* func) {
- ABI_CallFunction((const void*)func);
- }
- void ABI_CallFunctionC16(const void* func, u16 param1);
- void ABI_CallFunctionCC16(const void* func, u32 param1, u16 param2);
-
- // These only support u32 parameters, but that's enough for a lot of uses.
- // These will destroy the 1 or 2 first "parameter regs".
- void ABI_CallFunctionC(const void* func, u32 param1);
- void ABI_CallFunctionCC(const void* func, u32 param1, u32 param2);
- void ABI_CallFunctionCCC(const void* func, u32 param1, u32 param2, u32 param3);
- void ABI_CallFunctionCCP(const void* func, u32 param1, u32 param2, void* param3);
- void ABI_CallFunctionCCCP(const void* func, u32 param1, u32 param2, u32 param3, void* param4);
- void ABI_CallFunctionP(const void* func, void* param1);
- void ABI_CallFunctionPA(const void* func, void* param1, const OpArg& arg2);
- void ABI_CallFunctionPAA(const void* func, void* param1, const OpArg& arg2, const OpArg& arg3);
- void ABI_CallFunctionPPC(const void* func, void* param1, void* param2, u32 param3);
- void ABI_CallFunctionAC(const void* func, const OpArg& arg1, u32 param2);
- void ABI_CallFunctionACC(const void* func, const OpArg& arg1, u32 param2, u32 param3);
- void ABI_CallFunctionA(const void* func, const OpArg& arg1);
- void ABI_CallFunctionAA(const void* func, const OpArg& arg1, const OpArg& arg2);
-
- // Pass a register as a parameter.
- void ABI_CallFunctionR(const void* func, X64Reg reg1);
- void ABI_CallFunctionRR(const void* func, X64Reg reg1, X64Reg reg2);
-
- template <typename Tr, typename T1>
- void ABI_CallFunctionC(Tr (*func)(T1), u32 param1) {
- ABI_CallFunctionC((const void*)func, param1);
- }
-
- /**
- * Saves specified registers and adjusts the stack to be 16-byte aligned as required by the ABI
- *
- * @param mask Registers to push on the stack (high 16 bits are XMMs, low 16 bits are GPRs)
- * @param rsp_alignment Current alignment of the stack pointer, must be 0 or 8
- * @param needed_frame_size Additional space needed, e.g., for function arguments passed on the
- * stack
- * @return Size of the shadow space, i.e., offset of the frame
- */
- size_t ABI_PushRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment,
- size_t needed_frame_size = 0);
-
- /**
- * Restores specified registers and adjusts the stack to its original alignment, i.e., the
- * alignment before
- * the matching PushRegistersAndAdjustStack.
- *
- * @param mask Registers to restores from the stack (high 16 bits are XMMs, low 16 bits are
- * GPRs)
- * @param rsp_alignment Original alignment before the matching PushRegistersAndAdjustStack, must
- * be 0 or 8
- * @param needed_frame_size Additional space that was needed
- * @warning Stack must be currently 16-byte aligned
- */
- void ABI_PopRegistersAndAdjustStack(BitSet32 mask, size_t rsp_alignment,
- size_t needed_frame_size = 0);
-
-#ifdef _M_IX86
- static int ABI_GetNumXMMRegs() {
- return 8;
- }
-#else
- static int ABI_GetNumXMMRegs() {
- return 16;
- }
-#endif
-}; // class XEmitter
-
-// Everything that needs to generate X86 code should inherit from this.
-// You get memory management for free, plus, you can use all the MOV etc functions without
-// having to prefix them with gen-> or something similar.
-
-class XCodeBlock : public CodeBlock<XEmitter> {
-public:
- void PoisonMemory() override;
-};
-
-} // namespace
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index c33555bd5..b178914dd 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -2,6 +2,7 @@ set(SRCS
arm/disassembler/arm_disasm.cpp
arm/disassembler/load_symbol_map.cpp
arm/dynarmic/arm_dynarmic.cpp
+ arm/dynarmic/arm_dynarmic_cp15.cpp
arm/dyncom/arm_dyncom.cpp
arm/dyncom/arm_dyncom_dec.cpp
arm/dyncom/arm_dyncom_interpreter.cpp
@@ -29,13 +30,18 @@ set(SRCS
file_sys/ivfc_archive.cpp
file_sys/path_parser.cpp
file_sys/savedata_archive.cpp
+ frontend/camera/blank_camera.cpp
+ frontend/camera/factory.cpp
+ frontend/camera/interface.cpp
frontend/emu_window.cpp
frontend/key_map.cpp
+ frontend/motion_emu.cpp
gdbstub/gdbstub.cpp
hle/config_mem.cpp
hle/applets/applet.cpp
hle/applets/erreula.cpp
hle/applets/mii_selector.cpp
+ hle/applets/mint.cpp
hle/applets/swkbd.cpp
hle/kernel/address_arbiter.cpp
hle/kernel/client_port.cpp
@@ -53,7 +59,9 @@ set(SRCS
hle/kernel/thread.cpp
hle/kernel/timer.cpp
hle/kernel/vm_manager.cpp
- hle/service/ac_u.cpp
+ hle/service/ac/ac.cpp
+ hle/service/ac/ac_i.cpp
+ hle/service/ac/ac_u.cpp
hle/service/act/act.cpp
hle/service/act/act_a.cpp
hle/service/act/act_u.cpp
@@ -170,6 +178,7 @@ set(HEADERS
arm/disassembler/arm_disasm.h
arm/disassembler/load_symbol_map.h
arm/dynarmic/arm_dynarmic.h
+ arm/dynarmic/arm_dynarmic_cp15.h
arm/dyncom/arm_dyncom.h
arm/dyncom/arm_dyncom_dec.h
arm/dyncom/arm_dyncom_interpreter.h
@@ -200,8 +209,12 @@ set(HEADERS
file_sys/ivfc_archive.h
file_sys/path_parser.h
file_sys/savedata_archive.h
+ frontend/camera/blank_camera.h
+ frontend/camera/factory.h
+ frontend/camera/interface.h
frontend/emu_window.h
frontend/key_map.h
+ frontend/motion_emu.h
gdbstub/gdbstub.h
hle/config_mem.h
hle/function_wrappers.h
@@ -210,6 +223,7 @@ set(HEADERS
hle/applets/applet.h
hle/applets/erreula.h
hle/applets/mii_selector.h
+ hle/applets/mint.h
hle/applets/swkbd.h
hle/kernel/address_arbiter.h
hle/kernel/client_port.h
@@ -228,7 +242,9 @@ set(HEADERS
hle/kernel/timer.h
hle/kernel/vm_manager.h
hle/result.h
- hle/service/ac_u.h
+ hle/service/ac/ac.h
+ hle/service/ac/ac_i.h
+ hle/service/ac/ac_u.h
hle/service/act/act.h
hle/service/act/act_a.h
hle/service/act/act_u.h
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp
index 9f25e3b00..7d2790b08 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.cpp
+++ b/src/core/arm/dynarmic/arm_dynarmic.cpp
@@ -7,6 +7,7 @@
#include "common/assert.h"
#include "common/microprofile.h"
#include "core/arm/dynarmic/arm_dynarmic.h"
+#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
#include "core/arm/dyncom/arm_dyncom_interpreter.h"
#include "core/core.h"
#include "core/core_timing.h"
@@ -39,28 +40,30 @@ static bool IsReadOnlyMemory(u32 vaddr) {
return false;
}
-static Dynarmic::UserCallbacks GetUserCallbacks(ARMul_State* interpeter_state) {
+static Dynarmic::UserCallbacks GetUserCallbacks(
+ const std::shared_ptr<ARMul_State>& interpeter_state) {
Dynarmic::UserCallbacks user_callbacks{};
user_callbacks.InterpreterFallback = &InterpreterFallback;
- user_callbacks.user_arg = static_cast<void*>(interpeter_state);
+ user_callbacks.user_arg = static_cast<void*>(interpeter_state.get());
user_callbacks.CallSVC = &SVC::CallSVC;
- user_callbacks.IsReadOnlyMemory = &IsReadOnlyMemory;
- user_callbacks.MemoryReadCode = &Memory::Read32;
- user_callbacks.MemoryRead8 = &Memory::Read8;
- user_callbacks.MemoryRead16 = &Memory::Read16;
- user_callbacks.MemoryRead32 = &Memory::Read32;
- user_callbacks.MemoryRead64 = &Memory::Read64;
- user_callbacks.MemoryWrite8 = &Memory::Write8;
- user_callbacks.MemoryWrite16 = &Memory::Write16;
- user_callbacks.MemoryWrite32 = &Memory::Write32;
- user_callbacks.MemoryWrite64 = &Memory::Write64;
+ user_callbacks.memory.IsReadOnlyMemory = &IsReadOnlyMemory;
+ user_callbacks.memory.ReadCode = &Memory::Read32;
+ user_callbacks.memory.Read8 = &Memory::Read8;
+ user_callbacks.memory.Read16 = &Memory::Read16;
+ user_callbacks.memory.Read32 = &Memory::Read32;
+ user_callbacks.memory.Read64 = &Memory::Read64;
+ user_callbacks.memory.Write8 = &Memory::Write8;
+ user_callbacks.memory.Write16 = &Memory::Write16;
+ user_callbacks.memory.Write32 = &Memory::Write32;
+ user_callbacks.memory.Write64 = &Memory::Write64;
user_callbacks.page_table = Memory::GetCurrentPageTablePointers();
+ user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state);
return user_callbacks;
}
ARM_Dynarmic::ARM_Dynarmic(PrivilegeMode initial_mode) {
- interpreter_state = std::make_unique<ARMul_State>(initial_mode);
- jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state.get()));
+ interpreter_state = std::make_shared<ARMul_State>(initial_mode);
+ jit = std::make_unique<Dynarmic::Jit>(GetUserCallbacks(interpreter_state));
}
void ARM_Dynarmic::SetPC(u32 pc) {
diff --git a/src/core/arm/dynarmic/arm_dynarmic.h b/src/core/arm/dynarmic/arm_dynarmic.h
index 87ab53d81..834dc989e 100644
--- a/src/core/arm/dynarmic/arm_dynarmic.h
+++ b/src/core/arm/dynarmic/arm_dynarmic.h
@@ -39,5 +39,5 @@ public:
private:
std::unique_ptr<Dynarmic::Jit> jit;
- std::unique_ptr<ARMul_State> interpreter_state;
+ std::shared_ptr<ARMul_State> interpreter_state;
};
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
new file mode 100644
index 000000000..b1fdce096
--- /dev/null
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.cpp
@@ -0,0 +1,88 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/arm/dynarmic/arm_dynarmic_cp15.h"
+#include "core/arm/skyeye_common/arm_regformat.h"
+#include "core/arm/skyeye_common/armstate.h"
+
+using Callback = Dynarmic::Coprocessor::Callback;
+using CallbackOrAccessOneWord = Dynarmic::Coprocessor::CallbackOrAccessOneWord;
+using CallbackOrAccessTwoWords = Dynarmic::Coprocessor::CallbackOrAccessTwoWords;
+
+DynarmicCP15::DynarmicCP15(const std::shared_ptr<ARMul_State>& state) : interpreter_state(state) {}
+
+DynarmicCP15::~DynarmicCP15() = default;
+
+boost::optional<Callback> DynarmicCP15::CompileInternalOperation(bool two, unsigned opc1,
+ CoprocReg CRd, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) {
+ return boost::none;
+}
+
+CallbackOrAccessOneWord DynarmicCP15::CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) {
+ // TODO(merry): Privileged CP15 registers
+
+ if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C5 && opc2 == 4) {
+ // This is a dummy write, we ignore the value written here.
+ return &interpreter_state->CP15[CP15_FLUSH_PREFETCH_BUFFER];
+ }
+
+ if (!two && CRn == CoprocReg::C7 && opc1 == 0 && CRm == CoprocReg::C10) {
+ switch (opc2) {
+ case 4:
+ // This is a dummy write, we ignore the value written here.
+ return &interpreter_state->CP15[CP15_DATA_SYNC_BARRIER];
+ case 5:
+ // This is a dummy write, we ignore the value written here.
+ return &interpreter_state->CP15[CP15_DATA_MEMORY_BARRIER];
+ default:
+ return boost::blank{};
+ }
+ }
+
+ if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0 && opc2 == 2) {
+ return &interpreter_state->CP15[CP15_THREAD_UPRW];
+ }
+
+ return boost::blank{};
+}
+
+CallbackOrAccessTwoWords DynarmicCP15::CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) {
+ return boost::blank{};
+}
+
+CallbackOrAccessOneWord DynarmicCP15::CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) {
+ // TODO(merry): Privileged CP15 registers
+
+ if (!two && CRn == CoprocReg::C13 && opc1 == 0 && CRm == CoprocReg::C0) {
+ switch (opc2) {
+ case 2:
+ return &interpreter_state->CP15[CP15_THREAD_UPRW];
+ case 3:
+ return &interpreter_state->CP15[CP15_THREAD_URO];
+ default:
+ return boost::blank{};
+ }
+ }
+
+ return boost::blank{};
+}
+
+CallbackOrAccessTwoWords DynarmicCP15::CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) {
+ return boost::blank{};
+}
+
+boost::optional<Callback> DynarmicCP15::CompileLoadWords(bool two, bool long_transfer,
+ CoprocReg CRd,
+ boost::optional<u8> option) {
+ return boost::none;
+}
+
+boost::optional<Callback> DynarmicCP15::CompileStoreWords(bool two, bool long_transfer,
+ CoprocReg CRd,
+ boost::optional<u8> option) {
+ return boost::none;
+}
diff --git a/src/core/arm/dynarmic/arm_dynarmic_cp15.h b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
new file mode 100644
index 000000000..7fa54e14c
--- /dev/null
+++ b/src/core/arm/dynarmic/arm_dynarmic_cp15.h
@@ -0,0 +1,32 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <memory>
+#include <dynarmic/coprocessor.h>
+#include "common/common_types.h"
+
+struct ARMul_State;
+
+class DynarmicCP15 final : public Dynarmic::Coprocessor {
+public:
+ explicit DynarmicCP15(const std::shared_ptr<ARMul_State>&);
+ ~DynarmicCP15() override;
+
+ boost::optional<Callback> CompileInternalOperation(bool two, unsigned opc1, CoprocReg CRd,
+ CoprocReg CRn, CoprocReg CRm,
+ unsigned opc2) override;
+ CallbackOrAccessOneWord CompileSendOneWord(bool two, unsigned opc1, CoprocReg CRn,
+ CoprocReg CRm, unsigned opc2) override;
+ CallbackOrAccessTwoWords CompileSendTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
+ CallbackOrAccessOneWord CompileGetOneWord(bool two, unsigned opc1, CoprocReg CRn, CoprocReg CRm,
+ unsigned opc2) override;
+ CallbackOrAccessTwoWords CompileGetTwoWords(bool two, unsigned opc, CoprocReg CRm) override;
+ boost::optional<Callback> CompileLoadWords(bool two, bool long_transfer, CoprocReg CRd,
+ boost::optional<u8> option) override;
+ boost::optional<Callback> CompileStoreWords(bool two, bool long_transfer, CoprocReg CRd,
+ boost::optional<u8> option) override;
+
+private:
+ std::shared_ptr<ARMul_State> interpreter_state;
+};
diff --git a/src/core/core.cpp b/src/core/core.cpp
index ee5237096..202cd332b 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -159,6 +159,7 @@ void System::Shutdown() {
Kernel::Shutdown();
HW::Shutdown();
CoreTiming::Shutdown();
+ cpu_core.reset();
LOG_DEBUG(Core, "Shutdown OK");
}
diff --git a/src/core/core.h b/src/core/core.h
index 1015e8847..17572a74f 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -115,7 +115,7 @@ private:
static System s_instance;
};
-static ARM_Interface& CPU() {
+inline ARM_Interface& CPU() {
return System::GetInstance().CPU();
}
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index a437d0823..276ecfdf6 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -13,7 +13,7 @@
#include "core/core.h"
#include "core/core_timing.h"
-int g_clock_rate_arm11 = 268123480;
+int g_clock_rate_arm11 = BASE_CLOCK_RATE_ARM11;
// is this really necessary?
#define INITIAL_SLICE_LENGTH 20000
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index b72a1b500..d2f85cd4d 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -21,6 +21,7 @@
// inside callback:
// ScheduleEvent(periodInCycles - cycles_late, callback, "whatever")
+constexpr int BASE_CLOCK_RATE_ARM11 = 268123480;
extern int g_clock_rate_arm11;
inline s64 msToCycles(int ms) {
diff --git a/src/core/file_sys/archive_extsavedata.cpp b/src/core/file_sys/archive_extsavedata.cpp
index 51ce78435..dd2fb167f 100644
--- a/src/core/file_sys/archive_extsavedata.cpp
+++ b/src/core/file_sys/archive_extsavedata.cpp
@@ -107,6 +107,8 @@ public:
case PathParser::NotFound:
LOG_ERROR(Service_FS, "%s not found", full_path.c_str());
return ERROR_FILE_NOT_FOUND;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, "r+b");
diff --git a/src/core/file_sys/archive_sdmc.cpp b/src/core/file_sys/archive_sdmc.cpp
index 333dfb92e..72ff05c65 100644
--- a/src/core/file_sys/archive_sdmc.cpp
+++ b/src/core/file_sys/archive_sdmc.cpp
@@ -72,6 +72,8 @@ ResultVal<std::unique_ptr<FileBackend>> SDMCArchive::OpenFileBase(const Path& pa
FileUtil::CreateEmptyFile(full_path);
}
break;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
@@ -106,6 +108,8 @@ ResultCode SDMCArchive::DeleteFile(const Path& path) const {
case PathParser::DirectoryFound:
LOG_ERROR(Service_FS, "%s is not a file", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
if (FileUtil::Delete(full_path)) {
@@ -154,6 +158,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou
case PathParser::FileFound:
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
if (deleter(full_path)) {
@@ -197,6 +203,8 @@ ResultCode SDMCArchive::CreateFile(const FileSys::Path& path, u64 size) const {
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (size == 0) {
@@ -238,6 +246,8 @@ ResultCode SDMCArchive::CreateDirectory(const Path& path) const {
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (FileUtil::CreateDir(mount_point + path.AsString())) {
@@ -281,6 +291,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SDMCArchive::OpenDirectory(const Pa
case PathParser::FileInPath:
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY_SDMC;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
auto directory = std::make_unique<DiskDirectory>(full_path);
diff --git a/src/core/file_sys/savedata_archive.cpp b/src/core/file_sys/savedata_archive.cpp
index f2e6a06bc..f540c4a93 100644
--- a/src/core/file_sys/savedata_archive.cpp
+++ b/src/core/file_sys/savedata_archive.cpp
@@ -57,6 +57,8 @@ ResultVal<std::unique_ptr<FileBackend>> SaveDataArchive::OpenFile(const Path& pa
FileUtil::CreateEmptyFile(full_path);
}
break;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
FileUtil::IOFile file(full_path, mode.write_flag ? "r+b" : "rb");
@@ -91,6 +93,8 @@ ResultCode SaveDataArchive::DeleteFile(const Path& path) const {
case PathParser::NotFound:
LOG_ERROR(Service_FS, "File not found %s", full_path.c_str());
return ERROR_FILE_NOT_FOUND;
+ case PathParser::FileFound:
+ break; // Expected 'success' case
}
if (FileUtil::Delete(full_path)) {
@@ -139,6 +143,8 @@ static ResultCode DeleteDirectoryHelper(const Path& path, const std::string& mou
case PathParser::FileFound:
LOG_ERROR(Service_FS, "Unexpected file or directory %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
if (deleter(full_path)) {
@@ -182,6 +188,8 @@ ResultCode SaveDataArchive::CreateFile(const FileSys::Path& path, u64 size) cons
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_FILE_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (size == 0) {
@@ -225,6 +233,8 @@ ResultCode SaveDataArchive::CreateDirectory(const Path& path) const {
case PathParser::FileFound:
LOG_ERROR(Service_FS, "%s already exists", full_path.c_str());
return ERROR_DIRECTORY_ALREADY_EXISTS;
+ case PathParser::NotFound:
+ break; // Expected 'success' case
}
if (FileUtil::CreateDir(mount_point + path.AsString())) {
@@ -269,6 +279,8 @@ ResultVal<std::unique_ptr<DirectoryBackend>> SaveDataArchive::OpenDirectory(
case PathParser::FileFound:
LOG_ERROR(Service_FS, "Unexpected file in path %s", full_path.c_str());
return ERROR_UNEXPECTED_FILE_OR_DIRECTORY;
+ case PathParser::DirectoryFound:
+ break; // Expected 'success' case
}
auto directory = std::make_unique<DiskDirectory>(full_path);
diff --git a/src/core/frontend/camera/blank_camera.cpp b/src/core/frontend/camera/blank_camera.cpp
new file mode 100644
index 000000000..7995abcbd
--- /dev/null
+++ b/src/core/frontend/camera/blank_camera.cpp
@@ -0,0 +1,31 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/camera/blank_camera.h"
+
+namespace Camera {
+
+void BlankCamera::StartCapture() {}
+
+void BlankCamera::StopCapture() {}
+
+void BlankCamera::SetFormat(Service::CAM::OutputFormat output_format) {
+ output_rgb = output_format == Service::CAM::OutputFormat::RGB565;
+}
+
+void BlankCamera::SetResolution(const Service::CAM::Resolution& resolution) {
+ width = resolution.width;
+ height = resolution.height;
+};
+
+void BlankCamera::SetFlip(Service::CAM::Flip) {}
+
+void BlankCamera::SetEffect(Service::CAM::Effect) {}
+
+std::vector<u16> BlankCamera::ReceiveFrame() const {
+ // Note: 0x80008000 stands for two black pixels in YUV422
+ return std::vector<u16>(width * height, output_rgb ? 0 : 0x8000);
+}
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/blank_camera.h b/src/core/frontend/camera/blank_camera.h
new file mode 100644
index 000000000..c6619bd88
--- /dev/null
+++ b/src/core/frontend/camera/blank_camera.h
@@ -0,0 +1,28 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/frontend/camera/factory.h"
+#include "core/frontend/camera/interface.h"
+
+namespace Camera {
+
+class BlankCamera final : public CameraInterface {
+public:
+ void StartCapture() override;
+ void StopCapture() override;
+ void SetResolution(const Service::CAM::Resolution&) override;
+ void SetFlip(Service::CAM::Flip) override;
+ void SetEffect(Service::CAM::Effect) override;
+ void SetFormat(Service::CAM::OutputFormat) override;
+ std::vector<u16> ReceiveFrame() const override;
+
+private:
+ int width = 0;
+ int height = 0;
+ bool output_rgb = false;
+};
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/factory.cpp b/src/core/frontend/camera/factory.cpp
new file mode 100644
index 000000000..4b4da50dd
--- /dev/null
+++ b/src/core/frontend/camera/factory.cpp
@@ -0,0 +1,32 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <unordered_map>
+#include "common/logging/log.h"
+#include "core/frontend/camera/blank_camera.h"
+#include "core/frontend/camera/factory.h"
+
+namespace Camera {
+
+static std::unordered_map<std::string, std::unique_ptr<CameraFactory>> factories;
+
+CameraFactory::~CameraFactory() = default;
+
+void RegisterFactory(const std::string& name, std::unique_ptr<CameraFactory> factory) {
+ factories[name] = std::move(factory);
+}
+
+std::unique_ptr<CameraInterface> CreateCamera(const std::string& name, const std::string& config) {
+ auto pair = factories.find(name);
+ if (pair != factories.end()) {
+ return pair->second->Create(config);
+ }
+
+ if (name != "blank") {
+ LOG_ERROR(Service_CAM, "Unknown camera \"%s\"", name.c_str());
+ }
+ return std::make_unique<BlankCamera>();
+}
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/factory.h b/src/core/frontend/camera/factory.h
new file mode 100644
index 000000000..d68be16e5
--- /dev/null
+++ b/src/core/frontend/camera/factory.h
@@ -0,0 +1,41 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include <string>
+#include "core/frontend/camera/interface.h"
+
+namespace Camera {
+
+class CameraFactory {
+public:
+ virtual ~CameraFactory();
+
+ /**
+ * Creates a camera object based on the configuration string.
+ * @params config Configuration string to create the camera. The implementation can decide the
+ * meaning of this string.
+ * @returns a unique_ptr to the created camera object.
+ */
+ virtual std::unique_ptr<CameraInterface> Create(const std::string& config) const = 0;
+};
+
+/**
+ * Registers an external camera factory.
+ * @param name Identifier of the camera factory.
+ * @param factory Camera factory to register.
+ */
+void RegisterFactory(const std::string& name, std::unique_ptr<CameraFactory> factory);
+
+/**
+ * Creates a camera from the factory.
+ * @param name Identifier of the camera factory.
+ * @param config Configuration string to create the camera. The meaning of this string is
+ * defined by the factory.
+ */
+std::unique_ptr<CameraInterface> CreateCamera(const std::string& name, const std::string& config);
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/interface.cpp b/src/core/frontend/camera/interface.cpp
new file mode 100644
index 000000000..9aec9e7f1
--- /dev/null
+++ b/src/core/frontend/camera/interface.cpp
@@ -0,0 +1,11 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/frontend/camera/interface.h"
+
+namespace Camera {
+
+CameraInterface::~CameraInterface() = default;
+
+} // namespace Camera
diff --git a/src/core/frontend/camera/interface.h b/src/core/frontend/camera/interface.h
new file mode 100644
index 000000000..a55a495c9
--- /dev/null
+++ b/src/core/frontend/camera/interface.h
@@ -0,0 +1,61 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "core/hle/service/cam/cam.h"
+
+namespace Camera {
+
+/// An abstract class standing for a camera. All camera implementations should inherit from this.
+class CameraInterface {
+public:
+ virtual ~CameraInterface();
+
+ /// Starts the camera for video capturing.
+ virtual void StartCapture() = 0;
+
+ /// Stops the camera for video capturing.
+ virtual void StopCapture() = 0;
+
+ /**
+ * Sets the video resolution from raw CAM service parameters.
+ * For the meaning of the parameters, please refer to Service::CAM::Resolution. Note that the
+ * actual camera implementation doesn't need to respect all the parameters. However, the width
+ * and the height parameters must be respected and be used to determine the size of output
+ * frames.
+ * @param resolution The resolution parameters to set
+ */
+ virtual void SetResolution(const Service::CAM::Resolution& resolution) = 0;
+
+ /**
+ * Configures how received frames should be flipped by the camera.
+ * @param flip Flip applying to the frame
+ */
+ virtual void SetFlip(Service::CAM::Flip flip) = 0;
+
+ /**
+ * Configures what effect should be applied to received frames by the camera.
+ * @param effect Effect applying to the frame
+ */
+ virtual void SetEffect(Service::CAM::Effect effect) = 0;
+
+ /**
+ * Sets the output format of the all frames received after this function is called.
+ * @param format Output format of the frame
+ */
+ virtual void SetFormat(Service::CAM::OutputFormat format) = 0;
+
+ /**
+ * Receives a frame from the camera.
+ * This function should be only called between a StartCapture call and a StopCapture call.
+ * @returns A std::vector<u16> containing pixels. The total size of the vector is width * height
+ * where width and height are set by a call to SetResolution.
+ */
+ virtual std::vector<u16> ReceiveFrame() const = 0;
+};
+
+} // namespace Camera
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp
index f6f90f9e1..4f0f786ce 100644
--- a/src/core/frontend/emu_window.cpp
+++ b/src/core/frontend/emu_window.cpp
@@ -5,6 +5,7 @@
#include <algorithm>
#include <cmath>
#include "common/assert.h"
+#include "common/profiler_reporting.h"
#include "core/frontend/emu_window.h"
#include "core/frontend/key_map.h"
#include "video_core/video_core.h"
@@ -89,6 +90,30 @@ void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
TouchPressed(framebuffer_x, framebuffer_y);
}
+void EmuWindow::AccelerometerChanged(float x, float y, float z) {
+ constexpr float coef = 512;
+
+ std::lock_guard<std::mutex> lock(accel_mutex);
+
+ // TODO(wwylele): do a time stretch as it in GyroscopeChanged
+ // The time stretch formula should be like
+ // stretched_vector = (raw_vector - gravity) * stretch_ratio + gravity
+ accel_x = static_cast<s16>(x * coef);
+ accel_y = static_cast<s16>(y * coef);
+ accel_z = static_cast<s16>(z * coef);
+}
+
+void EmuWindow::GyroscopeChanged(float x, float y, float z) {
+ constexpr float FULL_FPS = 60;
+ float coef = GetGyroscopeRawToDpsCoefficient();
+ float stretch =
+ FULL_FPS / Common::Profiling::GetTimingResultsAggregator()->GetAggregatedResults().fps;
+ std::lock_guard<std::mutex> lock(gyro_mutex);
+ gyro_x = static_cast<s16>(x * coef * stretch);
+ gyro_y = static_cast<s16>(y * coef * stretch);
+ gyro_z = static_cast<s16>(z * coef * stretch);
+}
+
void EmuWindow::UpdateCurrentFramebufferLayout(unsigned width, unsigned height) {
Layout::FramebufferLayout layout;
switch (Settings::values.layout_option) {
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h
index 835c4d500..1ba64c92b 100644
--- a/src/core/frontend/emu_window.h
+++ b/src/core/frontend/emu_window.h
@@ -4,6 +4,7 @@
#pragma once
+#include <mutex>
#include <tuple>
#include <utility>
#include "common/common_types.h"
@@ -93,6 +94,27 @@ public:
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
/**
+ * Signal accelerometer state has changed.
+ * @param x X-axis accelerometer value
+ * @param y Y-axis accelerometer value
+ * @param z Z-axis accelerometer value
+ * @note all values are in unit of g (gravitational acceleration).
+ * e.g. x = 1.0 means 9.8m/s^2 in x direction.
+ * @see GetAccelerometerState for axis explanation.
+ */
+ void AccelerometerChanged(float x, float y, float z);
+
+ /**
+ * Signal gyroscope state has changed.
+ * @param x X-axis accelerometer value
+ * @param y Y-axis accelerometer value
+ * @param z Z-axis accelerometer value
+ * @note all values are in deg/sec.
+ * @see GetGyroscopeState for axis explanation.
+ */
+ void GyroscopeChanged(float x, float y, float z);
+
+ /**
* Gets the current pad state (which buttons are pressed).
* @note This should be called by the core emu thread to get a state set by the window thread.
* @note This doesn't include analog input like circle pad direction
@@ -134,12 +156,11 @@ public:
* 1 unit of return value = 1/512 g (measured by hw test),
* where g is the gravitational acceleration (9.8 m/sec2).
* @note This should be called by the core emu thread to get a state set by the window thread.
- * @todo Implement accelerometer input in front-end.
* @return std::tuple of (x, y, z)
*/
- std::tuple<s16, s16, s16> GetAccelerometerState() const {
- // stubbed
- return std::make_tuple(0, -512, 0);
+ std::tuple<s16, s16, s16> GetAccelerometerState() {
+ std::lock_guard<std::mutex> lock(accel_mutex);
+ return std::make_tuple(accel_x, accel_y, accel_z);
}
/**
@@ -153,12 +174,11 @@ public:
* 1 unit of return value = (1/coef) deg/sec,
* where coef is the return value of GetGyroscopeRawToDpsCoefficient().
* @note This should be called by the core emu thread to get a state set by the window thread.
- * @todo Implement gyroscope input in front-end.
* @return std::tuple of (x, y, z)
*/
- std::tuple<s16, s16, s16> GetGyroscopeState() const {
- // stubbed
- return std::make_tuple(0, 0, 0);
+ std::tuple<s16, s16, s16> GetGyroscopeState() {
+ std::lock_guard<std::mutex> lock(gyro_mutex);
+ return std::make_tuple(gyro_x, gyro_y, gyro_z);
}
/**
@@ -216,6 +236,12 @@ protected:
circle_pad_x = 0;
circle_pad_y = 0;
touch_pressed = false;
+ accel_x = 0;
+ accel_y = -512;
+ accel_z = 0;
+ gyro_x = 0;
+ gyro_y = 0;
+ gyro_z = 0;
}
virtual ~EmuWindow() {}
@@ -281,6 +307,16 @@ private:
s16 circle_pad_x; ///< Circle pad X-position in native 3DS pixel coordinates (-156 - 156)
s16 circle_pad_y; ///< Circle pad Y-position in native 3DS pixel coordinates (-156 - 156)
+ std::mutex accel_mutex;
+ s16 accel_x; ///< Accelerometer X-axis value in native 3DS units
+ s16 accel_y; ///< Accelerometer Y-axis value in native 3DS units
+ s16 accel_z; ///< Accelerometer Z-axis value in native 3DS units
+
+ std::mutex gyro_mutex;
+ s16 gyro_x; ///< Gyroscope X-axis value in native 3DS units
+ s16 gyro_y; ///< Gyroscope Y-axis value in native 3DS units
+ s16 gyro_z; ///< Gyroscope Z-axis value in native 3DS units
+
/**
* Clip the provided coordinates to be inside the touchscreen area.
*/
diff --git a/src/core/frontend/motion_emu.cpp b/src/core/frontend/motion_emu.cpp
new file mode 100644
index 000000000..9a5b3185d
--- /dev/null
+++ b/src/core/frontend/motion_emu.cpp
@@ -0,0 +1,89 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/math_util.h"
+#include "common/quaternion.h"
+#include "core/frontend/emu_window.h"
+#include "core/frontend/motion_emu.h"
+
+namespace Motion {
+
+static constexpr int update_millisecond = 100;
+static constexpr auto update_duration =
+ std::chrono::duration_cast<std::chrono::steady_clock::duration>(
+ std::chrono::milliseconds(update_millisecond));
+
+MotionEmu::MotionEmu(EmuWindow& emu_window)
+ : motion_emu_thread(&MotionEmu::MotionEmuThread, this, std::ref(emu_window)) {}
+
+MotionEmu::~MotionEmu() {
+ if (motion_emu_thread.joinable()) {
+ shutdown_event.Set();
+ motion_emu_thread.join();
+ }
+}
+
+void MotionEmu::MotionEmuThread(EmuWindow& emu_window) {
+ auto update_time = std::chrono::steady_clock::now();
+ Math::Quaternion<float> q = MakeQuaternion(Math::Vec3<float>(), 0);
+ Math::Quaternion<float> old_q;
+
+ while (!shutdown_event.WaitUntil(update_time)) {
+ update_time += update_duration;
+ old_q = q;
+
+ {
+ std::lock_guard<std::mutex> guard(tilt_mutex);
+
+ // Find the quaternion describing current 3DS tilting
+ q = MakeQuaternion(Math::MakeVec(-tilt_direction.y, 0.0f, tilt_direction.x),
+ tilt_angle);
+ }
+
+ auto inv_q = q.Inverse();
+
+ // Set the gravity vector in world space
+ auto gravity = Math::MakeVec(0.0f, -1.0f, 0.0f);
+
+ // Find the angular rate vector in world space
+ auto angular_rate = ((q - old_q) * inv_q).xyz * 2;
+ angular_rate *= 1000 / update_millisecond / MathUtil::PI * 180;
+
+ // Transform the two vectors from world space to 3DS space
+ gravity = QuaternionRotate(inv_q, gravity);
+ angular_rate = QuaternionRotate(inv_q, angular_rate);
+
+ // Update the sensor state
+ emu_window.AccelerometerChanged(gravity.x, gravity.y, gravity.z);
+ emu_window.GyroscopeChanged(angular_rate.x, angular_rate.y, angular_rate.z);
+ }
+}
+
+void MotionEmu::BeginTilt(int x, int y) {
+ mouse_origin = Math::MakeVec(x, y);
+ is_tilting = true;
+}
+
+void MotionEmu::Tilt(int x, int y) {
+ constexpr float SENSITIVITY = 0.01f;
+ auto mouse_move = Math::MakeVec(x, y) - mouse_origin;
+ if (is_tilting) {
+ std::lock_guard<std::mutex> guard(tilt_mutex);
+ if (mouse_move.x == 0 && mouse_move.y == 0) {
+ tilt_angle = 0;
+ } else {
+ tilt_direction = mouse_move.Cast<float>();
+ tilt_angle = MathUtil::Clamp(tilt_direction.Normalize() * SENSITIVITY, 0.0f,
+ MathUtil::PI * 0.5f);
+ }
+ }
+}
+
+void MotionEmu::EndTilt() {
+ std::lock_guard<std::mutex> guard(tilt_mutex);
+ tilt_angle = 0;
+ is_tilting = false;
+}
+
+} // namespace Motion
diff --git a/src/core/frontend/motion_emu.h b/src/core/frontend/motion_emu.h
new file mode 100644
index 000000000..99d41a726
--- /dev/null
+++ b/src/core/frontend/motion_emu.h
@@ -0,0 +1,52 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+#include "common/thread.h"
+#include "common/vector_math.h"
+
+class EmuWindow;
+
+namespace Motion {
+
+class MotionEmu final {
+public:
+ MotionEmu(EmuWindow& emu_window);
+ ~MotionEmu();
+
+ /**
+ * Signals that a motion sensor tilt has begun.
+ * @param x the x-coordinate of the cursor
+ * @param y the y-coordinate of the cursor
+ */
+ void BeginTilt(int x, int y);
+
+ /**
+ * Signals that a motion sensor tilt is occurring.
+ * @param x the x-coordinate of the cursor
+ * @param y the y-coordinate of the cursor
+ */
+ void Tilt(int x, int y);
+
+ /**
+ * Signals that a motion sensor tilt has ended.
+ */
+ void EndTilt();
+
+private:
+ Math::Vec2<int> mouse_origin;
+
+ std::mutex tilt_mutex;
+ Math::Vec2<float> tilt_direction;
+ float tilt_angle = 0;
+
+ bool is_tilting = false;
+
+ Common::Event shutdown_event;
+ std::thread motion_emu_thread;
+
+ void MotionEmuThread(EmuWindow& emu_window);
+};
+
+} // namespace Motion
diff --git a/src/core/gdbstub/gdbstub.cpp b/src/core/gdbstub/gdbstub.cpp
index d88e25073..5cf45ada5 100644
--- a/src/core/gdbstub/gdbstub.cpp
+++ b/src/core/gdbstub/gdbstub.cpp
@@ -57,7 +57,6 @@ const u32 SIGTERM = 15;
const u32 MSG_WAITALL = 8;
#endif
-const u32 R0_REGISTER = 0;
const u32 R15_REGISTER = 15;
const u32 CPSR_REGISTER = 25;
const u32 FPSCR_REGISTER = 58;
@@ -816,10 +815,6 @@ static void RemoveBreakpoint() {
auto addr_pos = std::find(start_offset, command_buffer + command_length, ',');
PAddr addr = HexToInt(start_offset, static_cast<u32>(addr_pos - start_offset));
- start_offset = addr_pos + 1;
- u32 len =
- HexToInt(start_offset, static_cast<u32>((command_buffer + command_length) - start_offset));
-
if (type == BreakpointType::Access) {
// Access is made up of Read and Write types, so add both breakpoints
type = BreakpointType::Read;
diff --git a/src/core/hle/applets/applet.cpp b/src/core/hle/applets/applet.cpp
index 645b2d5fe..9c43ed2fd 100644
--- a/src/core/hle/applets/applet.cpp
+++ b/src/core/hle/applets/applet.cpp
@@ -12,6 +12,7 @@
#include "core/hle/applets/applet.h"
#include "core/hle/applets/erreula.h"
#include "core/hle/applets/mii_selector.h"
+#include "core/hle/applets/mint.h"
#include "core/hle/applets/swkbd.h"
#include "core/hle/result.h"
#include "core/hle/service/apt/apt.h"
@@ -56,6 +57,10 @@ ResultCode Applet::Create(Service::APT::AppletId id) {
case Service::APT::AppletId::Error2:
applets[id] = std::make_shared<ErrEula>(id);
break;
+ case Service::APT::AppletId::Mint:
+ case Service::APT::AppletId::Mint2:
+ applets[id] = std::make_shared<Mint>(id);
+ break;
default:
LOG_ERROR(Service_APT, "Could not create applet %u", id);
// TODO(Subv): Find the right error code
diff --git a/src/core/hle/applets/mint.cpp b/src/core/hle/applets/mint.cpp
new file mode 100644
index 000000000..31a79ea17
--- /dev/null
+++ b/src/core/hle/applets/mint.cpp
@@ -0,0 +1,72 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "common/string_util.h"
+#include "core/hle/applets/mint.h"
+#include "core/hle/service/apt/apt.h"
+
+namespace HLE {
+namespace Applets {
+
+ResultCode Mint::ReceiveParameter(const Service::APT::MessageParameter& parameter) {
+ if (parameter.signal != static_cast<u32>(Service::APT::SignalType::Request)) {
+ LOG_ERROR(Service_APT, "unsupported signal %u", parameter.signal);
+ UNIMPLEMENTED();
+ // TODO(Subv): Find the right error code
+ return ResultCode(-1);
+ }
+
+ // The Request message contains a buffer with the size of the framebuffer shared
+ // memory.
+ // Create the SharedMemory that will hold the framebuffer data
+ Service::APT::CaptureBufferInfo capture_info;
+ ASSERT(sizeof(capture_info) == parameter.buffer.size());
+
+ memcpy(&capture_info, parameter.buffer.data(), sizeof(capture_info));
+
+ // TODO: allocated memory never released
+ using Kernel::MemoryPermission;
+ // Allocate a heap block of the required size for this applet.
+ heap_memory = std::make_shared<std::vector<u8>>(capture_info.size);
+ // Create a SharedMemory that directly points to this heap block.
+ framebuffer_memory = Kernel::SharedMemory::CreateForApplet(
+ heap_memory, 0, heap_memory->size(), MemoryPermission::ReadWrite,
+ MemoryPermission::ReadWrite, "Mint Memory");
+
+ // Send the response message with the newly created SharedMemory
+ Service::APT::MessageParameter result;
+ result.signal = static_cast<u32>(Service::APT::SignalType::Response);
+ result.buffer.clear();
+ result.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
+ result.sender_id = static_cast<u32>(id);
+ result.object = framebuffer_memory;
+
+ Service::APT::SendParameter(result);
+ return RESULT_SUCCESS;
+}
+
+ResultCode Mint::StartImpl(const Service::APT::AppletStartupParameter& parameter) {
+ is_running = true;
+
+ // TODO(Subv): Set the expected fields in the response buffer before resending it to the
+ // application.
+ // TODO(Subv): Reverse the parameter format for the Mint applet
+
+ // Let the application know that we're closing
+ Service::APT::MessageParameter message;
+ message.buffer.resize(parameter.buffer.size());
+ std::fill(message.buffer.begin(), message.buffer.end(), 0);
+ message.signal = static_cast<u32>(Service::APT::SignalType::WakeupByExit);
+ message.destination_id = static_cast<u32>(Service::APT::AppletId::Application);
+ message.sender_id = static_cast<u32>(id);
+ Service::APT::SendParameter(message);
+
+ is_running = false;
+ return RESULT_SUCCESS;
+}
+
+void Mint::Update() {}
+
+} // namespace Applets
+} // namespace HLE
diff --git a/src/core/hle/applets/mint.h b/src/core/hle/applets/mint.h
new file mode 100644
index 000000000..d23dc40f9
--- /dev/null
+++ b/src/core/hle/applets/mint.h
@@ -0,0 +1,29 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "core/hle/applets/applet.h"
+#include "core/hle/kernel/shared_memory.h"
+
+namespace HLE {
+namespace Applets {
+
+class Mint final : public Applet {
+public:
+ explicit Mint(Service::APT::AppletId id) : Applet(id) {}
+
+ ResultCode ReceiveParameter(const Service::APT::MessageParameter& parameter) override;
+ ResultCode StartImpl(const Service::APT::AppletStartupParameter& parameter) override;
+ void Update() override;
+
+private:
+ /// This SharedMemory will be created when we receive the Request message.
+ /// It holds the framebuffer info retrieved by the application with
+ /// GSPGPU::ImportDisplayCaptureInfo
+ Kernel::SharedPtr<Kernel::SharedMemory> framebuffer_memory;
+};
+
+} // namespace Applets
+} // namespace HLE
diff --git a/src/core/hle/config_mem.cpp b/src/core/hle/config_mem.cpp
index ccd73cfcb..e386ccdc6 100644
--- a/src/core/hle/config_mem.cpp
+++ b/src/core/hle/config_mem.cpp
@@ -14,15 +14,18 @@ ConfigMemDef config_mem;
void Init() {
std::memset(&config_mem, 0, sizeof(config_mem));
- config_mem.update_flag = 0; // No update
+ // Values extracted from firmware 11.2.0-35E
+ config_mem.kernel_version_min = 0x34;
+ config_mem.kernel_version_maj = 0x2;
+ config_mem.ns_tid = 0x0004013000008002;
config_mem.sys_core_ver = 0x2;
config_mem.unit_info = 0x1; // Bit 0 set for Retail
- config_mem.prev_firm = 0;
- config_mem.firm_unk = 0;
- config_mem.firm_version_rev = 0;
- config_mem.firm_version_min = 0x40;
+ config_mem.prev_firm = 0x1;
+ config_mem.ctr_sdk_ver = 0x0000F297;
+ config_mem.firm_version_min = 0x34;
config_mem.firm_version_maj = 0x2;
config_mem.firm_sys_core_ver = 0x2;
+ config_mem.firm_ctr_sdk_ver = 0x0000F297;
}
} // namespace
diff --git a/src/core/hle/hle.cpp b/src/core/hle/hle.cpp
deleted file mode 100644
index d73d98a70..000000000
--- a/src/core/hle/hle.cpp
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "core/arm/arm_interface.h"
-#include "core/core.h"
-#include "core/hle/hle.h"
-#include "core/hle/service/service.h"
-
-////////////////////////////////////////////////////////////////////////////////////////////////////
-
-namespace {
-
-bool reschedule; ///< If true, immediately reschedules the CPU to a new thread
-}
-
-namespace HLE {
-
-void Reschedule(const char* reason) {
- DEBUG_ASSERT_MSG(reason != nullptr && strlen(reason) < 256,
- "Reschedule: Invalid or too long reason.");
-
- // TODO(bunnei): It seems that games depend on some CPU execution time elapsing during HLE
- // routines. This simulates that time by artificially advancing the number of CPU "ticks".
- // The value was chosen empirically, it seems to work well enough for everything tested, but
- // is likely not ideal. We should find a more accurate way to simulate timing with HLE.
- Core::AppCore().AddTicks(4000);
-
- Core::AppCore().PrepareReschedule();
-
- reschedule = true;
-}
-
-bool IsReschedulePending() {
- return reschedule;
-}
-
-void DoneRescheduling() {
- reschedule = false;
-}
-
-void Init() {
- Service::Init();
-
- reschedule = false;
-
- LOG_DEBUG(Kernel, "initialized OK");
-}
-
-void Shutdown() {
- Service::Shutdown();
-
- LOG_DEBUG(Kernel, "shutdown OK");
-}
-
-} // namespace
diff --git a/src/core/hle/kernel/event.cpp b/src/core/hle/kernel/event.cpp
index 3e116e3df..23f9df0d6 100644
--- a/src/core/hle/kernel/event.cpp
+++ b/src/core/hle/kernel/event.cpp
@@ -22,23 +22,17 @@ SharedPtr<Event> Event::Create(ResetType reset_type, std::string name) {
evt->reset_type = reset_type;
evt->name = std::move(name);
- if (reset_type == ResetType::Pulse) {
- LOG_ERROR(Kernel, "Unimplemented event reset type Pulse");
- UNIMPLEMENTED();
- }
-
return evt;
}
-bool Event::ShouldWait() {
+bool Event::ShouldWait(Thread* thread) const {
return !signaled;
}
-void Event::Acquire() {
- ASSERT_MSG(!ShouldWait(), "object unavailable!");
+void Event::Acquire(Thread* thread) {
+ ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
- // Release the event if it's not sticky...
- if (reset_type != ResetType::Sticky)
+ if (reset_type == ResetType::OneShot)
signaled = false;
}
@@ -51,4 +45,11 @@ void Event::Clear() {
signaled = false;
}
+void Event::WakeupAllWaitingThreads() {
+ WaitObject::WakeupAllWaitingThreads();
+
+ if (reset_type == ResetType::Pulse)
+ signaled = false;
+}
+
} // namespace
diff --git a/src/core/hle/kernel/event.h b/src/core/hle/kernel/event.h
index 8dcd23edb..3e3673508 100644
--- a/src/core/hle/kernel/event.h
+++ b/src/core/hle/kernel/event.h
@@ -35,8 +35,10 @@ public:
bool signaled; ///< Whether the event has already been signaled
std::string name; ///< Name of event (optional)
- bool ShouldWait() override;
- void Acquire() override;
+ bool ShouldWait(Thread* thread) const override;
+ void Acquire(Thread* thread) override;
+
+ void WakeupAllWaitingThreads() override;
void Signal();
void Clear();
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp
index 1db8e102f..f599916f0 100644
--- a/src/core/hle/kernel/kernel.cpp
+++ b/src/core/hle/kernel/kernel.cpp
@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <algorithm>
-#include <boost/range/algorithm_ext/erase.hpp>
#include "common/assert.h"
#include "common/logging/log.h"
#include "core/hle/config_mem.h"
@@ -28,32 +27,39 @@ void WaitObject::AddWaitingThread(SharedPtr<Thread> thread) {
void WaitObject::RemoveWaitingThread(Thread* thread) {
auto itr = std::find(waiting_threads.begin(), waiting_threads.end(), thread);
+ // If a thread passed multiple handles to the same object,
+ // the kernel might attempt to remove the thread from the object's
+ // waiting threads list multiple times.
if (itr != waiting_threads.end())
waiting_threads.erase(itr);
}
SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
- // Remove the threads that are ready or already running from our waitlist
- boost::range::remove_erase_if(waiting_threads, [](const SharedPtr<Thread>& thread) {
- return thread->status == THREADSTATUS_RUNNING || thread->status == THREADSTATUS_READY ||
- thread->status == THREADSTATUS_DEAD;
- });
-
- // TODO(Subv): This call should be performed inside the loop below to check if an object can be
- // acquired by a particular thread. This is useful for things like recursive locking of Mutexes.
- if (ShouldWait())
- return nullptr;
-
Thread* candidate = nullptr;
s32 candidate_priority = THREADPRIO_LOWEST + 1;
for (const auto& thread : waiting_threads) {
+ // The list of waiting threads must not contain threads that are not waiting to be awakened.
+ ASSERT_MSG(thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
+ thread->status == THREADSTATUS_WAIT_SYNCH_ALL,
+ "Inconsistent thread statuses in waiting_threads");
+
if (thread->current_priority >= candidate_priority)
continue;
- bool ready_to_run =
- std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(),
- [](const SharedPtr<WaitObject>& object) { return object->ShouldWait(); });
+ if (ShouldWait(thread.get()))
+ continue;
+
+ // A thread is ready to run if it's either in THREADSTATUS_WAIT_SYNCH_ANY or
+ // in THREADSTATUS_WAIT_SYNCH_ALL and the rest of the objects it is waiting on are ready.
+ bool ready_to_run = true;
+ if (thread->status == THREADSTATUS_WAIT_SYNCH_ALL) {
+ ready_to_run = std::none_of(thread->wait_objects.begin(), thread->wait_objects.end(),
+ [&thread](const SharedPtr<WaitObject>& object) {
+ return object->ShouldWait(thread.get());
+ });
+ }
+
if (ready_to_run) {
candidate = thread.get();
candidate_priority = thread->current_priority;
@@ -66,7 +72,7 @@ SharedPtr<Thread> WaitObject::GetHighestPriorityReadyThread() {
void WaitObject::WakeupAllWaitingThreads() {
while (auto thread = GetHighestPriorityReadyThread()) {
if (!thread->IsSleepingOnWaitAll()) {
- Acquire();
+ Acquire(thread.get());
// Set the output index of the WaitSynchronizationN call to the index of this object.
if (thread->wait_set_output) {
thread->SetWaitSynchronizationOutput(thread->GetWaitObjectIndex(this));
@@ -74,18 +80,17 @@ void WaitObject::WakeupAllWaitingThreads() {
}
} else {
for (auto& object : thread->wait_objects) {
- object->Acquire();
- object->RemoveWaitingThread(thread.get());
+ object->Acquire(thread.get());
}
// Note: This case doesn't update the output index of WaitSynchronizationN.
- // Clear the thread's waitlist
- thread->wait_objects.clear();
}
+ for (auto& object : thread->wait_objects)
+ object->RemoveWaitingThread(thread.get());
+ thread->wait_objects.clear();
+
thread->SetWaitSynchronizationResult(RESULT_SUCCESS);
thread->ResumeFromWait();
- // Note: Removing the thread from the object's waitlist will be
- // done by GetHighestPriorityReadyThread.
}
}
diff --git a/src/core/hle/kernel/kernel.h b/src/core/hle/kernel/kernel.h
index 9503e7d04..bb8b99bb5 100644
--- a/src/core/hle/kernel/kernel.h
+++ b/src/core/hle/kernel/kernel.h
@@ -132,31 +132,32 @@ using SharedPtr = boost::intrusive_ptr<T>;
class WaitObject : public Object {
public:
/**
- * Check if the current thread should wait until the object is available
+ * Check if the specified thread should wait until the object is available
+ * @param thread The thread about which we're deciding.
* @return True if the current thread should wait due to this object being unavailable
*/
- virtual bool ShouldWait() = 0;
+ virtual bool ShouldWait(Thread* thread) const = 0;
- /// Acquire/lock the object if it is available
- virtual void Acquire() = 0;
+ /// Acquire/lock the object for the specified thread if it is available
+ virtual void Acquire(Thread* thread) = 0;
/**
* Add a thread to wait on this object
* @param thread Pointer to thread to add
*/
- void AddWaitingThread(SharedPtr<Thread> thread);
+ virtual void AddWaitingThread(SharedPtr<Thread> thread);
/**
* Removes a thread from waiting on this object (e.g. if it was resumed already)
* @param thread Pointer to thread to remove
*/
- void RemoveWaitingThread(Thread* thread);
+ virtual void RemoveWaitingThread(Thread* thread);
/**
* Wake up all threads waiting on this object that can be awoken, in priority order,
* and set the synchronization result and output of the thread.
*/
- void WakeupAllWaitingThreads();
+ virtual void WakeupAllWaitingThreads();
/// Obtains the highest priority thread that is ready to run from this object's waiting list.
SharedPtr<Thread> GetHighestPriorityReadyThread();
diff --git a/src/core/hle/kernel/mutex.cpp b/src/core/hle/kernel/mutex.cpp
index 736944bae..cef961289 100644
--- a/src/core/hle/kernel/mutex.cpp
+++ b/src/core/hle/kernel/mutex.cpp
@@ -6,26 +6,18 @@
#include <vector>
#include <boost/range/algorithm_ext/erase.hpp>
#include "common/assert.h"
+#include "core/core.h"
#include "core/hle/kernel/kernel.h"
#include "core/hle/kernel/mutex.h"
#include "core/hle/kernel/thread.h"
namespace Kernel {
-/**
- * Resumes a thread waiting for the specified mutex
- * @param mutex The mutex that some thread is waiting on
- */
-static void ResumeWaitingThread(Mutex* mutex) {
- // Reset mutex lock thread handle, nothing is waiting
- mutex->lock_count = 0;
- mutex->holding_thread = nullptr;
- mutex->WakeupAllWaitingThreads();
-}
-
void ReleaseThreadMutexes(Thread* thread) {
for (auto& mtx : thread->held_mutexes) {
- ResumeWaitingThread(mtx.get());
+ mtx->lock_count = 0;
+ mtx->holding_thread = nullptr;
+ mtx->WakeupAllWaitingThreads();
}
thread->held_mutexes.clear();
}
@@ -40,52 +32,74 @@ SharedPtr<Mutex> Mutex::Create(bool initial_locked, std::string name) {
mutex->name = std::move(name);
mutex->holding_thread = nullptr;
- // Acquire mutex with current thread if initialized as locked...
+ // Acquire mutex with current thread if initialized as locked
if (initial_locked)
- mutex->Acquire();
+ mutex->Acquire(GetCurrentThread());
return mutex;
}
-bool Mutex::ShouldWait() {
- auto thread = GetCurrentThread();
- bool wait = lock_count > 0 && holding_thread != thread;
-
- // If the holding thread of the mutex is lower priority than this thread, that thread should
- // temporarily inherit this thread's priority
- if (wait && thread->current_priority < holding_thread->current_priority)
- holding_thread->BoostPriority(thread->current_priority);
-
- return wait;
-}
-
-void Mutex::Acquire() {
- Acquire(GetCurrentThread());
+bool Mutex::ShouldWait(Thread* thread) const {
+ return lock_count > 0 && thread != holding_thread;
}
-void Mutex::Acquire(SharedPtr<Thread> thread) {
- ASSERT_MSG(!ShouldWait(), "object unavailable!");
+void Mutex::Acquire(Thread* thread) {
+ ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
- // Actually "acquire" the mutex only if we don't already have it...
+ // Actually "acquire" the mutex only if we don't already have it
if (lock_count == 0) {
+ priority = thread->current_priority;
thread->held_mutexes.insert(this);
- holding_thread = std::move(thread);
+ holding_thread = thread;
+ thread->UpdatePriority();
+ Core::System::GetInstance().PrepareReschedule();
}
lock_count++;
}
void Mutex::Release() {
- // Only release if the mutex is held...
+ // Only release if the mutex is held
if (lock_count > 0) {
lock_count--;
- // Yield to the next thread only if we've fully released the mutex...
+ // Yield to the next thread only if we've fully released the mutex
if (lock_count == 0) {
holding_thread->held_mutexes.erase(this);
- ResumeWaitingThread(this);
+ holding_thread->UpdatePriority();
+ holding_thread = nullptr;
+ WakeupAllWaitingThreads();
+ Core::System::GetInstance().PrepareReschedule();
}
}
}
+void Mutex::AddWaitingThread(SharedPtr<Thread> thread) {
+ WaitObject::AddWaitingThread(thread);
+ thread->pending_mutexes.insert(this);
+ UpdatePriority();
+}
+
+void Mutex::RemoveWaitingThread(Thread* thread) {
+ WaitObject::RemoveWaitingThread(thread);
+ thread->pending_mutexes.erase(this);
+ UpdatePriority();
+}
+
+void Mutex::UpdatePriority() {
+ if (!holding_thread)
+ return;
+
+ s32 best_priority = THREADPRIO_LOWEST;
+ for (auto& waiter : GetWaitingThreads()) {
+ if (waiter->current_priority < best_priority)
+ best_priority = waiter->current_priority;
+ }
+
+ if (best_priority != priority) {
+ priority = best_priority;
+ holding_thread->UpdatePriority();
+ }
+}
+
} // namespace
diff --git a/src/core/hle/kernel/mutex.h b/src/core/hle/kernel/mutex.h
index 53c3dc1f1..c57adf400 100644
--- a/src/core/hle/kernel/mutex.h
+++ b/src/core/hle/kernel/mutex.h
@@ -35,17 +35,22 @@ public:
}
int lock_count; ///< Number of times the mutex has been acquired
+ u32 priority; ///< The priority of the mutex, used for priority inheritance.
std::string name; ///< Name of mutex (optional)
SharedPtr<Thread> holding_thread; ///< Thread that has acquired the mutex
- bool ShouldWait() override;
- void Acquire() override;
-
/**
- * Acquires the specified mutex for the specified thread
- * @param thread Thread that will acquire the mutex
+ * Elevate the mutex priority to the best priority
+ * among the priorities of all its waiting threads.
*/
- void Acquire(SharedPtr<Thread> thread);
+ void UpdatePriority();
+
+ bool ShouldWait(Thread* thread) const override;
+ void Acquire(Thread* thread) override;
+
+ void AddWaitingThread(SharedPtr<Thread> thread) override;
+ void RemoveWaitingThread(Thread* thread) override;
+
void Release();
private:
diff --git a/src/core/hle/kernel/resource_limit.cpp b/src/core/hle/kernel/resource_limit.cpp
index 253ab7045..3f51bc5de 100644
--- a/src/core/hle/kernel/resource_limit.cpp
+++ b/src/core/hle/kernel/resource_limit.cpp
@@ -62,6 +62,8 @@ s32 ResourceLimit::GetCurrentResourceValue(u32 resource) const {
s32 ResourceLimit::GetMaxResourceValue(u32 resource) const {
switch (resource) {
+ case PRIORITY:
+ return max_priority;
case COMMIT:
return max_commit;
case THREAD:
diff --git a/src/core/hle/kernel/semaphore.cpp b/src/core/hle/kernel/semaphore.cpp
index bf7600780..8bda2f75d 100644
--- a/src/core/hle/kernel/semaphore.cpp
+++ b/src/core/hle/kernel/semaphore.cpp
@@ -30,12 +30,13 @@ ResultVal<SharedPtr<Semaphore>> Semaphore::Create(s32 initial_count, s32 max_cou
return MakeResult<SharedPtr<Semaphore>>(std::move(semaphore));
}
-bool Semaphore::ShouldWait() {
+bool Semaphore::ShouldWait(Thread* thread) const {
return available_count <= 0;
}
-void Semaphore::Acquire() {
- ASSERT_MSG(!ShouldWait(), "object unavailable!");
+void Semaphore::Acquire(Thread* thread) {
+ if (available_count <= 0)
+ return;
--available_count;
}
diff --git a/src/core/hle/kernel/semaphore.h b/src/core/hle/kernel/semaphore.h
index e01908a25..cde94f7cc 100644
--- a/src/core/hle/kernel/semaphore.h
+++ b/src/core/hle/kernel/semaphore.h
@@ -39,8 +39,8 @@ public:
s32 available_count; ///< Number of free slots left in the semaphore
std::string name; ///< Name of semaphore (optional)
- bool ShouldWait() override;
- void Acquire() override;
+ bool ShouldWait(Thread* thread) const override;
+ void Acquire(Thread* thread) override;
/**
* Releases a certain number of slots from a semaphore.
diff --git a/src/core/hle/kernel/server_port.cpp b/src/core/hle/kernel/server_port.cpp
index 6c19aa7c0..fd3bbbcad 100644
--- a/src/core/hle/kernel/server_port.cpp
+++ b/src/core/hle/kernel/server_port.cpp
@@ -14,13 +14,13 @@ namespace Kernel {
ServerPort::ServerPort() {}
ServerPort::~ServerPort() {}
-bool ServerPort::ShouldWait() {
+bool ServerPort::ShouldWait(Thread* thread) const {
// If there are no pending sessions, we wait until a new one is added.
return pending_sessions.size() == 0;
}
-void ServerPort::Acquire() {
- ASSERT_MSG(!ShouldWait(), "object unavailable!");
+void ServerPort::Acquire(Thread* thread) {
+ ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
std::tuple<SharedPtr<ServerPort>, SharedPtr<ClientPort>> ServerPort::CreatePortPair(
diff --git a/src/core/hle/kernel/server_port.h b/src/core/hle/kernel/server_port.h
index b0f8df62c..6f8bdb6a9 100644
--- a/src/core/hle/kernel/server_port.h
+++ b/src/core/hle/kernel/server_port.h
@@ -53,8 +53,8 @@ public:
/// ServerSessions created from this port inherit a reference to this handler.
std::shared_ptr<Service::SessionRequestHandler> hle_handler;
- bool ShouldWait() override;
- void Acquire() override;
+ bool ShouldWait(Thread* thread) const override;
+ void Acquire(Thread* thread) override;
private:
ServerPort();
diff --git a/src/core/hle/kernel/server_session.cpp b/src/core/hle/kernel/server_session.cpp
index 146458c1c..9447ff236 100644
--- a/src/core/hle/kernel/server_session.cpp
+++ b/src/core/hle/kernel/server_session.cpp
@@ -29,12 +29,12 @@ ResultVal<SharedPtr<ServerSession>> ServerSession::Create(
return MakeResult<SharedPtr<ServerSession>>(std::move(server_session));
}
-bool ServerSession::ShouldWait() {
+bool ServerSession::ShouldWait(Thread* thread) const {
return !signaled;
}
-void ServerSession::Acquire() {
- ASSERT_MSG(!ShouldWait(), "object unavailable!");
+void ServerSession::Acquire(Thread* thread) {
+ ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
signaled = false;
}
diff --git a/src/core/hle/kernel/server_session.h b/src/core/hle/kernel/server_session.h
index 458284a5d..c088b9a19 100644
--- a/src/core/hle/kernel/server_session.h
+++ b/src/core/hle/kernel/server_session.h
@@ -57,9 +57,9 @@ public:
*/
ResultCode HandleSyncRequest();
- bool ShouldWait() override;
+ bool ShouldWait(Thread* thread) const override;
- void Acquire() override;
+ void Acquire(Thread* thread) override;
std::string name; ///< The name of this session (optional)
bool signaled; ///< Whether there's new data available to this ServerSession
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp
index 5fb95dada..3b7555d87 100644
--- a/src/core/hle/kernel/thread.cpp
+++ b/src/core/hle/kernel/thread.cpp
@@ -27,12 +27,12 @@ namespace Kernel {
/// Event type for the thread wake up event
static int ThreadWakeupEventType;
-bool Thread::ShouldWait() {
+bool Thread::ShouldWait(Thread* thread) const {
return status != THREADSTATUS_DEAD;
}
-void Thread::Acquire() {
- ASSERT_MSG(!ShouldWait(), "object unavailable!");
+void Thread::Acquire(Thread* thread) {
+ ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
}
// TODO(yuriks): This can be removed if Thread objects are explicitly pooled in the future, allowing
@@ -66,20 +66,6 @@ Thread* GetCurrentThread() {
}
/**
- * Check if a thread is waiting on the specified wait object
- * @param thread The thread to test
- * @param wait_object The object to test against
- * @return True if the thread is waiting, false otherwise
- */
-static bool CheckWait_WaitObject(const Thread* thread, WaitObject* wait_object) {
- if (thread->status != THREADSTATUS_WAIT_SYNCH)
- return false;
-
- auto itr = std::find(thread->wait_objects.begin(), thread->wait_objects.end(), wait_object);
- return itr != thread->wait_objects.end();
-}
-
-/**
* Check if the specified thread is waiting on the specified address to be arbitrated
* @param thread The thread to test
* @param wait_address The address to test against
@@ -90,9 +76,6 @@ static bool CheckWait_AddressArbiter(const Thread* thread, VAddr wait_address) {
}
void Thread::Stop() {
- // Release all the mutexes that this thread holds
- ReleaseThreadMutexes(this);
-
// Cancel any outstanding wakeup events for this thread
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, callback_handle);
wakeup_callback_handle_table.Close(callback_handle);
@@ -114,6 +97,9 @@ void Thread::Stop() {
}
wait_objects.clear();
+ // Release all the mutexes that this thread holds
+ ReleaseThreadMutexes(this);
+
// Mark the TLS slot in the thread's page as free.
u32 tls_page = (tls_address - Memory::TLS_AREA_VADDR) / Memory::PAGE_SIZE;
u32 tls_slot =
@@ -155,28 +141,6 @@ void ArbitrateAllThreads(u32 address) {
}
}
-/// Boost low priority threads (temporarily) that have been starved
-static void PriorityBoostStarvedThreads() {
- u64 current_ticks = CoreTiming::GetTicks();
-
- for (auto& thread : thread_list) {
- // TODO(bunnei): Threads that have been waiting to be scheduled for `boost_ticks` (or
- // longer) will have their priority temporarily adjusted to 1 higher than the highest
- // priority thread to prevent thread starvation. This general behavior has been verified
- // on hardware. However, this is almost certainly not perfect, and the real CTR OS scheduler
- // should probably be reversed to verify this.
-
- const u64 boost_timeout = 2000000; // Boost threads that have been ready for > this long
-
- u64 delta = current_ticks - thread->last_running_ticks;
-
- if (thread->status == THREADSTATUS_READY && delta > boost_timeout) {
- const s32 priority = std::max(ready_queue.get_first()->current_priority - 1, 0);
- thread->BoostPriority(priority);
- }
- }
-}
-
/**
* Switches the CPU's active thread context to that of the specified thread
* @param new_thread The thread to switch to
@@ -199,8 +163,8 @@ static void SwitchContext(Thread* new_thread) {
// Load context of new thread
if (new_thread) {
- DEBUG_ASSERT_MSG(new_thread->status == THREADSTATUS_READY,
- "Thread must be ready to become running.");
+ ASSERT_MSG(new_thread->status == THREADSTATUS_READY,
+ "Thread must be ready to become running.");
// Cancel any outstanding wakeup events for this thread
CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle);
@@ -210,9 +174,6 @@ static void SwitchContext(Thread* new_thread) {
ready_queue.remove(new_thread->current_priority, new_thread);
new_thread->status = THREADSTATUS_RUNNING;
- // Restores thread to its nominal priority if it has been temporarily changed
- new_thread->current_priority = new_thread->nominal_priority;
-
Core::CPU().LoadContext(new_thread->context);
Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress());
} else {
@@ -248,14 +209,6 @@ void WaitCurrentThread_Sleep() {
thread->status = THREADSTATUS_WAIT_SLEEP;
}
-void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects,
- bool wait_set_output) {
- Thread* thread = GetCurrentThread();
- thread->wait_set_output = wait_set_output;
- thread->wait_objects = std::move(wait_objects);
- thread->status = THREADSTATUS_WAIT_SYNCH;
-}
-
void WaitCurrentThread_ArbitrateAddress(VAddr wait_address) {
Thread* thread = GetCurrentThread();
thread->wait_address = wait_address;
@@ -281,7 +234,8 @@ static void ThreadWakeupCallback(u64 thread_handle, int cycles_late) {
return;
}
- if (thread->status == THREADSTATUS_WAIT_SYNCH || thread->status == THREADSTATUS_WAIT_ARB) {
+ if (thread->status == THREADSTATUS_WAIT_SYNCH_ANY ||
+ thread->status == THREADSTATUS_WAIT_SYNCH_ALL || thread->status == THREADSTATUS_WAIT_ARB) {
thread->wait_set_output = false;
// Remove the thread from each of its waiting objects' waitlists
for (auto& object : thread->wait_objects)
@@ -305,8 +259,11 @@ void Thread::WakeAfterDelay(s64 nanoseconds) {
}
void Thread::ResumeFromWait() {
+ ASSERT_MSG(wait_objects.empty(), "Thread is waking up while waiting for objects");
+
switch (status) {
- case THREADSTATUS_WAIT_SYNCH:
+ case THREADSTATUS_WAIT_SYNCH_ALL:
+ case THREADSTATUS_WAIT_SYNCH_ANY:
case THREADSTATUS_WAIT_ARB:
case THREADSTATUS_WAIT_SLEEP:
break;
@@ -396,14 +353,8 @@ static void ResetThreadContext(ARM_Interface::ThreadContext& context, u32 stack_
ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point, s32 priority,
u32 arg, s32 processor_id, VAddr stack_top) {
- if (priority < THREADPRIO_HIGHEST || priority > THREADPRIO_LOWEST) {
- s32 new_priority = MathUtil::Clamp<s32>(priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST);
- LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d", name.c_str(),
- priority, new_priority);
- // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm
- // validity of this
- priority = new_priority;
- }
+ ASSERT_MSG(priority >= THREADPRIO_HIGHEST && priority <= THREADPRIO_LOWEST,
+ "Invalid thread priority");
if (!Memory::IsValidVirtualAddress(entry_point)) {
LOG_ERROR(Kernel_SVC, "(name=%s): invalid entry %08x", name.c_str(), entry_point);
@@ -487,25 +438,9 @@ ResultVal<SharedPtr<Thread>> Thread::Create(std::string name, VAddr entry_point,
return MakeResult<SharedPtr<Thread>>(std::move(thread));
}
-// TODO(peachum): Remove this. Range checking should be done, and an appropriate error should be
-// returned.
-static void ClampPriority(const Thread* thread, s32* priority) {
- if (*priority < THREADPRIO_HIGHEST || *priority > THREADPRIO_LOWEST) {
- DEBUG_ASSERT_MSG(
- false, "Application passed an out of range priority. An error should be returned.");
-
- s32 new_priority = MathUtil::Clamp<s32>(*priority, THREADPRIO_HIGHEST, THREADPRIO_LOWEST);
- LOG_WARNING(Kernel_SVC, "(name=%s): invalid priority=%d, clamping to %d",
- thread->name.c_str(), *priority, new_priority);
- // TODO(bunnei): Clamping to a valid priority is not necessarily correct behavior... Confirm
- // validity of this
- *priority = new_priority;
- }
-}
-
void Thread::SetPriority(s32 priority) {
- ClampPriority(this, &priority);
-
+ ASSERT_MSG(priority <= THREADPRIO_LOWEST && priority >= THREADPRIO_HIGHEST,
+ "Invalid priority value.");
// If thread was ready, adjust queues
if (status == THREADSTATUS_READY)
ready_queue.move(this, current_priority, priority);
@@ -515,8 +450,21 @@ void Thread::SetPriority(s32 priority) {
nominal_priority = current_priority = priority;
}
+void Thread::UpdatePriority() {
+ s32 best_priority = nominal_priority;
+ for (auto& mutex : held_mutexes) {
+ if (mutex->priority < best_priority)
+ best_priority = mutex->priority;
+ }
+ BoostPriority(best_priority);
+}
+
void Thread::BoostPriority(s32 priority) {
- ready_queue.move(this, current_priority, priority);
+ // If thread was ready, adjust queues
+ if (status == THREADSTATUS_READY)
+ ready_queue.move(this, current_priority, priority);
+ else
+ ready_queue.prepare(priority);
current_priority = priority;
}
@@ -538,9 +486,11 @@ SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority) {
return thread;
}
-void Reschedule() {
- PriorityBoostStarvedThreads();
+bool HaveReadyThreads() {
+ return ready_queue.get_first() != nullptr;
+}
+void Reschedule() {
Thread* cur = GetCurrentThread();
Thread* next = PopNextReadyThread();
@@ -563,6 +513,12 @@ void Thread::SetWaitSynchronizationOutput(s32 output) {
context.cpu_registers[1] = output;
}
+s32 Thread::GetWaitObjectIndex(WaitObject* object) const {
+ ASSERT_MSG(!wait_objects.empty(), "Thread is not waiting for anything");
+ auto match = std::find(wait_objects.rbegin(), wait_objects.rend(), object);
+ return std::distance(match, wait_objects.rend()) - 1;
+}
+
////////////////////////////////////////////////////////////////////////////////////////////////////
void ThreadingInit() {
diff --git a/src/core/hle/kernel/thread.h b/src/core/hle/kernel/thread.h
index c77ac644d..c557a2279 100644
--- a/src/core/hle/kernel/thread.h
+++ b/src/core/hle/kernel/thread.h
@@ -31,13 +31,14 @@ enum ThreadProcessorId : s32 {
};
enum ThreadStatus {
- THREADSTATUS_RUNNING, ///< Currently running
- THREADSTATUS_READY, ///< Ready to run
- THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter
- THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC
- THREADSTATUS_WAIT_SYNCH, ///< Waiting due to a WaitSynchronization SVC
- THREADSTATUS_DORMANT, ///< Created but not yet made ready
- THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
+ THREADSTATUS_RUNNING, ///< Currently running
+ THREADSTATUS_READY, ///< Ready to run
+ THREADSTATUS_WAIT_ARB, ///< Waiting on an address arbiter
+ THREADSTATUS_WAIT_SLEEP, ///< Waiting due to a SleepThread SVC
+ THREADSTATUS_WAIT_SYNCH_ANY, ///< Waiting due to WaitSynch1 or WaitSynchN with wait_all = false
+ THREADSTATUS_WAIT_SYNCH_ALL, ///< Waiting due to WaitSynchronizationN with wait_all = true
+ THREADSTATUS_DORMANT, ///< Created but not yet made ready
+ THREADSTATUS_DEAD ///< Run to completion, or forcefully terminated
};
namespace Kernel {
@@ -72,8 +73,8 @@ public:
return HANDLE_TYPE;
}
- bool ShouldWait() override;
- void Acquire() override;
+ bool ShouldWait(Thread* thread) const override;
+ void Acquire(Thread* thread) override;
/**
* Gets the thread's current priority
@@ -90,6 +91,12 @@ public:
void SetPriority(s32 priority);
/**
+ * Boost's a thread's priority to the best priority among the thread's held mutexes.
+ * This prevents priority inversion via priority inheritance.
+ */
+ void UpdatePriority();
+
+ /**
* Temporarily boosts the thread's priority until the next time it is scheduled
* @param priority The new priority
*/
@@ -128,13 +135,14 @@ public:
/**
* Retrieves the index that this particular object occupies in the list of objects
- * that the thread passed to WaitSynchronizationN.
+ * that the thread passed to WaitSynchronizationN, starting the search from the last element.
* It is used to set the output value of WaitSynchronizationN when the thread is awakened.
+ * When a thread wakes up due to an object signal, the kernel will use the index of the last
+ * matching object in the wait objects list in case of having multiple instances of the same
+ * object in the list.
* @param object Object to query the index of.
*/
- s32 GetWaitObjectIndex(const WaitObject* object) const {
- return wait_objects_index.at(object->GetObjectId());
- }
+ s32 GetWaitObjectIndex(WaitObject* object) const;
/**
* Stops a thread, invalidating it from further use
@@ -152,10 +160,10 @@ public:
/**
* Returns whether this thread is waiting for all the objects in
* its wait list to become ready, as a result of a WaitSynchronizationN call
- * with wait_all = true, or a ReplyAndReceive call.
+ * with wait_all = true.
*/
bool IsSleepingOnWaitAll() const {
- return !wait_objects.empty();
+ return status == THREADSTATUS_WAIT_SYNCH_ALL;
}
ARM_Interface::ThreadContext context;
@@ -178,15 +186,15 @@ public:
/// Mutexes currently held by this thread, which will be released when it exits.
boost::container::flat_set<SharedPtr<Mutex>> held_mutexes;
+ /// Mutexes that this thread is currently waiting for.
+ boost::container::flat_set<SharedPtr<Mutex>> pending_mutexes;
+
SharedPtr<Process> owner_process; ///< Process that owns this thread
- /// Objects that the thread is waiting on.
- /// This is only populated when the thread should wait for all the objects to become ready.
+ /// Objects that the thread is waiting on, in the same order as they were
+ // passed to WaitSynchronization1/N.
std::vector<SharedPtr<WaitObject>> wait_objects;
- /// Mapping of Object ids to their position in the last waitlist that this object waited on.
- boost::container::flat_map<int, s32> wait_objects_index;
-
VAddr wait_address; ///< If waiting on an AddressArbiter, this is the arbitration address
/// True if the WaitSynchronizationN output parameter should be set on thread wakeup.
@@ -211,6 +219,11 @@ private:
SharedPtr<Thread> SetupMainThread(u32 entry_point, s32 priority);
/**
+ * Returns whether there are any threads that are ready to run.
+ */
+bool HaveReadyThreads();
+
+/**
* Reschedules to the next available thread (call after current thread is suspended)
*/
void Reschedule();
@@ -238,15 +251,6 @@ Thread* GetCurrentThread();
void WaitCurrentThread_Sleep();
/**
- * Waits the current thread from a WaitSynchronization call
- * @param wait_objects Kernel objects that we are waiting on
- * @param wait_set_output If true, set the output parameter on thread wakeup (for
- * WaitSynchronizationN only)
- */
-void WaitCurrentThread_WaitSynchronization(std::vector<SharedPtr<WaitObject>> wait_objects,
- bool wait_set_output);
-
-/**
* Waits the current thread from an ArbitrateAddress call
* @param wait_address Arbitration address used to resume from wait
*/
diff --git a/src/core/hle/kernel/timer.cpp b/src/core/hle/kernel/timer.cpp
index b50cf520d..60537f355 100644
--- a/src/core/hle/kernel/timer.cpp
+++ b/src/core/hle/kernel/timer.cpp
@@ -31,20 +31,15 @@ SharedPtr<Timer> Timer::Create(ResetType reset_type, std::string name) {
timer->interval_delay = 0;
timer->callback_handle = timer_callback_handle_table.Create(timer).MoveFrom();
- if (reset_type == ResetType::Pulse) {
- LOG_ERROR(Kernel, "Unimplemented timer reset type Pulse");
- UNIMPLEMENTED();
- }
-
return timer;
}
-bool Timer::ShouldWait() {
+bool Timer::ShouldWait(Thread* thread) const {
return !signaled;
}
-void Timer::Acquire() {
- ASSERT_MSG(!ShouldWait(), "object unavailable!");
+void Timer::Acquire(Thread* thread) {
+ ASSERT_MSG(!ShouldWait(thread), "object unavailable!");
if (reset_type == ResetType::OneShot)
signaled = false;
@@ -70,6 +65,13 @@ void Timer::Clear() {
signaled = false;
}
+void Timer::WakeupAllWaitingThreads() {
+ WaitObject::WakeupAllWaitingThreads();
+
+ if (reset_type == ResetType::Pulse)
+ signaled = false;
+}
+
/// The timer callback event, called when a timer is fired
static void TimerCallback(u64 timer_handle, int cycles_late) {
SharedPtr<Timer> timer =
diff --git a/src/core/hle/kernel/timer.h b/src/core/hle/kernel/timer.h
index 18ea0236b..c174f5664 100644
--- a/src/core/hle/kernel/timer.h
+++ b/src/core/hle/kernel/timer.h
@@ -39,8 +39,10 @@ public:
u64 initial_delay; ///< The delay until the timer fires for the first time
u64 interval_delay; ///< The delay until the timer fires after the first time
- bool ShouldWait() override;
- void Acquire() override;
+ bool ShouldWait(Thread* thread) const override;
+ void Acquire(Thread* thread) override;
+
+ void WakeupAllWaitingThreads() override;
/**
* Starts the timer, with the specified initial delay and interval.
diff --git a/src/core/hle/service/ac/ac.cpp b/src/core/hle/service/ac/ac.cpp
new file mode 100644
index 000000000..aa270a2c3
--- /dev/null
+++ b/src/core/hle/service/ac/ac.cpp
@@ -0,0 +1,181 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <array>
+
+#include "common/logging/log.h"
+#include "core/hle/kernel/event.h"
+#include "core/hle/service/ac/ac.h"
+#include "core/hle/service/ac/ac_i.h"
+#include "core/hle/service/ac/ac_u.h"
+
+namespace Service {
+namespace AC {
+
+struct ACConfig {
+ std::array<u8, 0x200> data;
+};
+
+static ACConfig default_config{};
+
+static bool ac_connected = false;
+
+static Kernel::SharedPtr<Kernel::Event> close_event;
+static Kernel::SharedPtr<Kernel::Event> connect_event;
+static Kernel::SharedPtr<Kernel::Event> disconnect_event;
+
+void CreateDefaultConfig(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ u32 ac_config_addr = cmd_buff[65];
+
+ ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2),
+ "Output buffer size not equal ACConfig size");
+
+ Memory::WriteBlock(ac_config_addr, &default_config, sizeof(ACConfig));
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void ConnectAsync(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ connect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
+ if (connect_event) {
+ connect_event->name = "AC:connect_event";
+ connect_event->Signal();
+ ac_connected = true;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void GetConnectResult(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void CloseAsync(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ if (ac_connected && disconnect_event) {
+ disconnect_event->Signal();
+ }
+
+ close_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
+ if (close_event) {
+ close_event->name = "AC:close_event";
+ close_event->Signal();
+ }
+
+ ac_connected = false;
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void GetCloseResult(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void GetWifiStatus(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ // TODO(purpasmart96): This function is only a stub,
+ // it returns a valid result without implementing full functionality.
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = 0; // Connection type set to none
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void GetInfraPriority(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = 0; // Infra Priority, default 0
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void SetRequestEulaVersion(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ u32 major = cmd_buff[1] & 0xFF;
+ u32 minor = cmd_buff[2] & 0xFF;
+
+ ASSERT_MSG(cmd_buff[3] == (sizeof(ACConfig) << 14 | 2),
+ "Input buffer size not equal ACConfig size");
+ ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2),
+ "Output buffer size not equal ACConfig size");
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = 0; // Infra Priority
+
+ LOG_WARNING(Service_AC, "(STUBBED) called, major=%u, minor=%u", major, minor);
+}
+
+void RegisterDisconnectEvent(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ disconnect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
+ if (disconnect_event) {
+ disconnect_event->name = "AC:disconnect_event";
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void IsConnected(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = ac_connected;
+
+ LOG_WARNING(Service_AC, "(STUBBED) called");
+}
+
+void SetClientVersion(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const u32 version = cmd_buff[1];
+ self->SetVersion(version);
+
+ LOG_WARNING(Service_AC, "(STUBBED) called, version: 0x%08X", version);
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+}
+
+void Init() {
+ AddService(new AC_I);
+ AddService(new AC_U);
+
+ ac_connected = false;
+
+ close_event = nullptr;
+ connect_event = nullptr;
+ disconnect_event = nullptr;
+}
+
+void Shutdown() {
+ ac_connected = false;
+
+ close_event = nullptr;
+ connect_event = nullptr;
+ disconnect_event = nullptr;
+}
+
+} // namespace AC
+} // namespace Service
diff --git a/src/core/hle/service/ac/ac.h b/src/core/hle/service/ac/ac.h
new file mode 100644
index 000000000..6185faf9b
--- /dev/null
+++ b/src/core/hle/service/ac/ac.h
@@ -0,0 +1,134 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+namespace Service {
+
+class Interface;
+
+namespace AC {
+
+/**
+ * AC::CreateDefaultConfig service function
+ * Inputs:
+ * 64 : ACConfig size << 14 | 2
+ * 65 : pointer to ACConfig struct
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void CreateDefaultConfig(Interface* self);
+
+/**
+ * AC::ConnectAsync service function
+ * Inputs:
+ * 1 : ProcessId Header
+ * 3 : Copy Handle Header
+ * 4 : Connection Event handle
+ * 5 : ACConfig size << 14 | 2
+ * 6 : pointer to ACConfig struct
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void ConnectAsync(Interface* self);
+
+/**
+ * AC::GetConnectResult service function
+ * Inputs:
+ * 1 : ProcessId Header
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void GetConnectResult(Interface* self);
+
+/**
+ * AC::CloseAsync service function
+ * Inputs:
+ * 1 : ProcessId Header
+ * 3 : Copy Handle Header
+ * 4 : Event handle, should be signaled when AC connection is closed
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void CloseAsync(Interface* self);
+
+/**
+ * AC::GetCloseResult service function
+ * Inputs:
+ * 1 : ProcessId Header
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void GetCloseResult(Interface* self);
+
+/**
+ * AC::GetWifiStatus service function
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet.
+ */
+void GetWifiStatus(Interface* self);
+
+/**
+ * AC::GetInfraPriority service function
+ * Inputs:
+ * 1 : ACConfig size << 14 | 2
+ * 2 : pointer to ACConfig struct
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Infra Priority
+ */
+void GetInfraPriority(Interface* self);
+
+/**
+ * AC::SetRequestEulaVersion service function
+ * Inputs:
+ * 1 : Eula Version major
+ * 2 : Eula Version minor
+ * 3 : ACConfig size << 14 | 2
+ * 4 : Input pointer to ACConfig struct
+ * 64 : ACConfig size << 14 | 2
+ * 65 : Output pointer to ACConfig struct
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Infra Priority
+ */
+void SetRequestEulaVersion(Interface* self);
+
+/**
+ * AC::RegisterDisconnectEvent service function
+ * Inputs:
+ * 1 : ProcessId Header
+ * 3 : Copy Handle Header
+ * 4 : Event handle, should be signaled when AC connection is closed
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void RegisterDisconnectEvent(Interface* self);
+
+/**
+ * AC::IsConnected service function
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : bool, is connected
+ */
+void IsConnected(Interface* self);
+
+/**
+ * AC::SetClientVersion service function
+ * Inputs:
+ * 1 : Used SDK Version
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void SetClientVersion(Interface* self);
+
+/// Initialize AC service
+void Init();
+
+/// Shutdown AC service
+void Shutdown();
+
+} // namespace AC
+} // namespace Service
diff --git a/src/core/hle/service/ac/ac_i.cpp b/src/core/hle/service/ac/ac_i.cpp
new file mode 100644
index 000000000..b22fe3698
--- /dev/null
+++ b/src/core/hle/service/ac/ac_i.cpp
@@ -0,0 +1,39 @@
+// Copyright 2016 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ac/ac.h"
+#include "core/hle/service/ac/ac_i.h"
+
+namespace Service {
+namespace AC {
+
+const Interface::FunctionInfo FunctionTable[] = {
+ {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"},
+ {0x00040006, ConnectAsync, "ConnectAsync"},
+ {0x00050002, GetConnectResult, "GetConnectResult"},
+ {0x00070002, nullptr, "CancelConnectAsync"},
+ {0x00080004, CloseAsync, "CloseAsync"},
+ {0x00090002, GetCloseResult, "GetCloseResult"},
+ {0x000A0000, nullptr, "GetLastErrorCode"},
+ {0x000C0000, nullptr, "GetStatus"},
+ {0x000D0000, GetWifiStatus, "GetWifiStatus"},
+ {0x000E0042, nullptr, "GetCurrentAPInfo"},
+ {0x00100042, nullptr, "GetCurrentNZoneInfo"},
+ {0x00110042, nullptr, "GetNZoneApNumService"},
+ {0x001D0042, nullptr, "ScanAPs"},
+ {0x00240042, nullptr, "AddDenyApType"},
+ {0x00270002, GetInfraPriority, "GetInfraPriority"},
+ {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"},
+ {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"},
+ {0x003C0042, nullptr, "GetAPSSIDList"},
+ {0x003E0042, IsConnected, "IsConnected"},
+ {0x00400042, SetClientVersion, "SetClientVersion"},
+};
+
+AC_I::AC_I() {
+ Register(FunctionTable);
+}
+
+} // namespace AC
+} // namespace Service
diff --git a/src/core/hle/service/ac/ac_i.h b/src/core/hle/service/ac/ac_i.h
new file mode 100644
index 000000000..465bba59c
--- /dev/null
+++ b/src/core/hle/service/ac/ac_i.h
@@ -0,0 +1,22 @@
+// Copyright 2016 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 {
+namespace AC {
+
+class AC_I final : public Interface {
+public:
+ AC_I();
+
+ std::string GetPortName() const override {
+ return "ac:i";
+ }
+};
+
+} // namespace AC
+} // namespace Service
diff --git a/src/core/hle/service/ac/ac_u.cpp b/src/core/hle/service/ac/ac_u.cpp
new file mode 100644
index 000000000..346671b4a
--- /dev/null
+++ b/src/core/hle/service/ac/ac_u.cpp
@@ -0,0 +1,39 @@
+// Copyright 2014 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/ac/ac.h"
+#include "core/hle/service/ac/ac_u.h"
+
+namespace Service {
+namespace AC {
+
+const Interface::FunctionInfo FunctionTable[] = {
+ {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"},
+ {0x00040006, ConnectAsync, "ConnectAsync"},
+ {0x00050002, GetConnectResult, "GetConnectResult"},
+ {0x00070002, nullptr, "CancelConnectAsync"},
+ {0x00080004, CloseAsync, "CloseAsync"},
+ {0x00090002, GetCloseResult, "GetCloseResult"},
+ {0x000A0000, nullptr, "GetLastErrorCode"},
+ {0x000C0000, nullptr, "GetStatus"},
+ {0x000D0000, GetWifiStatus, "GetWifiStatus"},
+ {0x000E0042, nullptr, "GetCurrentAPInfo"},
+ {0x00100042, nullptr, "GetCurrentNZoneInfo"},
+ {0x00110042, nullptr, "GetNZoneApNumService"},
+ {0x001D0042, nullptr, "ScanAPs"},
+ {0x00240042, nullptr, "AddDenyApType"},
+ {0x00270002, GetInfraPriority, "GetInfraPriority"},
+ {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"},
+ {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"},
+ {0x003C0042, nullptr, "GetAPSSIDList"},
+ {0x003E0042, IsConnected, "IsConnected"},
+ {0x00400042, SetClientVersion, "SetClientVersion"},
+};
+
+AC_U::AC_U() {
+ Register(FunctionTable);
+}
+
+} // namespace AC
+} // namespace Service
diff --git a/src/core/hle/service/ac_u.h b/src/core/hle/service/ac/ac_u.h
index 573c32d7e..f9d21e112 100644
--- a/src/core/hle/service/ac_u.h
+++ b/src/core/hle/service/ac/ac_u.h
@@ -12,7 +12,6 @@ namespace AC {
class AC_U final : public Interface {
public:
AC_U();
- ~AC_U();
std::string GetPortName() const override {
return "ac:u";
diff --git a/src/core/hle/service/ac_u.cpp b/src/core/hle/service/ac_u.cpp
deleted file mode 100644
index 36204db4d..000000000
--- a/src/core/hle/service/ac_u.cpp
+++ /dev/null
@@ -1,291 +0,0 @@
-// Copyright 2014 Citra Emulator Project
-// Licensed under GPLv2 or any later version
-// Refer to the license.txt file included.
-
-#include <array>
-
-#include "common/logging/log.h"
-#include "core/hle/kernel/event.h"
-#include "core/hle/service/ac_u.h"
-
-namespace Service {
-namespace AC {
-
-struct ACConfig {
- std::array<u8, 0x200> data;
-};
-
-static ACConfig default_config{};
-
-static bool ac_connected = false;
-
-static Kernel::SharedPtr<Kernel::Event> close_event;
-static Kernel::SharedPtr<Kernel::Event> connect_event;
-static Kernel::SharedPtr<Kernel::Event> disconnect_event;
-
-/**
- * AC_U::CreateDefaultConfig service function
- * Inputs:
- * 64 : ACConfig size << 14 | 2
- * 65 : pointer to ACConfig struct
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-static void CreateDefaultConfig(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- u32 ac_config_addr = cmd_buff[65];
-
- ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2),
- "Output buffer size not equal ACConfig size");
-
- Memory::WriteBlock(ac_config_addr, &default_config, sizeof(ACConfig));
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::ConnectAsync service function
- * Inputs:
- * 1 : ProcessId Header
- * 3 : Copy Handle Header
- * 4 : Connection Event handle
- * 5 : ACConfig size << 14 | 2
- * 6 : pointer to ACConfig struct
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-static void ConnectAsync(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- connect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
- if (connect_event) {
- connect_event->name = "AC_U:connect_event";
- connect_event->Signal();
- ac_connected = true;
- }
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::GetConnectResult service function
- * Inputs:
- * 1 : ProcessId Header
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-static void GetConnectResult(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::CloseAsync service function
- * Inputs:
- * 1 : ProcessId Header
- * 3 : Copy Handle Header
- * 4 : Event handle, should be signaled when AC connection is closed
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-static void CloseAsync(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- if (ac_connected && disconnect_event) {
- disconnect_event->Signal();
- }
-
- close_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
- if (close_event) {
- close_event->name = "AC_U:close_event";
- close_event->Signal();
- }
-
- ac_connected = false;
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::GetCloseResult service function
- * Inputs:
- * 1 : ProcessId Header
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-static void GetCloseResult(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::GetWifiStatus service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 2 : Output connection type, 0 = none, 1 = Old3DS Internet, 2 = New3DS Internet.
- */
-static void GetWifiStatus(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- // TODO(purpasmart96): This function is only a stub,
- // it returns a valid result without implementing full functionality.
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[2] = 0; // Connection type set to none
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::GetInfraPriority service function
- * Inputs:
- * 1 : ACConfig size << 14 | 2
- * 2 : pointer to ACConfig struct
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 2 : Infra Priority
- */
-static void GetInfraPriority(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[2] = 0; // Infra Priority, default 0
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::SetRequestEulaVersion service function
- * Inputs:
- * 1 : Eula Version major
- * 2 : Eula Version minor
- * 3 : ACConfig size << 14 | 2
- * 4 : Input pointer to ACConfig struct
- * 64 : ACConfig size << 14 | 2
- * 65 : Output pointer to ACConfig struct
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 2 : Infra Priority
- */
-static void SetRequestEulaVersion(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- u32 major = cmd_buff[1] & 0xFF;
- u32 minor = cmd_buff[2] & 0xFF;
-
- ASSERT_MSG(cmd_buff[3] == (sizeof(ACConfig) << 14 | 2),
- "Input buffer size not equal ACConfig size");
- ASSERT_MSG(cmd_buff[64] == (sizeof(ACConfig) << 14 | 2),
- "Output buffer size not equal ACConfig size");
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[2] = 0; // Infra Priority
-
- LOG_WARNING(Service_AC, "(STUBBED) called, major=%u, minor=%u", major, minor);
-}
-
-/**
- * AC_U::RegisterDisconnectEvent service function
- * Inputs:
- * 1 : ProcessId Header
- * 3 : Copy Handle Header
- * 4 : Event handle, should be signaled when AC connection is closed
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-static void RegisterDisconnectEvent(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- disconnect_event = Kernel::g_handle_table.Get<Kernel::Event>(cmd_buff[4]);
- if (disconnect_event) {
- disconnect_event->name = "AC_U:disconnect_event";
- }
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::IsConnected service function
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- * 2 : bool, is connected
- */
-static void IsConnected(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- cmd_buff[2] = ac_connected;
-
- LOG_WARNING(Service_AC, "(STUBBED) called");
-}
-
-/**
- * AC_U::SetClientVersion service function
- * Inputs:
- * 1 : Used SDK Version
- * Outputs:
- * 1 : Result of function, 0 on success, otherwise error code
- */
-static void SetClientVersion(Interface* self) {
- u32* cmd_buff = Kernel::GetCommandBuffer();
-
- const u32 version = cmd_buff[1];
- self->SetVersion(version);
-
- LOG_WARNING(Service_AC, "(STUBBED) called, version: 0x%08X", version);
-
- cmd_buff[1] = RESULT_SUCCESS.raw; // No error
-}
-
-const Interface::FunctionInfo FunctionTable[] = {
- {0x00010000, CreateDefaultConfig, "CreateDefaultConfig"},
- {0x00040006, ConnectAsync, "ConnectAsync"},
- {0x00050002, GetConnectResult, "GetConnectResult"},
- {0x00070002, nullptr, "CancelConnectAsync"},
- {0x00080004, CloseAsync, "CloseAsync"},
- {0x00090002, GetCloseResult, "GetCloseResult"},
- {0x000A0000, nullptr, "GetLastErrorCode"},
- {0x000C0000, nullptr, "GetStatus"},
- {0x000D0000, GetWifiStatus, "GetWifiStatus"},
- {0x000E0042, nullptr, "GetCurrentAPInfo"},
- {0x00100042, nullptr, "GetCurrentNZoneInfo"},
- {0x00110042, nullptr, "GetNZoneApNumService"},
- {0x001D0042, nullptr, "ScanAPs"},
- {0x00240042, nullptr, "AddDenyApType"},
- {0x00270002, GetInfraPriority, "GetInfraPriority"},
- {0x002D0082, SetRequestEulaVersion, "SetRequestEulaVersion"},
- {0x00300004, RegisterDisconnectEvent, "RegisterDisconnectEvent"},
- {0x003C0042, nullptr, "GetAPSSIDList"},
- {0x003E0042, IsConnected, "IsConnected"},
- {0x00400042, SetClientVersion, "SetClientVersion"},
-};
-
-AC_U::AC_U() {
- Register(FunctionTable);
-
- ac_connected = false;
-
- close_event = nullptr;
- connect_event = nullptr;
- disconnect_event = nullptr;
-}
-
-AC_U::~AC_U() {
- close_event = nullptr;
- connect_event = nullptr;
- disconnect_event = nullptr;
-}
-
-} // namespace AC
-} // namespace Service
diff --git a/src/core/hle/service/boss/boss.cpp b/src/core/hle/service/boss/boss.cpp
index 6ab16ccd5..e0de037f8 100644
--- a/src/core/hle/service/boss/boss.cpp
+++ b/src/core/hle/service/boss/boss.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <cinttypes>
#include "core/hle/service/boss/boss.h"
#include "core/hle/service/boss/boss_p.h"
#include "core/hle/service/boss/boss_u.h"
@@ -33,7 +34,8 @@ void InitializeSession(Service::Interface* self) {
cmd_buff[0] = IPC::MakeHeader(0x1, 0x1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_BOSS, "(STUBBED) unk_param=0x%016X, translation=0x%08X, unk_param4=0x%08X",
+ LOG_WARNING(Service_BOSS,
+ "(STUBBED) unk_param=0x%016" PRIX64 ", translation=0x%08X, unk_param4=0x%08X",
unk_param, translation, unk_param4);
}
diff --git a/src/core/hle/service/cam/cam.cpp b/src/core/hle/service/cam/cam.cpp
index 5594aedab..95665e754 100644
--- a/src/core/hle/service/cam/cam.cpp
+++ b/src/core/hle/service/cam/cam.cpp
@@ -2,7 +2,15 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include <algorithm>
+#include <array>
+#include <future>
+#include <memory>
+#include <vector>
+#include "common/bit_set.h"
#include "common/logging/log.h"
+#include "core/core_timing.h"
+#include "core/frontend/camera/factory.h"
#include "core/hle/kernel/event.h"
#include "core/hle/service/cam/cam.h"
#include "core/hle/service/cam/cam_c.h"
@@ -10,206 +18,924 @@
#include "core/hle/service/cam/cam_s.h"
#include "core/hle/service/cam/cam_u.h"
#include "core/hle/service/service.h"
+#include "core/settings.h"
namespace Service {
namespace CAM {
-static const u32 TRANSFER_BYTES = 5 * 1024;
+namespace {
+
+struct ContextConfig {
+ Flip flip;
+ Effect effect;
+ OutputFormat format;
+ Resolution resolution;
+};
+
+struct CameraConfig {
+ std::unique_ptr<Camera::CameraInterface> impl;
+ std::array<ContextConfig, 2> contexts;
+ int current_context;
+ FrameRate frame_rate;
+};
+
+struct PortConfig {
+ int camera_id;
+
+ bool is_active; // set when the port is activated by an Activate call.
+ bool is_pending_receiving; // set if SetReceiving is called when is_busy = false. When
+ // StartCapture is called then, this will trigger a receiving
+ // process and reset itself.
+ bool is_busy; // set when StartCapture is called and reset when StopCapture is called.
+ bool is_receiving; // set when there is an ongoing receiving process.
+
+ bool is_trimming;
+ u16 x0; // x-coordinate of starting position for trimming
+ u16 y0; // y-coordinate of starting position for trimming
+ u16 x1; // x-coordinate of ending position for trimming
+ u16 y1; // y-coordinate of ending position for trimming
+
+ u32 transfer_bytes;
+
+ Kernel::SharedPtr<Kernel::Event> completion_event;
+ Kernel::SharedPtr<Kernel::Event> buffer_error_interrupt_event;
+ Kernel::SharedPtr<Kernel::Event> vsync_interrupt_event;
+
+ std::future<std::vector<u16>> capture_result; // will hold the received frame.
+ VAddr dest; // the destination address of a receiving process
+ u32 dest_size; // the destination size of a receiving process
+
+ void Clear() {
+ completion_event->Clear();
+ buffer_error_interrupt_event->Clear();
+ vsync_interrupt_event->Clear();
+ is_receiving = false;
+ is_active = false;
+ is_pending_receiving = false;
+ is_busy = false;
+ is_trimming = false;
+ x0 = 0;
+ y0 = 0;
+ x1 = 0;
+ y1 = 0;
+ transfer_bytes = 256;
+ }
+};
+
+// built-in resolution parameters
+constexpr std::array<Resolution, 8> PRESET_RESOLUTION{{
+ {640, 480, 0, 0, 639, 479}, // VGA
+ {320, 240, 0, 0, 639, 479}, // QVGA
+ {160, 120, 0, 0, 639, 479}, // QQVGA
+ {352, 288, 26, 0, 613, 479}, // CIF
+ {176, 144, 26, 0, 613, 479}, // QCIF
+ {256, 192, 0, 0, 639, 479}, // DS_LCD
+ {512, 384, 0, 0, 639, 479}, // DS_LCDx4
+ {400, 240, 0, 48, 639, 431}, // CTR_TOP_LCD
+}};
+
+// latency in ms for each frame rate option
+constexpr std::array<int, 13> LATENCY_BY_FRAME_RATE{{
+ 67, // Rate_15
+ 67, // Rate_15_To_5
+ 67, // Rate_15_To_2
+ 100, // Rate_10
+ 118, // Rate_8_5
+ 200, // Rate_5
+ 50, // Rate_20
+ 50, // Rate_20_To_5
+ 33, // Rate_30
+ 33, // Rate_30_To_5
+ 67, // Rate_15_To_10
+ 50, // Rate_20_To_10
+ 33, // Rate_30_To_10
+}};
+
+std::array<CameraConfig, NumCameras> cameras;
+std::array<PortConfig, 2> ports;
+int completion_event_callback;
+
+const ResultCode ERROR_INVALID_ENUM_VALUE(ErrorDescription::InvalidEnumValue, ErrorModule::CAM,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+const ResultCode ERROR_OUT_OF_RANGE(ErrorDescription::OutOfRange, ErrorModule::CAM,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+
+void CompletionEventCallBack(u64 port_id, int) {
+ PortConfig& port = ports[port_id];
+ const CameraConfig& camera = cameras[port.camera_id];
+ const auto buffer = port.capture_result.get();
+
+ if (port.is_trimming) {
+ u32 trim_width;
+ u32 trim_height;
+ const int original_width = camera.contexts[camera.current_context].resolution.width;
+ const int original_height = camera.contexts[camera.current_context].resolution.height;
+ if (port.x1 <= port.x0 || port.y1 <= port.y0 || port.x1 > original_width ||
+ port.y1 > original_height) {
+ LOG_ERROR(Service_CAM, "Invalid trimming coordinates x0=%u, y0=%u, x1=%u, y1=%u",
+ port.x0, port.y0, port.x1, port.y1);
+ trim_width = 0;
+ trim_height = 0;
+ } else {
+ trim_width = port.x1 - port.x0;
+ trim_height = port.y1 - port.y0;
+ }
+
+ u32 trim_size = (port.x1 - port.x0) * (port.y1 - port.y0) * 2;
+ if (port.dest_size != trim_size) {
+ LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%u)!",
+ port.dest_size, trim_size);
+ }
+
+ const u32 src_offset = port.y0 * original_width + port.x0;
+ const u16* src_ptr = buffer.data() + src_offset;
+ // Note: src_size_left is int because it can be negative if the buffer size doesn't match.
+ int src_size_left = static_cast<int>((buffer.size() - src_offset) * sizeof(u16));
+ VAddr dest_ptr = port.dest;
+ // Note: dest_size_left and line_bytes are int to match the type of src_size_left.
+ int dest_size_left = static_cast<int>(port.dest_size);
+ const int line_bytes = static_cast<int>(trim_width * sizeof(u16));
+
+ for (u32 y = 0; y < trim_height; ++y) {
+ int copy_length = std::min({line_bytes, dest_size_left, src_size_left});
+ if (copy_length <= 0) {
+ break;
+ }
+ Memory::WriteBlock(dest_ptr, src_ptr, copy_length);
+ dest_ptr += copy_length;
+ dest_size_left -= copy_length;
+ src_ptr += original_width;
+ src_size_left -= original_width * sizeof(u16);
+ }
+ } else {
+ std::size_t buffer_size = buffer.size() * sizeof(u16);
+ if (port.dest_size != buffer_size) {
+ LOG_ERROR(Service_CAM, "The destination size (%u) doesn't match the source (%zu)!",
+ port.dest_size, buffer_size);
+ }
+ Memory::WriteBlock(port.dest, buffer.data(), std::min<u32>(port.dest_size, buffer_size));
+ }
+
+ port.is_receiving = false;
+ port.completion_event->Signal();
+}
+
+// Starts a receiving process on the specified port. This can only be called when is_busy = true and
+// is_receiving = false.
+void StartReceiving(int port_id) {
+ PortConfig& port = ports[port_id];
+ port.is_receiving = true;
+
+ // launches a capture task asynchronously
+ const CameraConfig& camera = cameras[port.camera_id];
+ port.capture_result =
+ std::async(std::launch::async, &Camera::CameraInterface::ReceiveFrame, camera.impl.get());
+
+ // schedules a completion event according to the frame rate. The event will block on the
+ // capture task if it is not finished within the expected time
+ CoreTiming::ScheduleEvent(
+ msToCycles(LATENCY_BY_FRAME_RATE[static_cast<int>(camera.frame_rate)]),
+ completion_event_callback, port_id);
+}
+
+// Cancels any ongoing receiving processes at the specified port. This is used by functions that
+// stop capturing.
+// TODO: what is the exact behaviour on real 3DS when stopping capture during an ongoing process?
+// Will the completion event still be signaled?
+void CancelReceiving(int port_id) {
+ if (!ports[port_id].is_receiving)
+ return;
+ LOG_WARNING(Service_CAM, "tries to cancel an ongoing receiving process.");
+ CoreTiming::UnscheduleEvent(completion_event_callback, port_id);
+ ports[port_id].capture_result.wait();
+ ports[port_id].is_receiving = false;
+}
+
+// Activates the specified port with the specfied camera.
+static void ActivatePort(int port_id, int camera_id) {
+ if (ports[port_id].is_busy && ports[port_id].camera_id != camera_id) {
+ CancelReceiving(port_id);
+ cameras[ports[port_id].camera_id].impl->StopCapture();
+ ports[port_id].is_busy = false;
+ }
+ ports[port_id].is_active = true;
+ ports[port_id].camera_id = camera_id;
+}
+
+template <int max_index>
+class CommandParamBitSet : public BitSet8 {
+public:
+ explicit CommandParamBitSet(u32 command_param)
+ : BitSet8(static_cast<u8>(command_param & 0xFF)) {}
-static Kernel::SharedPtr<Kernel::Event> completion_event_cam1;
-static Kernel::SharedPtr<Kernel::Event> completion_event_cam2;
-static Kernel::SharedPtr<Kernel::Event> interrupt_error_event;
-static Kernel::SharedPtr<Kernel::Event> vsync_interrupt_error_event;
+ bool IsValid() const {
+ return m_val < (1 << max_index);
+ }
+
+ bool IsSingle() const {
+ return IsValid() && Count() == 1;
+ }
+};
+
+using PortSet = CommandParamBitSet<2>;
+using ContextSet = CommandParamBitSet<2>;
+using CameraSet = CommandParamBitSet<3>;
+
+} // namespace
void StartCapture(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ if (!ports[i].is_busy) {
+ if (!ports[i].is_active) {
+ // This doesn't return an error, but seems to put the camera in an undefined
+ // state
+ LOG_ERROR(Service_CAM, "port %u hasn't been activated", i);
+ } else {
+ cameras[ports[i].camera_id].impl->StartCapture();
+ ports[i].is_busy = true;
+ if (ports[i].is_pending_receiving) {
+ ports[i].is_pending_receiving = false;
+ StartReceiving(i);
+ }
+ }
+ } else {
+ LOG_WARNING(Service_CAM, "port %u already started", i);
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x1, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
}
void StopCapture(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ if (ports[i].is_busy) {
+ CancelReceiving(i);
+ cameras[ports[i].camera_id].impl->StopCapture();
+ ports[i].is_busy = false;
+ } else {
+ LOG_WARNING(Service_CAM, "port %u already stopped", i);
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x2, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
+}
+
+void IsBusy(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsValid()) {
+ bool is_busy = true;
+ // Note: the behaviour on no or both ports selected are verified against real 3DS.
+ for (int i : port_select) {
+ is_busy &= ports[i].is_busy;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = is_busy ? 1 : 0;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x3, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
+}
+
+void ClearBuffer(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ cmd_buff[0] = IPC::MakeHeader(0x4, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
}
void GetVsyncInterruptEvent(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] = Kernel::g_handle_table.Create(ports[port].vsync_interrupt_event).MoveFrom();
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[2] = 0;
+ }
cmd_buff[0] = IPC::MakeHeader(0x5, 1, 2);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(vsync_interrupt_error_event).MoveFrom();
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
}
void GetBufferErrorInterruptEvent(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
-
- cmd_buff[0] = IPC::MakeHeader(0x6, 1, 2);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(interrupt_error_event).MoveFrom();
-
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] =
+ Kernel::g_handle_table.Create(ports[port].buffer_error_interrupt_event).MoveFrom();
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[2] = 0;
+ }
+
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u", port_select.m_val);
}
void SetReceiving(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- VAddr dest = cmd_buff[1];
- u8 port = cmd_buff[2] & 0xFF;
- u32 image_size = cmd_buff[3];
- u16 trans_unit = cmd_buff[4] & 0xFFFF;
+ const VAddr dest = cmd_buff[1];
+ const PortSet port_select(cmd_buff[2]);
+ const u32 image_size = cmd_buff[3];
+ const u32 trans_unit = cmd_buff[4] & 0xFFFF;
+
+ if (port_select.IsSingle()) {
+ int port_id = *port_select.begin();
+ PortConfig& port = ports[port_id];
+ CancelReceiving(port_id);
+ port.completion_event->Clear();
+ port.dest = dest;
+ port.dest_size = image_size;
+
+ if (port.is_busy) {
+ StartReceiving(port_id);
+ } else {
+ port.is_pending_receiving = true;
+ }
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] = Kernel::g_handle_table.Create(port.completion_event).MoveFrom();
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2);
- Kernel::Event* completion_event =
- (Port)port == Port::Cam2 ? completion_event_cam2.get() : completion_event_cam1.get();
+ LOG_DEBUG(Service_CAM, "called, addr=0x%X, port_select=%u, image_size=%u, trans_unit=%u", dest,
+ port_select.m_val, image_size, trans_unit);
+}
- completion_event->Signal();
+void IsFinishedReceiving(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
- cmd_buff[0] = IPC::MakeHeader(0x7, 1, 2);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = IPC::CopyHandleDesc();
- cmd_buff[3] = Kernel::g_handle_table.Create(completion_event).MoveFrom();
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = (ports[port].is_receiving || ports[port].is_pending_receiving) ? 0 : 1;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
- LOG_WARNING(Service_CAM, "(STUBBED) called, addr=0x%X, port=%d, image_size=%d, trans_unit=%d",
- dest, port, image_size, trans_unit);
+ cmd_buff[0] = IPC::MakeHeader(0x8, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
}
void SetTransferLines(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
- u16 transfer_lines = cmd_buff[2] & 0xFFFF;
- u16 width = cmd_buff[3] & 0xFFFF;
- u16 height = cmd_buff[4] & 0xFFFF;
+ const PortSet port_select(cmd_buff[1]);
+ const u32 transfer_lines = cmd_buff[2] & 0xFFFF;
+ const u32 width = cmd_buff[3] & 0xFFFF;
+ const u32 height = cmd_buff[4] & 0xFFFF;
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].transfer_bytes = transfer_lines * width * 2;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x9, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, lines=%d, width=%d, height=%d", port,
- transfer_lines, width, height);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, port_select=%u, lines=%u, width=%u, height=%u",
+ port_select.m_val, transfer_lines, width, height);
}
void GetMaxLines(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u16 width = cmd_buff[1] & 0xFFFF;
- u16 height = cmd_buff[2] & 0xFFFF;
+ const u32 width = cmd_buff[1] & 0xFFFF;
+ const u32 height = cmd_buff[2] & 0xFFFF;
+
+ // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480
+ constexpr u32 MIN_TRANSFER_UNIT = 256;
+ constexpr u32 MAX_BUFFER_SIZE = 2560;
+ if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
+ cmd_buff[1] = ERROR_OUT_OF_RANGE.raw;
+ } else {
+ u32 lines = MAX_BUFFER_SIZE / width;
+ if (lines > height) {
+ lines = height;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ while (height % lines != 0 || (lines * width * 2 % MIN_TRANSFER_UNIT != 0)) {
+ --lines;
+ if (lines == 0) {
+ cmd_buff[1] = ERROR_OUT_OF_RANGE.raw;
+ break;
+ }
+ }
+ cmd_buff[2] = lines;
+ }
cmd_buff[0] = IPC::MakeHeader(0xA, 2, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = TRANSFER_BYTES / (2 * width);
- LOG_WARNING(Service_CAM, "(STUBBED) called, width=%d, height=%d, lines = %d", width, height,
- cmd_buff[2]);
+ LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);
+}
+
+void SetTransferBytes(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+ const u32 transfer_bytes = cmd_buff[2] & 0xFFFF;
+ const u32 width = cmd_buff[3] & 0xFFFF;
+ const u32 height = cmd_buff[4] & 0xFFFF;
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].transfer_bytes = transfer_bytes;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0xB, 1, 0);
+
+ LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u, bytes=%u, width=%u, height=%u",
+ port_select.m_val, transfer_bytes, width, height);
}
void GetTransferBytes(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = ports[port].transfer_bytes;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0xC, 2, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = TRANSFER_BYTES;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d", port);
+ LOG_WARNING(Service_CAM, "(STUBBED)called, port_select=%u", port_select.m_val);
+}
+
+void GetMaxBytes(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const u32 width = cmd_buff[1] & 0xFFFF;
+ const u32 height = cmd_buff[2] & 0xFFFF;
+
+ // Note: the result of the algorithm below are hwtested with width < 640 and with height < 480
+ constexpr u32 MIN_TRANSFER_UNIT = 256;
+ constexpr u32 MAX_BUFFER_SIZE = 2560;
+ if (width * height * 2 % MIN_TRANSFER_UNIT != 0) {
+ cmd_buff[1] = ERROR_OUT_OF_RANGE.raw;
+ } else {
+ u32 bytes = MAX_BUFFER_SIZE;
+
+ while (width * height * 2 % bytes != 0) {
+ bytes -= MIN_TRANSFER_UNIT;
+ }
+
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = bytes;
+ }
+ cmd_buff[0] = IPC::MakeHeader(0xD, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, width=%u, height=%u", width, height);
}
void SetTrimming(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
- bool trim = (cmd_buff[2] & 0xFF) != 0;
+ const PortSet port_select(cmd_buff[1]);
+ const bool trim = (cmd_buff[2] & 0xFF) != 0;
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].is_trimming = trim;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0xE, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, trim=%d", port, trim);
+ LOG_DEBUG(Service_CAM, "called, port_select=%u, trim=%d", port_select.m_val, trim);
+}
+
+void IsTrimming(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = ports[port].is_trimming;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0xF, 2, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
+}
+
+void SetTrimmingParams(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+ const u16 x0 = static_cast<u16>(cmd_buff[2] & 0xFFFF);
+ const u16 y0 = static_cast<u16>(cmd_buff[3] & 0xFFFF);
+ const u16 x1 = static_cast<u16>(cmd_buff[4] & 0xFFFF);
+ const u16 y1 = static_cast<u16>(cmd_buff[5] & 0xFFFF);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].x0 = x0;
+ ports[i].y0 = y0;
+ ports[i].x1 = x1;
+ ports[i].y1 = y1;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x10, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u, x0=%u, y0=%u, x1=%u, y1=%u", port_select.m_val,
+ x0, y0, x1, y1);
+}
+
+void GetTrimmingParams(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const PortSet port_select(cmd_buff[1]);
+
+ if (port_select.IsSingle()) {
+ int port = *port_select.begin();
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = ports[port].x0;
+ cmd_buff[3] = ports[port].y0;
+ cmd_buff[4] = ports[port].x1;
+ cmd_buff[5] = ports[port].y1;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x11, 5, 0);
+
+ LOG_DEBUG(Service_CAM, "called, port_select=%u", port_select.m_val);
}
void SetTrimmingParamsCenter(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 port = cmd_buff[1] & 0xFF;
- s16 trimW = cmd_buff[2] & 0xFFFF;
- s16 trimH = cmd_buff[3] & 0xFFFF;
- s16 camW = cmd_buff[4] & 0xFFFF;
- s16 camH = cmd_buff[5] & 0xFFFF;
+ const PortSet port_select(cmd_buff[1]);
+ const u16 trim_w = static_cast<u16>(cmd_buff[2] & 0xFFFF);
+ const u16 trim_h = static_cast<u16>(cmd_buff[3] & 0xFFFF);
+ const u16 cam_w = static_cast<u16>(cmd_buff[4] & 0xFFFF);
+ const u16 cam_h = static_cast<u16>(cmd_buff[5] & 0xFFFF);
+
+ if (port_select.IsValid()) {
+ for (int i : port_select) {
+ ports[i].x0 = (cam_w - trim_w) / 2;
+ ports[i].y0 = (cam_h - trim_h) / 2;
+ ports[i].x1 = ports[i].x0 + trim_w;
+ ports[i].y1 = ports[i].y0 + trim_h;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid port_select=%u", port_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x12, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, port=%d, trimW=%d, trimH=%d, camW=%d, camH=%d",
- port, trimW, trimH, camW, camH);
+ LOG_DEBUG(Service_CAM, "called, port_select=%u, trim_w=%u, trim_h=%u, cam_w=%u, cam_h=%u",
+ port_select.m_val, trim_w, trim_h, cam_w, cam_h);
}
void Activate(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+
+ if (camera_select.IsValid()) {
+ if (camera_select.m_val == 0) { // deactive all
+ for (int i = 0; i < 2; ++i) {
+ if (ports[i].is_busy) {
+ CancelReceiving(i);
+ cameras[ports[i].camera_id].impl->StopCapture();
+ ports[i].is_busy = false;
+ }
+ ports[i].is_active = false;
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else if (camera_select[0] && camera_select[1]) {
+ LOG_ERROR(Service_CAM, "camera 0 and 1 can't be both activated");
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ } else {
+ if (camera_select[0]) {
+ ActivatePort(0, 0);
+ } else if (camera_select[1]) {
+ ActivatePort(0, 1);
+ }
+
+ if (camera_select[2]) {
+ ActivatePort(1, 2);
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ }
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x13, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d", cam_select);
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u", camera_select.m_val);
+}
+
+void SwitchContext(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ const ContextSet context_select(cmd_buff[2]);
+
+ if (camera_select.IsValid() && context_select.IsSingle()) {
+ int context = *context_select.begin();
+ for (int camera : camera_select) {
+ cameras[camera].current_context = context;
+ const ContextConfig& context_config = cameras[camera].contexts[context];
+ cameras[camera].impl->SetFlip(context_config.flip);
+ cameras[camera].impl->SetEffect(context_config.effect);
+ cameras[camera].impl->SetFormat(context_config.format);
+ cameras[camera].impl->SetResolution(context_config.resolution);
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x14, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
}
void FlipImage(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
- u8 flip = cmd_buff[2] & 0xFF;
- u8 context = cmd_buff[3] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+ const Flip flip = static_cast<Flip>(cmd_buff[2] & 0xFF);
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].flip = flip;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetFlip(flip);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x1D, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, flip=%d, context=%d", cam_select,
- flip, context);
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, flip=%d, context_select=%u",
+ camera_select.m_val, static_cast<int>(flip), context_select.m_val);
+}
+
+void SetDetailSize(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ Resolution resolution;
+ resolution.width = static_cast<u16>(cmd_buff[2] & 0xFFFF);
+ resolution.height = static_cast<u16>(cmd_buff[3] & 0xFFFF);
+ resolution.crop_x0 = static_cast<u16>(cmd_buff[4] & 0xFFFF);
+ resolution.crop_y0 = static_cast<u16>(cmd_buff[5] & 0xFFFF);
+ resolution.crop_x1 = static_cast<u16>(cmd_buff[6] & 0xFFFF);
+ resolution.crop_y1 = static_cast<u16>(cmd_buff[7] & 0xFFFF);
+ const ContextSet context_select(cmd_buff[8]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].resolution = resolution;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetResolution(resolution);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x1E, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, width=%u, height=%u, crop_x0=%u, crop_y0=%u, "
+ "crop_x1=%u, crop_y1=%u, context_select=%u",
+ camera_select.m_val, resolution.width, resolution.height, resolution.crop_x0,
+ resolution.crop_y0, resolution.crop_x1, resolution.crop_y1, context_select.m_val);
}
void SetSize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
- u8 size = cmd_buff[2] & 0xFF;
- u8 context = cmd_buff[3] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+ const u32 size = cmd_buff[2] & 0xFF;
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].resolution = PRESET_RESOLUTION[size];
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetResolution(PRESET_RESOLUTION[size]);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x1F, 1, 0);
- cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, size=%d, context=%d", cam_select,
- size, context);
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, size=%u, context_select=%u",
+ camera_select.m_val, size, context_select.m_val);
}
void SetFrameRate(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- u8 cam_select = cmd_buff[1] & 0xFF;
- u8 frame_rate = cmd_buff[2] & 0xFF;
+ const CameraSet camera_select(cmd_buff[1]);
+ const FrameRate frame_rate = static_cast<FrameRate>(cmd_buff[2] & 0xFF);
+
+ if (camera_select.IsValid()) {
+ for (int camera : camera_select) {
+ cameras[camera].frame_rate = frame_rate;
+ // TODO(wwylele): consider hinting the actual camera with the expected frame rate
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u", camera_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
cmd_buff[0] = IPC::MakeHeader(0x20, 1, 0);
+
+ LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select=%u, frame_rate=%d",
+ camera_select.m_val, static_cast<int>(frame_rate));
+}
+
+void SetEffect(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ const Effect effect = static_cast<Effect>(cmd_buff[2] & 0xFF);
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].effect = effect;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetEffect(effect);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x22, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, effect=%d, context_select=%u",
+ camera_select.m_val, static_cast<int>(effect), context_select.m_val);
+}
+
+void SetOutputFormat(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const CameraSet camera_select(cmd_buff[1]);
+ const OutputFormat format = static_cast<OutputFormat>(cmd_buff[2] & 0xFF);
+ const ContextSet context_select(cmd_buff[3]);
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera : camera_select) {
+ for (int context : context_select) {
+ cameras[camera].contexts[context].format = format;
+ if (cameras[camera].current_context == context) {
+ cameras[camera].impl->SetFormat(format);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", camera_select.m_val,
+ context_select.m_val);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(0x25, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called, camera_select=%u, format=%d, context_select=%u",
+ camera_select.m_val, static_cast<int>(format), context_select.m_val);
+}
+
+void SynchronizeVsyncTiming(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ const u32 camera_select1 = cmd_buff[1] & 0xFF;
+ const u32 camera_select2 = cmd_buff[2] & 0xFF;
+
+ cmd_buff[0] = IPC::MakeHeader(0x29, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called, cam_select=%d, frame_rate=%d", cam_select,
- frame_rate);
+ LOG_WARNING(Service_CAM, "(STUBBED) called, camera_select1=%u, camera_select2=%u",
+ camera_select1, camera_select2);
}
void GetStereoCameraCalibrationData(Service::Interface* self) {
@@ -239,6 +965,67 @@ void GetStereoCameraCalibrationData(Service::Interface* self) {
LOG_TRACE(Service_CAM, "called");
}
+void SetPackageParameterWithoutContext(Service::Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ PackageParameterWithoutContext package;
+ std::memcpy(&package, cmd_buff + 1, sizeof(package));
+
+ cmd_buff[0] = IPC::MakeHeader(0x33, 1, 0);
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+
+ LOG_WARNING(Service_CAM, "(STUBBED) called");
+}
+
+template <typename PackageParameterType, int command_id>
+static void SetPackageParameter() {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ PackageParameterType package;
+ std::memcpy(&package, cmd_buff + 1, sizeof(package));
+
+ const CameraSet camera_select(static_cast<u32>(package.camera_select));
+ const ContextSet context_select(static_cast<u32>(package.context_select));
+
+ if (camera_select.IsValid() && context_select.IsValid()) {
+ for (int camera_id : camera_select) {
+ CameraConfig& camera = cameras[camera_id];
+ for (int context_id : context_select) {
+ ContextConfig& context = camera.contexts[context_id];
+ context.effect = package.effect;
+ context.flip = package.flip;
+ context.resolution = package.GetResolution();
+ if (context_id == camera.current_context) {
+ camera.impl->SetEffect(context.effect);
+ camera.impl->SetFlip(context.flip);
+ camera.impl->SetResolution(context.resolution);
+ }
+ }
+ }
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ } else {
+ LOG_ERROR(Service_CAM, "invalid camera_select=%u, context_select=%u", package.camera_select,
+ package.context_select);
+ cmd_buff[1] = ERROR_INVALID_ENUM_VALUE.raw;
+ }
+
+ cmd_buff[0] = IPC::MakeHeader(command_id, 1, 0);
+
+ LOG_DEBUG(Service_CAM, "called");
+}
+
+Resolution PackageParameterWithContext::GetResolution() {
+ return PRESET_RESOLUTION[static_cast<int>(size)];
+}
+
+void SetPackageParameterWithContext(Service::Interface* self) {
+ SetPackageParameter<PackageParameterWithContext, 0x34>();
+}
+
+void SetPackageParameterWithContextDetail(Service::Interface* self) {
+ SetPackageParameter<PackageParameterWithContextDetail, 0x35>();
+}
+
void GetSuitableY2rStandardCoefficient(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
@@ -263,24 +1050,50 @@ void PlayShutterSound(Service::Interface* self) {
void DriverInitialize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- completion_event_cam1->Clear();
- completion_event_cam2->Clear();
- interrupt_error_event->Clear();
- vsync_interrupt_error_event->Clear();
+ for (int camera_id = 0; camera_id < NumCameras; ++camera_id) {
+ CameraConfig& camera = cameras[camera_id];
+ camera.current_context = 0;
+ for (int context_id = 0; context_id < 2; ++context_id) {
+ // Note: the following default values are verified against real 3DS
+ ContextConfig& context = camera.contexts[context_id];
+ context.flip = camera_id == 1 ? Flip::Horizontal : Flip::None;
+ context.effect = Effect::None;
+ context.format = OutputFormat::YUV422;
+ context.resolution =
+ context_id == 0 ? PRESET_RESOLUTION[5 /*DS_LCD*/] : PRESET_RESOLUTION[0 /*VGA*/];
+ }
+ camera.impl = Camera::CreateCamera(Settings::values.camera_name[camera_id],
+ Settings::values.camera_config[camera_id]);
+ camera.impl->SetFlip(camera.contexts[0].flip);
+ camera.impl->SetEffect(camera.contexts[0].effect);
+ camera.impl->SetFormat(camera.contexts[0].format);
+ camera.impl->SetResolution(camera.contexts[0].resolution);
+ }
+
+ for (PortConfig& port : ports) {
+ port.Clear();
+ }
cmd_buff[0] = IPC::MakeHeader(0x39, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called");
+ LOG_DEBUG(Service_CAM, "called");
}
void DriverFinalize(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
+ CancelReceiving(0);
+ CancelReceiving(1);
+
+ for (CameraConfig& camera : cameras) {
+ camera.impl = nullptr;
+ }
+
cmd_buff[0] = IPC::MakeHeader(0x3A, 1, 0);
cmd_buff[1] = RESULT_SUCCESS.raw;
- LOG_WARNING(Service_CAM, "(STUBBED) called");
+ LOG_DEBUG(Service_CAM, "called");
}
void Init() {
@@ -291,21 +1104,28 @@ void Init() {
AddService(new CAM_S_Interface);
AddService(new CAM_U_Interface);
- completion_event_cam1 =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::completion_event_cam1");
- completion_event_cam2 =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::completion_event_cam2");
- interrupt_error_event =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::interrupt_error_event");
- vsync_interrupt_error_event =
- Kernel::Event::Create(ResetType::OneShot, "CAM_U::vsync_interrupt_error_event");
+ for (PortConfig& port : ports) {
+ port.completion_event = Event::Create(ResetType::Sticky, "CAM_U::completion_event");
+ port.buffer_error_interrupt_event =
+ Event::Create(ResetType::OneShot, "CAM_U::buffer_error_interrupt_event");
+ port.vsync_interrupt_event =
+ Event::Create(ResetType::OneShot, "CAM_U::vsync_interrupt_event");
+ }
+ completion_event_callback =
+ CoreTiming::RegisterEvent("CAM_U::CompletionEventCallBack", CompletionEventCallBack);
}
void Shutdown() {
- completion_event_cam1 = nullptr;
- completion_event_cam2 = nullptr;
- interrupt_error_event = nullptr;
- vsync_interrupt_error_event = nullptr;
+ CancelReceiving(0);
+ CancelReceiving(1);
+ for (PortConfig& port : ports) {
+ port.completion_event = nullptr;
+ port.buffer_error_interrupt_event = nullptr;
+ port.vsync_interrupt_event = nullptr;
+ }
+ for (CameraConfig& camera : cameras) {
+ camera.impl = nullptr;
+ }
}
} // namespace CAM
diff --git a/src/core/hle/service/cam/cam.h b/src/core/hle/service/cam/cam.h
index c9b6f8acf..f6bff8bc6 100644
--- a/src/core/hle/service/cam/cam.h
+++ b/src/core/hle/service/cam/cam.h
@@ -13,17 +13,12 @@
namespace Service {
namespace CAM {
-enum class Port : u8 { None = 0, Cam1 = 1, Cam2 = 2, Both = Cam1 | Cam2 };
+enum CameraIndex {
+ OuterRightCamera = 0,
+ InnerCamera = 1,
+ OuterLeftCamera = 2,
-enum class CameraSelect : u8 {
- None = 0,
- Out1 = 1,
- In1 = 2,
- Out2 = 4,
- In1Out1 = Out1 | In1,
- Out1Out2 = Out1 | Out2,
- In1Out2 = In1 | Out2,
- All = Out1 | In1 | Out2,
+ NumCameras = 3,
};
enum class Effect : u8 {
@@ -35,13 +30,6 @@ enum class Effect : u8 {
Sepia01 = 5,
};
-enum class Context : u8 {
- None = 0,
- A = 1,
- B = 2,
- Both = A | B,
-};
-
enum class Flip : u8 {
None = 0,
Horizontal = 1,
@@ -160,8 +148,23 @@ struct StereoCameraCalibrationData {
static_assert(sizeof(StereoCameraCalibrationData) == 64,
"StereoCameraCalibrationData structure size is wrong");
-struct PackageParameterCameraSelect {
- CameraSelect camera;
+/**
+ * Resolution parameters for the camera.
+ * The native resolution of 3DS camera is 640 * 480. The captured image will be cropped in the
+ * region [crop_x0, crop_x1] * [crop_y0, crop_y1], and then scaled to size width * height as the
+ * output image. Note that all cropping coordinates are inclusive.
+ */
+struct Resolution {
+ u16 width;
+ u16 height;
+ u16 crop_x0;
+ u16 crop_y0;
+ u16 crop_x1;
+ u16 crop_y1;
+};
+
+struct PackageParameterWithoutContext {
+ u8 camera_select;
s8 exposure;
WhiteBalance white_balance;
s8 sharpness;
@@ -183,14 +186,43 @@ struct PackageParameterCameraSelect {
s16 auto_white_balance_window_height;
};
-static_assert(sizeof(PackageParameterCameraSelect) == 28,
- "PackageParameterCameraSelect structure size is wrong");
+static_assert(sizeof(PackageParameterWithoutContext) == 28,
+ "PackageParameterCameraWithoutContext structure size is wrong");
+
+struct PackageParameterWithContext {
+ u8 camera_select;
+ u8 context_select;
+ Flip flip;
+ Effect effect;
+ Size size;
+ INSERT_PADDING_BYTES(3);
+
+ Resolution GetResolution();
+};
+
+static_assert(sizeof(PackageParameterWithContext) == 8,
+ "PackageParameterWithContext structure size is wrong");
+
+struct PackageParameterWithContextDetail {
+ u8 camera_select;
+ u8 context_select;
+ Flip flip;
+ Effect effect;
+ Resolution resolution;
+
+ Resolution GetResolution() {
+ return resolution;
+ }
+};
+
+static_assert(sizeof(PackageParameterWithContextDetail) == 16,
+ "PackageParameterWithContextDetail structure size is wrong");
/**
- * Unknown
+ * Starts capturing at the selected port.
* Inputs:
* 0: 0x00010040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00010040
* 1: ResultCode
@@ -198,10 +230,10 @@ static_assert(sizeof(PackageParameterCameraSelect) == 28,
void StartCapture(Service::Interface* self);
/**
- * Unknown
+ * Stops capturing from the selected port.
* Inputs:
* 0: 0x00020040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00020040
* 1: ResultCode
@@ -209,10 +241,33 @@ void StartCapture(Service::Interface* self);
void StopCapture(Service::Interface* self);
/**
+ * Gets whether the selected port is currently capturing.
+ * Inputs:
+ * 0: 0x00030040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x00030080
+ * 1: ResultCode
+ * 2: 0 if not capturing, 1 if capturing
+ */
+void IsBusy(Service::Interface* self);
+
+/**
+ * Clears the buffer of selected ports.
+ * Inputs:
+ * 0: 0x00040040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x00040040
+ * 2: ResultCode
+ */
+void ClearBuffer(Service::Interface* self);
+
+/**
* Unknown
* Inputs:
* 0: 0x00050040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00050042
* 1: ResultCode
@@ -225,7 +280,7 @@ void GetVsyncInterruptEvent(Service::Interface* self);
* Unknown
* Inputs:
* 0: 0x00060040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x00060042
* 1: ResultCode
@@ -241,9 +296,9 @@ void GetBufferErrorInterruptEvent(Service::Interface* self);
* Inputs:
* 0: 0x00070102
* 1: Destination address in calling process
- * 2: u8 Camera port (`Port` enum)
- * 3: Image size (in bytes?)
- * 4: u16 Transfer unit size (in bytes?)
+ * 2: u8 selected port
+ * 3: Image size (in bytes)
+ * 4: u16 Transfer unit size (in bytes)
* 5: Descriptor: Handle
* 6: Handle to destination process
* Outputs:
@@ -255,21 +310,34 @@ void GetBufferErrorInterruptEvent(Service::Interface* self);
void SetReceiving(Service::Interface* self);
/**
- * Unknown
+ * Gets whether the selected port finished receiving a frame.
+ * Inputs:
+ * 0: 0x00080040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x00080080
+ * 1: ResultCode
+ * 2: 0 if not finished, 1 if finished
+ */
+void IsFinishedReceiving(Service::Interface* self);
+
+/**
+ * Sets the number of lines the buffer contains.
* Inputs:
* 0: 0x00090100
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* 2: u16 Number of lines to transfer
* 3: u16 Width
* 4: u16 Height
* Outputs:
* 0: 0x00090040
* 1: ResultCode
+ * @todo figure out how the "buffer" actually works.
*/
void SetTransferLines(Service::Interface* self);
/**
- * Unknown
+ * Gets the maximum number of lines that fit in the buffer
* Inputs:
* 0: 0x000A0080
* 1: u16 Width
@@ -277,27 +345,58 @@ void SetTransferLines(Service::Interface* self);
* Outputs:
* 0: 0x000A0080
* 1: ResultCode
- * 2: Maximum number of lines that fit in the buffer(?)
+ * 2: Maximum number of lines that fit in the buffer
+ * @todo figure out how the "buffer" actually works.
*/
void GetMaxLines(Service::Interface* self);
/**
- * Unknown
+ * Sets the number of bytes the buffer contains.
+ * Inputs:
+ * 0: 0x000B0100
+ * 1: u8 selected port
+ * 2: u16 Number of bytes to transfer
+ * 3: u16 Width
+ * 4: u16 Height
+ * Outputs:
+ * 0: 0x000B0040
+ * 1: ResultCode
+ * @todo figure out how the "buffer" actually works.
+ */
+void SetTransferBytes(Service::Interface* self);
+
+/**
+ * Gets the number of bytes to the buffer contains.
* Inputs:
* 0: 0x000C0040
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* Outputs:
* 0: 0x000C0080
* 1: ResultCode
- * 2: Total number of bytes for each frame with current settings(?)
+ * 2: The number of bytes the buffer contains
+ * @todo figure out how the "buffer" actually works.
*/
void GetTransferBytes(Service::Interface* self);
/**
- * Unknown
+ * Gets the maximum number of bytes that fit in the buffer.
+ * Inputs:
+ * 0: 0x000D0080
+ * 1: u16 Width
+ * 2: u16 Height
+ * Outputs:
+ * 0: 0x000D0080
+ * 1: ResultCode
+ * 2: Maximum number of bytes that fit in the buffer
+ * @todo figure out how the "buffer" actually works.
+ */
+void GetMaxBytes(Service::Interface* self);
+
+/**
+ * Enables or disables trimming.
* Inputs:
* 0: 0x000E0080
- * 1: u8 Camera port (`Port` enum)
+ * 1: u8 selected port
* 2: u8 bool Enable trimming if true
* Outputs:
* 0: 0x000E0040
@@ -306,14 +405,58 @@ void GetTransferBytes(Service::Interface* self);
void SetTrimming(Service::Interface* self);
/**
- * Unknown
+ * Gets whether trimming is enabled.
+ * Inputs:
+ * 0: 0x000F0040
+ * 1: u8 selected port
+ * Outputs:
+ * 0: 0x000F0080
+ * 1: ResultCode
+ * 2: u8 bool Enable trimming if true
+ */
+void IsTrimming(Service::Interface* self);
+
+/**
+ * Sets the position to trim.
+ * Inputs:
+ * 0: 0x00100140
+ * 1: u8 selected port
+ * 2: x start
+ * 3: y start
+ * 4: x end (exclusive)
+ * 5: y end (exclusive)
+ * Outputs:
+ * 0: 0x00100040
+ * 1: ResultCode
+ */
+void SetTrimmingParams(Service::Interface* self);
+
+/**
+ * Gets the position to trim.
+ * Inputs:
+ * 0: 0x00110040
+ * 1: u8 selected port
+ *
+ * Outputs:
+ * 0: 0x00110140
+ * 1: ResultCode
+ * 2: x start
+ * 3: y start
+ * 4: x end (exclusive)
+ * 5: y end (exclusive)
+ */
+void GetTrimmingParams(Service::Interface* self);
+
+/**
+ * Sets the position to trim by giving the width and height. The trimming window is always at the
+ * center.
* Inputs:
* 0: 0x00120140
- * 1: u8 Camera port (`Port` enum)
- * 2: s16 Trim width(?)
- * 3: s16 Trim height(?)
- * 4: s16 Camera width(?)
- * 5: s16 Camera height(?)
+ * 1: u8 selected port
+ * 2: s16 Trim width
+ * 3: s16 Trim height
+ * 4: s16 Camera width
+ * 5: s16 Camera height
* Outputs:
* 0: 0x00120040
* 1: ResultCode
@@ -324,7 +467,7 @@ void SetTrimmingParamsCenter(Service::Interface* self);
* Selects up to two physical cameras to enable.
* Inputs:
* 0: 0x00130040
- * 1: u8 Cameras to activate (`CameraSelect` enum)
+ * 1: u8 selected camera
* Outputs:
* 0: 0x00130040
* 1: ResultCode
@@ -332,12 +475,24 @@ void SetTrimmingParamsCenter(Service::Interface* self);
void Activate(Service::Interface* self);
/**
- * Unknown
+ * Switches the context of camera settings.
+ * Inputs:
+ * 0: 0x00140080
+ * 1: u8 selected camera
+ * 2: u8 selected context
+ * Outputs:
+ * 0: 0x00140040
+ * 1: ResultCode
+ */
+void SwitchContext(Service::Interface* self);
+
+/**
+ * Sets flipping of images
* Inputs:
* 0: 0x001D00C0
- * 1: u8 Camera select (`CameraSelect` enum)
+ * 1: u8 selected camera
* 2: u8 Type of flipping to perform (`Flip` enum)
- * 3: u8 Context (`Context` enum)
+ * 3: u8 selected context
* Outputs:
* 0: 0x001D0040
* 1: ResultCode
@@ -345,12 +500,30 @@ void Activate(Service::Interface* self);
void FlipImage(Service::Interface* self);
/**
- * Unknown
+ * Sets camera resolution from custom parameters. For more details see the Resolution struct.
+ * Inputs:
+ * 0: 0x001E0200
+ * 1: u8 selected camera
+ * 2: width
+ * 3: height
+ * 4: crop x0
+ * 5: crop y0
+ * 6: crop x1
+ * 7: crop y1
+ * 8: u8 selected context
+ * Outputs:
+ * 0: 0x001E0040
+ * 1: ResultCode
+ */
+void SetDetailSize(Service::Interface* self);
+
+/**
+ * Sets camera resolution from preset resolution parameters. .
* Inputs:
* 0: 0x001F00C0
- * 1: u8 Camera select (`CameraSelect` enum)
+ * 1: u8 selected camera
* 2: u8 Camera frame resolution (`Size` enum)
- * 3: u8 Context id (`Context` enum)
+ * 3: u8 selected context
* Outputs:
* 0: 0x001F0040
* 1: ResultCode
@@ -358,10 +531,10 @@ void FlipImage(Service::Interface* self);
void SetSize(Service::Interface* self);
/**
- * Unknown
+ * Sets camera framerate.
* Inputs:
* 0: 0x00200080
- * 1: u8 Camera select (`CameraSelect` enum)
+ * 1: u8 selected camera
* 2: u8 Camera framerate (`FrameRate` enum)
* Outputs:
* 0: 0x00200040
@@ -370,6 +543,44 @@ void SetSize(Service::Interface* self);
void SetFrameRate(Service::Interface* self);
/**
+ * Sets effect on the output image
+ * Inputs:
+ * 0: 0x002200C0
+ * 1: u8 selected camera
+ * 2: u8 image effect (`Effect` enum)
+ * 3: u8 selected context
+ * Outputs:
+ * 0: 0x00220040
+ * 1: ResultCode
+ */
+void SetEffect(Service::Interface* self);
+
+/**
+ * Sets format of the output image
+ * Inputs:
+ * 0: 0x002500C0
+ * 1: u8 selected camera
+ * 2: u8 image format (`OutputFormat` enum)
+ * 3: u8 selected context
+ * Outputs:
+ * 0: 0x00250040
+ * 1: ResultCode
+ */
+void SetOutputFormat(Service::Interface* self);
+
+/**
+ * Synchronizes the V-Sync timing of two cameras.
+ * Inputs:
+ * 0: 0x00290080
+ * 1: u8 selected camera 1
+ * 2: u8 selected camera 2
+ * Outputs:
+ * 0: 0x00280040
+ * 1: ResultCode
+ */
+void SynchronizeVsyncTiming(Service::Interface* self);
+
+/**
* Returns calibration data relating the outside cameras to eachother, for use in AR applications.
*
* Inputs:
@@ -382,6 +593,45 @@ void SetFrameRate(Service::Interface* self);
void GetStereoCameraCalibrationData(Service::Interface* self);
/**
+ * Batch-configures context-free settings.
+ *
+ * Inputs:
+ * 0: 0x003302C0
+ * 1-7: struct PachageParameterWithoutContext
+ * 8-11: unused
+ * Outputs:
+ * 0: 0x00330040
+ * 1: ResultCode
+ */
+void SetPackageParameterWithoutContext(Service::Interface* self);
+
+/**
+ * Batch-configures context-related settings with preset resolution parameters.
+ *
+ * Inputs:
+ * 0: 0x00340140
+ * 1-2: struct PackageParameterWithContext
+ * 3-5: unused
+ * Outputs:
+ * 0: 0x00340040
+ * 1: ResultCode
+ */
+void SetPackageParameterWithContext(Service::Interface* self);
+
+/**
+ * Batch-configures context-related settings with custom resolution parameters
+ *
+ * Inputs:
+ * 0: 0x003501C0
+ * 1-4: struct PackageParameterWithContextDetail
+ * 5-7: unused
+ * Outputs:
+ * 0: 0x00350040
+ * 1: ResultCode
+ */
+void SetPackageParameterWithContextDetail(Service::Interface* self);
+
+/**
* Unknown
* Inputs:
* 0: 0x00360000
diff --git a/src/core/hle/service/cam/cam_u.cpp b/src/core/hle/service/cam/cam_u.cpp
index af2123e5b..251c1e6d4 100644
--- a/src/core/hle/service/cam/cam_u.cpp
+++ b/src/core/hle/service/cam/cam_u.cpp
@@ -11,24 +11,24 @@ namespace CAM {
const Interface::FunctionInfo FunctionTable[] = {
{0x00010040, StartCapture, "StartCapture"},
{0x00020040, StopCapture, "StopCapture"},
- {0x00030040, nullptr, "IsBusy"},
- {0x00040040, nullptr, "ClearBuffer"},
+ {0x00030040, IsBusy, "IsBusy"},
+ {0x00040040, ClearBuffer, "ClearBuffer"},
{0x00050040, GetVsyncInterruptEvent, "GetVsyncInterruptEvent"},
{0x00060040, GetBufferErrorInterruptEvent, "GetBufferErrorInterruptEvent"},
{0x00070102, SetReceiving, "SetReceiving"},
- {0x00080040, nullptr, "IsFinishedReceiving"},
+ {0x00080040, IsFinishedReceiving, "IsFinishedReceiving"},
{0x00090100, SetTransferLines, "SetTransferLines"},
{0x000A0080, GetMaxLines, "GetMaxLines"},
- {0x000B0100, nullptr, "SetTransferBytes"},
+ {0x000B0100, SetTransferBytes, "SetTransferBytes"},
{0x000C0040, GetTransferBytes, "GetTransferBytes"},
- {0x000D0080, nullptr, "GetMaxBytes"},
+ {0x000D0080, GetMaxBytes, "GetMaxBytes"},
{0x000E0080, SetTrimming, "SetTrimming"},
- {0x000F0040, nullptr, "IsTrimming"},
- {0x00100140, nullptr, "SetTrimmingParams"},
- {0x00110040, nullptr, "GetTrimmingParams"},
+ {0x000F0040, IsTrimming, "IsTrimming"},
+ {0x00100140, SetTrimmingParams, "SetTrimmingParams"},
+ {0x00110040, GetTrimmingParams, "GetTrimmingParams"},
{0x00120140, SetTrimmingParamsCenter, "SetTrimmingParamsCenter"},
{0x00130040, Activate, "Activate"},
- {0x00140080, nullptr, "SwitchContext"},
+ {0x00140080, SwitchContext, "SwitchContext"},
{0x00150080, nullptr, "SetExposure"},
{0x00160080, nullptr, "SetWhiteBalance"},
{0x00170080, nullptr, "SetWhiteBalanceWithoutBaseUp"},
@@ -38,18 +38,18 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x001B0080, nullptr, "SetAutoWhiteBalance"},
{0x001C0040, nullptr, "IsAutoWhiteBalance"},
{0x001D00C0, FlipImage, "FlipImage"},
- {0x001E0200, nullptr, "SetDetailSize"},
+ {0x001E0200, SetDetailSize, "SetDetailSize"},
{0x001F00C0, SetSize, "SetSize"},
{0x00200080, SetFrameRate, "SetFrameRate"},
{0x00210080, nullptr, "SetPhotoMode"},
- {0x002200C0, nullptr, "SetEffect"},
+ {0x002200C0, SetEffect, "SetEffect"},
{0x00230080, nullptr, "SetContrast"},
{0x00240080, nullptr, "SetLensCorrection"},
- {0x002500C0, nullptr, "SetOutputFormat"},
+ {0x002500C0, SetOutputFormat, "SetOutputFormat"},
{0x00260140, nullptr, "SetAutoExposureWindow"},
{0x00270140, nullptr, "SetAutoWhiteBalanceWindow"},
{0x00280080, nullptr, "SetNoiseFilter"},
- {0x00290080, nullptr, "SynchronizeVsyncTiming"},
+ {0x00290080, SynchronizeVsyncTiming, "SynchronizeVsyncTiming"},
{0x002A0080, nullptr, "GetLatestVsyncTiming"},
{0x002B0000, GetStereoCameraCalibrationData, "GetStereoCameraCalibrationData"},
{0x002C0400, nullptr, "SetStereoCameraCalibrationData"},
@@ -59,9 +59,9 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x00300080, nullptr, "ReadMcuVariableI2cExclusive"},
{0x00310180, nullptr, "SetImageQualityCalibrationData"},
{0x00320000, nullptr, "GetImageQualityCalibrationData"},
- {0x003302C0, nullptr, "SetPackageParameterWithoutContext"},
- {0x00340140, nullptr, "SetPackageParameterWithContext"},
- {0x003501C0, nullptr, "SetPackageParameterWithContextDetail"},
+ {0x003302C0, SetPackageParameterWithoutContext, "SetPackageParameterWithoutContext"},
+ {0x00340140, SetPackageParameterWithContext, "SetPackageParameterWithContext"},
+ {0x003501C0, SetPackageParameterWithContextDetail, "SetPackageParameterWithContextDetail"},
{0x00360000, GetSuitableY2rStandardCoefficient, "GetSuitableY2rStandardCoefficient"},
{0x00370202, nullptr, "PlayShutterSoundWithWave"},
{0x00380040, PlayShutterSound, "PlayShutterSound"},
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp
index 65655f45d..6f13cde27 100644
--- a/src/core/hle/service/cfg/cfg.cpp
+++ b/src/core/hle/service/cfg/cfg.cpp
@@ -84,7 +84,6 @@ struct ConsoleCountryInfo {
static_assert(sizeof(ConsoleCountryInfo) == 4, "ConsoleCountryInfo must be exactly 4 bytes");
}
-static const u64 CFG_SAVE_ID = 0x00010017;
static const u64 CONSOLE_UNIQUE_ID = 0xDEADC0DE;
static const ConsoleModelInfo CONSOLE_MODEL = {NINTENDO_3DS_XL, {0, 0, 0}};
static const u8 CONSOLE_LANGUAGE = LANGUAGE_EN;
@@ -115,6 +114,8 @@ static const std::vector<u8> cfg_system_savedata_id = {
0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x01, 0x00,
};
+static u32 preferred_region_code = 0;
+
void GetCountryCodeString(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
u32 country_code_id = cmd_buff[1];
@@ -160,11 +161,18 @@ void GetCountryCodeID(Service::Interface* self) {
cmd_buff[2] = country_code_id;
}
+static u32 GetRegionValue() {
+ if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT)
+ return preferred_region_code;
+
+ return Settings::values.region_value;
+}
+
void SecureInfoGetRegion(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
cmd_buff[1] = RESULT_SUCCESS.raw;
- cmd_buff[2] = Settings::values.region_value;
+ cmd_buff[2] = GetRegionValue();
}
void GenHashConsoleUnique(Service::Interface* self) {
@@ -184,7 +192,7 @@ void GetRegionCanadaUSA(Service::Interface* self) {
cmd_buff[1] = RESULT_SUCCESS.raw;
u8 canada_or_usa = 1;
- if (canada_or_usa == Settings::values.region_value) {
+ if (canada_or_usa == GetRegionValue()) {
cmd_buff[2] = 1;
} else {
cmd_buff[2] = 0;
@@ -318,6 +326,7 @@ ResultCode GetConfigInfoBlock(u32 block_id, u32 size, u32 flag, void* output) {
void* pointer;
CASCADE_RESULT(pointer, GetConfigInfoBlockPointer(block_id, size, flag));
memcpy(output, pointer, size);
+
return RESULT_SUCCESS;
}
@@ -535,10 +544,55 @@ void Init() {
AddService(new CFG_U);
LoadConfigNANDSaveFile();
+
+ preferred_region_code = 0;
}
void Shutdown() {}
+/// Checks if the language is available in the chosen region, and returns a proper one
+static SystemLanguage AdjustLanguageInfoBlock(u32 region, SystemLanguage language) {
+ static const std::array<std::vector<SystemLanguage>, 7> region_languages{{
+ // JPN
+ {LANGUAGE_JP},
+ // USA
+ {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_ES, LANGUAGE_PT},
+ // EUR
+ {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT,
+ LANGUAGE_RU},
+ // AUS
+ {LANGUAGE_EN, LANGUAGE_FR, LANGUAGE_DE, LANGUAGE_IT, LANGUAGE_ES, LANGUAGE_NL, LANGUAGE_PT,
+ LANGUAGE_RU},
+ // CHN
+ {LANGUAGE_ZH},
+ // KOR
+ {LANGUAGE_KO},
+ // TWN
+ {LANGUAGE_TW},
+ }};
+ const auto& available = region_languages[region];
+ if (std::find(available.begin(), available.end(), language) == available.end()) {
+ return available[0];
+ }
+ return language;
+}
+
+void SetPreferredRegionCode(u32 region_code) {
+ preferred_region_code = region_code;
+ LOG_INFO(Service_CFG, "Preferred region code set to %u", preferred_region_code);
+
+ if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) {
+ const SystemLanguage current_language = GetSystemLanguage();
+ const SystemLanguage adjusted_language =
+ AdjustLanguageInfoBlock(region_code, current_language);
+ if (current_language != adjusted_language) {
+ LOG_WARNING(Service_CFG, "System language %d does not fit the region. Adjusted to %d",
+ static_cast<int>(current_language), static_cast<int>(adjusted_language));
+ SetSystemLanguage(adjusted_language);
+ }
+ }
+}
+
void SetUsername(const std::u16string& name) {
ASSERT(name.size() <= 10);
UsernameBlock block{};
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h
index fb47c2aa5..618c9647e 100644
--- a/src/core/hle/service/cfg/cfg.h
+++ b/src/core/hle/service/cfg/cfg.h
@@ -282,6 +282,13 @@ void Init();
/// Shutdown the config service
void Shutdown();
+/**
+ * Set the region code preferred by the game so that CFG will adjust to it when the region setting
+ * is auto.
+ * @param region_code the preferred region code to set
+ */
+void SetPreferredRegionCode(u32 region_code);
+
// Utilities for frontend to set config data.
// Note: before calling these functions, LoadConfigNANDSaveFile should be called,
// and UpdateConfigNANDSavegame should be called after making changes to config data.
diff --git a/src/core/hle/service/err_f.cpp b/src/core/hle/service/err_f.cpp
index cd0a1a598..9da55f328 100644
--- a/src/core/hle/service/err_f.cpp
+++ b/src/core/hle/service/err_f.cpp
@@ -227,6 +227,8 @@ static void ThrowFatalError(Interface* self) {
LOG_CRITICAL(Service_ERR, "FINST2: 0x%08X",
errtype.exception_data.exception_info.fpinst2);
break;
+ case ExceptionType::Undefined:
+ break; // Not logging exception_info for this case
}
LOG_CRITICAL(Service_ERR, "Datetime: %s", GetCurrentSystemTime().c_str());
break;
diff --git a/src/core/hle/service/gsp_gpu.cpp b/src/core/hle/service/gsp_gpu.cpp
index 947958703..1457518d4 100644
--- a/src/core/hle/service/gsp_gpu.cpp
+++ b/src/core/hle/service/gsp_gpu.cpp
@@ -149,7 +149,7 @@ static ResultCode WriteHWRegsWithMask(u32 base_address, u32 size_in_bytes, VAddr
u32 mask = Memory::Read32(masks_vaddr);
// Update the current value of the register only for set mask bits
- reg_value = (reg_value & ~mask) | (data | mask);
+ reg_value = (reg_value & ~mask) | (data & mask);
WriteSingleHWReg(base_address, reg_value);
@@ -705,6 +705,33 @@ static void ReleaseRight(Interface* self) {
LOG_WARNING(Service_GSP, "called");
}
+/**
+ * GSP_GPU::StoreDataCache service function
+ *
+ * This Function is a no-op, We aren't emulating the CPU cache any time soon.
+ *
+ * Inputs:
+ * 0 : Header code [0x001F0082]
+ * 1 : Address
+ * 2 : Size
+ * 3 : Value 0, some descriptor for the KProcess Handle
+ * 4 : KProcess handle
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+static void StoreDataCache(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+ u32 address = cmd_buff[1];
+ u32 size = cmd_buff[2];
+ u32 process = cmd_buff[4];
+
+ cmd_buff[0] = IPC::MakeHeader(0x1F, 0x1, 0);
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+
+ LOG_DEBUG(Service_GSP, "(STUBBED) called address=0x%08X, size=0x%08X, process=0x%08X", address,
+ size, process);
+}
+
const Interface::FunctionInfo FunctionTable[] = {
{0x00010082, WriteHWRegs, "WriteHWRegs"},
{0x00020084, WriteHWRegsWithMask, "WriteHWRegsWithMask"},
@@ -736,7 +763,7 @@ const Interface::FunctionInfo FunctionTable[] = {
{0x001C0040, nullptr, "SetLedForceOff"},
{0x001D0040, nullptr, "SetTestCommand"},
{0x001E0080, nullptr, "SetInternalPriorities"},
- {0x001F0082, nullptr, "StoreDataCache"},
+ {0x001F0082, StoreDataCache, "StoreDataCache"},
};
GSP_GPU::GSP_GPU() {
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 676154bd4..f14ab3811 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -35,6 +35,15 @@ static u32 next_gyroscope_index;
static int enable_accelerometer_count = 0; // positive means enabled
static int enable_gyroscope_count = 0; // positive means enabled
+static int pad_update_event;
+static int accelerometer_update_event;
+static int gyroscope_update_event;
+
+// Updating period for each HID device. These empirical values are measured from a 11.2 3DS.
+constexpr u64 pad_update_ticks = BASE_CLOCK_RATE_ARM11 / 234;
+constexpr u64 accelerometer_update_ticks = BASE_CLOCK_RATE_ARM11 / 104;
+constexpr u64 gyroscope_update_ticks = BASE_CLOCK_RATE_ARM11 / 101;
+
static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
// 30 degree and 60 degree are angular thresholds for directions
constexpr float TAN30 = 0.577350269f;
@@ -65,14 +74,9 @@ static PadState GetCirclePadDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
return state;
}
-void Update() {
+static void UpdatePadCallback(u64 userdata, int cycles_late) {
SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
- if (mem == nullptr) {
- LOG_DEBUG(Service_HID, "Cannot update HID prior to mapping shared memory!");
- return;
- }
-
PadState state = VideoCore::g_emu_window->GetPadState();
// Get current circle pad position and update circle pad direction
@@ -131,59 +135,68 @@ void Update() {
event_pad_or_touch_1->Signal();
event_pad_or_touch_2->Signal();
- // Update accelerometer
- if (enable_accelerometer_count > 0) {
- mem->accelerometer.index = next_accelerometer_index;
- next_accelerometer_index =
- (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
-
- AccelerometerDataEntry& accelerometer_entry =
- mem->accelerometer.entries[mem->accelerometer.index];
- std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) =
- VideoCore::g_emu_window->GetAccelerometerState();
-
- // Make up "raw" entry
- // TODO(wwylele):
- // From hardware testing, the raw_entry values are approximately,
- // but not exactly, as twice as corresponding entries (or with a minus sign).
- // It may caused by system calibration to the accelerometer.
- // Figure out how it works, or, if no game reads raw_entry,
- // the following three lines can be removed and leave raw_entry unimplemented.
- mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x;
- mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y;
- mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z;
-
- // If we just updated index 0, provide a new timestamp
- if (mem->accelerometer.index == 0) {
- mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks;
- mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks();
- }
+ // Reschedule recurrent event
+ CoreTiming::ScheduleEvent(pad_update_ticks - cycles_late, pad_update_event);
+}
+
+static void UpdateAccelerometerCallback(u64 userdata, int cycles_late) {
+ SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
+
+ mem->accelerometer.index = next_accelerometer_index;
+ next_accelerometer_index = (next_accelerometer_index + 1) % mem->accelerometer.entries.size();
- event_accelerometer->Signal();
+ AccelerometerDataEntry& accelerometer_entry =
+ mem->accelerometer.entries[mem->accelerometer.index];
+ std::tie(accelerometer_entry.x, accelerometer_entry.y, accelerometer_entry.z) =
+ VideoCore::g_emu_window->GetAccelerometerState();
+
+ // Make up "raw" entry
+ // TODO(wwylele):
+ // From hardware testing, the raw_entry values are approximately, but not exactly, as twice as
+ // corresponding entries (or with a minus sign). It may caused by system calibration to the
+ // accelerometer. Figure out how it works, or, if no game reads raw_entry, the following three
+ // lines can be removed and leave raw_entry unimplemented.
+ mem->accelerometer.raw_entry.x = -2 * accelerometer_entry.x;
+ mem->accelerometer.raw_entry.z = 2 * accelerometer_entry.y;
+ mem->accelerometer.raw_entry.y = -2 * accelerometer_entry.z;
+
+ // If we just updated index 0, provide a new timestamp
+ if (mem->accelerometer.index == 0) {
+ mem->accelerometer.index_reset_ticks_previous = mem->accelerometer.index_reset_ticks;
+ mem->accelerometer.index_reset_ticks = (s64)CoreTiming::GetTicks();
}
- // Update gyroscope
- if (enable_gyroscope_count > 0) {
- mem->gyroscope.index = next_gyroscope_index;
- next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();
+ event_accelerometer->Signal();
- GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
- std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) =
- VideoCore::g_emu_window->GetGyroscopeState();
+ // Reschedule recurrent event
+ CoreTiming::ScheduleEvent(accelerometer_update_ticks - cycles_late, accelerometer_update_event);
+}
- // Make up "raw" entry
- mem->gyroscope.raw_entry.x = gyroscope_entry.x;
- mem->gyroscope.raw_entry.z = -gyroscope_entry.y;
- mem->gyroscope.raw_entry.y = gyroscope_entry.z;
+static void UpdateGyroscopeCallback(u64 userdata, int cycles_late) {
+ SharedMem* mem = reinterpret_cast<SharedMem*>(shared_mem->GetPointer());
- // If we just updated index 0, provide a new timestamp
- if (mem->gyroscope.index == 0) {
- mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks;
- mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks();
- }
+ mem->gyroscope.index = next_gyroscope_index;
+ next_gyroscope_index = (next_gyroscope_index + 1) % mem->gyroscope.entries.size();
+
+ GyroscopeDataEntry& gyroscope_entry = mem->gyroscope.entries[mem->gyroscope.index];
+ std::tie(gyroscope_entry.x, gyroscope_entry.y, gyroscope_entry.z) =
+ VideoCore::g_emu_window->GetGyroscopeState();
+
+ // Make up "raw" entry
+ mem->gyroscope.raw_entry.x = gyroscope_entry.x;
+ mem->gyroscope.raw_entry.z = -gyroscope_entry.y;
+ mem->gyroscope.raw_entry.y = gyroscope_entry.z;
- event_gyroscope->Signal();
+ // If we just updated index 0, provide a new timestamp
+ if (mem->gyroscope.index == 0) {
+ mem->gyroscope.index_reset_ticks_previous = mem->gyroscope.index_reset_ticks;
+ mem->gyroscope.index_reset_ticks = (s64)CoreTiming::GetTicks();
}
+
+ event_gyroscope->Signal();
+
+ // Reschedule recurrent event
+ CoreTiming::ScheduleEvent(gyroscope_update_ticks - cycles_late, gyroscope_update_event);
}
void GetIPCHandles(Service::Interface* self) {
@@ -204,7 +217,11 @@ void EnableAccelerometer(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
++enable_accelerometer_count;
- event_accelerometer->Signal();
+
+ // Schedules the accelerometer update event if the accelerometer was just enabled
+ if (enable_accelerometer_count == 1) {
+ CoreTiming::ScheduleEvent(accelerometer_update_ticks, accelerometer_update_event);
+ }
cmd_buff[1] = RESULT_SUCCESS.raw;
@@ -215,7 +232,11 @@ void DisableAccelerometer(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
--enable_accelerometer_count;
- event_accelerometer->Signal();
+
+ // Unschedules the accelerometer update event if the accelerometer was just disabled
+ if (enable_accelerometer_count == 0) {
+ CoreTiming::UnscheduleEvent(accelerometer_update_event, 0);
+ }
cmd_buff[1] = RESULT_SUCCESS.raw;
@@ -226,7 +247,11 @@ void EnableGyroscopeLow(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
++enable_gyroscope_count;
- event_gyroscope->Signal();
+
+ // Schedules the gyroscope update event if the gyroscope was just enabled
+ if (enable_gyroscope_count == 1) {
+ CoreTiming::ScheduleEvent(gyroscope_update_ticks, gyroscope_update_event);
+ }
cmd_buff[1] = RESULT_SUCCESS.raw;
@@ -237,7 +262,11 @@ void DisableGyroscopeLow(Service::Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
--enable_gyroscope_count;
- event_gyroscope->Signal();
+
+ // Unschedules the gyroscope update event if the gyroscope was just disabled
+ if (enable_gyroscope_count == 0) {
+ CoreTiming::UnscheduleEvent(gyroscope_update_event, 0);
+ }
cmd_buff[1] = RESULT_SUCCESS.raw;
@@ -291,6 +320,8 @@ void Init() {
next_pad_index = 0;
next_touch_index = 0;
+ next_accelerometer_index = 0;
+ next_gyroscope_index = 0;
// Create event handles
event_pad_or_touch_1 = Event::Create(ResetType::OneShot, "HID:EventPadOrTouch1");
@@ -298,6 +329,15 @@ void Init() {
event_accelerometer = Event::Create(ResetType::OneShot, "HID:EventAccelerometer");
event_gyroscope = Event::Create(ResetType::OneShot, "HID:EventGyroscope");
event_debug_pad = Event::Create(ResetType::OneShot, "HID:EventDebugPad");
+
+ // Register update callbacks
+ pad_update_event = CoreTiming::RegisterEvent("HID::UpdatePadCallback", UpdatePadCallback);
+ accelerometer_update_event =
+ CoreTiming::RegisterEvent("HID::UpdateAccelerometerCallback", UpdateAccelerometerCallback);
+ gyroscope_update_event =
+ CoreTiming::RegisterEvent("HID::UpdateGyroscopeCallback", UpdateGyroscopeCallback);
+
+ CoreTiming::ScheduleEvent(pad_update_ticks, pad_update_event);
}
void Shutdown() {
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 7904e7355..21e66dfe0 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -296,9 +296,6 @@ void GetGyroscopeLowRawToDpsCoefficient(Service::Interface* self);
*/
void GetGyroscopeLowCalibrateParam(Service::Interface* self);
-/// Checks for user input updates
-void Update();
-
/// Initialize HID service
void Init();
diff --git a/src/core/hle/service/mic_u.cpp b/src/core/hle/service/mic_u.cpp
index 4f1dd2fce..e98388560 100644
--- a/src/core/hle/service/mic_u.cpp
+++ b/src/core/hle/service/mic_u.cpp
@@ -93,13 +93,14 @@ static void StartSampling(Interface* self) {
sample_rate = static_cast<SampleRate>(cmd_buff[2] & 0xFF);
audio_buffer_offset = cmd_buff[3];
audio_buffer_size = cmd_buff[4];
- audio_buffer_loop = static_cast<bool>(cmd_buff[5] & 0xFF);
+ audio_buffer_loop = (cmd_buff[5] & 0xFF) != 0;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
is_sampling = true;
LOG_WARNING(Service_MIC, "(STUBBED) called, encoding=%u, sample_rate=%u, "
"audio_buffer_offset=%d, audio_buffer_size=%u, audio_buffer_loop=%u",
- encoding, sample_rate, audio_buffer_offset, audio_buffer_size, audio_buffer_loop);
+ static_cast<u32>(encoding), static_cast<u32>(sample_rate), audio_buffer_offset,
+ audio_buffer_size, audio_buffer_loop);
}
/**
@@ -114,7 +115,7 @@ static void AdjustSampling(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
sample_rate = static_cast<SampleRate>(cmd_buff[1] & 0xFF);
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
- LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", sample_rate);
+ LOG_WARNING(Service_MIC, "(STUBBED) called, sample_rate=%u", static_cast<u32>(sample_rate));
}
/**
@@ -201,7 +202,7 @@ static void GetGain(Interface* self) {
*/
static void SetPower(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- mic_power = static_cast<bool>(cmd_buff[1] & 0xFF);
+ mic_power = (cmd_buff[1] & 0xFF) != 0;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_MIC, "(STUBBED) called, mic_power=%u", mic_power);
}
@@ -251,7 +252,7 @@ static void SetIirFilterMic(Interface* self) {
*/
static void SetClamp(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- clamp = static_cast<bool>(cmd_buff[1] & 0xFF);
+ clamp = (cmd_buff[1] & 0xFF) != 0;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_MIC, "(STUBBED) called, clamp=%u", clamp);
}
@@ -281,7 +282,7 @@ static void GetClamp(Interface* self) {
*/
static void SetAllowShellClosed(Interface* self) {
u32* cmd_buff = Kernel::GetCommandBuffer();
- allow_shell_closed = static_cast<bool>(cmd_buff[1] & 0xFF);
+ allow_shell_closed = (cmd_buff[1] & 0xFF) != 0;
cmd_buff[1] = RESULT_SUCCESS.raw; // No error
LOG_WARNING(Service_MIC, "(STUBBED) called, allow_shell_closed=%u", allow_shell_closed);
}
diff --git a/src/core/hle/service/nfc/nfc.cpp b/src/core/hle/service/nfc/nfc.cpp
index d9738c6a1..fd3c7d9c2 100644
--- a/src/core/hle/service/nfc/nfc.cpp
+++ b/src/core/hle/service/nfc/nfc.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/kernel/event.h"
#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfc/nfc_m.h"
#include "core/hle/service/nfc/nfc_u.h"
@@ -9,9 +10,133 @@
namespace Service {
namespace NFC {
+static Kernel::SharedPtr<Kernel::Event> tag_in_range_event;
+static Kernel::SharedPtr<Kernel::Event> tag_out_of_range_event;
+static TagState nfc_tag_state = TagState::NotInitialized;
+static CommunicationStatus nfc_status = CommunicationStatus::NfcInitialized;
+
+void Initialize(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ u8 param = static_cast<u8>(cmd_buff[1] & 0xFF);
+
+ nfc_tag_state = TagState::NotScanning;
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called, param=%u", param);
+}
+
+void Shutdown(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ u8 param = static_cast<u8>(cmd_buff[1] & 0xFF);
+ nfc_tag_state = TagState::NotInitialized;
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called, param=%u", param);
+}
+
+void StartCommunication(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void StopCommunication(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void StartTagScanning(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ nfc_tag_state = TagState::TagInRange;
+ tag_in_range_event->Signal();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void StopTagScanning(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ nfc_tag_state = TagState::NotScanning;
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void LoadAmiiboData(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ nfc_tag_state = TagState::TagDataLoaded;
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void ResetTagScanState(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ nfc_tag_state = TagState::NotScanning;
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void GetTagInRangeEvent(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[0] = IPC::MakeHeader(0xB, 1, 2);
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_in_range_event).MoveFrom();
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void GetTagOutOfRangeEvent(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[0] = IPC::MakeHeader(0xC, 1, 2);
+ cmd_buff[1] = RESULT_SUCCESS.raw;
+ cmd_buff[2] = IPC::CopyHandleDesc();
+ cmd_buff[3] = Kernel::g_handle_table.Create(tag_out_of_range_event).MoveFrom();
+ LOG_WARNING(Service_NFC, "(STUBBED) called");
+}
+
+void GetTagState(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = static_cast<u8>(nfc_tag_state);
+ LOG_DEBUG(Service_NFC, "(STUBBED) called");
+}
+
+void CommunicationGetStatus(Interface* self) {
+ u32* cmd_buff = Kernel::GetCommandBuffer();
+
+ cmd_buff[1] = RESULT_SUCCESS.raw; // No error
+ cmd_buff[2] = static_cast<u8>(nfc_status);
+ LOG_DEBUG(Service_NFC, "(STUBBED) called");
+}
+
void Init() {
AddService(new NFC_M());
AddService(new NFC_U());
+
+ tag_in_range_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_in_range_event");
+ tag_out_of_range_event =
+ Kernel::Event::Create(Kernel::ResetType::OneShot, "NFC::tag_out_range_event");
+ nfc_tag_state = TagState::NotInitialized;
+}
+
+void Shutdown() {
+ tag_in_range_event = nullptr;
+ tag_out_of_range_event = nullptr;
}
} // namespace NFC
diff --git a/src/core/hle/service/nfc/nfc.h b/src/core/hle/service/nfc/nfc.h
index cd65a5fdc..a013bdae7 100644
--- a/src/core/hle/service/nfc/nfc.h
+++ b/src/core/hle/service/nfc/nfc.h
@@ -4,11 +4,150 @@
#pragma once
+#include "common/common_types.h"
+
namespace Service {
+
+class Interface;
+
namespace NFC {
+enum class TagState : u8 {
+ NotInitialized = 0,
+ NotScanning = 1,
+ Scanning = 2,
+ TagInRange = 3,
+ TagOutOfRange = 4,
+ TagDataLoaded = 5,
+};
+
+enum class CommunicationStatus : u8 {
+ AttemptInitialize = 1,
+ NfcInitialized = 2,
+};
+
+/**
+ * NFC::Initialize service function
+ * Inputs:
+ * 0 : Header code [0x00010040]
+ * 1 : (u8) unknown parameter. Can be either value 0x1 or 0x2
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void Initialize(Interface* self);
+
+/**
+ * NFC::Shutdown service function
+ * Inputs:
+ * 0 : Header code [0x00020040]
+ * 1 : (u8) unknown parameter
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void Shutdown(Interface* self);
+
+/**
+ * NFC::StartCommunication service function
+ * Inputs:
+ * 0 : Header code [0x00030000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void StartCommunication(Interface* self);
+
+/**
+ * NFC::StopCommunication service function
+ * Inputs:
+ * 0 : Header code [0x00040000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void StopCommunication(Interface* self);
+
+/**
+ * NFC::StartTagScanning service function
+ * Inputs:
+ * 0 : Header code [0x00050040]
+ * 1 : (u16) unknown. This is normally 0x0
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void StartTagScanning(Interface* self);
+
+/**
+ * NFC::StopTagScanning service function
+ * Inputs:
+ * 0 : Header code [0x00060000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void StopTagScanning(Interface* self);
+
+/**
+ * NFC::LoadAmiiboData service function
+ * Inputs:
+ * 0 : Header code [0x00070000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void LoadAmiiboData(Interface* self);
+
+/**
+ * NFC::ResetTagScanState service function
+ * Inputs:
+ * 0 : Header code [0x00080000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ */
+void ResetTagScanState(Interface* self);
+
+/**
+ * NFC::GetTagInRangeEvent service function
+ * Inputs:
+ * 0 : Header code [0x000B0000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Copy handle descriptor
+ * 3 : Event Handle
+ */
+void GetTagInRangeEvent(Interface* self);
+
+/**
+ * NFC::GetTagOutOfRangeEvent service function
+ * Inputs:
+ * 0 : Header code [0x000C0000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : Copy handle descriptor
+ * 3 : Event Handle
+ */
+void GetTagOutOfRangeEvent(Interface* self);
+
+/**
+ * NFC::GetTagState service function
+ * Inputs:
+ * 0 : Header code [0x000D0000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : (u8) Tag state
+ */
+void GetTagState(Interface* self);
+
+/**
+ * NFC::CommunicationGetStatus service function
+ * Inputs:
+ * 0 : Header code [0x000F0000]
+ * Outputs:
+ * 1 : Result of function, 0 on success, otherwise error code
+ * 2 : (u8) Communication state
+ */
+void CommunicationGetStatus(Interface* self);
+
/// Initialize all NFC services.
void Init();
+/// Shutdown all NFC services.
+void Shutdown();
+
} // namespace NFC
} // namespace Service
diff --git a/src/core/hle/service/nfc/nfc_m.cpp b/src/core/hle/service/nfc/nfc_m.cpp
index 717335c11..ebe637650 100644
--- a/src/core/hle/service/nfc/nfc_m.cpp
+++ b/src/core/hle/service/nfc/nfc_m.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfc/nfc_m.h"
namespace Service {
@@ -10,17 +11,19 @@ namespace NFC {
const Interface::FunctionInfo FunctionTable[] = {
// clang-format off
// nfc:u shared commands
- {0x00010040, nullptr, "Initialize"},
- {0x00020040, nullptr, "Shutdown"},
- {0x00030000, nullptr, "StartCommunication"},
- {0x00040000, nullptr, "StopCommunication"},
- {0x00050040, nullptr, "StartTagScanning"},
- {0x00060000, nullptr, "StopTagScanning"},
- {0x00070000, nullptr, "LoadAmiiboData"},
- {0x00080000, nullptr, "ResetTagScanState"},
+ {0x00010040, Initialize, "Initialize"},
+ {0x00020040, Shutdown, "Shutdown"},
+ {0x00030000, StartCommunication, "StartCommunication"},
+ {0x00040000, StopCommunication, "StopCommunication"},
+ {0x00050040, StartTagScanning, "StartTagScanning"},
+ {0x00060000, StopTagScanning, "StopTagScanning"},
+ {0x00070000, LoadAmiiboData, "LoadAmiiboData"},
+ {0x00080000, ResetTagScanState, "ResetTagScanState"},
{0x00090002, nullptr, "UpdateStoredAmiiboData"},
- {0x000D0000, nullptr, "GetTagState"},
- {0x000F0000, nullptr, "CommunicationGetStatus"},
+ {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"},
+ {0x000C0000, GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"},
+ {0x000D0000, GetTagState, "GetTagState"},
+ {0x000F0000, CommunicationGetStatus, "CommunicationGetStatus"},
{0x00100000, nullptr, "GetTagInfo2"},
{0x00110000, nullptr, "GetTagInfo"},
{0x00120000, nullptr, "CommunicationGetResult"},
diff --git a/src/core/hle/service/nfc/nfc_u.cpp b/src/core/hle/service/nfc/nfc_u.cpp
index deffb0b4f..5a40c7874 100644
--- a/src/core/hle/service/nfc/nfc_u.cpp
+++ b/src/core/hle/service/nfc/nfc_u.cpp
@@ -2,6 +2,7 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+#include "core/hle/service/nfc/nfc.h"
#include "core/hle/service/nfc/nfc_u.h"
namespace Service {
@@ -9,17 +10,19 @@ namespace NFC {
const Interface::FunctionInfo FunctionTable[] = {
// clang-format off
- {0x00010040, nullptr, "Initialize"},
- {0x00020040, nullptr, "Shutdown"},
- {0x00030000, nullptr, "StartCommunication"},
- {0x00040000, nullptr, "StopCommunication"},
- {0x00050040, nullptr, "StartTagScanning"},
- {0x00060000, nullptr, "StopTagScanning"},
- {0x00070000, nullptr, "LoadAmiiboData"},
- {0x00080000, nullptr, "ResetTagScanState"},
+ {0x00010040, Initialize, "Initialize"},
+ {0x00020040, Shutdown, "Shutdown"},
+ {0x00030000, StartCommunication, "StartCommunication"},
+ {0x00040000, StopCommunication, "StopCommunication"},
+ {0x00050040, StartTagScanning, "StartTagScanning"},
+ {0x00060000, StopTagScanning, "StopTagScanning"},
+ {0x00070000, LoadAmiiboData, "LoadAmiiboData"},
+ {0x00080000, ResetTagScanState, "ResetTagScanState"},
{0x00090002, nullptr, "UpdateStoredAmiiboData"},
- {0x000D0000, nullptr, "GetTagState"},
- {0x000F0000, nullptr, "CommunicationGetStatus"},
+ {0x000B0000, GetTagInRangeEvent, "GetTagInRangeEvent"},
+ {0x000C0000, GetTagOutOfRangeEvent, "GetTagOutOfRangeEvent"},
+ {0x000D0000, GetTagState, "GetTagState"},
+ {0x000F0000, CommunicationGetStatus, "CommunicationGetStatus"},
{0x00100000, nullptr, "GetTagInfo2"},
{0x00110000, nullptr, "GetTagInfo"},
{0x00120000, nullptr, "CommunicationGetResult"},
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp
index 7e52a05d9..0672ac2e3 100644
--- a/src/core/hle/service/service.cpp
+++ b/src/core/hle/service/service.cpp
@@ -6,9 +6,8 @@
#include "common/logging/log.h"
#include "common/string_util.h"
-
#include "core/hle/kernel/server_port.h"
-#include "core/hle/service/ac_u.h"
+#include "core/hle/service/ac/ac.h"
#include "core/hle/service/act/act.h"
#include "core/hle/service/am/am.h"
#include "core/hle/service/apt/apt.h"
@@ -138,6 +137,7 @@ void Init() {
AddNamedPort(new ERR::ERR_F);
FS::ArchiveInit();
+ AC::Init();
ACT::Init();
AM::Init();
APT::Init();
@@ -158,7 +158,6 @@ void Init() {
PTM::Init();
QTM::Init();
- AddService(new AC::AC_U);
AddService(new CSND::CSND_SND);
AddService(new DSP_DSP::Interface);
AddService(new GSP::GSP_GPU);
@@ -178,6 +177,7 @@ void Init() {
/// Shutdown ServiceManager
void Shutdown() {
PTM::Shutdown();
+ NFC::Shutdown();
NIM::Shutdown();
NEWS::Shutdown();
NDM::Shutdown();
@@ -191,6 +191,7 @@ void Shutdown() {
BOSS::Shutdown();
APT::Shutdown();
AM::Shutdown();
+ AC::Shutdown();
FS::ArchiveShutdown();
g_srv_services.clear();
diff --git a/src/core/hle/service/soc_u.cpp b/src/core/hle/service/soc_u.cpp
index c3918cdd0..dcc5c3c90 100644
--- a/src/core/hle/service/soc_u.cpp
+++ b/src/core/hle/service/soc_u.cpp
@@ -603,7 +603,6 @@ static void RecvFrom(Interface* self) {
u32 socket_handle = cmd_buffer[1];
u32 len = cmd_buffer[2];
u32 flags = cmd_buffer[3];
- socklen_t addr_len = static_cast<socklen_t>(cmd_buffer[4]);
struct {
u32 output_buffer_descriptor;
@@ -693,7 +692,6 @@ static void Poll(Interface* self) {
static void GetSockName(Interface* self) {
u32* cmd_buffer = Kernel::GetCommandBuffer();
u32 socket_handle = cmd_buffer[1];
- socklen_t ctr_len = cmd_buffer[2];
// Memory address of the ctr_dest_addr structure
VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2];
@@ -734,7 +732,6 @@ static void Shutdown(Interface* self) {
static void GetPeerName(Interface* self) {
u32* cmd_buffer = Kernel::GetCommandBuffer();
u32 socket_handle = cmd_buffer[1];
- socklen_t len = cmd_buffer[2];
// Memory address of the ctr_dest_addr structure
VAddr ctr_dest_addr_addr = cmd_buffer[0x104 >> 2];
@@ -765,7 +762,6 @@ static void Connect(Interface* self) {
// performing nonblocking operations and spinlock until the data is available
u32* cmd_buffer = Kernel::GetCommandBuffer();
u32 socket_handle = cmd_buffer[1];
- socklen_t len = cmd_buffer[2];
// Memory address of the ctr_input_addr structure
VAddr ctr_input_addr_addr = cmd_buffer[6];
diff --git a/src/core/hle/service/y2r_u.cpp b/src/core/hle/service/y2r_u.cpp
index 35eb2b597..907d9c8fa 100644
--- a/src/core/hle/service/y2r_u.cpp
+++ b/src/core/hle/service/y2r_u.cpp
@@ -533,7 +533,9 @@ static void GetStandardCoefficient(Interface* self) {
LOG_DEBUG(Service_Y2R, "called standard_coefficient=%u ", index);
} else {
cmd_buff[0] = IPC::MakeHeader(0x21, 1, 0);
- cmd_buff[1] = -1; // TODO(bunnei): Identify the correct error code for this
+ cmd_buff[1] = ResultCode(ErrorDescription::InvalidEnumValue, ErrorModule::CAM,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage)
+ .raw;
LOG_ERROR(Service_Y2R, "called standard_coefficient=%u The argument is invalid!", index);
}
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp
index 2ca270de3..96db39ad9 100644
--- a/src/core/hle/svc.cpp
+++ b/src/core/hle/svc.cpp
@@ -248,6 +248,8 @@ static ResultCode SendSyncRequest(Kernel::Handle handle) {
LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s)", handle, session->GetName().c_str());
+ Core::System::GetInstance().PrepareReschedule();
+
// TODO(Subv): svcSendSyncRequest should put the caller thread to sleep while the server
// responds and cause a reschedule.
return session->SendSyncRequest();
@@ -270,27 +272,27 @@ static ResultCode WaitSynchronization1(Kernel::Handle handle, s64 nano_seconds)
LOG_TRACE(Kernel_SVC, "called handle=0x%08X(%s:%s), nanoseconds=%lld", handle,
object->GetTypeName().c_str(), object->GetName().c_str(), nano_seconds);
- if (object->ShouldWait()) {
+ if (object->ShouldWait(thread)) {
if (nano_seconds == 0)
return ERR_SYNC_TIMEOUT;
+ thread->wait_objects = {object};
object->AddWaitingThread(thread);
- // TODO(Subv): Perform things like update the mutex lock owner's priority to
- // prevent priority inversion. Currently this is done in Mutex::ShouldWait,
- // but it should be moved to a function that is called from here.
- thread->status = THREADSTATUS_WAIT_SYNCH;
+ thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
+ Core::System::GetInstance().PrepareReschedule();
+
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread
// resumes due to a signal in its wait objects.
// Otherwise we retain the default value of timeout.
return ERR_SYNC_TIMEOUT;
}
- object->Acquire();
+ object->Acquire(thread);
return RESULT_SUCCESS;
}
@@ -324,19 +326,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
objects[i] = object;
}
- // Clear the mapping of wait object indices.
- // We don't want any lingering state in this map.
- // It will be repopulated later in the wait_all = false case.
- thread->wait_objects_index.clear();
-
if (wait_all) {
bool all_available =
std::all_of(objects.begin(), objects.end(),
- [](const ObjectPtr& object) { return !object->ShouldWait(); });
+ [thread](const ObjectPtr& object) { return !object->ShouldWait(thread); });
if (all_available) {
// We can acquire all objects right now, do so.
for (auto& object : objects)
- object->Acquire();
+ object->Acquire(thread);
// Note: In this case, the `out` parameter is not set,
// and retains whatever value it had before.
return RESULT_SUCCESS;
@@ -350,22 +347,20 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
return ERR_SYNC_TIMEOUT;
// Put the thread to sleep
- thread->status = THREADSTATUS_WAIT_SYNCH;
+ thread->status = THREADSTATUS_WAIT_SYNCH_ALL;
// Add the thread to each of the objects' waiting threads.
for (auto& object : objects) {
object->AddWaitingThread(thread);
- // TODO(Subv): Perform things like update the mutex lock owner's priority to
- // prevent priority inversion. Currently this is done in Mutex::ShouldWait,
- // but it should be moved to a function that is called from here.
}
- // Set the thread's waitlist to the list of objects passed to WaitSynchronizationN
thread->wait_objects = std::move(objects);
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
+ Core::System::GetInstance().PrepareReschedule();
+
// This value gets set to -1 by default in this case, it is not modified after this.
*out = -1;
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to
@@ -373,13 +368,14 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
return ERR_SYNC_TIMEOUT;
} else {
// Find the first object that is acquirable in the provided list of objects
- auto itr = std::find_if(objects.begin(), objects.end(),
- [](const ObjectPtr& object) { return !object->ShouldWait(); });
+ auto itr = std::find_if(objects.begin(), objects.end(), [thread](const ObjectPtr& object) {
+ return !object->ShouldWait(thread);
+ });
if (itr != objects.end()) {
// We found a ready object, acquire it and set the result value
Kernel::WaitObject* object = itr->get();
- object->Acquire();
+ object->Acquire(thread);
*out = std::distance(objects.begin(), itr);
return RESULT_SUCCESS;
}
@@ -392,28 +388,24 @@ static ResultCode WaitSynchronizationN(s32* out, Kernel::Handle* handles, s32 ha
return ERR_SYNC_TIMEOUT;
// Put the thread to sleep
- thread->status = THREADSTATUS_WAIT_SYNCH;
-
- // Clear the thread's waitlist, we won't use it for wait_all = false
- thread->wait_objects.clear();
+ thread->status = THREADSTATUS_WAIT_SYNCH_ANY;
// Add the thread to each of the objects' waiting threads.
for (size_t i = 0; i < objects.size(); ++i) {
Kernel::WaitObject* object = objects[i].get();
- // Set the index of this object in the mapping of Objects -> index for this thread.
- thread->wait_objects_index[object->GetObjectId()] = static_cast<int>(i);
object->AddWaitingThread(thread);
- // TODO(Subv): Perform things like update the mutex lock owner's priority to
- // prevent priority inversion. Currently this is done in Mutex::ShouldWait,
- // but it should be moved to a function that is called from here.
}
+ thread->wait_objects = std::move(objects);
+
// Note: If no handles and no timeout were given, then the thread will deadlock, this is
// consistent with hardware behavior.
// Create an event to wake the thread up after the specified nanosecond delay has passed
thread->WakeAfterDelay(nano_seconds);
+ Core::System::GetInstance().PrepareReschedule();
+
// Note: The output of this SVC will be set to RESULT_SUCCESS if the thread resumes due to a
// signal in one of its wait objects.
// Otherwise we retain the default value of timeout, and -1 in the out parameter
@@ -448,6 +440,9 @@ static ResultCode ArbitrateAddress(Kernel::Handle handle, u32 address, u32 type,
auto res = arbiter->ArbitrateAddress(static_cast<Kernel::ArbitrationType>(type), address, value,
nanoseconds);
+ // TODO(Subv): Identify in which specific cases this call should cause a reschedule.
+ Core::System::GetInstance().PrepareReschedule();
+
return res;
}
@@ -537,16 +532,18 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent
name = Common::StringFromFormat("unknown-%08x", entry_point);
}
- // TODO(bunnei): Implement resource limits to return an error code instead of the below assert.
- // The error code should be: Description::NotAuthorized, Module::OS, Summary::WrongArgument,
- // Level::Permanent
- ASSERT_MSG(priority >= THREADPRIO_USERLAND_MAX, "Unexpected thread priority!");
-
if (priority > THREADPRIO_LOWEST) {
return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
}
+ using Kernel::ResourceLimit;
+ Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit;
+ if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) {
+ return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS,
+ ErrorSummary::WrongArgument, ErrorLevel::Permanent);
+ }
+
switch (processor_id) {
case THREADPROCESSORID_ALL:
case THREADPROCESSORID_DEFAULT:
@@ -574,6 +571,8 @@ static ResultCode CreateThread(Kernel::Handle* out_handle, s32 priority, u32 ent
CASCADE_RESULT(*out_handle, Kernel::g_handle_table.Create(std::move(thread)));
+ Core::System::GetInstance().PrepareReschedule();
+
LOG_TRACE(Kernel_SVC, "called entrypoint=0x%08X (%s), arg=0x%08X, stacktop=0x%08X, "
"threadpriority=0x%08X, processorid=0x%08X : created handle=0x%08X",
entry_point, name.c_str(), arg, stack_top, priority, processor_id, *out_handle);
@@ -586,6 +585,7 @@ static void ExitThread() {
LOG_TRACE(Kernel_SVC, "called, pc=0x%08X", Core::CPU().GetPC());
Kernel::ExitCurrentThread();
+ Core::System::GetInstance().PrepareReschedule();
}
/// Gets the priority for the specified thread
@@ -600,11 +600,32 @@ static ResultCode GetThreadPriority(s32* priority, Kernel::Handle handle) {
/// Sets the priority for the specified thread
static ResultCode SetThreadPriority(Kernel::Handle handle, s32 priority) {
+ if (priority > THREADPRIO_LOWEST) {
+ return ResultCode(ErrorDescription::OutOfRange, ErrorModule::OS,
+ ErrorSummary::InvalidArgument, ErrorLevel::Usage);
+ }
+
SharedPtr<Kernel::Thread> thread = Kernel::g_handle_table.Get<Kernel::Thread>(handle);
if (thread == nullptr)
return ERR_INVALID_HANDLE;
+ using Kernel::ResourceLimit;
+ // Note: The kernel uses the current process's resource limit instead of
+ // the one from the thread owner's resource limit.
+ Kernel::SharedPtr<ResourceLimit>& resource_limit = Kernel::g_current_process->resource_limit;
+ if (resource_limit->GetMaxResourceValue(Kernel::ResourceTypes::PRIORITY) > priority) {
+ return ResultCode(ErrorDescription::NotAuthorized, ErrorModule::OS,
+ ErrorSummary::WrongArgument, ErrorLevel::Permanent);
+ }
+
thread->SetPriority(priority);
+ thread->UpdatePriority();
+
+ // Update the mutexes that this thread is waiting for
+ for (auto& mutex : thread->pending_mutexes)
+ mutex->UpdatePriority();
+
+ Core::System::GetInstance().PrepareReschedule();
return RESULT_SUCCESS;
}
@@ -844,11 +865,18 @@ static ResultCode CancelTimer(Kernel::Handle handle) {
static void SleepThread(s64 nanoseconds) {
LOG_TRACE(Kernel_SVC, "called nanoseconds=%lld", nanoseconds);
+ // Don't attempt to yield execution if there are no available threads to run,
+ // this way we avoid a useless reschedule to the idle thread.
+ if (nanoseconds == 0 && !Kernel::HaveReadyThreads())
+ return;
+
// Sleep current thread and check for next thread to schedule
Kernel::WaitCurrentThread_Sleep();
// Create an event to wake the thread up after the specified nanosecond delay has passed
Kernel::GetCurrentThread()->WakeAfterDelay(nanoseconds);
+
+ Core::System::GetInstance().PrepareReschedule();
}
/// This returns the total CPU ticks elapsed since the CPU was powered-on
@@ -890,7 +918,11 @@ static ResultCode CreateMemoryBlock(Kernel::Handle* out_handle, u32 addr, u32 si
return ResultCode(ErrorDescription::InvalidCombination, ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
- if (addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) {
+ // TODO(Subv): Processes with memory type APPLICATION are not allowed
+ // to create memory blocks with addr = 0, any attempts to do so
+ // should return error 0xD92007EA.
+ if ((addr < Memory::PROCESS_IMAGE_VADDR || addr + size > Memory::SHARED_MEMORY_VADDR_END) &&
+ addr != 0) {
return ResultCode(ErrorDescription::InvalidAddress, ErrorModule::OS,
ErrorSummary::InvalidArgument, ErrorLevel::Usage);
}
@@ -1184,8 +1216,6 @@ void CallSVC(u32 immediate) {
if (info) {
if (info->func) {
info->func();
- // TODO(Subv): Not all service functions should cause a reschedule in all cases.
- Core::System::GetInstance().PrepareReschedule();
} else {
LOG_ERROR(Kernel_SVC, "unimplemented SVC function %s(..)", info->name);
}
diff --git a/src/core/hw/gpu.cpp b/src/core/hw/gpu.cpp
index 1a1ee90b2..fa8c13d36 100644
--- a/src/core/hw/gpu.cpp
+++ b/src/core/hw/gpu.cpp
@@ -15,7 +15,6 @@
#include "common/vector_math.h"
#include "core/core_timing.h"
#include "core/hle/service/gsp_gpu.h"
-#include "core/hle/service/hid/hid.h"
#include "core/hw/gpu.h"
#include "core/hw/hw.h"
#include "core/memory.h"
@@ -33,7 +32,7 @@ namespace GPU {
Regs g_regs;
/// 268MHz CPU clocks / 60Hz frames per second
-const u64 frame_ticks = 268123480ull / 60;
+const u64 frame_ticks = BASE_CLOCK_RATE_ARM11 / 60;
/// Event id for CoreTiming
static int vblank_event;
/// Total number of frames drawn
@@ -551,9 +550,6 @@ static void VBlankCallback(u64 userdata, int cycles_late) {
Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC0);
Service::GSP::SignalInterrupt(Service::GSP::InterruptId::PDC1);
- // Check for user input updates
- Service::HID::Update();
-
if (!Settings::values.use_vsync && Settings::values.toggle_framelimit) {
FrameLimiter();
}
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp
index 1c10740a0..09266e8b0 100644
--- a/src/core/loader/3dsx.cpp
+++ b/src/core/loader/3dsx.cpp
@@ -177,18 +177,34 @@ static THREEDSX_Error Load3DSXFile(FileUtil::IOFile& file, u32 base_addr,
pos += table.skip;
s32 num_patches = table.patch;
while (0 < num_patches && pos < end_pos) {
- u32 in_addr =
- static_cast<u32>(reinterpret_cast<u8*>(pos) - program_image.data());
- u32 addr = TranslateAddr(*pos, &loadinfo, offsets);
- LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)",
- base_addr + in_addr, addr, current_segment_reloc_table, *pos);
+ u32 in_addr = base_addr + static_cast<u32>(reinterpret_cast<u8*>(pos) -
+ program_image.data());
+ u32 orig_data = *pos;
+ u32 sub_type = orig_data >> (32 - 4);
+ u32 addr = TranslateAddr(orig_data & ~0xF0000000, &loadinfo, offsets);
+ LOG_TRACE(Loader, "Patching %08X <-- rel(%08X,%d) (%08X)", in_addr, addr,
+ current_segment_reloc_table, *pos);
switch (current_segment_reloc_table) {
- case 0:
- *pos = (addr);
+ case 0: {
+ if (sub_type != 0)
+ return ERROR_READ;
+ *pos = addr;
break;
- case 1:
- *pos = static_cast<u32>(addr - in_addr);
+ }
+ case 1: {
+ u32 data = addr - in_addr;
+ switch (sub_type) {
+ case 0: // 32-bit signed offset
+ *pos = data;
+ break;
+ case 1: // 31-bit signed offset
+ *pos = data & ~(1U << 31);
+ break;
+ default:
+ return ERROR_READ;
+ }
break;
+ }
default:
break; // this should never happen
}
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp
index 6f2164428..5df33f6d2 100644
--- a/src/core/loader/ncch.cpp
+++ b/src/core/loader/ncch.cpp
@@ -11,8 +11,10 @@
#include "core/file_sys/archive_romfs.h"
#include "core/hle/kernel/process.h"
#include "core/hle/kernel/resource_limit.h"
+#include "core/hle/service/cfg/cfg.h"
#include "core/hle/service/fs/archive.h"
#include "core/loader/ncch.h"
+#include "core/loader/smdh.h"
#include "core/memory.h"
////////////////////////////////////////////////////////////////////////////////////////////////////
@@ -286,7 +288,7 @@ ResultStatus AppLoader_NCCH::LoadExeFS() {
LOG_DEBUG(Loader, "Thread priority: 0x%X", priority);
LOG_DEBUG(Loader, "Resource limit category: %d", resource_limit_category);
LOG_DEBUG(Loader, "System Mode: %d",
- exheader_header.arm11_system_local_caps.system_mode);
+ static_cast<int>(exheader_header.arm11_system_local_caps.system_mode));
if (exheader_header.arm11_system_local_caps.program_id != ncch_header.program_id) {
LOG_ERROR(Loader, "ExHeader Program ID mismatch: the ROM is probably encrypted.");
@@ -309,6 +311,23 @@ ResultStatus AppLoader_NCCH::LoadExeFS() {
return ResultStatus::Success;
}
+void AppLoader_NCCH::ParseRegionLockoutInfo() {
+ std::vector<u8> smdh_buffer;
+ if (ReadIcon(smdh_buffer) == ResultStatus::Success && smdh_buffer.size() >= sizeof(SMDH)) {
+ SMDH smdh;
+ memcpy(&smdh, smdh_buffer.data(), sizeof(SMDH));
+ u32 region_lockout = smdh.region_lockout;
+ constexpr u32 REGION_COUNT = 7;
+ for (u32 region = 0; region < REGION_COUNT; ++region) {
+ if (region_lockout & 1) {
+ Service::CFG::SetPreferredRegionCode(region);
+ break;
+ }
+ region_lockout >>= 1;
+ }
+ }
+}
+
ResultStatus AppLoader_NCCH::Load() {
if (is_loaded)
return ResultStatus::ErrorAlreadyLoaded;
@@ -325,6 +344,9 @@ ResultStatus AppLoader_NCCH::Load() {
Service::FS::RegisterArchiveType(std::make_unique<FileSys::ArchiveFactory_RomFS>(*this),
Service::FS::ArchiveIdCode::RomFS);
+
+ ParseRegionLockoutInfo();
+
return ResultStatus::Success;
}
diff --git a/src/core/loader/ncch.h b/src/core/loader/ncch.h
index 6afc171a5..4ef95b5c6 100644
--- a/src/core/loader/ncch.h
+++ b/src/core/loader/ncch.h
@@ -181,7 +181,7 @@ public:
* Loads the Exheader and returns the system mode for this application.
* @return Optional with the kernel system mode
*/
- boost::optional<u32> LoadKernelSystemMode();
+ boost::optional<u32> LoadKernelSystemMode() override;
ResultStatus ReadCode(std::vector<u8>& buffer) override;
@@ -229,6 +229,9 @@ private:
*/
ResultStatus LoadExeFS();
+ /// Reads the region lockout info in the SMDH and send it to CFG service
+ void ParseRegionLockoutInfo();
+
bool is_exefs_loaded = false;
bool is_compressed = false;
diff --git a/src/core/settings.cpp b/src/core/settings.cpp
index 5d23c52f9..3a32b70aa 100644
--- a/src/core/settings.cpp
+++ b/src/core/settings.cpp
@@ -15,12 +15,11 @@ Values values = {};
void Apply() {
- GDBStub::SetServerPort(static_cast<u32>(values.gdbstub_port));
+ GDBStub::SetServerPort(values.gdbstub_port);
GDBStub::ToggleServer(values.use_gdbstub);
VideoCore::g_hw_renderer_enabled = values.use_hw_renderer;
VideoCore::g_shader_jit_enabled = values.use_shader_jit;
- VideoCore::g_scaled_resolution_enabled = values.use_scaled_resolution;
VideoCore::g_toggle_framelimit_enabled = values.toggle_framelimit;
if (VideoCore::g_emu_window) {
diff --git a/src/core/settings.h b/src/core/settings.h
index db4c8fada..b6c75531f 100644
--- a/src/core/settings.h
+++ b/src/core/settings.h
@@ -7,6 +7,7 @@
#include <array>
#include <string>
#include "common/common_types.h"
+#include "core/hle/service/cam/cam.h"
namespace Settings {
@@ -88,7 +89,7 @@ struct Values {
// Renderer
bool use_hw_renderer;
bool use_shader_jit;
- bool use_scaled_resolution;
+ float resolution_factor;
bool use_vsync;
bool toggle_framelimit;
@@ -104,11 +105,20 @@ struct Values {
// Audio
std::string sink_id;
bool enable_audio_stretching;
+ std::string audio_device_id;
+
+ // Camera
+ std::array<std::string, Service::CAM::NumCameras> camera_name;
+ std::array<std::string, Service::CAM::NumCameras> camera_config;
// Debugging
bool use_gdbstub;
u16 gdbstub_port;
} extern values;
+// a special value for Values::region_value indicating that citra will automatically select a region
+// value to fit the region lockout info of the game
+static constexpr int REGION_VALUE_AUTO_SELECT = -1;
+
void Apply();
}
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt
index 6ca319b59..ad984cd94 100644
--- a/src/video_core/CMakeLists.txt
+++ b/src/video_core/CMakeLists.txt
@@ -1,36 +1,30 @@
set(SRCS
- renderer_opengl/gl_rasterizer.cpp
- renderer_opengl/gl_rasterizer_cache.cpp
- renderer_opengl/gl_shader_gen.cpp
- renderer_opengl/gl_shader_util.cpp
- renderer_opengl/gl_state.cpp
- renderer_opengl/renderer_opengl.cpp
- debug_utils/debug_utils.cpp
clipper.cpp
command_processor.cpp
+ debug_utils/debug_utils.cpp
pica.cpp
primitive_assembly.cpp
rasterizer.cpp
renderer_base.cpp
+ renderer_opengl/gl_rasterizer.cpp
+ renderer_opengl/gl_rasterizer_cache.cpp
+ renderer_opengl/gl_shader_gen.cpp
+ renderer_opengl/gl_shader_util.cpp
+ renderer_opengl/gl_state.cpp
+ renderer_opengl/renderer_opengl.cpp
shader/shader.cpp
shader/shader_interpreter.cpp
swrasterizer.cpp
+ texture/etc1.cpp
+ texture/texture_decode.cpp
vertex_loader.cpp
video_core.cpp
)
set(HEADERS
- debug_utils/debug_utils.h
- renderer_opengl/gl_rasterizer.h
- renderer_opengl/gl_rasterizer_cache.h
- renderer_opengl/gl_resource_manager.h
- renderer_opengl/gl_shader_gen.h
- renderer_opengl/gl_shader_util.h
- renderer_opengl/gl_state.h
- renderer_opengl/pica_to_gl.h
- renderer_opengl/renderer_opengl.h
clipper.h
command_processor.h
+ debug_utils/debug_utils.h
gpu_debugger.h
pica.h
pica_state.h
@@ -39,10 +33,20 @@ set(HEADERS
rasterizer.h
rasterizer_interface.h
renderer_base.h
+ renderer_opengl/gl_rasterizer.h
+ renderer_opengl/gl_rasterizer_cache.h
+ renderer_opengl/gl_resource_manager.h
+ renderer_opengl/gl_shader_gen.h
+ renderer_opengl/gl_shader_util.h
+ renderer_opengl/gl_state.h
+ renderer_opengl/pica_to_gl.h
+ renderer_opengl/renderer_opengl.h
shader/debug_data.h
shader/shader.h
shader/shader_interpreter.h
swrasterizer.h
+ texture/etc1.h
+ texture/texture_decode.h
utils.h
vertex_loader.h
video_core.h
@@ -50,10 +54,12 @@ set(HEADERS
if(ARCHITECTURE_x86_64)
set(SRCS ${SRCS}
- shader/shader_jit_x64.cpp)
+ shader/shader_jit_x64.cpp
+ shader/shader_jit_x64_compiler.cpp)
set(HEADERS ${HEADERS}
- shader/shader_jit_x64.h)
+ shader/shader_jit_x64.h
+ shader/shader_jit_x64_compiler.h)
endif()
create_directory_groups(${SRCS} ${HEADERS})
diff --git a/src/video_core/clipper.cpp b/src/video_core/clipper.cpp
index 05b5cea73..0774ffc53 100644
--- a/src/video_core/clipper.cpp
+++ b/src/video_core/clipper.cpp
@@ -18,6 +18,8 @@
#include "video_core/rasterizer.h"
#include "video_core/shader/shader.h"
+using Pica::Rasterizer::Vertex;
+
namespace Pica {
namespace Clipper {
@@ -29,20 +31,20 @@ public:
float24::FromFloat32(0), float24::FromFloat32(0)))
: coeffs(coeffs), bias(bias) {}
- bool IsInside(const OutputVertex& vertex) const {
+ bool IsInside(const Vertex& vertex) const {
return Math::Dot(vertex.pos + bias, coeffs) <= float24::FromFloat32(0);
}
- bool IsOutSide(const OutputVertex& vertex) const {
+ bool IsOutSide(const Vertex& vertex) const {
return !IsInside(vertex);
}
- OutputVertex GetIntersection(const OutputVertex& v0, const OutputVertex& v1) const {
+ Vertex GetIntersection(const Vertex& v0, const Vertex& v1) const {
float24 dp = Math::Dot(v0.pos + bias, coeffs);
float24 dp_prev = Math::Dot(v1.pos + bias, coeffs);
float24 factor = dp_prev / (dp_prev - dp);
- return OutputVertex::Lerp(factor, v0, v1);
+ return Vertex::Lerp(factor, v0, v1);
}
private:
@@ -51,7 +53,7 @@ private:
Math::Vec4<float24> bias;
};
-static void InitScreenCoordinates(OutputVertex& vtx) {
+static void InitScreenCoordinates(Vertex& vtx) {
struct {
float24 halfsize_x;
float24 offset_x;
@@ -91,8 +93,8 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu
// introduces at most 1 new vertex to the polygon. Since we start with a triangle and have a
// fixed 6 clipping planes, the maximum number of vertices of the clipped polygon is 3 + 6 = 9.
static const size_t MAX_VERTICES = 9;
- static_vector<OutputVertex, MAX_VERTICES> buffer_a = {v0, v1, v2};
- static_vector<OutputVertex, MAX_VERTICES> buffer_b;
+ static_vector<Vertex, MAX_VERTICES> buffer_a = {v0, v1, v2};
+ static_vector<Vertex, MAX_VERTICES> buffer_b;
auto* output_list = &buffer_a;
auto* input_list = &buffer_b;
@@ -123,7 +125,7 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu
std::swap(input_list, output_list);
output_list->clear();
- const OutputVertex* reference_vertex = &input_list->back();
+ const Vertex* reference_vertex = &input_list->back();
for (const auto& vertex : *input_list) {
// NOTE: This algorithm changes vertex order in some cases!
@@ -148,9 +150,9 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu
InitScreenCoordinates((*output_list)[1]);
for (size_t i = 0; i < output_list->size() - 2; i++) {
- OutputVertex& vtx0 = (*output_list)[0];
- OutputVertex& vtx1 = (*output_list)[i + 1];
- OutputVertex& vtx2 = (*output_list)[i + 2];
+ Vertex& vtx0 = (*output_list)[0];
+ Vertex& vtx1 = (*output_list)[i + 1];
+ Vertex& vtx2 = (*output_list)[i + 2];
InitScreenCoordinates(vtx2);
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp
index ea58e9f54..4955ff9f9 100644
--- a/src/video_core/command_processor.cpp
+++ b/src/video_core/command_processor.cpp
@@ -125,33 +125,37 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// TODO: Verify that this actually modifies the register!
if (setup.index < 15) {
- g_state.vs_default_attributes[setup.index] = attribute;
+ g_state.input_default_attributes.attr[setup.index] = attribute;
setup.index++;
} else {
- // Put each attribute into an immediate input buffer.
- // When all specified immediate attributes are present, the Vertex Shader is invoked
- // and everything is
- // sent to the primitive assembler.
+ // Put each attribute into an immediate input buffer. When all specified immediate
+ // attributes are present, the Vertex Shader is invoked and everything is sent to
+ // the primitive assembler.
auto& immediate_input = g_state.immediate.input_vertex;
auto& immediate_attribute_id = g_state.immediate.current_attribute;
- immediate_input.attr[immediate_attribute_id++] = attribute;
+ immediate_input.attr[immediate_attribute_id] = attribute;
- if (immediate_attribute_id >= regs.vs.num_input_attributes + 1) {
+ if (immediate_attribute_id < regs.max_input_attrib_index) {
+ immediate_attribute_id += 1;
+ } else {
MICROPROFILE_SCOPE(GPU_Drawing);
immediate_attribute_id = 0;
- Shader::UnitState shader_unit;
- g_state.vs.Setup();
+ auto* shader_engine = Shader::GetEngine();
+ shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset);
// Send to vertex shader
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation,
static_cast<void*>(&immediate_input));
- g_state.vs.Run(shader_unit, immediate_input, regs.vs.num_input_attributes + 1);
- Shader::OutputVertex output_vertex =
- shader_unit.output_registers.ToVertex(regs.vs);
+ Shader::UnitState shader_unit;
+ Shader::AttributeBuffer output{};
+
+ shader_unit.LoadInput(regs.vs, immediate_input);
+ shader_engine->Run(g_state.vs, shader_unit);
+ shader_unit.WriteOutput(regs.vs, output);
// Send to renderer
using Pica::Shader::OutputVertex;
@@ -160,7 +164,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
};
- g_state.primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
+ g_state.primitive_assembler.SubmitVertex(
+ Shader::OutputVertex::FromAttributeBuffer(regs, output), AddTriangle);
}
}
}
@@ -243,8 +248,10 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
unsigned int vertex_cache_pos = 0;
vertex_cache_ids.fill(-1);
+ auto* shader_engine = Shader::GetEngine();
Shader::UnitState shader_unit;
- g_state.vs.Setup();
+
+ shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset);
for (unsigned int index = 0; index < regs.num_vertices; ++index) {
// Indexed rendering doesn't use the start offset
@@ -276,17 +283,19 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
if (!vertex_cache_hit) {
// Initialize data for the current vertex
- Shader::InputVertex input;
+ Shader::AttributeBuffer input, output{};
loader.LoadVertex(base_address, index, vertex, input, memory_accesses);
// Send to vertex shader
if (g_debug_context)
g_debug_context->OnEvent(DebugContext::Event::VertexShaderInvocation,
(void*)&input);
- g_state.vs.Run(shader_unit, input, loader.GetNumTotalAttributes());
+ shader_unit.LoadInput(regs.vs, input);
+ shader_engine->Run(g_state.vs, shader_unit);
+ shader_unit.WriteOutput(regs.vs, output);
// Retrieve vertex from register data
- output_vertex = shader_unit.output_registers.ToVertex(regs.vs);
+ output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs, output);
if (is_indexed) {
vertex_cache[vertex_cache_pos] = output_vertex;
diff --git a/src/video_core/debug_utils/debug_utils.cpp b/src/video_core/debug_utils/debug_utils.cpp
index c44b3d95a..2d40f7d4f 100644
--- a/src/video_core/debug_utils/debug_utils.cpp
+++ b/src/video_core/debug_utils/debug_utils.cpp
@@ -35,6 +35,7 @@
#include "video_core/rasterizer_interface.h"
#include "video_core/renderer_base.h"
#include "video_core/shader/shader.h"
+#include "video_core/texture/texture_decode.h"
#include "video_core/utils.h"
#include "video_core/video_core.h"
@@ -315,257 +316,6 @@ std::unique_ptr<PicaTrace> FinishPicaTracing() {
return ret;
}
-const Math::Vec4<u8> LookupTexture(const u8* source, int x, int y, const TextureInfo& info,
- bool disable_alpha) {
- const unsigned int coarse_x = x & ~7;
- const unsigned int coarse_y = y & ~7;
-
- if (info.format != Regs::TextureFormat::ETC1 && info.format != Regs::TextureFormat::ETC1A4) {
- // TODO(neobrain): Fix code design to unify vertical block offsets!
- source += coarse_y * info.stride;
- }
-
- // TODO: Assert that width/height are multiples of block dimensions
-
- switch (info.format) {
- case Regs::TextureFormat::RGBA8: {
- auto res = Color::DecodeRGBA8(source + VideoCore::GetMortonOffset(x, y, 4));
- return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
- }
-
- case Regs::TextureFormat::RGB8: {
- auto res = Color::DecodeRGB8(source + VideoCore::GetMortonOffset(x, y, 3));
- return {res.r(), res.g(), res.b(), 255};
- }
-
- case Regs::TextureFormat::RGB5A1: {
- auto res = Color::DecodeRGB5A1(source + VideoCore::GetMortonOffset(x, y, 2));
- return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
- }
-
- case Regs::TextureFormat::RGB565: {
- auto res = Color::DecodeRGB565(source + VideoCore::GetMortonOffset(x, y, 2));
- return {res.r(), res.g(), res.b(), 255};
- }
-
- case Regs::TextureFormat::RGBA4: {
- auto res = Color::DecodeRGBA4(source + VideoCore::GetMortonOffset(x, y, 2));
- return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
- }
-
- case Regs::TextureFormat::IA8: {
- const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 2);
-
- if (disable_alpha) {
- // Show intensity as red, alpha as green
- return {source_ptr[1], source_ptr[0], 0, 255};
- } else {
- return {source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0]};
- }
- }
-
- case Regs::TextureFormat::RG8: {
- auto res = Color::DecodeRG8(source + VideoCore::GetMortonOffset(x, y, 2));
- return {res.r(), res.g(), 0, 255};
- }
-
- case Regs::TextureFormat::I8: {
- const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1);
- return {*source_ptr, *source_ptr, *source_ptr, 255};
- }
-
- case Regs::TextureFormat::A8: {
- const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1);
-
- if (disable_alpha) {
- return {*source_ptr, *source_ptr, *source_ptr, 255};
- } else {
- return {0, 0, 0, *source_ptr};
- }
- }
-
- case Regs::TextureFormat::IA4: {
- const u8* source_ptr = source + VideoCore::GetMortonOffset(x, y, 1);
-
- u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4);
- u8 a = Color::Convert4To8((*source_ptr) & 0xF);
-
- if (disable_alpha) {
- // Show intensity as red, alpha as green
- return {i, a, 0, 255};
- } else {
- return {i, i, i, a};
- }
- }
-
- case Regs::TextureFormat::I4: {
- u32 morton_offset = VideoCore::GetMortonOffset(x, y, 1);
- const u8* source_ptr = source + morton_offset / 2;
-
- u8 i = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF);
- i = Color::Convert4To8(i);
-
- return {i, i, i, 255};
- }
-
- case Regs::TextureFormat::A4: {
- u32 morton_offset = VideoCore::GetMortonOffset(x, y, 1);
- const u8* source_ptr = source + morton_offset / 2;
-
- u8 a = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF);
- a = Color::Convert4To8(a);
-
- if (disable_alpha) {
- return {a, a, a, 255};
- } else {
- return {0, 0, 0, a};
- }
- }
-
- case Regs::TextureFormat::ETC1:
- case Regs::TextureFormat::ETC1A4: {
- bool has_alpha = (info.format == Regs::TextureFormat::ETC1A4);
-
- // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles
- const int subtile_width = 4;
- const int subtile_height = 4;
-
- int subtile_index = ((x / subtile_width) & 1) + 2 * ((y / subtile_height) & 1);
- unsigned subtile_bytes = has_alpha ? 2 : 1; // TODO: Name...
-
- const u64* source_ptr = (const u64*)(source + coarse_x * subtile_bytes * 4 +
- coarse_y * subtile_bytes * 4 * (info.width / 8) +
- subtile_index * subtile_bytes * 8);
- u64 alpha = 0xFFFFFFFFFFFFFFFF;
- if (has_alpha) {
- alpha = *source_ptr;
- source_ptr++;
- }
-
- union ETC1Tile {
- // Each of these two is a collection of 16 bits (one per lookup value)
- BitField<0, 16, u64> table_subindexes;
- BitField<16, 16, u64> negation_flags;
-
- unsigned GetTableSubIndex(unsigned index) const {
- return (table_subindexes >> index) & 1;
- }
-
- bool GetNegationFlag(unsigned index) const {
- return ((negation_flags >> index) & 1) == 1;
- }
-
- BitField<32, 1, u64> flip;
- BitField<33, 1, u64> differential_mode;
-
- BitField<34, 3, u64> table_index_2;
- BitField<37, 3, u64> table_index_1;
-
- union {
- // delta value + base value
- BitField<40, 3, s64> db;
- BitField<43, 5, u64> b;
-
- BitField<48, 3, s64> dg;
- BitField<51, 5, u64> g;
-
- BitField<56, 3, s64> dr;
- BitField<59, 5, u64> r;
- } differential;
-
- union {
- BitField<40, 4, u64> b2;
- BitField<44, 4, u64> b1;
-
- BitField<48, 4, u64> g2;
- BitField<52, 4, u64> g1;
-
- BitField<56, 4, u64> r2;
- BitField<60, 4, u64> r1;
- } separate;
-
- const Math::Vec3<u8> GetRGB(int x, int y) const {
- int texel = 4 * x + y;
-
- if (flip)
- std::swap(x, y);
-
- // Lookup base value
- Math::Vec3<int> ret;
- if (differential_mode) {
- ret.r() = static_cast<int>(differential.r);
- ret.g() = static_cast<int>(differential.g);
- ret.b() = static_cast<int>(differential.b);
- if (x >= 2) {
- ret.r() += static_cast<int>(differential.dr);
- ret.g() += static_cast<int>(differential.dg);
- ret.b() += static_cast<int>(differential.db);
- }
- ret.r() = Color::Convert5To8(ret.r());
- ret.g() = Color::Convert5To8(ret.g());
- ret.b() = Color::Convert5To8(ret.b());
- } else {
- if (x < 2) {
- ret.r() = Color::Convert4To8(static_cast<u8>(separate.r1));
- ret.g() = Color::Convert4To8(static_cast<u8>(separate.g1));
- ret.b() = Color::Convert4To8(static_cast<u8>(separate.b1));
- } else {
- ret.r() = Color::Convert4To8(static_cast<u8>(separate.r2));
- ret.g() = Color::Convert4To8(static_cast<u8>(separate.g2));
- ret.b() = Color::Convert4To8(static_cast<u8>(separate.b2));
- }
- }
-
- // Add modifier
- unsigned table_index =
- static_cast<int>((x < 2) ? table_index_1.Value() : table_index_2.Value());
-
- static const std::array<std::array<u8, 2>, 8> etc1_modifier_table = {{
- {{2, 8}},
- {{5, 17}},
- {{9, 29}},
- {{13, 42}},
- {{18, 60}},
- {{24, 80}},
- {{33, 106}},
- {{47, 183}},
- }};
-
- int modifier = etc1_modifier_table.at(table_index).at(GetTableSubIndex(texel));
- if (GetNegationFlag(texel))
- modifier *= -1;
-
- ret.r() = MathUtil::Clamp(ret.r() + modifier, 0, 255);
- ret.g() = MathUtil::Clamp(ret.g() + modifier, 0, 255);
- ret.b() = MathUtil::Clamp(ret.b() + modifier, 0, 255);
-
- return ret.Cast<u8>();
- }
- } const* etc1_tile = reinterpret_cast<const ETC1Tile*>(source_ptr);
-
- alpha >>= 4 * ((x & 3) * 4 + (y & 3));
- return Math::MakeVec(etc1_tile->GetRGB(x & 3, y & 3),
- disable_alpha ? (u8)255 : Color::Convert4To8(alpha & 0xF));
- }
-
- default:
- LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format);
- DEBUG_ASSERT(false);
- return {};
- }
-}
-
-TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config,
- const Regs::TextureFormat& format) {
- TextureInfo info;
- info.physical_address = config.GetPhysicalAddress();
- info.width = config.width;
- info.height = config.height;
- info.format = format;
- info.stride = Pica::Regs::NibblesPerPixel(info.format) * info.width / 2;
- return info;
-}
-
#ifdef HAVE_PNG
// Adapter functions to libpng to write/flush to File::IOFile instances.
static void WriteIOFile(png_structp png_ptr, png_bytep data, png_size_t length) {
@@ -642,12 +392,12 @@ void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data) {
buf = new u8[row_stride * texture_config.height];
for (unsigned y = 0; y < texture_config.height; ++y) {
for (unsigned x = 0; x < texture_config.width; ++x) {
- TextureInfo info;
+ Pica::Texture::TextureInfo info;
info.width = texture_config.width;
info.height = texture_config.height;
info.stride = row_stride;
info.format = g_state.regs.texture0_format;
- Math::Vec4<u8> texture_color = LookupTexture(data, x, y, info);
+ Math::Vec4<u8> texture_color = Pica::Texture::LookupTexture(data, x, y, info);
buf[3 * x + y * row_stride] = texture_color.r();
buf[3 * x + y * row_stride + 1] = texture_color.g();
buf[3 * x + y * row_stride + 2] = texture_color.b();
diff --git a/src/video_core/debug_utils/debug_utils.h b/src/video_core/debug_utils/debug_utils.h
index 46ea8d9c7..938a2e1b5 100644
--- a/src/video_core/debug_utils/debug_utils.h
+++ b/src/video_core/debug_utils/debug_utils.h
@@ -205,31 +205,6 @@ inline bool IsPicaTracing() {
void OnPicaRegWrite(PicaTrace::Write write);
std::unique_ptr<PicaTrace> FinishPicaTracing();
-struct TextureInfo {
- PAddr physical_address;
- int width;
- int height;
- int stride;
- Pica::Regs::TextureFormat format;
-
- static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config,
- const Pica::Regs::TextureFormat& format);
-};
-
-/**
- * Lookup texel located at the given coordinates and return an RGBA vector of its color.
- * @param source Source pointer to read data from
- * @param s,t Texture coordinates to read from
- * @param info TextureInfo object describing the texture setup
- * @param disable_alpha This is used for debug widgets which use this method to display textures
- * without providing a good way to visualize alpha by themselves. If true, this will return 255 for
- * the alpha component, and either drop the information entirely or store it in an "unused" color
- * channel.
- * @todo Eventually we should get rid of the disable_alpha parameter.
- */
-const Math::Vec4<u8> LookupTexture(const u8* source, int s, int t, const TextureInfo& info,
- bool disable_alpha = false);
-
void DumpTexture(const Pica::Regs::TextureConfig& texture_config, u8* data);
std::string GetTevStageConfigColorCombinerString(const Pica::Regs::TevStageConfig& tev_stage);
diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp
index ce2bd455e..b4a77c632 100644
--- a/src/video_core/pica.cpp
+++ b/src/video_core/pica.cpp
@@ -499,7 +499,7 @@ void Init() {
}
void Shutdown() {
- Shader::ClearCache();
+ Shader::Shutdown();
}
template <typename T>
diff --git a/src/video_core/pica.h b/src/video_core/pica.h
index b2db609ec..731540b99 100644
--- a/src/video_core/pica.h
+++ b/src/video_core/pica.h
@@ -99,7 +99,8 @@ struct Regs {
TEXCOORD1_U = 14,
TEXCOORD1_V = 15,
- // TODO: Not verified
+ TEXCOORD0_W = 16,
+
VIEW_X = 18,
VIEW_Y = 19,
VIEW_Z = 20,
@@ -275,8 +276,11 @@ struct Regs {
case TextureFormat::I8:
case TextureFormat::A8:
case TextureFormat::IA4:
- default: // placeholder for yet unknown formats
return 2;
+
+ default: // placeholder for yet unknown formats
+ UNIMPLEMENTED();
+ return 0;
}
}
@@ -868,7 +872,7 @@ struct Regs {
LightSrc light[8];
LightColor global_ambient; // Emission + (material.ambient * lighting.ambient)
INSERT_PADDING_WORDS(0x1);
- BitField<0, 3, u32> num_lights; // Number of enabled lights - 1
+ BitField<0, 3, u32> max_light_index; // Number of enabled lights - 1
union {
BitField<2, 2, LightingFresnelSelector> fresnel_selector;
@@ -1045,7 +1049,7 @@ struct Regs {
BitField<48, 12, u64> attribute_mask;
// number of total attributes minus 1
- BitField<60, 4, u64> num_extra_attributes;
+ BitField<60, 4, u64> max_attribute_index;
};
inline VertexAttributeFormat GetFormat(int n) const {
@@ -1076,7 +1080,7 @@ struct Regs {
}
inline int GetNumTotalAttributes() const {
- return (int)num_extra_attributes + 1;
+ return (int)max_attribute_index + 1;
}
// Attribute loaders map the source vertex data to input attributes
@@ -1176,7 +1180,12 @@ struct Regs {
}
} command_buffer;
- INSERT_PADDING_WORDS(0x07);
+ INSERT_PADDING_WORDS(4);
+
+ /// Number of input attributes to the vertex shader minus 1
+ BitField<0, 4, u32> max_input_attrib_index;
+
+ INSERT_PADDING_WORDS(2);
enum class GPUMode : u32 {
Drawing = 0,
@@ -1214,42 +1223,21 @@ struct Regs {
union {
// Number of input attributes to shader unit - 1
- BitField<0, 4, u32> num_input_attributes;
+ BitField<0, 4, u32> max_input_attribute_index;
};
// Offset to shader program entry point (in words)
BitField<0, 16, u32> main_offset;
- union {
- BitField<0, 4, u64> attribute0_register;
- BitField<4, 4, u64> attribute1_register;
- BitField<8, 4, u64> attribute2_register;
- BitField<12, 4, u64> attribute3_register;
- BitField<16, 4, u64> attribute4_register;
- BitField<20, 4, u64> attribute5_register;
- BitField<24, 4, u64> attribute6_register;
- BitField<28, 4, u64> attribute7_register;
- BitField<32, 4, u64> attribute8_register;
- BitField<36, 4, u64> attribute9_register;
- BitField<40, 4, u64> attribute10_register;
- BitField<44, 4, u64> attribute11_register;
- BitField<48, 4, u64> attribute12_register;
- BitField<52, 4, u64> attribute13_register;
- BitField<56, 4, u64> attribute14_register;
- BitField<60, 4, u64> attribute15_register;
-
- int GetRegisterForAttribute(int attribute_index) const {
- u64 fields[] = {
- attribute0_register, attribute1_register, attribute2_register,
- attribute3_register, attribute4_register, attribute5_register,
- attribute6_register, attribute7_register, attribute8_register,
- attribute9_register, attribute10_register, attribute11_register,
- attribute12_register, attribute13_register, attribute14_register,
- attribute15_register,
- };
- return (int)fields[attribute_index];
- }
- } input_register_map;
+ /// Maps input attributes to registers. 4-bits per attribute, specifying a register index
+ u32 input_attribute_to_register_map_low;
+ u32 input_attribute_to_register_map_high;
+
+ unsigned int GetRegisterForAttribute(unsigned int attribute_index) const {
+ u64 map = ((u64)input_attribute_to_register_map_high << 32) |
+ (u64)input_attribute_to_register_map_low;
+ return (map >> (attribute_index * 4)) & 0b1111;
+ }
BitField<0, 16, u32> output_mask;
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h
index e4f2e6d5d..785d05650 100644
--- a/src/video_core/pica_state.h
+++ b/src/video_core/pica_state.h
@@ -23,7 +23,7 @@ struct State {
Shader::ShaderSetup vs;
Shader::ShaderSetup gs;
- std::array<Math::Vec4<float24>, 16> vs_default_attributes;
+ Shader::AttributeBuffer input_default_attributes;
struct {
union LutEntry {
@@ -66,7 +66,7 @@ struct State {
/// Struct used to describe immediate mode rendering state
struct ImmediateModeState {
// Used to buffer partial vertices for immediate-mode rendering.
- Shader::InputVertex input_vertex;
+ Shader::AttributeBuffer input_vertex;
// Index of the next attribute to be loaded into `input_vertex`.
u32 current_attribute = 0;
} immediate;
diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp
index be7377290..e71ff5719 100644
--- a/src/video_core/primitive_assembly.cpp
+++ b/src/video_core/primitive_assembly.cpp
@@ -14,7 +14,7 @@ PrimitiveAssembler<VertexType>::PrimitiveAssembler(Regs::TriangleTopology topolo
: topology(topology), buffer_index(0) {}
template <typename VertexType>
-void PrimitiveAssembler<VertexType>::SubmitVertex(VertexType& vtx,
+void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
TriangleHandler triangle_handler) {
switch (topology) {
// TODO: Figure out what's different with TriangleTopology::Shader.
diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h
index 0384d5984..24da47382 100644
--- a/src/video_core/primitive_assembly.h
+++ b/src/video_core/primitive_assembly.h
@@ -15,7 +15,8 @@ namespace Pica {
*/
template <typename VertexType>
struct PrimitiveAssembler {
- using TriangleHandler = std::function<void(VertexType& v0, VertexType& v1, VertexType& v2)>;
+ using TriangleHandler =
+ std::function<void(const VertexType& v0, const VertexType& v1, const VertexType& v2)>;
PrimitiveAssembler(Regs::TriangleTopology topology = Regs::TriangleTopology::List);
@@ -25,7 +26,7 @@ struct PrimitiveAssembler {
* NOTE: We could specify the triangle handler in the constructor, but this way we can
* keep event and handler code next to each other.
*/
- void SubmitVertex(VertexType& vtx, TriangleHandler triangle_handler);
+ void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler);
/**
* Resets the internal state of the PrimitiveAssembler.
diff --git a/src/video_core/rasterizer.cpp b/src/video_core/rasterizer.cpp
index b9f5d4533..287d732b5 100644
--- a/src/video_core/rasterizer.cpp
+++ b/src/video_core/rasterizer.cpp
@@ -21,6 +21,7 @@
#include "video_core/pica_types.h"
#include "video_core/rasterizer.h"
#include "video_core/shader/shader.h"
+#include "video_core/texture/texture_decode.h"
#include "video_core/utils.h"
namespace Pica {
@@ -307,8 +308,8 @@ MICROPROFILE_DEFINE(GPU_Rasterization, "GPU", "Rasterization", MP_RGB(50, 50, 24
* Helper function for ProcessTriangle with the "reversed" flag to allow for implementing
* culling via recursion.
*/
-static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1,
- const Shader::OutputVertex& v2, bool reversed = false) {
+static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Vertex& v2,
+ bool reversed = false) {
const auto& regs = g_state.regs;
MICROPROFILE_SCOPE(GPU_Rasterization);
@@ -579,10 +580,10 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader
u8* texture_data =
Memory::GetPhysicalPointer(texture.config.GetPhysicalAddress());
auto info =
- DebugUtils::TextureInfo::FromPicaRegister(texture.config, texture.format);
+ Texture::TextureInfo::FromPicaRegister(texture.config, texture.format);
// TODO: Apply the min and mag filters to the texture
- texture_color[i] = DebugUtils::LookupTexture(texture_data, s, t, info);
+ texture_color[i] = Texture::LookupTexture(texture_data, s, t, info);
#if PICA_DUMP_TEXTURES
DebugUtils::DumpTexture(texture.config, texture_data);
#endif
@@ -1276,8 +1277,7 @@ static void ProcessTriangleInternal(const Shader::OutputVertex& v0, const Shader
}
}
-void ProcessTriangle(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1,
- const Shader::OutputVertex& v2) {
+void ProcessTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2) {
ProcessTriangleInternal(v0, v1, v2);
}
diff --git a/src/video_core/rasterizer.h b/src/video_core/rasterizer.h
index 6cbda3067..3a72ac343 100644
--- a/src/video_core/rasterizer.h
+++ b/src/video_core/rasterizer.h
@@ -4,16 +4,44 @@
#pragma once
-namespace Pica {
+#include "video_core/shader/shader.h"
-namespace Shader {
-struct OutputVertex;
-}
+namespace Pica {
namespace Rasterizer {
-void ProcessTriangle(const Shader::OutputVertex& v0, const Shader::OutputVertex& v1,
- const Shader::OutputVertex& v2);
+struct Vertex : Shader::OutputVertex {
+ Vertex(const OutputVertex& v) : OutputVertex(v) {}
+
+ // Attributes used to store intermediate results
+ // position after perspective divide
+ Math::Vec3<float24> screenpos;
+
+ // Linear interpolation
+ // factor: 0=this, 1=vtx
+ void Lerp(float24 factor, const Vertex& vtx) {
+ pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor);
+
+ // TODO: Should perform perspective correct interpolation here...
+ tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor);
+ tc1 = tc1 * factor + vtx.tc1 * (float24::FromFloat32(1) - factor);
+ tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor);
+
+ screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor);
+
+ color = color * factor + vtx.color * (float24::FromFloat32(1) - factor);
+ }
+
+ // Linear interpolation
+ // factor: 0=v0, 1=v1
+ static Vertex Lerp(float24 factor, const Vertex& v0, const Vertex& v1) {
+ Vertex ret = v0;
+ ret.Lerp(factor, v1);
+ return ret;
+ }
+};
+
+void ProcessTriangle(const Vertex& v0, const Vertex& v1, const Vertex& v2);
} // namespace Rasterizer
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index 5a306a5c8..071e4ace0 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -467,7 +467,7 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) {
// Fragment lighting switches
case PICA_REG_INDEX(lighting.disable):
- case PICA_REG_INDEX(lighting.num_lights):
+ case PICA_REG_INDEX(lighting.max_light_index):
case PICA_REG_INDEX(lighting.config0):
case PICA_REG_INDEX(lighting.config1):
case PICA_REG_INDEX(lighting.abs_lut_input):
@@ -716,8 +716,6 @@ void RasterizerOpenGL::FlushAndInvalidateRegion(PAddr addr, u32 size) {
bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransferConfig& config) {
MICROPROFILE_SCOPE(OpenGL_Blits);
- using PixelFormat = CachedSurface::PixelFormat;
- using SurfaceType = CachedSurface::SurfaceType;
CachedSurface src_params;
src_params.addr = config.GetPhysicalInputAddress();
@@ -748,7 +746,8 @@ bool RasterizerOpenGL::AccelerateDisplayTransfer(const GPU::Regs::DisplayTransfe
// Adjust the source rectangle to take into account parts of the input lines being cropped
if (config.input_width > config.output_width) {
- src_rect.right -= (config.input_width - config.output_width) * src_surface->res_scale_width;
+ src_rect.right -= static_cast<int>((config.input_width - config.output_width) *
+ src_surface->res_scale_width);
}
// Require destination surface to have same resolution scale as source to preserve scaling
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index e1a9cb361..a1aa07074 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -76,7 +76,7 @@ union PicaShaderConfig {
}
state.fog_mode = regs.fog_mode;
- state.fog_flip = regs.fog_flip;
+ state.fog_flip = regs.fog_flip != 0;
state.combiner_buffer_input = regs.tev_combiner_buffer_input.update_mask_rgb.Value() |
regs.tev_combiner_buffer_input.update_mask_a.Value() << 4;
@@ -84,7 +84,7 @@ union PicaShaderConfig {
// Fragment lighting
state.lighting.enable = !regs.lighting.disable;
- state.lighting.src_num = regs.lighting.num_lights + 1;
+ state.lighting.src_num = regs.lighting.max_light_index + 1;
for (unsigned light_index = 0; light_index < state.lighting.src_num; ++light_index) {
unsigned num = regs.lighting.light_enable.GetNum(light_index);
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
index 85aa06cd5..60380257a 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.cpp
@@ -17,10 +17,10 @@
#include "common/vector_math.h"
#include "core/frontend/emu_window.h"
#include "core/memory.h"
-#include "video_core/debug_utils/debug_utils.h"
#include "video_core/pica_state.h"
#include "video_core/renderer_opengl/gl_rasterizer_cache.h"
#include "video_core/renderer_opengl/gl_state.h"
+#include "video_core/texture/texture_decode.h"
#include "video_core/utils.h"
#include "video_core/video_core.h"
@@ -172,7 +172,6 @@ bool RasterizerCacheOpenGL::TryBlitSurfaces(CachedSurface* src_surface,
const MathUtil::Rectangle<int>& src_rect,
CachedSurface* dst_surface,
const MathUtil::Rectangle<int>& dst_rect) {
- using SurfaceType = CachedSurface::SurfaceType;
if (!CachedSurface::CheckFormatsBlittable(src_surface->pixel_format,
dst_surface->pixel_format)) {
@@ -340,17 +339,16 @@ CachedSurface* RasterizerCacheOpenGL::GetSurface(const CachedSurface& params, bo
std::vector<Math::Vec4<u8>> tex_buffer(params.width * params.height);
- Pica::DebugUtils::TextureInfo tex_info;
+ Pica::Texture::TextureInfo tex_info;
tex_info.width = params.width;
tex_info.height = params.height;
- tex_info.stride =
- params.width * CachedSurface::GetFormatBpp(params.pixel_format) / 8;
tex_info.format = (Pica::Regs::TextureFormat)params.pixel_format;
+ tex_info.SetDefaultStride();
tex_info.physical_address = params.addr;
for (unsigned y = 0; y < params.height; ++y) {
for (unsigned x = 0; x < params.width; ++x) {
- tex_buffer[x + params.width * y] = Pica::DebugUtils::LookupTexture(
+ tex_buffer[x + params.width * y] = Pica::Texture::LookupTexture(
texture_src_data, x, params.height - 1 - y, tex_info);
}
}
@@ -513,8 +511,9 @@ CachedSurface* RasterizerCacheOpenGL::GetSurfaceRect(const CachedSurface& params
CachedSurface* RasterizerCacheOpenGL::GetTextureSurface(
const Pica::Regs::FullTextureConfig& config) {
- Pica::DebugUtils::TextureInfo info =
- Pica::DebugUtils::TextureInfo::FromPicaRegister(config.config, config.format);
+
+ Pica::Texture::TextureInfo info =
+ Pica::Texture::TextureInfo::FromPicaRegister(config.config, config.format);
CachedSurface params;
params.addr = info.physical_address;
@@ -556,14 +555,21 @@ RasterizerCacheOpenGL::GetFramebufferSurfaces(const Pica::Regs::FramebufferConfi
color_params.width = depth_params.width = config.GetWidth();
color_params.height = depth_params.height = config.GetHeight();
color_params.is_tiled = depth_params.is_tiled = true;
- if (VideoCore::g_scaled_resolution_enabled) {
- auto layout = VideoCore::g_emu_window->GetFramebufferLayout();
- // Assume same scaling factor for top and bottom screens
+ // Set the internal resolution, assume the same scaling factor for top and bottom screens
+ const Layout::FramebufferLayout& layout = VideoCore::g_emu_window->GetFramebufferLayout();
+ if (Settings::values.resolution_factor == 0.0f) {
+ // Auto - scale resolution to the window size
color_params.res_scale_width = depth_params.res_scale_width =
(float)layout.top_screen.GetWidth() / VideoCore::kScreenTopWidth;
color_params.res_scale_height = depth_params.res_scale_height =
(float)layout.top_screen.GetHeight() / VideoCore::kScreenTopHeight;
+ } else {
+ // Otherwise, scale the resolution by the specified factor
+ color_params.res_scale_width = Settings::values.resolution_factor;
+ depth_params.res_scale_width = Settings::values.resolution_factor;
+ color_params.res_scale_height = Settings::values.resolution_factor;
+ depth_params.res_scale_height = Settings::values.resolution_factor;
}
color_params.addr = config.GetColorBufferPhysicalAddress();
diff --git a/src/video_core/renderer_opengl/gl_rasterizer_cache.h b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
index b50e8292b..f57fdb3cc 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer_cache.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer_cache.h
@@ -8,7 +8,14 @@
#include <memory>
#include <set>
#include <tuple>
+#ifdef __GNUC__
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wunused-local-typedef"
+#endif
#include <boost/icl/interval_map.hpp>
+#ifdef __GNUC__
+#pragma GCC diagnostic pop
+#endif
#include <glad/glad.h>
#include "common/assert.h"
#include "common/common_funcs.h"
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index 8f278722d..4c4f98ac9 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -293,7 +293,7 @@ static void AppendAlphaTestCondition(std::string& out, Regs::CompareFunc func) {
case CompareFunc::GreaterThanOrEqual: {
static const char* op[] = {"!=", "==", ">=", ">", "<=", "<"};
unsigned index = (unsigned)func - (unsigned)CompareFunc::Equal;
- out += "int(last_tex_env_out.a * 255.0f) " + std::string(op[index]) + " alphatest_ref";
+ out += "int(last_tex_env_out.a * 255.0) " + std::string(op[index]) + " alphatest_ref";
break;
}
@@ -422,16 +422,13 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (abs) {
// LUT index is in the range of (0.0, 1.0)
index = lighting.light[light_num].two_sided_diffuse ? "abs(" + index + ")"
- : "max(" + index + ", 0.f)";
- return "(FLOAT_255 * clamp(" + index + ", 0.0, 1.0))";
+ : "max(" + index + ", 0.0)";
} else {
// LUT index is in the range of (-1.0, 1.0)
- index = "clamp(" + index + ", -1.0, 1.0)";
- return "(FLOAT_255 * ((" + index + " < 0) ? " + index + " + 2.0 : " + index +
- ") / 2.0)";
+ index = "((" + index + " < 0) ? " + index + " + 2.0 : " + index + ") / 2.0";
}
- return std::string();
+ return "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))";
};
// Gets the lighting lookup table value given the specified sampler and index
@@ -462,7 +459,7 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
if (light_config.dist_atten_enable) {
std::string index = "(" + light_src + ".dist_atten_scale * length(-view - " +
light_src + ".position) + " + light_src + ".dist_atten_bias)";
- index = "((clamp(" + index + ", 0.0, FLOAT_255)))";
+ index = "(OFFSET_256 + SCALE_256 * clamp(" + index + ", 0.0, 1.0))";
const unsigned lut_num =
((unsigned)Regs::LightingSampler::DistanceAttenuation + light_config.num);
dist_atten = GetLutValue((Regs::LightingSampler)lut_num, index);
@@ -580,8 +577,10 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) {
#version 330 core
#define NUM_TEV_STAGES 6
#define NUM_LIGHTS 8
-#define LIGHTING_LUT_SIZE 256
-#define FLOAT_255 (255.0 / 256.0)
+
+// Texture coordinate offsets and scales
+#define OFFSET_256 (0.5 / 256.0)
+#define SCALE_256 (255.0 / 256.0)
in vec4 primary_color;
in vec2 texcoord[3];
diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp
index a4aa3c9e0..f5f7ea61d 100644
--- a/src/video_core/shader/shader.cpp
+++ b/src/video_core/shader/shader.cpp
@@ -2,14 +2,9 @@
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <atomic>
#include <cmath>
#include <cstring>
-#include <unordered_map>
-#include <utility>
-#include <boost/range/algorithm/fill.hpp>
-#include "common/bit_field.h"
-#include "common/hash.h"
+#include "common/bit_set.h"
#include "common/logging/log.h"
#include "common/microprofile.h"
#include "video_core/pica.h"
@@ -25,37 +20,32 @@ namespace Pica {
namespace Shader {
-OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) const {
+OutputVertex OutputVertex::FromAttributeBuffer(const Regs& regs, AttributeBuffer& input) {
// Setup output data
- OutputVertex ret;
- // TODO(neobrain): Under some circumstances, up to 16 attributes may be output. We need to
- // figure out what those circumstances are and enable the remaining outputs then.
- unsigned index = 0;
- for (unsigned i = 0; i < 7; ++i) {
+ union {
+ OutputVertex ret{};
+ std::array<float24, 24> vertex_slots;
+ };
+ static_assert(sizeof(vertex_slots) == sizeof(ret), "Struct and array have different sizes.");
- if (index >= g_state.regs.vs_output_total)
- break;
+ unsigned int num_attributes = regs.vs_output_total;
+ ASSERT(num_attributes <= 7);
+ for (unsigned int i = 0; i < num_attributes; ++i) {
+ const auto& output_register_map = regs.vs_output_attributes[i];
- if ((config.output_mask & (1 << i)) == 0)
- continue;
-
- const auto& output_register_map = g_state.regs.vs_output_attributes[index];
-
- u32 semantics[4] = {output_register_map.map_x, output_register_map.map_y,
- output_register_map.map_z, output_register_map.map_w};
+ Regs::VSOutputAttributes::Semantic semantics[4] = {
+ output_register_map.map_x, output_register_map.map_y, output_register_map.map_z,
+ output_register_map.map_w};
for (unsigned comp = 0; comp < 4; ++comp) {
- float24* out = ((float24*)&ret) + semantics[comp];
- if (semantics[comp] != Regs::VSOutputAttributes::INVALID) {
- *out = value[i][comp];
- } else {
- // Zero output so that attributes which aren't output won't have denormals in them,
- // which would slow us down later.
- memset(out, 0, sizeof(*out));
+ Regs::VSOutputAttributes::Semantic semantic = semantics[comp];
+ float24* out = &vertex_slots[semantic];
+ if (semantic < vertex_slots.size()) {
+ *out = input.attr[i][comp];
+ } else if (semantic != Regs::VSOutputAttributes::INVALID) {
+ LOG_ERROR(HW_GPU, "Invalid/unknown semantic id: %u", (unsigned int)semantic);
}
}
-
- index++;
}
// The hardware takes the absolute and saturates vertex colors like this, *before* doing
@@ -76,84 +66,47 @@ OutputVertex OutputRegisters::ToVertex(const Regs::ShaderConfig& config) const {
return ret;
}
-#ifdef ARCHITECTURE_x86_64
-static std::unordered_map<u64, std::unique_ptr<JitShader>> shader_map;
-static const JitShader* jit_shader;
-#endif // ARCHITECTURE_x86_64
+void UnitState::LoadInput(const Regs::ShaderConfig& config, const AttributeBuffer& input) {
+ const unsigned max_attribute = config.max_input_attribute_index;
-void ClearCache() {
-#ifdef ARCHITECTURE_x86_64
- shader_map.clear();
-#endif // ARCHITECTURE_x86_64
+ for (unsigned attr = 0; attr <= max_attribute; ++attr) {
+ unsigned reg = config.GetRegisterForAttribute(attr);
+ registers.input[reg] = input.attr[attr];
+ }
}
-void ShaderSetup::Setup() {
-#ifdef ARCHITECTURE_x86_64
- if (VideoCore::g_shader_jit_enabled) {
- u64 cache_key =
- Common::ComputeHash64(&g_state.vs.program_code, sizeof(g_state.vs.program_code)) ^
- Common::ComputeHash64(&g_state.vs.swizzle_data, sizeof(g_state.vs.swizzle_data));
-
- auto iter = shader_map.find(cache_key);
- if (iter != shader_map.end()) {
- jit_shader = iter->second.get();
- } else {
- auto shader = std::make_unique<JitShader>();
- shader->Compile();
- jit_shader = shader.get();
- shader_map[cache_key] = std::move(shader);
- }
+void UnitState::WriteOutput(const Regs::ShaderConfig& config, AttributeBuffer& output) {
+ unsigned int output_i = 0;
+ for (unsigned int reg : Common::BitSet<u32>(config.output_mask)) {
+ output.attr[output_i++] = registers.output[reg];
}
-#endif // ARCHITECTURE_x86_64
}
MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
-void ShaderSetup::Run(UnitState& state, const InputVertex& input, int num_attributes) {
- auto& config = g_state.regs.vs;
- auto& setup = g_state.vs;
-
- MICROPROFILE_SCOPE(GPU_Shader);
-
- // Setup input register table
- const auto& attribute_register_map = config.input_register_map;
-
- for (unsigned i = 0; i < num_attributes; i++)
- state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i];
-
- state.conditional_code[0] = false;
- state.conditional_code[1] = false;
+#ifdef ARCHITECTURE_x86_64
+static std::unique_ptr<JitX64Engine> jit_engine;
+#endif // ARCHITECTURE_x86_64
+static InterpreterEngine interpreter_engine;
+ShaderEngine* GetEngine() {
#ifdef ARCHITECTURE_x86_64
+ // TODO(yuriks): Re-initialize on each change rather than being persistent
if (VideoCore::g_shader_jit_enabled) {
- jit_shader->Run(setup, state, config.main_offset);
- } else {
- DebugData<false> dummy_debug_data;
- RunInterpreter(setup, state, dummy_debug_data, config.main_offset);
+ if (jit_engine == nullptr) {
+ jit_engine = std::make_unique<JitX64Engine>();
+ }
+ return jit_engine.get();
}
-#else
- DebugData<false> dummy_debug_data;
- RunInterpreter(setup, state, dummy_debug_data, config.main_offset);
#endif // ARCHITECTURE_x86_64
-}
-
-DebugData<true> ShaderSetup::ProduceDebugInfo(const InputVertex& input, int num_attributes,
- const Regs::ShaderConfig& config,
- const ShaderSetup& setup) {
- UnitState state;
- DebugData<true> debug_data;
-
- // Setup input register table
- boost::fill(state.registers.input, Math::Vec4<float24>::AssignToAll(float24::Zero()));
- const auto& attribute_register_map = config.input_register_map;
- for (unsigned i = 0; i < num_attributes; i++)
- state.registers.input[attribute_register_map.GetRegisterForAttribute(i)] = input.attr[i];
- state.conditional_code[0] = false;
- state.conditional_code[1] = false;
+ return &interpreter_engine;
+}
- RunInterpreter(setup, state, debug_data, config.main_offset);
- return debug_data;
+void Shutdown() {
+#ifdef ARCHITECTURE_x86_64
+ jit_engine = nullptr;
+#endif // ARCHITECTURE_x86_64
}
} // namespace Shader
diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h
index 2b07759b9..b188d3edf 100644
--- a/src/video_core/shader/shader.h
+++ b/src/video_core/shader/shader.h
@@ -6,7 +6,6 @@
#include <array>
#include <cstddef>
-#include <memory>
#include <type_traits>
#include <nihstro/shader_bytecode.h>
#include "common/assert.h"
@@ -15,7 +14,6 @@
#include "common/vector_math.h"
#include "video_core/pica.h"
#include "video_core/pica_types.h"
-#include "video_core/shader/debug_data.h"
using nihstro::RegisterType;
using nihstro::SourceRegister;
@@ -25,14 +23,11 @@ namespace Pica {
namespace Shader {
-struct InputVertex {
+struct AttributeBuffer {
alignas(16) Math::Vec4<float24> attr[16];
};
struct OutputVertex {
- OutputVertex() = default;
-
- // VS output attributes
Math::Vec4<float24> pos;
Math::Vec4<float24> quat;
Math::Vec4<float24> color;
@@ -44,49 +39,22 @@ struct OutputVertex {
INSERT_PADDING_WORDS(1);
Math::Vec2<float24> tc2;
- // Padding for optimal alignment
- INSERT_PADDING_WORDS(4);
-
- // Attributes used to store intermediate results
-
- // position after perspective divide
- Math::Vec3<float24> screenpos;
- INSERT_PADDING_WORDS(1);
-
- // Linear interpolation
- // factor: 0=this, 1=vtx
- void Lerp(float24 factor, const OutputVertex& vtx) {
- pos = pos * factor + vtx.pos * (float24::FromFloat32(1) - factor);
-
- // TODO: Should perform perspective correct interpolation here...
- tc0 = tc0 * factor + vtx.tc0 * (float24::FromFloat32(1) - factor);
- tc1 = tc1 * factor + vtx.tc1 * (float24::FromFloat32(1) - factor);
- tc2 = tc2 * factor + vtx.tc2 * (float24::FromFloat32(1) - factor);
-
- screenpos = screenpos * factor + vtx.screenpos * (float24::FromFloat32(1) - factor);
-
- color = color * factor + vtx.color * (float24::FromFloat32(1) - factor);
- }
-
- // Linear interpolation
- // factor: 0=v0, 1=v1
- static OutputVertex Lerp(float24 factor, const OutputVertex& v0, const OutputVertex& v1) {
- OutputVertex ret = v0;
- ret.Lerp(factor, v1);
- return ret;
- }
+ static OutputVertex FromAttributeBuffer(const Regs& regs, AttributeBuffer& output);
};
+#define ASSERT_POS(var, pos) \
+ static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong " \
+ "offset.")
+ASSERT_POS(pos, Regs::VSOutputAttributes::POSITION_X);
+ASSERT_POS(quat, Regs::VSOutputAttributes::QUATERNION_X);
+ASSERT_POS(color, Regs::VSOutputAttributes::COLOR_R);
+ASSERT_POS(tc0, Regs::VSOutputAttributes::TEXCOORD0_U);
+ASSERT_POS(tc1, Regs::VSOutputAttributes::TEXCOORD1_U);
+ASSERT_POS(tc0_w, Regs::VSOutputAttributes::TEXCOORD0_W);
+ASSERT_POS(view, Regs::VSOutputAttributes::VIEW_X);
+ASSERT_POS(tc2, Regs::VSOutputAttributes::TEXCOORD2_U);
+#undef ASSERT_POS
static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD");
-static_assert(sizeof(OutputVertex) == 32 * sizeof(float), "OutputVertex has invalid size");
-
-struct OutputRegisters {
- OutputRegisters() = default;
-
- alignas(16) Math::Vec4<float24> value[16];
-
- OutputVertex ToVertex(const Regs::ShaderConfig& config) const;
-};
-static_assert(std::is_pod<OutputRegisters>::value, "Structure is not POD");
+static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size");
/**
* This structure contains the state information that needs to be unique for a shader unit. The 3DS
@@ -100,11 +68,10 @@ struct UnitState {
// required to be 16-byte aligned.
alignas(16) Math::Vec4<float24> input[16];
alignas(16) Math::Vec4<float24> temporary[16];
+ alignas(16) Math::Vec4<float24> output[16];
} registers;
static_assert(std::is_pod<Registers>::value, "Structure is not POD");
- OutputRegisters output_registers;
-
bool conditional_code[2];
// Two Address registers and one loop counter
@@ -130,7 +97,7 @@ struct UnitState {
static size_t OutputOffset(const DestRegister& reg) {
switch (reg.GetRegisterType()) {
case RegisterType::Output:
- return offsetof(UnitState, output_registers.value) +
+ return offsetof(UnitState, registers.output) +
reg.GetIndex() * sizeof(Math::Vec4<float24>);
case RegisterType::Temporary:
@@ -142,13 +109,19 @@ struct UnitState {
return 0;
}
}
-};
-/// Clears the shader cache
-void ClearCache();
+ /**
+ * Loads the unit state with an input vertex.
+ *
+ * @param config Shader configuration registers corresponding to the unit.
+ * @param input Attribute buffer to load into the input registers.
+ */
+ void LoadInput(const Regs::ShaderConfig& config, const AttributeBuffer& input);
-struct ShaderSetup {
+ void WriteOutput(const Regs::ShaderConfig& config, AttributeBuffer& output);
+};
+struct ShaderSetup {
struct {
// The float uniforms are accessed by the shader JIT using SSE instructions, and are
// therefore required to be 16-byte aligned.
@@ -173,32 +146,37 @@ struct ShaderSetup {
std::array<u32, 1024> program_code;
std::array<u32, 1024> swizzle_data;
+ /// Data private to ShaderEngines
+ struct EngineData {
+ unsigned int entry_point;
+ /// Used by the JIT, points to a compiled shader object.
+ const void* cached_shader = nullptr;
+ } engine_data;
+};
+
+class ShaderEngine {
+public:
+ virtual ~ShaderEngine() = default;
+
/**
* Performs any shader unit setup that only needs to happen once per shader (as opposed to once
* per vertex, which would happen within the `Run` function).
*/
- void Setup();
+ virtual void SetupBatch(ShaderSetup& setup, unsigned int entry_point) = 0;
/**
- * Runs the currently setup shader
- * @param state Shader unit state, must be setup per shader and per shader unit
- * @param input Input vertex into the shader
- * @param num_attributes The number of vertex shader attributes
+ * Runs the currently setup shader.
+ *
+ * @param setup Shader engine state, must be setup with SetupBatch on each shader change.
+ * @param state Shader unit state, must be setup with input data before each shader invocation.
*/
- void Run(UnitState& state, const InputVertex& input, int num_attributes);
-
- /**
- * Produce debug information based on the given shader and input vertex
- * @param input Input vertex into the shader
- * @param num_attributes The number of vertex shader attributes
- * @param config Configuration object for the shader pipeline
- * @param setup Setup object for the shader pipeline
- * @return Debug information for this shader with regards to the given vertex
- */
- DebugData<true> ProduceDebugInfo(const InputVertex& input, int num_attributes,
- const Regs::ShaderConfig& config, const ShaderSetup& setup);
+ virtual void Run(const ShaderSetup& setup, UnitState& state) const = 0;
};
+// TODO(yuriks): Remove and make it non-global state somewhere
+ShaderEngine* GetEngine();
+void Shutdown();
+
} // namespace Shader
} // namespace Pica
diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp
index 70db4167e..81522b8f5 100644
--- a/src/video_core/shader/shader_interpreter.cpp
+++ b/src/video_core/shader/shader_interpreter.cpp
@@ -7,10 +7,12 @@
#include <cmath>
#include <numeric>
#include <boost/container/static_vector.hpp>
+#include <boost/range/algorithm/fill.hpp>
#include <nihstro/shader_bytecode.h>
#include "common/assert.h"
#include "common/common_types.h"
#include "common/logging/log.h"
+#include "common/microprofile.h"
#include "common/vector_math.h"
#include "video_core/pica_state.h"
#include "video_core/pica_types.h"
@@ -27,8 +29,6 @@ namespace Pica {
namespace Shader {
-constexpr u32 INVALID_ADDRESS = 0xFFFFFFFF;
-
struct CallStackElement {
u32 final_address; // Address upon which we jump to return_address
u32 return_address; // Where to jump when leaving scope
@@ -39,12 +39,15 @@ struct CallStackElement {
};
template <bool Debug>
-void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data,
- unsigned offset) {
+static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data,
+ unsigned offset) {
// TODO: Is there a maximal size for this?
boost::container::static_vector<CallStackElement, 16> call_stack;
u32 program_counter = offset;
+ state.conditional_code[0] = false;
+ state.conditional_code[1] = false;
+
auto call = [&program_counter, &call_stack](u32 offset, u32 num_instructions, u32 return_offset,
u8 repeat_count, u8 loop_increment) {
// -1 to make sure when incrementing the PC we end up at the correct offset
@@ -75,9 +78,9 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>
}
};
- const auto& uniforms = g_state.vs.uniforms;
- const auto& swizzle_data = g_state.vs.swizzle_data;
- const auto& program_code = g_state.vs.program_code;
+ const auto& uniforms = setup.uniforms;
+ const auto& swizzle_data = setup.swizzle_data;
+ const auto& program_code = setup.program_code;
// Placeholder for invalid inputs
static float24 dummy_vec4_float24[4];
@@ -172,7 +175,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>
float24* dest =
(instr.common.dest.Value() < 0x10)
- ? &state.output_registers.value[instr.common.dest.Value().GetIndex()][0]
+ ? &state.registers.output[instr.common.dest.Value().GetIndex()][0]
: (instr.common.dest.Value() < 0x20)
? &state.registers.temporary[instr.common.dest.Value().GetIndex()][0]
: dummy_vec4_float24;
@@ -515,7 +518,7 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>
float24* dest =
(instr.mad.dest.Value() < 0x10)
- ? &state.output_registers.value[instr.mad.dest.Value().GetIndex()][0]
+ ? &state.registers.output[instr.mad.dest.Value().GetIndex()][0]
: (instr.mad.dest.Value() < 0x20)
? &state.registers.temporary[instr.mad.dest.Value().GetIndex()][0]
: dummy_vec4_float24;
@@ -649,9 +652,33 @@ void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>
}
}
-// Explicit instantiation
-template void RunInterpreter(const ShaderSetup&, UnitState&, DebugData<false>&, unsigned offset);
-template void RunInterpreter(const ShaderSetup&, UnitState&, DebugData<true>&, unsigned offset);
+void InterpreterEngine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) {
+ ASSERT(entry_point < 1024);
+ setup.engine_data.entry_point = entry_point;
+}
+
+MICROPROFILE_DECLARE(GPU_Shader);
+
+void InterpreterEngine::Run(const ShaderSetup& setup, UnitState& state) const {
+
+ MICROPROFILE_SCOPE(GPU_Shader);
+
+ DebugData<false> dummy_debug_data;
+ RunInterpreter(setup, state, dummy_debug_data, setup.engine_data.entry_point);
+}
+
+DebugData<true> InterpreterEngine::ProduceDebugInfo(const ShaderSetup& setup,
+ const AttributeBuffer& input,
+ const Regs::ShaderConfig& config) const {
+ UnitState state;
+ DebugData<true> debug_data;
+
+ // Setup input register table
+ boost::fill(state.registers.input, Math::Vec4<float24>::AssignToAll(float24::Zero()));
+ state.LoadInput(config, input);
+ RunInterpreter(setup, state, debug_data, setup.engine_data.entry_point);
+ return debug_data;
+}
} // namespace
diff --git a/src/video_core/shader/shader_interpreter.h b/src/video_core/shader/shader_interpreter.h
index d31dcd7a6..d7a61e122 100644
--- a/src/video_core/shader/shader_interpreter.h
+++ b/src/video_core/shader/shader_interpreter.h
@@ -4,18 +4,27 @@
#pragma once
+#include "video_core/shader/debug_data.h"
+#include "video_core/shader/shader.h"
+
namespace Pica {
namespace Shader {
-struct UnitState;
-
-template <bool Debug>
-struct DebugData;
-
-template <bool Debug>
-void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData<Debug>& debug_data,
- unsigned offset);
+class InterpreterEngine final : public ShaderEngine {
+public:
+ void SetupBatch(ShaderSetup& setup, unsigned int entry_point) override;
+ void Run(const ShaderSetup& setup, UnitState& state) const override;
+
+ /**
+ * Produce debug information based on the given shader and input vertex
+ * @param input Input vertex into the shader
+ * @param config Configuration object for the shader pipeline
+ * @return Debug information for this shader with regards to the given vertex
+ */
+ DebugData<true> ProduceDebugInfo(const ShaderSetup& setup, const AttributeBuffer& input,
+ const Regs::ShaderConfig& config) const;
+};
} // namespace
diff --git a/src/video_core/shader/shader_jit_x64.cpp b/src/video_core/shader/shader_jit_x64.cpp
index c588b778b..0ee0dd9ef 100644
--- a/src/video_core/shader/shader_jit_x64.cpp
+++ b/src/video_core/shader/shader_jit_x64.cpp
@@ -1,888 +1,48 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
-#include <algorithm>
-#include <cmath>
-#include <cstdint>
-#include <nihstro/shader_bytecode.h>
-#include <smmintrin.h>
-#include <xmmintrin.h>
-#include "common/assert.h"
-#include "common/logging/log.h"
-#include "common/vector_math.h"
-#include "common/x64/cpu_detect.h"
-#include "common/x64/xbyak_abi.h"
-#include "common/x64/xbyak_util.h"
-#include "video_core/pica_state.h"
-#include "video_core/pica_types.h"
+#include "common/hash.h"
+#include "common/microprofile.h"
#include "video_core/shader/shader.h"
#include "video_core/shader/shader_jit_x64.h"
-
-using namespace Common::X64;
-using namespace Xbyak::util;
-using Xbyak::Label;
-using Xbyak::Reg32;
-using Xbyak::Reg64;
-using Xbyak::Xmm;
+#include "video_core/shader/shader_jit_x64_compiler.h"
namespace Pica {
-
namespace Shader {
-typedef void (JitShader::*JitFunction)(Instruction instr);
-
-const JitFunction instr_table[64] = {
- &JitShader::Compile_ADD, // add
- &JitShader::Compile_DP3, // dp3
- &JitShader::Compile_DP4, // dp4
- &JitShader::Compile_DPH, // dph
- nullptr, // unknown
- &JitShader::Compile_EX2, // ex2
- &JitShader::Compile_LG2, // lg2
- nullptr, // unknown
- &JitShader::Compile_MUL, // mul
- &JitShader::Compile_SGE, // sge
- &JitShader::Compile_SLT, // slt
- &JitShader::Compile_FLR, // flr
- &JitShader::Compile_MAX, // max
- &JitShader::Compile_MIN, // min
- &JitShader::Compile_RCP, // rcp
- &JitShader::Compile_RSQ, // rsq
- nullptr, // unknown
- nullptr, // unknown
- &JitShader::Compile_MOVA, // mova
- &JitShader::Compile_MOV, // mov
- nullptr, // unknown
- nullptr, // unknown
- nullptr, // unknown
- nullptr, // unknown
- &JitShader::Compile_DPH, // dphi
- nullptr, // unknown
- &JitShader::Compile_SGE, // sgei
- &JitShader::Compile_SLT, // slti
- nullptr, // unknown
- nullptr, // unknown
- nullptr, // unknown
- nullptr, // unknown
- nullptr, // unknown
- &JitShader::Compile_NOP, // nop
- &JitShader::Compile_END, // end
- nullptr, // break
- &JitShader::Compile_CALL, // call
- &JitShader::Compile_CALLC, // callc
- &JitShader::Compile_CALLU, // callu
- &JitShader::Compile_IF, // ifu
- &JitShader::Compile_IF, // ifc
- &JitShader::Compile_LOOP, // loop
- nullptr, // emit
- nullptr, // sete
- &JitShader::Compile_JMP, // jmpc
- &JitShader::Compile_JMP, // jmpu
- &JitShader::Compile_CMP, // cmp
- &JitShader::Compile_CMP, // cmp
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // madi
- &JitShader::Compile_MAD, // mad
- &JitShader::Compile_MAD, // mad
- &JitShader::Compile_MAD, // mad
- &JitShader::Compile_MAD, // mad
- &JitShader::Compile_MAD, // mad
- &JitShader::Compile_MAD, // mad
- &JitShader::Compile_MAD, // mad
- &JitShader::Compile_MAD, // mad
-};
-
-// The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can
-// be used as scratch registers within a compiler function. The other registers have designated
-// purposes, as documented below:
+JitX64Engine::JitX64Engine() = default;
+JitX64Engine::~JitX64Engine() = default;
-/// Pointer to the uniform memory
-static const Reg64 SETUP = r9;
-/// The two 32-bit VS address offset registers set by the MOVA instruction
-static const Reg64 ADDROFFS_REG_0 = r10;
-static const Reg64 ADDROFFS_REG_1 = r11;
-/// VS loop count register (Multiplied by 16)
-static const Reg32 LOOPCOUNT_REG = r12d;
-/// Current VS loop iteration number (we could probably use LOOPCOUNT_REG, but this quicker)
-static const Reg32 LOOPCOUNT = esi;
-/// Number to increment LOOPCOUNT_REG by on each loop iteration (Multiplied by 16)
-static const Reg32 LOOPINC = edi;
-/// Result of the previous CMP instruction for the X-component comparison
-static const Reg64 COND0 = r13;
-/// Result of the previous CMP instruction for the Y-component comparison
-static const Reg64 COND1 = r14;
-/// Pointer to the UnitState instance for the current VS unit
-static const Reg64 STATE = r15;
-/// SIMD scratch register
-static const Xmm SCRATCH = xmm0;
-/// Loaded with the first swizzled source register, otherwise can be used as a scratch register
-static const Xmm SRC1 = xmm1;
-/// Loaded with the second swizzled source register, otherwise can be used as a scratch register
-static const Xmm SRC2 = xmm2;
-/// Loaded with the third swizzled source register, otherwise can be used as a scratch register
-static const Xmm SRC3 = xmm3;
-/// Additional scratch register
-static const Xmm SCRATCH2 = xmm4;
-/// Constant vector of [1.0f, 1.0f, 1.0f, 1.0f], used to efficiently set a vector to one
-static const Xmm ONE = xmm14;
-/// Constant vector of [-0.f, -0.f, -0.f, -0.f], used to efficiently negate a vector with XOR
-static const Xmm NEGBIT = xmm15;
+void JitX64Engine::SetupBatch(ShaderSetup& setup, unsigned int entry_point) {
+ ASSERT(entry_point < 1024);
+ setup.engine_data.entry_point = entry_point;
-// State registers that must not be modified by external functions calls
-// Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed
-static const BitSet32 persistent_regs = BuildRegSet({
- // Pointers to register blocks
- SETUP, STATE,
- // Cached registers
- ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1,
- // Constants
- ONE, NEGBIT,
-});
+ u64 code_hash = Common::ComputeHash64(&setup.program_code, sizeof(setup.program_code));
+ u64 swizzle_hash = Common::ComputeHash64(&setup.swizzle_data, sizeof(setup.swizzle_data));
-/// Raw constant for the source register selector that indicates no swizzling is performed
-static const u8 NO_SRC_REG_SWIZZLE = 0x1b;
-/// Raw constant for the destination register enable mask that indicates all components are enabled
-static const u8 NO_DEST_REG_MASK = 0xf;
-
-/**
- * Get the vertex shader instruction for a given offset in the current shader program
- * @param offset Offset in the current shader program of the instruction
- * @return Instruction at the specified offset
- */
-static Instruction GetVertexShaderInstruction(size_t offset) {
- return {g_state.vs.program_code[offset]};
-}
-
-static void LogCritical(const char* msg) {
- LOG_CRITICAL(HW_GPU, "%s", msg);
-}
-
-void JitShader::Compile_Assert(bool condition, const char* msg) {
- if (!condition) {
- mov(ABI_PARAM1, reinterpret_cast<size_t>(msg));
- CallFarFunction(*this, LogCritical);
- }
-}
-
-/**
- * Loads and swizzles a source register into the specified XMM register.
- * @param instr VS instruction, used for determining how to load the source register
- * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3)
- * @param src_reg SourceRegister object corresponding to the source register to load
- * @param dest Destination XMM register to store the loaded, swizzled source register
- */
-void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg,
- Xmm dest) {
- Reg64 src_ptr;
- size_t src_offset;
-
- if (src_reg.GetRegisterType() == RegisterType::FloatUniform) {
- src_ptr = SETUP;
- src_offset = ShaderSetup::GetFloatUniformOffset(src_reg.GetIndex());
+ u64 cache_key = code_hash ^ swizzle_hash;
+ auto iter = cache.find(cache_key);
+ if (iter != cache.end()) {
+ setup.engine_data.cached_shader = iter->second.get();
} else {
- src_ptr = STATE;
- src_offset = UnitState::InputOffset(src_reg);
- }
-
- int src_offset_disp = (int)src_offset;
- ASSERT_MSG(src_offset == src_offset_disp, "Source register offset too large for int type");
-
- unsigned operand_desc_id;
-
- const bool is_inverted =
- (0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed));
-
- unsigned address_register_index;
- unsigned offset_src;
-
- if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD ||
- instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) {
- operand_desc_id = instr.mad.operand_desc_id;
- offset_src = is_inverted ? 3 : 2;
- address_register_index = instr.mad.address_register_index;
- } else {
- operand_desc_id = instr.common.operand_desc_id;
- offset_src = is_inverted ? 2 : 1;
- address_register_index = instr.common.address_register_index;
- }
-
- if (src_num == offset_src && address_register_index != 0) {
- switch (address_register_index) {
- case 1: // address offset 1
- movaps(dest, xword[src_ptr + ADDROFFS_REG_0 + src_offset_disp]);
- break;
- case 2: // address offset 2
- movaps(dest, xword[src_ptr + ADDROFFS_REG_1 + src_offset_disp]);
- break;
- case 3: // address offset 3
- movaps(dest, xword[src_ptr + LOOPCOUNT_REG.cvt64() + src_offset_disp]);
- break;
- default:
- UNREACHABLE();
- break;
- }
- } else {
- // Load the source
- movaps(dest, xword[src_ptr + src_offset_disp]);
- }
-
- SwizzlePattern swiz = {g_state.vs.swizzle_data[operand_desc_id]};
-
- // Generate instructions for source register swizzling as needed
- u8 sel = swiz.GetRawSelector(src_num);
- if (sel != NO_SRC_REG_SWIZZLE) {
- // Selector component order needs to be reversed for the SHUFPS instruction
- sel = ((sel & 0xc0) >> 6) | ((sel & 3) << 6) | ((sel & 0xc) << 2) | ((sel & 0x30) >> 2);
-
- // Shuffle inputs for swizzle
- shufps(dest, dest, sel);
- }
-
- // If the source register should be negated, flip the negative bit using XOR
- const bool negate[] = {swiz.negate_src1, swiz.negate_src2, swiz.negate_src3};
- if (negate[src_num - 1]) {
- xorps(dest, NEGBIT);
+ auto shader = std::make_unique<JitShader>();
+ shader->Compile(&setup.program_code, &setup.swizzle_data);
+ setup.engine_data.cached_shader = shader.get();
+ cache.emplace_hint(iter, cache_key, std::move(shader));
}
}
-void JitShader::Compile_DestEnable(Instruction instr, Xmm src) {
- DestRegister dest;
- unsigned operand_desc_id;
- if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD ||
- instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) {
- operand_desc_id = instr.mad.operand_desc_id;
- dest = instr.mad.dest.Value();
- } else {
- operand_desc_id = instr.common.operand_desc_id;
- dest = instr.common.dest.Value();
- }
-
- SwizzlePattern swiz = {g_state.vs.swizzle_data[operand_desc_id]};
-
- size_t dest_offset_disp = UnitState::OutputOffset(dest);
-
- // If all components are enabled, write the result to the destination register
- if (swiz.dest_mask == NO_DEST_REG_MASK) {
- // Store dest back to memory
- movaps(xword[STATE + dest_offset_disp], src);
-
- } else {
- // Not all components are enabled, so mask the result when storing to the destination
- // register...
- movaps(SCRATCH, xword[STATE + dest_offset_disp]);
-
- if (Common::GetCPUCaps().sse4_1) {
- u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) |
- ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1);
- blendps(SCRATCH, src, mask);
- } else {
- movaps(SCRATCH2, src);
- unpckhps(SCRATCH2, SCRATCH); // Unpack X/Y components of source and destination
- unpcklps(SCRATCH, src); // Unpack Z/W components of source and destination
-
- // Compute selector to selectively copy source components to destination for SHUFPS
- // instruction
- u8 sel = ((swiz.DestComponentEnabled(0) ? 1 : 0) << 0) |
- ((swiz.DestComponentEnabled(1) ? 3 : 2) << 2) |
- ((swiz.DestComponentEnabled(2) ? 0 : 1) << 4) |
- ((swiz.DestComponentEnabled(3) ? 2 : 3) << 6);
- shufps(SCRATCH, SCRATCH2, sel);
- }
-
- // Store dest back to memory
- movaps(xword[STATE + dest_offset_disp], SCRATCH);
- }
-}
-
-void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) {
- movaps(scratch, src1);
- cmpordps(scratch, src2);
-
- mulps(src1, src2);
+MICROPROFILE_DECLARE(GPU_Shader);
- movaps(src2, src1);
- cmpunordps(src2, src2);
+void JitX64Engine::Run(const ShaderSetup& setup, UnitState& state) const {
+ ASSERT(setup.engine_data.cached_shader != nullptr);
- xorps(scratch, src2);
- andps(src1, scratch);
-}
-
-void JitShader::Compile_EvaluateCondition(Instruction instr) {
- // Note: NXOR is used below to check for equality
- switch (instr.flow_control.op) {
- case Instruction::FlowControlType::Or:
- mov(eax, COND0);
- mov(ebx, COND1);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
- xor(ebx, (instr.flow_control.refy.Value() ^ 1));
- or (eax, ebx);
- break;
-
- case Instruction::FlowControlType::And:
- mov(eax, COND0);
- mov(ebx, COND1);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
- xor(ebx, (instr.flow_control.refy.Value() ^ 1));
- and(eax, ebx);
- break;
-
- case Instruction::FlowControlType::JustX:
- mov(eax, COND0);
- xor(eax, (instr.flow_control.refx.Value() ^ 1));
- break;
-
- case Instruction::FlowControlType::JustY:
- mov(eax, COND1);
- xor(eax, (instr.flow_control.refy.Value() ^ 1));
- break;
- }
-}
+ MICROPROFILE_SCOPE(GPU_Shader);
-void JitShader::Compile_UniformCondition(Instruction instr) {
- size_t offset = ShaderSetup::GetBoolUniformOffset(instr.flow_control.bool_uniform_id);
- cmp(byte[SETUP + offset], 0);
+ const JitShader* shader = static_cast<const JitShader*>(setup.engine_data.cached_shader);
+ shader->Run(setup, state, setup.engine_data.entry_point);
}
-BitSet32 JitShader::PersistentCallerSavedRegs() {
- return persistent_regs & ABI_ALL_CALLER_SAVED;
-}
-
-void JitShader::Compile_ADD(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
- addps(SRC1, SRC2);
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_DP3(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
-
- Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
-
- movaps(SRC2, SRC1);
- shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1));
-
- movaps(SRC3, SRC1);
- shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2));
-
- shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0));
- addps(SRC1, SRC2);
- addps(SRC1, SRC3);
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_DP4(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
-
- Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
-
- movaps(SRC2, SRC1);
- shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY
- addps(SRC1, SRC2);
-
- movaps(SRC2, SRC1);
- shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX
- addps(SRC1, SRC2);
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_DPH(Instruction instr) {
- if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::DPHI) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2);
- } else {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
- }
-
- if (Common::GetCPUCaps().sse4_1) {
- // Set 4th component to 1.0
- blendps(SRC1, ONE, 0b1000);
- } else {
- // Set 4th component to 1.0
- movaps(SCRATCH, SRC1);
- unpckhps(SCRATCH, ONE); // XYZW, 1111 -> Z1__
- unpcklpd(SRC1, SCRATCH); // XYZW, Z1__ -> XYZ1
- }
-
- Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
-
- movaps(SRC2, SRC1);
- shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY
- addps(SRC1, SRC2);
-
- movaps(SRC2, SRC1);
- shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX
- addps(SRC1, SRC2);
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_EX2(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- movss(xmm0, SRC1); // ABI_PARAM1
-
- ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
- CallFarFunction(*this, exp2f);
- ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
-
- shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN
- movaps(SRC1, xmm0);
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_LG2(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- movss(xmm0, SRC1); // ABI_PARAM1
-
- ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
- CallFarFunction(*this, log2f);
- ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
-
- shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN
- movaps(SRC1, xmm0);
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_MUL(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
- Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_SGE(Instruction instr) {
- if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SGEI) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2);
- } else {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
- }
-
- cmpleps(SRC2, SRC1);
- andps(SRC2, ONE);
-
- Compile_DestEnable(instr, SRC2);
-}
-
-void JitShader::Compile_SLT(Instruction instr) {
- if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SLTI) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2);
- } else {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
- }
-
- cmpltps(SRC1, SRC2);
- andps(SRC1, ONE);
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_FLR(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
-
- if (Common::GetCPUCaps().sse4_1) {
- roundps(SRC1, SRC1, _MM_FROUND_FLOOR);
- } else {
- cvttps2dq(SRC1, SRC1);
- cvtdq2ps(SRC1, SRC1);
- }
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_MAX(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
- // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned.
- maxps(SRC1, SRC2);
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_MIN(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
- // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned.
- minps(SRC1, SRC2);
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_MOVA(Instruction instr) {
- SwizzlePattern swiz = {g_state.vs.swizzle_data[instr.common.operand_desc_id]};
-
- if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) {
- return; // NoOp
- }
-
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
-
- // Convert floats to integers using truncation (only care about X and Y components)
- cvttps2dq(SRC1, SRC1);
-
- // Get result
- movq(rax, SRC1);
-
- // Handle destination enable
- if (swiz.DestComponentEnabled(0) && swiz.DestComponentEnabled(1)) {
- // Move and sign-extend low 32 bits
- movsxd(ADDROFFS_REG_0, eax);
-
- // Move and sign-extend high 32 bits
- shr(rax, 32);
- movsxd(ADDROFFS_REG_1, eax);
-
- // Multiply by 16 to be used as an offset later
- shl(ADDROFFS_REG_0, 4);
- shl(ADDROFFS_REG_1, 4);
- } else {
- if (swiz.DestComponentEnabled(0)) {
- // Move and sign-extend low 32 bits
- movsxd(ADDROFFS_REG_0, eax);
-
- // Multiply by 16 to be used as an offset later
- shl(ADDROFFS_REG_0, 4);
- } else if (swiz.DestComponentEnabled(1)) {
- // Move and sign-extend high 32 bits
- shr(rax, 32);
- movsxd(ADDROFFS_REG_1, eax);
-
- // Multiply by 16 to be used as an offset later
- shl(ADDROFFS_REG_1, 4);
- }
- }
-}
-
-void JitShader::Compile_MOV(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_RCP(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
-
- // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica
- // performs this operation more accurately. This should be checked on hardware.
- rcpss(SRC1, SRC1);
- shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_RSQ(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
-
- // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica
- // performs this operation more accurately. This should be checked on hardware.
- rsqrtss(SRC1, SRC1);
- shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_NOP(Instruction instr) {}
-
-void JitShader::Compile_END(Instruction instr) {
- ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8);
- ret();
-}
-
-void JitShader::Compile_CALL(Instruction instr) {
- // Push offset of the return
- push(qword, (instr.flow_control.dest_offset + instr.flow_control.num_instructions));
-
- // Call the subroutine
- call(instruction_labels[instr.flow_control.dest_offset]);
-
- // Skip over the return offset that's on the stack
- add(rsp, 8);
-}
-
-void JitShader::Compile_CALLC(Instruction instr) {
- Compile_EvaluateCondition(instr);
- Label b;
- jz(b);
- Compile_CALL(instr);
- L(b);
-}
-
-void JitShader::Compile_CALLU(Instruction instr) {
- Compile_UniformCondition(instr);
- Label b;
- jz(b);
- Compile_CALL(instr);
- L(b);
-}
-
-void JitShader::Compile_CMP(Instruction instr) {
- using Op = Instruction::Common::CompareOpType::Op;
- Op op_x = instr.common.compare_op.x;
- Op op_y = instr.common.compare_op.y;
-
- Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
- Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
-
- // SSE doesn't have greater-than (GT) or greater-equal (GE) comparison operators. You need to
- // emulate them by swapping the lhs and rhs and using LT and LE. NLT and NLE can't be used here
- // because they don't match when used with NaNs.
- static const u8 cmp[] = {CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_LT, CMP_LE};
-
- bool invert_op_x = (op_x == Op::GreaterThan || op_x == Op::GreaterEqual);
- Xmm lhs_x = invert_op_x ? SRC2 : SRC1;
- Xmm rhs_x = invert_op_x ? SRC1 : SRC2;
-
- if (op_x == op_y) {
- // Compare X-component and Y-component together
- cmpps(lhs_x, rhs_x, cmp[op_x]);
- movq(COND0, lhs_x);
-
- mov(COND1, COND0);
- } else {
- bool invert_op_y = (op_y == Op::GreaterThan || op_y == Op::GreaterEqual);
- Xmm lhs_y = invert_op_y ? SRC2 : SRC1;
- Xmm rhs_y = invert_op_y ? SRC1 : SRC2;
-
- // Compare X-component
- movaps(SCRATCH, lhs_x);
- cmpss(SCRATCH, rhs_x, cmp[op_x]);
-
- // Compare Y-component
- cmpps(lhs_y, rhs_y, cmp[op_y]);
-
- movq(COND0, SCRATCH);
- movq(COND1, lhs_y);
- }
-
- shr(COND0.cvt32(), 31); // ignores upper 32 bits in source
- shr(COND1, 63);
-}
-
-void JitShader::Compile_MAD(Instruction instr) {
- Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1);
-
- if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) {
- Compile_SwizzleSrc(instr, 2, instr.mad.src2i, SRC2);
- Compile_SwizzleSrc(instr, 3, instr.mad.src3i, SRC3);
- } else {
- Compile_SwizzleSrc(instr, 2, instr.mad.src2, SRC2);
- Compile_SwizzleSrc(instr, 3, instr.mad.src3, SRC3);
- }
-
- Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
- addps(SRC1, SRC3);
-
- Compile_DestEnable(instr, SRC1);
-}
-
-void JitShader::Compile_IF(Instruction instr) {
- Compile_Assert(instr.flow_control.dest_offset >= program_counter,
- "Backwards if-statements not supported");
- Label l_else, l_endif;
-
- // Evaluate the "IF" condition
- if (instr.opcode.Value() == OpCode::Id::IFU) {
- Compile_UniformCondition(instr);
- } else if (instr.opcode.Value() == OpCode::Id::IFC) {
- Compile_EvaluateCondition(instr);
- }
- jz(l_else, T_NEAR);
-
- // Compile the code that corresponds to the condition evaluating as true
- Compile_Block(instr.flow_control.dest_offset);
-
- // If there isn't an "ELSE" condition, we are done here
- if (instr.flow_control.num_instructions == 0) {
- L(l_else);
- return;
- }
-
- jmp(l_endif, T_NEAR);
-
- L(l_else);
- // This code corresponds to the "ELSE" condition
- // Comple the code that corresponds to the condition evaluating as false
- Compile_Block(instr.flow_control.dest_offset + instr.flow_control.num_instructions);
-
- L(l_endif);
-}
-
-void JitShader::Compile_LOOP(Instruction instr) {
- Compile_Assert(instr.flow_control.dest_offset >= program_counter,
- "Backwards loops not supported");
- Compile_Assert(!looping, "Nested loops not supported");
-
- looping = true;
-
- // This decodes the fields from the integer uniform at index instr.flow_control.int_uniform_id.
- // The Y (LOOPCOUNT_REG) and Z (LOOPINC) component are kept multiplied by 16 (Left shifted by
- // 4 bits) to be used as an offset into the 16-byte vector registers later
- size_t offset = ShaderSetup::GetIntUniformOffset(instr.flow_control.int_uniform_id);
- mov(LOOPCOUNT, dword[SETUP + offset]);
- mov(LOOPCOUNT_REG, LOOPCOUNT);
- shr(LOOPCOUNT_REG, 4);
- and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start
- mov(LOOPINC, LOOPCOUNT);
- shr(LOOPINC, 12);
- and(LOOPINC, 0xFF0); // Z-component is the incrementer
- movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count
- add(LOOPCOUNT, 1); // Iteration count is X-component + 1
-
- Label l_loop_start;
- L(l_loop_start);
-
- Compile_Block(instr.flow_control.dest_offset + 1);
-
- add(LOOPCOUNT_REG, LOOPINC); // Increment LOOPCOUNT_REG by Z-component
- sub(LOOPCOUNT, 1); // Increment loop count by 1
- jnz(l_loop_start); // Loop if not equal
-
- looping = false;
-}
-
-void JitShader::Compile_JMP(Instruction instr) {
- if (instr.opcode.Value() == OpCode::Id::JMPC)
- Compile_EvaluateCondition(instr);
- else if (instr.opcode.Value() == OpCode::Id::JMPU)
- Compile_UniformCondition(instr);
- else
- UNREACHABLE();
-
- bool inverted_condition =
- (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1);
-
- Label& b = instruction_labels[instr.flow_control.dest_offset];
- if (inverted_condition) {
- jz(b, T_NEAR);
- } else {
- jnz(b, T_NEAR);
- }
-}
-
-void JitShader::Compile_Block(unsigned end) {
- while (program_counter < end) {
- Compile_NextInstr();
- }
-}
-
-void JitShader::Compile_Return() {
- // Peek return offset on the stack and check if we're at that offset
- mov(rax, qword[rsp + 8]);
- cmp(eax, (program_counter));
-
- // If so, jump back to before CALL
- Label b;
- jnz(b);
- ret();
- L(b);
-}
-
-void JitShader::Compile_NextInstr() {
- if (std::binary_search(return_offsets.begin(), return_offsets.end(), program_counter)) {
- Compile_Return();
- }
-
- L(instruction_labels[program_counter]);
-
- Instruction instr = GetVertexShaderInstruction(program_counter++);
-
- OpCode::Id opcode = instr.opcode.Value();
- auto instr_func = instr_table[static_cast<unsigned>(opcode)];
-
- if (instr_func) {
- // JIT the instruction!
- ((*this).*instr_func)(instr);
- } else {
- // Unhandled instruction
- LOG_CRITICAL(HW_GPU, "Unhandled instruction: 0x%02x (0x%08x)",
- instr.opcode.Value().EffectiveOpCode(), instr.hex);
- }
-}
-
-void JitShader::FindReturnOffsets() {
- return_offsets.clear();
-
- for (size_t offset = 0; offset < g_state.vs.program_code.size(); ++offset) {
- Instruction instr = GetVertexShaderInstruction(offset);
-
- switch (instr.opcode.Value()) {
- case OpCode::Id::CALL:
- case OpCode::Id::CALLC:
- case OpCode::Id::CALLU:
- return_offsets.push_back(instr.flow_control.dest_offset +
- instr.flow_control.num_instructions);
- break;
- default:
- break;
- }
- }
-
- // Sort for efficient binary search later
- std::sort(return_offsets.begin(), return_offsets.end());
-}
-
-void JitShader::Compile() {
- // Reset flow control state
- program = (CompiledShader*)getCurr();
- program_counter = 0;
- looping = false;
- instruction_labels.fill(Xbyak::Label());
-
- // Find all `CALL` instructions and identify return locations
- FindReturnOffsets();
-
- // The stack pointer is 8 modulo 16 at the entry of a procedure
- ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8);
-
- mov(SETUP, ABI_PARAM1);
- mov(STATE, ABI_PARAM2);
-
- // Zero address/loop registers
- xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32());
- xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32());
- xor(LOOPCOUNT_REG, LOOPCOUNT_REG);
-
- // Used to set a register to one
- static const __m128 one = {1.f, 1.f, 1.f, 1.f};
- mov(rax, reinterpret_cast<size_t>(&one));
- movaps(ONE, xword[rax]);
-
- // Used to negate registers
- static const __m128 neg = {-0.f, -0.f, -0.f, -0.f};
- mov(rax, reinterpret_cast<size_t>(&neg));
- movaps(NEGBIT, xword[rax]);
-
- // Jump to start of the shader program
- jmp(ABI_PARAM3);
-
- // Compile entire program
- Compile_Block(static_cast<unsigned>(g_state.vs.program_code.size()));
-
- // Free memory that's no longer needed
- return_offsets.clear();
- return_offsets.shrink_to_fit();
-
- ready();
-
- uintptr_t size = reinterpret_cast<uintptr_t>(getCurr()) - reinterpret_cast<uintptr_t>(program);
- ASSERT_MSG(size <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!");
- LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", size);
-}
-
-JitShader::JitShader() : Xbyak::CodeGenerator(MAX_SHADER_SIZE) {}
-
} // namespace Shader
-
} // namespace Pica
diff --git a/src/video_core/shader/shader_jit_x64.h b/src/video_core/shader/shader_jit_x64.h
index f37548306..078b2cba5 100644
--- a/src/video_core/shader/shader_jit_x64.h
+++ b/src/video_core/shader/shader_jit_x64.h
@@ -1,121 +1,30 @@
-// Copyright 2015 Citra Emulator Project
+// Copyright 2016 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
#pragma once
-#include <array>
-#include <cstddef>
-#include <utility>
-#include <vector>
-#include <nihstro/shader_bytecode.h>
-#include <xbyak.h>
-#include "common/bit_set.h"
+#include <memory>
+#include <unordered_map>
#include "common/common_types.h"
-#include "common/x64/emitter.h"
#include "video_core/shader/shader.h"
-using nihstro::Instruction;
-using nihstro::OpCode;
-using nihstro::SwizzlePattern;
-
namespace Pica {
-
namespace Shader {
-/// Memory allocated for each compiled shader (64Kb)
-constexpr size_t MAX_SHADER_SIZE = 1024 * 64;
+class JitShader;
-/**
- * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64
- * code that can be executed on the host machine directly.
- */
-class JitShader : public Xbyak::CodeGenerator {
+class JitX64Engine final : public ShaderEngine {
public:
- JitShader();
-
- void Run(const ShaderSetup& setup, UnitState& state, unsigned offset) const {
- program(&setup, &state, instruction_labels[offset].getAddress());
- }
-
- void Compile();
+ JitX64Engine();
+ ~JitX64Engine() override;
- void Compile_ADD(Instruction instr);
- void Compile_DP3(Instruction instr);
- void Compile_DP4(Instruction instr);
- void Compile_DPH(Instruction instr);
- void Compile_EX2(Instruction instr);
- void Compile_LG2(Instruction instr);
- void Compile_MUL(Instruction instr);
- void Compile_SGE(Instruction instr);
- void Compile_SLT(Instruction instr);
- void Compile_FLR(Instruction instr);
- void Compile_MAX(Instruction instr);
- void Compile_MIN(Instruction instr);
- void Compile_RCP(Instruction instr);
- void Compile_RSQ(Instruction instr);
- void Compile_MOVA(Instruction instr);
- void Compile_MOV(Instruction instr);
- void Compile_NOP(Instruction instr);
- void Compile_END(Instruction instr);
- void Compile_CALL(Instruction instr);
- void Compile_CALLC(Instruction instr);
- void Compile_CALLU(Instruction instr);
- void Compile_IF(Instruction instr);
- void Compile_LOOP(Instruction instr);
- void Compile_JMP(Instruction instr);
- void Compile_CMP(Instruction instr);
- void Compile_MAD(Instruction instr);
+ void SetupBatch(ShaderSetup& setup, unsigned int entry_point) override;
+ void Run(const ShaderSetup& setup, UnitState& state) const override;
private:
- void Compile_Block(unsigned end);
- void Compile_NextInstr();
-
- void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg,
- Xbyak::Xmm dest);
- void Compile_DestEnable(Instruction instr, Xbyak::Xmm dest);
-
- /**
- * Compiles a `MUL src1, src2` operation, properly handling the PICA semantics when multiplying
- * zero by inf. Clobbers `src2` and `scratch`.
- */
- void Compile_SanitizedMul(Xbyak::Xmm src1, Xbyak::Xmm src2, Xbyak::Xmm scratch);
-
- void Compile_EvaluateCondition(Instruction instr);
- void Compile_UniformCondition(Instruction instr);
-
- /**
- * Emits the code to conditionally return from a subroutine envoked by the `CALL` instruction.
- */
- void Compile_Return();
-
- BitSet32 PersistentCallerSavedRegs();
-
- /**
- * Assertion evaluated at compile-time, but only triggered if executed at runtime.
- * @param msg Message to be logged if the assertion fails.
- */
- void Compile_Assert(bool condition, const char* msg);
-
- /**
- * Analyzes the entire shader program for `CALL` instructions before emitting any code,
- * identifying the locations where a return needs to be inserted.
- */
- void FindReturnOffsets();
-
- /// Mapping of Pica VS instructions to pointers in the emitted code
- std::array<Xbyak::Label, 1024> instruction_labels;
-
- /// Offsets in code where a return needs to be inserted
- std::vector<unsigned> return_offsets;
-
- unsigned program_counter = 0; ///< Offset of the next instruction to decode
- bool looping = false; ///< True if compiling a loop, used to check for nested loops
-
- using CompiledShader = void(const void* setup, void* state, const u8* start_addr);
- CompiledShader* program = nullptr;
+ std::unordered_map<u64, std::unique_ptr<JitShader>> cache;
};
-} // Shader
-
-} // Pica
+} // namespace Shader
+} // namespace Pica
diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp
new file mode 100644
index 000000000..92b35dbc0
--- /dev/null
+++ b/src/video_core/shader/shader_jit_x64_compiler.cpp
@@ -0,0 +1,889 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <nihstro/shader_bytecode.h>
+#include <smmintrin.h>
+#include <xmmintrin.h>
+#include "common/assert.h"
+#include "common/logging/log.h"
+#include "common/vector_math.h"
+#include "common/x64/cpu_detect.h"
+#include "common/x64/xbyak_abi.h"
+#include "common/x64/xbyak_util.h"
+#include "video_core/pica_state.h"
+#include "video_core/pica_types.h"
+#include "video_core/shader/shader.h"
+#include "video_core/shader/shader_jit_x64_compiler.h"
+
+using namespace Common::X64;
+using namespace Xbyak::util;
+using Xbyak::Label;
+using Xbyak::Reg32;
+using Xbyak::Reg64;
+using Xbyak::Xmm;
+
+namespace Pica {
+
+namespace Shader {
+
+typedef void (JitShader::*JitFunction)(Instruction instr);
+
+const JitFunction instr_table[64] = {
+ &JitShader::Compile_ADD, // add
+ &JitShader::Compile_DP3, // dp3
+ &JitShader::Compile_DP4, // dp4
+ &JitShader::Compile_DPH, // dph
+ nullptr, // unknown
+ &JitShader::Compile_EX2, // ex2
+ &JitShader::Compile_LG2, // lg2
+ nullptr, // unknown
+ &JitShader::Compile_MUL, // mul
+ &JitShader::Compile_SGE, // sge
+ &JitShader::Compile_SLT, // slt
+ &JitShader::Compile_FLR, // flr
+ &JitShader::Compile_MAX, // max
+ &JitShader::Compile_MIN, // min
+ &JitShader::Compile_RCP, // rcp
+ &JitShader::Compile_RSQ, // rsq
+ nullptr, // unknown
+ nullptr, // unknown
+ &JitShader::Compile_MOVA, // mova
+ &JitShader::Compile_MOV, // mov
+ nullptr, // unknown
+ nullptr, // unknown
+ nullptr, // unknown
+ nullptr, // unknown
+ &JitShader::Compile_DPH, // dphi
+ nullptr, // unknown
+ &JitShader::Compile_SGE, // sgei
+ &JitShader::Compile_SLT, // slti
+ nullptr, // unknown
+ nullptr, // unknown
+ nullptr, // unknown
+ nullptr, // unknown
+ nullptr, // unknown
+ &JitShader::Compile_NOP, // nop
+ &JitShader::Compile_END, // end
+ nullptr, // break
+ &JitShader::Compile_CALL, // call
+ &JitShader::Compile_CALLC, // callc
+ &JitShader::Compile_CALLU, // callu
+ &JitShader::Compile_IF, // ifu
+ &JitShader::Compile_IF, // ifc
+ &JitShader::Compile_LOOP, // loop
+ nullptr, // emit
+ nullptr, // sete
+ &JitShader::Compile_JMP, // jmpc
+ &JitShader::Compile_JMP, // jmpu
+ &JitShader::Compile_CMP, // cmp
+ &JitShader::Compile_CMP, // cmp
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // madi
+ &JitShader::Compile_MAD, // mad
+ &JitShader::Compile_MAD, // mad
+ &JitShader::Compile_MAD, // mad
+ &JitShader::Compile_MAD, // mad
+ &JitShader::Compile_MAD, // mad
+ &JitShader::Compile_MAD, // mad
+ &JitShader::Compile_MAD, // mad
+ &JitShader::Compile_MAD, // mad
+};
+
+// The following is used to alias some commonly used registers. Generally, RAX-RDX and XMM0-XMM3 can
+// be used as scratch registers within a compiler function. The other registers have designated
+// purposes, as documented below:
+
+/// Pointer to the uniform memory
+static const Reg64 SETUP = r9;
+/// The two 32-bit VS address offset registers set by the MOVA instruction
+static const Reg64 ADDROFFS_REG_0 = r10;
+static const Reg64 ADDROFFS_REG_1 = r11;
+/// VS loop count register (Multiplied by 16)
+static const Reg32 LOOPCOUNT_REG = r12d;
+/// Current VS loop iteration number (we could probably use LOOPCOUNT_REG, but this quicker)
+static const Reg32 LOOPCOUNT = esi;
+/// Number to increment LOOPCOUNT_REG by on each loop iteration (Multiplied by 16)
+static const Reg32 LOOPINC = edi;
+/// Result of the previous CMP instruction for the X-component comparison
+static const Reg64 COND0 = r13;
+/// Result of the previous CMP instruction for the Y-component comparison
+static const Reg64 COND1 = r14;
+/// Pointer to the UnitState instance for the current VS unit
+static const Reg64 STATE = r15;
+/// SIMD scratch register
+static const Xmm SCRATCH = xmm0;
+/// Loaded with the first swizzled source register, otherwise can be used as a scratch register
+static const Xmm SRC1 = xmm1;
+/// Loaded with the second swizzled source register, otherwise can be used as a scratch register
+static const Xmm SRC2 = xmm2;
+/// Loaded with the third swizzled source register, otherwise can be used as a scratch register
+static const Xmm SRC3 = xmm3;
+/// Additional scratch register
+static const Xmm SCRATCH2 = xmm4;
+/// Constant vector of [1.0f, 1.0f, 1.0f, 1.0f], used to efficiently set a vector to one
+static const Xmm ONE = xmm14;
+/// Constant vector of [-0.f, -0.f, -0.f, -0.f], used to efficiently negate a vector with XOR
+static const Xmm NEGBIT = xmm15;
+
+// State registers that must not be modified by external functions calls
+// Scratch registers, e.g., SRC1 and SCRATCH, have to be saved on the side if needed
+static const BitSet32 persistent_regs = BuildRegSet({
+ // Pointers to register blocks
+ SETUP, STATE,
+ // Cached registers
+ ADDROFFS_REG_0, ADDROFFS_REG_1, LOOPCOUNT_REG, COND0, COND1,
+ // Constants
+ ONE, NEGBIT,
+ // Loop variables
+ LOOPCOUNT, LOOPINC,
+});
+
+/// Raw constant for the source register selector that indicates no swizzling is performed
+static const u8 NO_SRC_REG_SWIZZLE = 0x1b;
+/// Raw constant for the destination register enable mask that indicates all components are enabled
+static const u8 NO_DEST_REG_MASK = 0xf;
+
+static void LogCritical(const char* msg) {
+ LOG_CRITICAL(HW_GPU, "%s", msg);
+}
+
+void JitShader::Compile_Assert(bool condition, const char* msg) {
+ if (!condition) {
+ mov(ABI_PARAM1, reinterpret_cast<size_t>(msg));
+ CallFarFunction(*this, LogCritical);
+ }
+}
+
+/**
+ * Loads and swizzles a source register into the specified XMM register.
+ * @param instr VS instruction, used for determining how to load the source register
+ * @param src_num Number indicating which source register to load (1 = src1, 2 = src2, 3 = src3)
+ * @param src_reg SourceRegister object corresponding to the source register to load
+ * @param dest Destination XMM register to store the loaded, swizzled source register
+ */
+void JitShader::Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg,
+ Xmm dest) {
+ Reg64 src_ptr;
+ size_t src_offset;
+
+ if (src_reg.GetRegisterType() == RegisterType::FloatUniform) {
+ src_ptr = SETUP;
+ src_offset = ShaderSetup::GetFloatUniformOffset(src_reg.GetIndex());
+ } else {
+ src_ptr = STATE;
+ src_offset = UnitState::InputOffset(src_reg);
+ }
+
+ int src_offset_disp = (int)src_offset;
+ ASSERT_MSG(src_offset == src_offset_disp, "Source register offset too large for int type");
+
+ unsigned operand_desc_id;
+
+ const bool is_inverted =
+ (0 != (instr.opcode.Value().GetInfo().subtype & OpCode::Info::SrcInversed));
+
+ unsigned address_register_index;
+ unsigned offset_src;
+
+ if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD ||
+ instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) {
+ operand_desc_id = instr.mad.operand_desc_id;
+ offset_src = is_inverted ? 3 : 2;
+ address_register_index = instr.mad.address_register_index;
+ } else {
+ operand_desc_id = instr.common.operand_desc_id;
+ offset_src = is_inverted ? 2 : 1;
+ address_register_index = instr.common.address_register_index;
+ }
+
+ if (src_num == offset_src && address_register_index != 0) {
+ switch (address_register_index) {
+ case 1: // address offset 1
+ movaps(dest, xword[src_ptr + ADDROFFS_REG_0 + src_offset_disp]);
+ break;
+ case 2: // address offset 2
+ movaps(dest, xword[src_ptr + ADDROFFS_REG_1 + src_offset_disp]);
+ break;
+ case 3: // address offset 3
+ movaps(dest, xword[src_ptr + LOOPCOUNT_REG.cvt64() + src_offset_disp]);
+ break;
+ default:
+ UNREACHABLE();
+ break;
+ }
+ } else {
+ // Load the source
+ movaps(dest, xword[src_ptr + src_offset_disp]);
+ }
+
+ SwizzlePattern swiz = {(*swizzle_data)[operand_desc_id]};
+
+ // Generate instructions for source register swizzling as needed
+ u8 sel = swiz.GetRawSelector(src_num);
+ if (sel != NO_SRC_REG_SWIZZLE) {
+ // Selector component order needs to be reversed for the SHUFPS instruction
+ sel = ((sel & 0xc0) >> 6) | ((sel & 3) << 6) | ((sel & 0xc) << 2) | ((sel & 0x30) >> 2);
+
+ // Shuffle inputs for swizzle
+ shufps(dest, dest, sel);
+ }
+
+ // If the source register should be negated, flip the negative bit using XOR
+ const bool negate[] = {swiz.negate_src1, swiz.negate_src2, swiz.negate_src3};
+ if (negate[src_num - 1]) {
+ xorps(dest, NEGBIT);
+ }
+}
+
+void JitShader::Compile_DestEnable(Instruction instr, Xmm src) {
+ DestRegister dest;
+ unsigned operand_desc_id;
+ if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MAD ||
+ instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) {
+ operand_desc_id = instr.mad.operand_desc_id;
+ dest = instr.mad.dest.Value();
+ } else {
+ operand_desc_id = instr.common.operand_desc_id;
+ dest = instr.common.dest.Value();
+ }
+
+ SwizzlePattern swiz = {(*swizzle_data)[operand_desc_id]};
+
+ size_t dest_offset_disp = UnitState::OutputOffset(dest);
+
+ // If all components are enabled, write the result to the destination register
+ if (swiz.dest_mask == NO_DEST_REG_MASK) {
+ // Store dest back to memory
+ movaps(xword[STATE + dest_offset_disp], src);
+
+ } else {
+ // Not all components are enabled, so mask the result when storing to the destination
+ // register...
+ movaps(SCRATCH, xword[STATE + dest_offset_disp]);
+
+ if (Common::GetCPUCaps().sse4_1) {
+ u8 mask = ((swiz.dest_mask & 1) << 3) | ((swiz.dest_mask & 8) >> 3) |
+ ((swiz.dest_mask & 2) << 1) | ((swiz.dest_mask & 4) >> 1);
+ blendps(SCRATCH, src, mask);
+ } else {
+ movaps(SCRATCH2, src);
+ unpckhps(SCRATCH2, SCRATCH); // Unpack X/Y components of source and destination
+ unpcklps(SCRATCH, src); // Unpack Z/W components of source and destination
+
+ // Compute selector to selectively copy source components to destination for SHUFPS
+ // instruction
+ u8 sel = ((swiz.DestComponentEnabled(0) ? 1 : 0) << 0) |
+ ((swiz.DestComponentEnabled(1) ? 3 : 2) << 2) |
+ ((swiz.DestComponentEnabled(2) ? 0 : 1) << 4) |
+ ((swiz.DestComponentEnabled(3) ? 2 : 3) << 6);
+ shufps(SCRATCH, SCRATCH2, sel);
+ }
+
+ // Store dest back to memory
+ movaps(xword[STATE + dest_offset_disp], SCRATCH);
+ }
+}
+
+void JitShader::Compile_SanitizedMul(Xmm src1, Xmm src2, Xmm scratch) {
+ movaps(scratch, src1);
+ cmpordps(scratch, src2);
+
+ mulps(src1, src2);
+
+ movaps(src2, src1);
+ cmpunordps(src2, src2);
+
+ xorps(scratch, src2);
+ andps(src1, scratch);
+}
+
+void JitShader::Compile_EvaluateCondition(Instruction instr) {
+ // Note: NXOR is used below to check for equality
+ switch (instr.flow_control.op) {
+ case Instruction::FlowControlType::Or:
+ mov(eax, COND0);
+ mov(ebx, COND1);
+ xor(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor(ebx, (instr.flow_control.refy.Value() ^ 1));
+ or (eax, ebx);
+ break;
+
+ case Instruction::FlowControlType::And:
+ mov(eax, COND0);
+ mov(ebx, COND1);
+ xor(eax, (instr.flow_control.refx.Value() ^ 1));
+ xor(ebx, (instr.flow_control.refy.Value() ^ 1));
+ and(eax, ebx);
+ break;
+
+ case Instruction::FlowControlType::JustX:
+ mov(eax, COND0);
+ xor(eax, (instr.flow_control.refx.Value() ^ 1));
+ break;
+
+ case Instruction::FlowControlType::JustY:
+ mov(eax, COND1);
+ xor(eax, (instr.flow_control.refy.Value() ^ 1));
+ break;
+ }
+}
+
+void JitShader::Compile_UniformCondition(Instruction instr) {
+ size_t offset = ShaderSetup::GetBoolUniformOffset(instr.flow_control.bool_uniform_id);
+ cmp(byte[SETUP + offset], 0);
+}
+
+BitSet32 JitShader::PersistentCallerSavedRegs() {
+ return persistent_regs & ABI_ALL_CALLER_SAVED;
+}
+
+void JitShader::Compile_ADD(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+ addps(SRC1, SRC2);
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_DP3(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+
+ Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
+
+ movaps(SRC2, SRC1);
+ shufps(SRC2, SRC2, _MM_SHUFFLE(1, 1, 1, 1));
+
+ movaps(SRC3, SRC1);
+ shufps(SRC3, SRC3, _MM_SHUFFLE(2, 2, 2, 2));
+
+ shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0));
+ addps(SRC1, SRC2);
+ addps(SRC1, SRC3);
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_DP4(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+
+ Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
+
+ movaps(SRC2, SRC1);
+ shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY
+ addps(SRC1, SRC2);
+
+ movaps(SRC2, SRC1);
+ shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX
+ addps(SRC1, SRC2);
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_DPH(Instruction instr) {
+ if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::DPHI) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2);
+ } else {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+ }
+
+ if (Common::GetCPUCaps().sse4_1) {
+ // Set 4th component to 1.0
+ blendps(SRC1, ONE, 0b1000);
+ } else {
+ // Set 4th component to 1.0
+ movaps(SCRATCH, SRC1);
+ unpckhps(SCRATCH, ONE); // XYZW, 1111 -> Z1__
+ unpcklpd(SRC1, SCRATCH); // XYZW, Z1__ -> XYZ1
+ }
+
+ Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
+
+ movaps(SRC2, SRC1);
+ shufps(SRC1, SRC1, _MM_SHUFFLE(2, 3, 0, 1)); // XYZW -> ZWXY
+ addps(SRC1, SRC2);
+
+ movaps(SRC2, SRC1);
+ shufps(SRC1, SRC1, _MM_SHUFFLE(0, 1, 2, 3)); // XYZW -> WZYX
+ addps(SRC1, SRC2);
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_EX2(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ movss(xmm0, SRC1); // ABI_PARAM1
+
+ ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ CallFarFunction(*this, exp2f);
+ ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+
+ shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN
+ movaps(SRC1, xmm0);
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_LG2(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ movss(xmm0, SRC1); // ABI_PARAM1
+
+ ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ CallFarFunction(*this, log2f);
+ ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+
+ shufps(xmm0, xmm0, _MM_SHUFFLE(0, 0, 0, 0)); // ABI_RETURN
+ movaps(SRC1, xmm0);
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_MUL(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+ Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_SGE(Instruction instr) {
+ if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SGEI) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2);
+ } else {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+ }
+
+ cmpleps(SRC2, SRC1);
+ andps(SRC2, ONE);
+
+ Compile_DestEnable(instr, SRC2);
+}
+
+void JitShader::Compile_SLT(Instruction instr) {
+ if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::SLTI) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1i, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2i, SRC2);
+ } else {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+ }
+
+ cmpltps(SRC1, SRC2);
+ andps(SRC1, ONE);
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_FLR(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+
+ if (Common::GetCPUCaps().sse4_1) {
+ roundps(SRC1, SRC1, _MM_FROUND_FLOOR);
+ } else {
+ cvttps2dq(SRC1, SRC1);
+ cvtdq2ps(SRC1, SRC1);
+ }
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_MAX(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+ // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned.
+ maxps(SRC1, SRC2);
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_MIN(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+ // SSE semantics match PICA200 ones: In case of NaN, SRC2 is returned.
+ minps(SRC1, SRC2);
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_MOVA(Instruction instr) {
+ SwizzlePattern swiz = {(*swizzle_data)[instr.common.operand_desc_id]};
+
+ if (!swiz.DestComponentEnabled(0) && !swiz.DestComponentEnabled(1)) {
+ return; // NoOp
+ }
+
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+
+ // Convert floats to integers using truncation (only care about X and Y components)
+ cvttps2dq(SRC1, SRC1);
+
+ // Get result
+ movq(rax, SRC1);
+
+ // Handle destination enable
+ if (swiz.DestComponentEnabled(0) && swiz.DestComponentEnabled(1)) {
+ // Move and sign-extend low 32 bits
+ movsxd(ADDROFFS_REG_0, eax);
+
+ // Move and sign-extend high 32 bits
+ shr(rax, 32);
+ movsxd(ADDROFFS_REG_1, eax);
+
+ // Multiply by 16 to be used as an offset later
+ shl(ADDROFFS_REG_0, 4);
+ shl(ADDROFFS_REG_1, 4);
+ } else {
+ if (swiz.DestComponentEnabled(0)) {
+ // Move and sign-extend low 32 bits
+ movsxd(ADDROFFS_REG_0, eax);
+
+ // Multiply by 16 to be used as an offset later
+ shl(ADDROFFS_REG_0, 4);
+ } else if (swiz.DestComponentEnabled(1)) {
+ // Move and sign-extend high 32 bits
+ shr(rax, 32);
+ movsxd(ADDROFFS_REG_1, eax);
+
+ // Multiply by 16 to be used as an offset later
+ shl(ADDROFFS_REG_1, 4);
+ }
+ }
+}
+
+void JitShader::Compile_MOV(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_RCP(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+
+ // TODO(bunnei): RCPSS is a pretty rough approximation, this might cause problems if Pica
+ // performs this operation more accurately. This should be checked on hardware.
+ rcpss(SRC1, SRC1);
+ shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_RSQ(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+
+ // TODO(bunnei): RSQRTSS is a pretty rough approximation, this might cause problems if Pica
+ // performs this operation more accurately. This should be checked on hardware.
+ rsqrtss(SRC1, SRC1);
+ shufps(SRC1, SRC1, _MM_SHUFFLE(0, 0, 0, 0)); // XYWZ -> XXXX
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_NOP(Instruction instr) {}
+
+void JitShader::Compile_END(Instruction instr) {
+ ABI_PopRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16);
+ ret();
+}
+
+void JitShader::Compile_CALL(Instruction instr) {
+ // Push offset of the return
+ push(qword, (instr.flow_control.dest_offset + instr.flow_control.num_instructions));
+
+ // Call the subroutine
+ call(instruction_labels[instr.flow_control.dest_offset]);
+
+ // Skip over the return offset that's on the stack
+ add(rsp, 8);
+}
+
+void JitShader::Compile_CALLC(Instruction instr) {
+ Compile_EvaluateCondition(instr);
+ Label b;
+ jz(b);
+ Compile_CALL(instr);
+ L(b);
+}
+
+void JitShader::Compile_CALLU(Instruction instr) {
+ Compile_UniformCondition(instr);
+ Label b;
+ jz(b);
+ Compile_CALL(instr);
+ L(b);
+}
+
+void JitShader::Compile_CMP(Instruction instr) {
+ using Op = Instruction::Common::CompareOpType::Op;
+ Op op_x = instr.common.compare_op.x;
+ Op op_y = instr.common.compare_op.y;
+
+ Compile_SwizzleSrc(instr, 1, instr.common.src1, SRC1);
+ Compile_SwizzleSrc(instr, 2, instr.common.src2, SRC2);
+
+ // SSE doesn't have greater-than (GT) or greater-equal (GE) comparison operators. You need to
+ // emulate them by swapping the lhs and rhs and using LT and LE. NLT and NLE can't be used here
+ // because they don't match when used with NaNs.
+ static const u8 cmp[] = {CMP_EQ, CMP_NEQ, CMP_LT, CMP_LE, CMP_LT, CMP_LE};
+
+ bool invert_op_x = (op_x == Op::GreaterThan || op_x == Op::GreaterEqual);
+ Xmm lhs_x = invert_op_x ? SRC2 : SRC1;
+ Xmm rhs_x = invert_op_x ? SRC1 : SRC2;
+
+ if (op_x == op_y) {
+ // Compare X-component and Y-component together
+ cmpps(lhs_x, rhs_x, cmp[op_x]);
+ movq(COND0, lhs_x);
+
+ mov(COND1, COND0);
+ } else {
+ bool invert_op_y = (op_y == Op::GreaterThan || op_y == Op::GreaterEqual);
+ Xmm lhs_y = invert_op_y ? SRC2 : SRC1;
+ Xmm rhs_y = invert_op_y ? SRC1 : SRC2;
+
+ // Compare X-component
+ movaps(SCRATCH, lhs_x);
+ cmpss(SCRATCH, rhs_x, cmp[op_x]);
+
+ // Compare Y-component
+ cmpps(lhs_y, rhs_y, cmp[op_y]);
+
+ movq(COND0, SCRATCH);
+ movq(COND1, lhs_y);
+ }
+
+ shr(COND0.cvt32(), 31); // ignores upper 32 bits in source
+ shr(COND1, 63);
+}
+
+void JitShader::Compile_MAD(Instruction instr) {
+ Compile_SwizzleSrc(instr, 1, instr.mad.src1, SRC1);
+
+ if (instr.opcode.Value().EffectiveOpCode() == OpCode::Id::MADI) {
+ Compile_SwizzleSrc(instr, 2, instr.mad.src2i, SRC2);
+ Compile_SwizzleSrc(instr, 3, instr.mad.src3i, SRC3);
+ } else {
+ Compile_SwizzleSrc(instr, 2, instr.mad.src2, SRC2);
+ Compile_SwizzleSrc(instr, 3, instr.mad.src3, SRC3);
+ }
+
+ Compile_SanitizedMul(SRC1, SRC2, SCRATCH);
+ addps(SRC1, SRC3);
+
+ Compile_DestEnable(instr, SRC1);
+}
+
+void JitShader::Compile_IF(Instruction instr) {
+ Compile_Assert(instr.flow_control.dest_offset >= program_counter,
+ "Backwards if-statements not supported");
+ Label l_else, l_endif;
+
+ // Evaluate the "IF" condition
+ if (instr.opcode.Value() == OpCode::Id::IFU) {
+ Compile_UniformCondition(instr);
+ } else if (instr.opcode.Value() == OpCode::Id::IFC) {
+ Compile_EvaluateCondition(instr);
+ }
+ jz(l_else, T_NEAR);
+
+ // Compile the code that corresponds to the condition evaluating as true
+ Compile_Block(instr.flow_control.dest_offset);
+
+ // If there isn't an "ELSE" condition, we are done here
+ if (instr.flow_control.num_instructions == 0) {
+ L(l_else);
+ return;
+ }
+
+ jmp(l_endif, T_NEAR);
+
+ L(l_else);
+ // This code corresponds to the "ELSE" condition
+ // Comple the code that corresponds to the condition evaluating as false
+ Compile_Block(instr.flow_control.dest_offset + instr.flow_control.num_instructions);
+
+ L(l_endif);
+}
+
+void JitShader::Compile_LOOP(Instruction instr) {
+ Compile_Assert(instr.flow_control.dest_offset >= program_counter,
+ "Backwards loops not supported");
+ Compile_Assert(!looping, "Nested loops not supported");
+
+ looping = true;
+
+ // This decodes the fields from the integer uniform at index instr.flow_control.int_uniform_id.
+ // The Y (LOOPCOUNT_REG) and Z (LOOPINC) component are kept multiplied by 16 (Left shifted by
+ // 4 bits) to be used as an offset into the 16-byte vector registers later
+ size_t offset = ShaderSetup::GetIntUniformOffset(instr.flow_control.int_uniform_id);
+ mov(LOOPCOUNT, dword[SETUP + offset]);
+ mov(LOOPCOUNT_REG, LOOPCOUNT);
+ shr(LOOPCOUNT_REG, 4);
+ and(LOOPCOUNT_REG, 0xFF0); // Y-component is the start
+ mov(LOOPINC, LOOPCOUNT);
+ shr(LOOPINC, 12);
+ and(LOOPINC, 0xFF0); // Z-component is the incrementer
+ movzx(LOOPCOUNT, LOOPCOUNT.cvt8()); // X-component is iteration count
+ add(LOOPCOUNT, 1); // Iteration count is X-component + 1
+
+ Label l_loop_start;
+ L(l_loop_start);
+
+ Compile_Block(instr.flow_control.dest_offset + 1);
+
+ add(LOOPCOUNT_REG, LOOPINC); // Increment LOOPCOUNT_REG by Z-component
+ sub(LOOPCOUNT, 1); // Increment loop count by 1
+ jnz(l_loop_start); // Loop if not equal
+
+ looping = false;
+}
+
+void JitShader::Compile_JMP(Instruction instr) {
+ if (instr.opcode.Value() == OpCode::Id::JMPC)
+ Compile_EvaluateCondition(instr);
+ else if (instr.opcode.Value() == OpCode::Id::JMPU)
+ Compile_UniformCondition(instr);
+ else
+ UNREACHABLE();
+
+ bool inverted_condition =
+ (instr.opcode.Value() == OpCode::Id::JMPU) && (instr.flow_control.num_instructions & 1);
+
+ Label& b = instruction_labels[instr.flow_control.dest_offset];
+ if (inverted_condition) {
+ jz(b, T_NEAR);
+ } else {
+ jnz(b, T_NEAR);
+ }
+}
+
+void JitShader::Compile_Block(unsigned end) {
+ while (program_counter < end) {
+ Compile_NextInstr();
+ }
+}
+
+void JitShader::Compile_Return() {
+ // Peek return offset on the stack and check if we're at that offset
+ mov(rax, qword[rsp + 8]);
+ cmp(eax, (program_counter));
+
+ // If so, jump back to before CALL
+ Label b;
+ jnz(b);
+ ret();
+ L(b);
+}
+
+void JitShader::Compile_NextInstr() {
+ if (std::binary_search(return_offsets.begin(), return_offsets.end(), program_counter)) {
+ Compile_Return();
+ }
+
+ L(instruction_labels[program_counter]);
+
+ Instruction instr = {(*program_code)[program_counter++]};
+
+ OpCode::Id opcode = instr.opcode.Value();
+ auto instr_func = instr_table[static_cast<unsigned>(opcode)];
+
+ if (instr_func) {
+ // JIT the instruction!
+ ((*this).*instr_func)(instr);
+ } else {
+ // Unhandled instruction
+ LOG_CRITICAL(HW_GPU, "Unhandled instruction: 0x%02x (0x%08x)",
+ instr.opcode.Value().EffectiveOpCode(), instr.hex);
+ }
+}
+
+void JitShader::FindReturnOffsets() {
+ return_offsets.clear();
+
+ for (size_t offset = 0; offset < program_code->size(); ++offset) {
+ Instruction instr = {(*program_code)[offset]};
+
+ switch (instr.opcode.Value()) {
+ case OpCode::Id::CALL:
+ case OpCode::Id::CALLC:
+ case OpCode::Id::CALLU:
+ return_offsets.push_back(instr.flow_control.dest_offset +
+ instr.flow_control.num_instructions);
+ break;
+ default:
+ break;
+ }
+ }
+
+ // Sort for efficient binary search later
+ std::sort(return_offsets.begin(), return_offsets.end());
+}
+
+void JitShader::Compile(const std::array<u32, 1024>* program_code_,
+ const std::array<u32, 1024>* swizzle_data_) {
+ program_code = program_code_;
+ swizzle_data = swizzle_data_;
+
+ // Reset flow control state
+ program = (CompiledShader*)getCurr();
+ program_counter = 0;
+ looping = false;
+ instruction_labels.fill(Xbyak::Label());
+
+ // Find all `CALL` instructions and identify return locations
+ FindReturnOffsets();
+
+ // The stack pointer is 8 modulo 16 at the entry of a procedure
+ // We reserve 16 bytes and assign a dummy value to the first 8 bytes, to catch any potential
+ // return checks (see Compile_Return) that happen in shader main routine.
+ ABI_PushRegistersAndAdjustStack(*this, ABI_ALL_CALLEE_SAVED, 8, 16);
+ mov(qword[rsp + 8], 0xFFFFFFFFFFFFFFFFULL);
+
+ mov(SETUP, ABI_PARAM1);
+ mov(STATE, ABI_PARAM2);
+
+ // Zero address/loop registers
+ xor(ADDROFFS_REG_0.cvt32(), ADDROFFS_REG_0.cvt32());
+ xor(ADDROFFS_REG_1.cvt32(), ADDROFFS_REG_1.cvt32());
+ xor(LOOPCOUNT_REG, LOOPCOUNT_REG);
+
+ // Used to set a register to one
+ static const __m128 one = {1.f, 1.f, 1.f, 1.f};
+ mov(rax, reinterpret_cast<size_t>(&one));
+ movaps(ONE, xword[rax]);
+
+ // Used to negate registers
+ static const __m128 neg = {-0.f, -0.f, -0.f, -0.f};
+ mov(rax, reinterpret_cast<size_t>(&neg));
+ movaps(NEGBIT, xword[rax]);
+
+ // Jump to start of the shader program
+ jmp(ABI_PARAM3);
+
+ // Compile entire program
+ Compile_Block(static_cast<unsigned>(program_code->size()));
+
+ // Free memory that's no longer needed
+ program_code = nullptr;
+ swizzle_data = nullptr;
+ return_offsets.clear();
+ return_offsets.shrink_to_fit();
+
+ ready();
+
+ ASSERT_MSG(getSize() <= MAX_SHADER_SIZE, "Compiled a shader that exceeds the allocated size!");
+ LOG_DEBUG(HW_GPU, "Compiled shader size=%lu", getSize());
+}
+
+JitShader::JitShader() : Xbyak::CodeGenerator(MAX_SHADER_SIZE) {}
+
+} // namespace Shader
+
+} // namespace Pica
diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h
new file mode 100644
index 000000000..599e43ffd
--- /dev/null
+++ b/src/video_core/shader/shader_jit_x64_compiler.h
@@ -0,0 +1,124 @@
+// Copyright 2015 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include <cstddef>
+#include <utility>
+#include <vector>
+#include <nihstro/shader_bytecode.h>
+#include <xbyak.h>
+#include "common/bit_set.h"
+#include "common/common_types.h"
+#include "video_core/shader/shader.h"
+
+using nihstro::Instruction;
+using nihstro::OpCode;
+using nihstro::SwizzlePattern;
+
+namespace Pica {
+
+namespace Shader {
+
+/// Memory allocated for each compiled shader (64Kb)
+constexpr size_t MAX_SHADER_SIZE = 1024 * 64;
+
+/**
+ * This class implements the shader JIT compiler. It recompiles a Pica shader program into x86_64
+ * code that can be executed on the host machine directly.
+ */
+class JitShader : public Xbyak::CodeGenerator {
+public:
+ JitShader();
+
+ void Run(const ShaderSetup& setup, UnitState& state, unsigned offset) const {
+ program(&setup, &state, instruction_labels[offset].getAddress());
+ }
+
+ void Compile(const std::array<u32, 1024>* program_code,
+ const std::array<u32, 1024>* swizzle_data);
+
+ void Compile_ADD(Instruction instr);
+ void Compile_DP3(Instruction instr);
+ void Compile_DP4(Instruction instr);
+ void Compile_DPH(Instruction instr);
+ void Compile_EX2(Instruction instr);
+ void Compile_LG2(Instruction instr);
+ void Compile_MUL(Instruction instr);
+ void Compile_SGE(Instruction instr);
+ void Compile_SLT(Instruction instr);
+ void Compile_FLR(Instruction instr);
+ void Compile_MAX(Instruction instr);
+ void Compile_MIN(Instruction instr);
+ void Compile_RCP(Instruction instr);
+ void Compile_RSQ(Instruction instr);
+ void Compile_MOVA(Instruction instr);
+ void Compile_MOV(Instruction instr);
+ void Compile_NOP(Instruction instr);
+ void Compile_END(Instruction instr);
+ void Compile_CALL(Instruction instr);
+ void Compile_CALLC(Instruction instr);
+ void Compile_CALLU(Instruction instr);
+ void Compile_IF(Instruction instr);
+ void Compile_LOOP(Instruction instr);
+ void Compile_JMP(Instruction instr);
+ void Compile_CMP(Instruction instr);
+ void Compile_MAD(Instruction instr);
+
+private:
+ void Compile_Block(unsigned end);
+ void Compile_NextInstr();
+
+ void Compile_SwizzleSrc(Instruction instr, unsigned src_num, SourceRegister src_reg,
+ Xbyak::Xmm dest);
+ void Compile_DestEnable(Instruction instr, Xbyak::Xmm dest);
+
+ /**
+ * Compiles a `MUL src1, src2` operation, properly handling the PICA semantics when multiplying
+ * zero by inf. Clobbers `src2` and `scratch`.
+ */
+ void Compile_SanitizedMul(Xbyak::Xmm src1, Xbyak::Xmm src2, Xbyak::Xmm scratch);
+
+ void Compile_EvaluateCondition(Instruction instr);
+ void Compile_UniformCondition(Instruction instr);
+
+ /**
+ * Emits the code to conditionally return from a subroutine envoked by the `CALL` instruction.
+ */
+ void Compile_Return();
+
+ BitSet32 PersistentCallerSavedRegs();
+
+ /**
+ * Assertion evaluated at compile-time, but only triggered if executed at runtime.
+ * @param msg Message to be logged if the assertion fails.
+ */
+ void Compile_Assert(bool condition, const char* msg);
+
+ /**
+ * Analyzes the entire shader program for `CALL` instructions before emitting any code,
+ * identifying the locations where a return needs to be inserted.
+ */
+ void FindReturnOffsets();
+
+ const std::array<u32, 1024>* program_code = nullptr;
+ const std::array<u32, 1024>* swizzle_data = nullptr;
+
+ /// Mapping of Pica VS instructions to pointers in the emitted code
+ std::array<Xbyak::Label, 1024> instruction_labels;
+
+ /// Offsets in code where a return needs to be inserted
+ std::vector<unsigned> return_offsets;
+
+ unsigned program_counter = 0; ///< Offset of the next instruction to decode
+ bool looping = false; ///< True if compiling a loop, used to check for nested loops
+
+ using CompiledShader = void(const void* setup, void* state, const u8* start_addr);
+ CompiledShader* program = nullptr;
+};
+
+} // Shader
+
+} // Pica
diff --git a/src/video_core/texture/etc1.cpp b/src/video_core/texture/etc1.cpp
new file mode 100644
index 000000000..af60cde1e
--- /dev/null
+++ b/src/video_core/texture/etc1.cpp
@@ -0,0 +1,124 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <array>
+#include "common/bit_field.h"
+#include "common/color.h"
+#include "common/common_types.h"
+#include "common/math_util.h"
+#include "common/vector_math.h"
+#include "video_core/texture/etc1.h"
+
+namespace Pica {
+namespace Texture {
+
+namespace {
+
+constexpr std::array<u8[2], 8> etc1_modifier_table = {{
+ {2, 8}, {5, 17}, {9, 29}, {13, 42}, {18, 60}, {24, 80}, {33, 106}, {47, 183},
+}};
+
+union ETC1Tile {
+ u64 raw;
+
+ // Each of these two is a collection of 16 bits (one per lookup value)
+ BitField<0, 16, u64> table_subindexes;
+ BitField<16, 16, u64> negation_flags;
+
+ unsigned GetTableSubIndex(unsigned index) const {
+ return (table_subindexes >> index) & 1;
+ }
+
+ bool GetNegationFlag(unsigned index) const {
+ return ((negation_flags >> index) & 1) == 1;
+ }
+
+ BitField<32, 1, u64> flip;
+ BitField<33, 1, u64> differential_mode;
+
+ BitField<34, 3, u64> table_index_2;
+ BitField<37, 3, u64> table_index_1;
+
+ union {
+ // delta value + base value
+ BitField<40, 3, s64> db;
+ BitField<43, 5, u64> b;
+
+ BitField<48, 3, s64> dg;
+ BitField<51, 5, u64> g;
+
+ BitField<56, 3, s64> dr;
+ BitField<59, 5, u64> r;
+ } differential;
+
+ union {
+ BitField<40, 4, u64> b2;
+ BitField<44, 4, u64> b1;
+
+ BitField<48, 4, u64> g2;
+ BitField<52, 4, u64> g1;
+
+ BitField<56, 4, u64> r2;
+ BitField<60, 4, u64> r1;
+ } separate;
+
+ const Math::Vec3<u8> GetRGB(unsigned int x, unsigned int y) const {
+ int texel = 4 * x + y;
+
+ if (flip)
+ std::swap(x, y);
+
+ // Lookup base value
+ Math::Vec3<int> ret;
+ if (differential_mode) {
+ ret.r() = static_cast<int>(differential.r);
+ ret.g() = static_cast<int>(differential.g);
+ ret.b() = static_cast<int>(differential.b);
+ if (x >= 2) {
+ ret.r() += static_cast<int>(differential.dr);
+ ret.g() += static_cast<int>(differential.dg);
+ ret.b() += static_cast<int>(differential.db);
+ }
+ ret.r() = Color::Convert5To8(ret.r());
+ ret.g() = Color::Convert5To8(ret.g());
+ ret.b() = Color::Convert5To8(ret.b());
+ } else {
+ if (x < 2) {
+ ret.r() = Color::Convert4To8(static_cast<u8>(separate.r1));
+ ret.g() = Color::Convert4To8(static_cast<u8>(separate.g1));
+ ret.b() = Color::Convert4To8(static_cast<u8>(separate.b1));
+ } else {
+ ret.r() = Color::Convert4To8(static_cast<u8>(separate.r2));
+ ret.g() = Color::Convert4To8(static_cast<u8>(separate.g2));
+ ret.b() = Color::Convert4To8(static_cast<u8>(separate.b2));
+ }
+ }
+
+ // Add modifier
+ unsigned table_index =
+ static_cast<int>((x < 2) ? table_index_1.Value() : table_index_2.Value());
+
+ int modifier = etc1_modifier_table[table_index][GetTableSubIndex(texel)];
+ if (GetNegationFlag(texel))
+ modifier *= -1;
+
+ ret.r() = MathUtil::Clamp(ret.r() + modifier, 0, 255);
+ ret.g() = MathUtil::Clamp(ret.g() + modifier, 0, 255);
+ ret.b() = MathUtil::Clamp(ret.b() + modifier, 0, 255);
+
+ return ret.Cast<u8>();
+ }
+};
+
+} // anonymous namespace
+
+Math::Vec3<u8> SampleETC1Subtile(u64 value, unsigned int x, unsigned int y) {
+ ETC1Tile tile{value};
+ return tile.GetRGB(x, y);
+}
+
+} // namespace Texture
+} // namespace Pica
diff --git a/src/video_core/texture/etc1.h b/src/video_core/texture/etc1.h
new file mode 100644
index 000000000..e188b19df
--- /dev/null
+++ b/src/video_core/texture/etc1.h
@@ -0,0 +1,16 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/vector_math.h"
+
+namespace Pica {
+namespace Texture {
+
+Math::Vec3<u8> SampleETC1Subtile(u64 value, unsigned int x, unsigned int y);
+
+} // namespace Texture
+} // namespace Pica
diff --git a/src/video_core/texture/texture_decode.cpp b/src/video_core/texture/texture_decode.cpp
new file mode 100644
index 000000000..f611a1aa9
--- /dev/null
+++ b/src/video_core/texture/texture_decode.cpp
@@ -0,0 +1,229 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/assert.h"
+#include "common/color.h"
+#include "common/logging/log.h"
+#include "common/math_util.h"
+#include "common/swap.h"
+#include "common/vector_math.h"
+#include "video_core/pica.h"
+#include "video_core/texture/etc1.h"
+#include "video_core/texture/texture_decode.h"
+#include "video_core/utils.h"
+
+using TextureFormat = Pica::Regs::TextureFormat;
+
+namespace Pica {
+namespace Texture {
+
+constexpr size_t TILE_SIZE = 8 * 8;
+constexpr size_t ETC1_SUBTILES = 2 * 2;
+
+size_t CalculateTileSize(TextureFormat format) {
+ switch (format) {
+ case TextureFormat::RGBA8:
+ return 4 * TILE_SIZE;
+
+ case TextureFormat::RGB8:
+ return 3 * TILE_SIZE;
+
+ case TextureFormat::RGB5A1:
+ case TextureFormat::RGB565:
+ case TextureFormat::RGBA4:
+ case TextureFormat::IA8:
+ case TextureFormat::RG8:
+ return 2 * TILE_SIZE;
+
+ case TextureFormat::I8:
+ case TextureFormat::A8:
+ case TextureFormat::IA4:
+ return 1 * TILE_SIZE;
+
+ case TextureFormat::I4:
+ case TextureFormat::A4:
+ return TILE_SIZE / 2;
+
+ case TextureFormat::ETC1:
+ return ETC1_SUBTILES * 8;
+
+ case TextureFormat::ETC1A4:
+ return ETC1_SUBTILES * 16;
+
+ default: // placeholder for yet unknown formats
+ UNIMPLEMENTED();
+ return 0;
+ }
+}
+
+Math::Vec4<u8> LookupTexture(const u8* source, unsigned int x, unsigned int y,
+ const TextureInfo& info, bool disable_alpha) {
+ // Coordinate in tiles
+ const unsigned int coarse_x = x / 8;
+ const unsigned int coarse_y = y / 8;
+
+ // Coordinate inside the tile
+ const unsigned int fine_x = x % 8;
+ const unsigned int fine_y = y % 8;
+
+ const u8* line = source + coarse_y * info.stride;
+ const u8* tile = line + coarse_x * CalculateTileSize(info.format);
+ return LookupTexelInTile(tile, fine_x, fine_y, info, disable_alpha);
+}
+
+Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int y,
+ const TextureInfo& info, bool disable_alpha) {
+ DEBUG_ASSERT(x < 8);
+ DEBUG_ASSERT(y < 8);
+
+ using VideoCore::MortonInterleave;
+
+ switch (info.format) {
+ case Regs::TextureFormat::RGBA8: {
+ auto res = Color::DecodeRGBA8(source + MortonInterleave(x, y) * 4);
+ return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
+ }
+
+ case Regs::TextureFormat::RGB8: {
+ auto res = Color::DecodeRGB8(source + MortonInterleave(x, y) * 3);
+ return {res.r(), res.g(), res.b(), 255};
+ }
+
+ case Regs::TextureFormat::RGB5A1: {
+ auto res = Color::DecodeRGB5A1(source + MortonInterleave(x, y) * 2);
+ return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
+ }
+
+ case Regs::TextureFormat::RGB565: {
+ auto res = Color::DecodeRGB565(source + MortonInterleave(x, y) * 2);
+ return {res.r(), res.g(), res.b(), 255};
+ }
+
+ case Regs::TextureFormat::RGBA4: {
+ auto res = Color::DecodeRGBA4(source + MortonInterleave(x, y) * 2);
+ return {res.r(), res.g(), res.b(), static_cast<u8>(disable_alpha ? 255 : res.a())};
+ }
+
+ case Regs::TextureFormat::IA8: {
+ const u8* source_ptr = source + MortonInterleave(x, y) * 2;
+
+ if (disable_alpha) {
+ // Show intensity as red, alpha as green
+ return {source_ptr[1], source_ptr[0], 0, 255};
+ } else {
+ return {source_ptr[1], source_ptr[1], source_ptr[1], source_ptr[0]};
+ }
+ }
+
+ case Regs::TextureFormat::RG8: {
+ auto res = Color::DecodeRG8(source + MortonInterleave(x, y) * 2);
+ return {res.r(), res.g(), 0, 255};
+ }
+
+ case Regs::TextureFormat::I8: {
+ const u8* source_ptr = source + MortonInterleave(x, y);
+ return {*source_ptr, *source_ptr, *source_ptr, 255};
+ }
+
+ case Regs::TextureFormat::A8: {
+ const u8* source_ptr = source + MortonInterleave(x, y);
+
+ if (disable_alpha) {
+ return {*source_ptr, *source_ptr, *source_ptr, 255};
+ } else {
+ return {0, 0, 0, *source_ptr};
+ }
+ }
+
+ case Regs::TextureFormat::IA4: {
+ const u8* source_ptr = source + MortonInterleave(x, y);
+
+ u8 i = Color::Convert4To8(((*source_ptr) & 0xF0) >> 4);
+ u8 a = Color::Convert4To8((*source_ptr) & 0xF);
+
+ if (disable_alpha) {
+ // Show intensity as red, alpha as green
+ return {i, a, 0, 255};
+ } else {
+ return {i, i, i, a};
+ }
+ }
+
+ case Regs::TextureFormat::I4: {
+ u32 morton_offset = MortonInterleave(x, y);
+ const u8* source_ptr = source + morton_offset / 2;
+
+ u8 i = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF);
+ i = Color::Convert4To8(i);
+
+ return {i, i, i, 255};
+ }
+
+ case Regs::TextureFormat::A4: {
+ u32 morton_offset = MortonInterleave(x, y);
+ const u8* source_ptr = source + morton_offset / 2;
+
+ u8 a = (morton_offset % 2) ? ((*source_ptr & 0xF0) >> 4) : (*source_ptr & 0xF);
+ a = Color::Convert4To8(a);
+
+ if (disable_alpha) {
+ return {a, a, a, 255};
+ } else {
+ return {0, 0, 0, a};
+ }
+ }
+
+ case Regs::TextureFormat::ETC1:
+ case Regs::TextureFormat::ETC1A4: {
+ bool has_alpha = (info.format == Regs::TextureFormat::ETC1A4);
+ size_t subtile_size = has_alpha ? 16 : 8;
+
+ // ETC1 further subdivides each 8x8 tile into four 4x4 subtiles
+ constexpr unsigned int subtile_width = 4;
+ constexpr unsigned int subtile_height = 4;
+
+ unsigned int subtile_index = (x / subtile_width) + 2 * (y / subtile_height);
+ x %= subtile_width;
+ y %= subtile_height;
+
+ const u8* subtile_ptr = source + subtile_index * subtile_size;
+
+ u8 alpha = 255;
+ if (has_alpha) {
+ u64_le packed_alpha;
+ memcpy(&packed_alpha, subtile_ptr, sizeof(u64));
+ subtile_ptr += sizeof(u64);
+
+ alpha = Color::Convert4To8((packed_alpha >> (4 * (x * subtile_width + y))) & 0xF);
+ }
+
+ u64_le subtile_data;
+ memcpy(&subtile_data, subtile_ptr, sizeof(u64));
+
+ return Math::MakeVec(SampleETC1Subtile(subtile_data, x, y),
+ disable_alpha ? (u8)255 : alpha);
+ }
+
+ default:
+ LOG_ERROR(HW_GPU, "Unknown texture format: %x", (u32)info.format);
+ DEBUG_ASSERT(false);
+ return {};
+ }
+}
+
+TextureInfo TextureInfo::FromPicaRegister(const Regs::TextureConfig& config,
+ const Regs::TextureFormat& format) {
+ TextureInfo info;
+ info.physical_address = config.GetPhysicalAddress();
+ info.width = config.width;
+ info.height = config.height;
+ info.format = format;
+ info.SetDefaultStride();
+ return info;
+}
+
+} // namespace Texture
+} // namespace Pica
diff --git a/src/video_core/texture/texture_decode.h b/src/video_core/texture/texture_decode.h
new file mode 100644
index 000000000..5c636939a
--- /dev/null
+++ b/src/video_core/texture/texture_decode.h
@@ -0,0 +1,60 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include "common/common_types.h"
+#include "common/vector_math.h"
+#include "video_core/pica.h"
+
+namespace Pica {
+namespace Texture {
+
+/// Returns the byte size of a 8*8 tile of the specified texture format.
+size_t CalculateTileSize(Pica::Regs::TextureFormat format);
+
+struct TextureInfo {
+ PAddr physical_address;
+ unsigned int width;
+ unsigned int height;
+ ptrdiff_t stride;
+ Pica::Regs::TextureFormat format;
+
+ static TextureInfo FromPicaRegister(const Pica::Regs::TextureConfig& config,
+ const Pica::Regs::TextureFormat& format);
+
+ /// Calculates stride from format and width, assuming that the entire texture is contiguous.
+ void SetDefaultStride() {
+ stride = Pica::Texture::CalculateTileSize(format) * (width / 8);
+ }
+};
+
+/**
+ * Lookup texel located at the given coordinates and return an RGBA vector of its color.
+ * @param source Source pointer to read data from
+ * @param x,y Texture coordinates to read from
+ * @param info TextureInfo object describing the texture setup
+ * @param disable_alpha This is used for debug widgets which use this method to display textures
+ * without providing a good way to visualize alpha by themselves. If true, this will return 255 for
+ * the alpha component, and either drop the information entirely or store it in an "unused" color
+ * channel.
+ * @todo Eventually we should get rid of the disable_alpha parameter.
+ */
+Math::Vec4<u8> LookupTexture(const u8* source, unsigned int x, unsigned int y,
+ const TextureInfo& info, bool disable_alpha = false);
+
+/**
+ * Looks up a texel from a single 8x8 texture tile.
+ *
+ * @param source Pointer to the beginning of the tile.
+ * @param x, y In-tile coordinates to read from. Must be < 8.
+ * @param info TextureInfo describing the texture format.
+ * @param disable_alpha Used for debugging. Sets the result alpha to 255 and either discards the
+ * real alpha or inserts it in an otherwise unused channel.
+ */
+Math::Vec4<u8> LookupTexelInTile(const u8* source, unsigned int x, unsigned int y,
+ const TextureInfo& info, bool disable_alpha);
+
+} // namespace Texture
+} // namespace Pica
diff --git a/src/video_core/vertex_loader.cpp b/src/video_core/vertex_loader.cpp
index 2b8ef7018..bf83b61ca 100644
--- a/src/video_core/vertex_loader.cpp
+++ b/src/video_core/vertex_loader.cpp
@@ -70,7 +70,8 @@ void VertexLoader::Setup(const Pica::Regs& regs) {
is_setup = true;
}
-void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input,
+void VertexLoader::LoadVertex(u32 base_address, int index, int vertex,
+ Shader::AttributeBuffer& input,
DebugUtils::MemoryAccessTracker& memory_accesses) {
ASSERT_MSG(is_setup, "A VertexLoader needs to be setup before loading vertices.");
@@ -142,7 +143,7 @@ void VertexLoader::LoadVertex(u32 base_address, int index, int vertex, Shader::I
input.attr[i][2].ToFloat32(), input.attr[i][3].ToFloat32());
} else if (vertex_attribute_is_default[i]) {
// Load the default attribute if we're configured to do so
- input.attr[i] = g_state.vs_default_attributes[i];
+ input.attr[i] = g_state.input_default_attributes.attr[i];
LOG_TRACE(HW_GPU,
"Loaded default attribute %x for vertex %x (index %x): (%f, %f, %f, %f)", i,
vertex, index, input.attr[i][0].ToFloat32(), input.attr[i][1].ToFloat32(),
diff --git a/src/video_core/vertex_loader.h b/src/video_core/vertex_loader.h
index 9f2098bb2..51f3d45b4 100644
--- a/src/video_core/vertex_loader.h
+++ b/src/video_core/vertex_loader.h
@@ -11,7 +11,7 @@ class MemoryAccessTracker;
}
namespace Shader {
-struct InputVertex;
+struct AttributeBuffer;
}
class VertexLoader {
@@ -22,7 +22,7 @@ public:
}
void Setup(const Pica::Regs& regs);
- void LoadVertex(u32 base_address, int index, int vertex, Shader::InputVertex& input,
+ void LoadVertex(u32 base_address, int index, int vertex, Shader::AttributeBuffer& input,
DebugUtils::MemoryAccessTracker& memory_accesses);
int GetNumTotalAttributes() const {
diff --git a/src/video_core/video_core.cpp b/src/video_core/video_core.cpp
index 8db882f59..7186a7652 100644
--- a/src/video_core/video_core.cpp
+++ b/src/video_core/video_core.cpp
@@ -19,7 +19,6 @@ std::unique_ptr<RendererBase> g_renderer; ///< Renderer plugin
std::atomic<bool> g_hw_renderer_enabled;
std::atomic<bool> g_shader_jit_enabled;
-std::atomic<bool> g_scaled_resolution_enabled;
std::atomic<bool> g_vsync_enabled;
std::atomic<bool> g_toggle_framelimit_enabled;
diff --git a/src/video_core/video_core.h b/src/video_core/video_core.h
index c397c1974..4aba19ca0 100644
--- a/src/video_core/video_core.h
+++ b/src/video_core/video_core.h
@@ -37,7 +37,6 @@ extern EmuWindow* g_emu_window; ///< Emu window
// qt ui)
extern std::atomic<bool> g_hw_renderer_enabled;
extern std::atomic<bool> g_shader_jit_enabled;
-extern std::atomic<bool> g_scaled_resolution_enabled;
extern std::atomic<bool> g_toggle_framelimit_enabled;
/// Start the video core