diff options
71 files changed, 1812 insertions, 645 deletions
diff --git a/.travis-build.sh b/.travis-build.sh index 64f5aed94..bb4e6fc47 100755 --- a/.travis-build.sh +++ b/.travis-build.sh | |||
| @@ -52,7 +52,7 @@ elif [ "$TRAVIS_OS_NAME" = "osx" ]; then | |||
| 52 | export Qt5_DIR=$(brew --prefix)/opt/qt5 | 52 | export Qt5_DIR=$(brew --prefix)/opt/qt5 |
| 53 | 53 | ||
| 54 | mkdir build && cd build | 54 | mkdir build && cd build |
| 55 | cmake .. -GXcode | 55 | cmake .. -DUSE_SYSTEM_CURL=ON -GXcode |
| 56 | xcodebuild -configuration Release | 56 | xcodebuild -configuration Release |
| 57 | 57 | ||
| 58 | ctest -VV -C Release | 58 | ctest -VV -C Release |
diff --git a/CMakeLists.txt b/CMakeLists.txt index ddba04ef9..d9c2f78a2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | cmake_minimum_required(VERSION 3.6) | 2 | cmake_minimum_required(VERSION 3.6) |
| 3 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") | 3 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") |
| 4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") | 4 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") |
| 5 | include(DownloadExternals) | ||
| 5 | 6 | ||
| 6 | project(citra) | 7 | project(citra) |
| 7 | 8 | ||
| @@ -12,6 +13,15 @@ option(ENABLE_QT "Enable the Qt frontend" ON) | |||
| 12 | option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) | 13 | option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) |
| 13 | 14 | ||
| 14 | option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) | 15 | option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) |
| 16 | option(CITRA_USE_BUNDLED_CURL "FOR MINGW ONLY: Download curl configured against winssl instead of openssl" OFF) | ||
| 17 | if (ENABLE_WEB_SERVICE AND CITRA_USE_BUNDLED_CURL AND WINDOWS AND MSVC) | ||
| 18 | message("Turning off use bundled curl as msvc can compile curl on cpr") | ||
| 19 | SET(CITRA_USE_BUNDLED_CURL OFF CACHE BOOL "" FORCE) | ||
| 20 | endif() | ||
| 21 | if (ENABLE_WEB_SERVICE AND NOT CITRA_USE_BUNDLED_CURL AND MINGW) | ||
| 22 | message(AUTHOR_WARNING "Turning on CITRA_USE_BUNDLED_CURL. Override it only if you know what you are doing.") | ||
| 23 | SET(CITRA_USE_BUNDLED_CURL ON CACHE BOOL "" FORCE) | ||
| 24 | endif() | ||
| 15 | 25 | ||
| 16 | if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) | 26 | if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) |
| 17 | message(STATUS "Copying pre-commit hook") | 27 | message(STATUS "Copying pre-commit hook") |
| @@ -129,8 +139,8 @@ else() | |||
| 129 | set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE) | 139 | set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE) |
| 130 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE) | 140 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE) |
| 131 | 141 | ||
| 132 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG" CACHE STRING "" FORCE) | 142 | set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE) |
| 133 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) | 143 | set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE) |
| 134 | endif() | 144 | endif() |
| 135 | 145 | ||
| 136 | # Set file offset size to 64 bits. | 146 | # Set file offset size to 64 bits. |
| @@ -151,24 +161,6 @@ set_property(DIRECTORY APPEND PROPERTY | |||
| 151 | # System imported libraries | 161 | # System imported libraries |
| 152 | # ====================== | 162 | # ====================== |
| 153 | 163 | ||
| 154 | # This function downloads a binary library package from our external repo. | ||
| 155 | # Params: | ||
| 156 | # remote_path: path to the file to download, relative to the remote repository root | ||
| 157 | # prefix_var: name of a variable which will be set with the path to the extracted contents | ||
| 158 | function(download_bundled_external remote_path lib_name prefix_var) | ||
| 159 | set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") | ||
| 160 | if (NOT EXISTS "${prefix}") | ||
| 161 | message(STATUS "Downloading binaries for ${lib_name}...") | ||
| 162 | file(DOWNLOAD | ||
| 163 | https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z | ||
| 164 | "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS) | ||
| 165 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" | ||
| 166 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") | ||
| 167 | endif() | ||
| 168 | message(STATUS "Using bundled binaries at ${prefix}") | ||
| 169 | set(${prefix_var} "${prefix}" PARENT_SCOPE) | ||
| 170 | endfunction() | ||
| 171 | |||
| 172 | find_package(PNG QUIET) | 164 | find_package(PNG QUIET) |
| 173 | if (NOT PNG_FOUND) | 165 | if (NOT PNG_FOUND) |
| 174 | message(STATUS "libpng not found. Some debugging features have been disabled.") | 166 | message(STATUS "libpng not found. Some debugging features have been disabled.") |
diff --git a/CMakeModules/DownloadExternals.cmake b/CMakeModules/DownloadExternals.cmake new file mode 100644 index 000000000..138a15d5a --- /dev/null +++ b/CMakeModules/DownloadExternals.cmake | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | |||
| 2 | # This function downloads a binary library package from our external repo. | ||
| 3 | # Params: | ||
| 4 | # remote_path: path to the file to download, relative to the remote repository root | ||
| 5 | # prefix_var: name of a variable which will be set with the path to the extracted contents | ||
| 6 | function(download_bundled_external remote_path lib_name prefix_var) | ||
| 7 | set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}") | ||
| 8 | if (NOT EXISTS "${prefix}") | ||
| 9 | message(STATUS "Downloading binaries for ${lib_name}...") | ||
| 10 | file(DOWNLOAD | ||
| 11 | https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z | ||
| 12 | "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS) | ||
| 13 | execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" | ||
| 14 | WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals") | ||
| 15 | endif() | ||
| 16 | message(STATUS "Using bundled binaries at ${prefix}") | ||
| 17 | set(${prefix_var} "${prefix}" PARENT_SCOPE) | ||
| 18 | endfunction() \ No newline at end of file | ||
diff --git a/appveyor.yml b/appveyor.yml index 94e9969f5..5524eb576 100644 --- a/appveyor.yml +++ b/appveyor.yml | |||
| @@ -7,6 +7,15 @@ cache: | |||
| 7 | 7 | ||
| 8 | os: Visual Studio 2017 | 8 | os: Visual Studio 2017 |
| 9 | 9 | ||
| 10 | environment: | ||
| 11 | # Tell msys2 to add mingw64 to the path | ||
| 12 | MSYSTEM: MINGW64 | ||
| 13 | # Tell msys2 to inherit the current directory when starting the shell | ||
| 14 | CHERE_INVOKING: 1 | ||
| 15 | matrix: | ||
| 16 | - BUILD_TYPE: mingw | ||
| 17 | - BUILD_TYPE: msvc | ||
| 18 | |||
| 10 | platform: | 19 | platform: |
| 11 | - x64 | 20 | - x64 |
| 12 | 21 | ||
| @@ -15,72 +24,149 @@ configuration: | |||
| 15 | 24 | ||
| 16 | install: | 25 | install: |
| 17 | - git submodule update --init --recursive | 26 | - git submodule update --init --recursive |
| 27 | - ps: | | ||
| 28 | if ($env:BUILD_TYPE -eq 'mingw') { | ||
| 29 | $dependencies = "mingw64/mingw-w64-x86_64-cmake", | ||
| 30 | "mingw64/mingw-w64-x86_64-qt5", | ||
| 31 | "mingw64/mingw-w64-x86_64-curl", | ||
| 32 | "mingw64/mingw-w64-x86_64-SDL2" | ||
| 33 | # redirect err to null to prevent warnings from becoming errors | ||
| 34 | # workaround to prevent pacman from failing due to cyclical dependencies | ||
| 35 | C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw64/mingw-w64-x86_64-freetype mingw64/mingw-w64-x86_64-fontconfig" 2> $null | ||
| 36 | C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S $dependencies" 2> $null | ||
| 37 | } | ||
| 18 | 38 | ||
| 19 | before_build: | 39 | before_build: |
| 20 | - mkdir build | 40 | - mkdir %BUILD_TYPE%_build |
| 21 | - cd build | 41 | - cd %BUILD_TYPE%_build |
| 22 | - cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 .. | 42 | - ps: | |
| 43 | if ($env:BUILD_TYPE -eq 'msvc') { | ||
| 44 | # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning | ||
| 45 | cmd /C 'cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 .. 2>&1 && exit 0' | ||
| 46 | } else { | ||
| 47 | C:\msys64\usr\bin\bash.exe -lc "cmake -G 'MSYS Makefiles' -DUSE_SYSTEM_CURL=1 -DCITRA_USE_BUNDLED_CURL=1 -DCMAKE_BUILD_TYPE=Release .. 2>&1" | ||
| 48 | } | ||
| 23 | - cd .. | 49 | - cd .. |
| 24 | 50 | ||
| 25 | build: | 51 | build_script: |
| 26 | project: build/citra.sln | 52 | - ps: | |
| 27 | parallel: true | 53 | if ($env:BUILD_TYPE -eq 'msvc') { |
| 54 | # https://www.appveyor.com/docs/build-phase | ||
| 55 | msbuild msvc_build/citra.sln /maxcpucount /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll" | ||
| 56 | } else { | ||
| 57 | C:\msys64\usr\bin\bash.exe -lc 'mingw32-make -C mingw_build/ 2>&1' | ||
| 58 | } | ||
| 28 | 59 | ||
| 29 | after_build: | 60 | after_build: |
| 30 | - ps: | | 61 | - ps: | |
| 31 | $GITDATE = $(git show -s --date=short --format='%ad') -replace "-","" | 62 | $GITDATE = $(git show -s --date=short --format='%ad') -replace "-","" |
| 32 | $GITREV = $(git show -s --format='%h') | 63 | $GITREV = $(git show -s --format='%h') |
| 33 | $GIT_LONG_HASH = $(git rev-parse HEAD) | ||
| 34 | # Where are these spaces coming from? Regardless, let's remove them | ||
| 35 | $MSVC_BUILD_NAME = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", "" | ||
| 36 | $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", "" | ||
| 37 | $MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", "" | ||
| 38 | $BINTRAY_VERSION = "nightly-$GIT_LONG_HASH" -replace " ", "" | ||
| 39 | |||
| 40 | # set the build names as env vars so the artifacts can upload them | ||
| 41 | $env:MSVC_BUILD_NAME = $MSVC_BUILD_NAME | ||
| 42 | $env:MSVC_BUILD_PDB = $MSVC_BUILD_PDB | ||
| 43 | $env:MSVC_SEVENZIP = $MSVC_SEVENZIP | ||
| 44 | $env:GITREV = $GITREV | ||
| 45 | |||
| 46 | 7z a -tzip $MSVC_BUILD_PDB .\build\bin\release\*.pdb | ||
| 47 | rm .\build\bin\release\*.pdb | ||
| 48 | 64 | ||
| 49 | # Find out which kind of release we are producing by tag name | 65 | # Find out which kind of release we are producing by tag name |
| 50 | if ($env:APPVEYOR_REPO_TAG_NAME) { | 66 | if ($env:APPVEYOR_REPO_TAG_NAME) { |
| 51 | $RELEASE_DIST, $RELEASE_VERSION = $env:APPVEYOR_REPO_TAG_NAME.split('-') | 67 | $RELEASE_DIST, $RELEASE_VERSION = $env:APPVEYOR_REPO_TAG_NAME.split('-') |
| 52 | } else { | 68 | } else { |
| 53 | # There is no repo tag - make assumptions | 69 | # There is no repo tag - make assumptions |
| 54 | $RELEASE_DIST = "head" | 70 | $RELEASE_DIST = "head" |
| 55 | } | 71 | } |
| 56 | 72 | ||
| 57 | mkdir $RELEASE_DIST | 73 | if ($env:BUILD_TYPE -eq 'msvc') { |
| 58 | Copy-Item .\build\bin\release\* -Destination $RELEASE_DIST -Recurse | 74 | # Where are these spaces coming from? Regardless, let's remove them |
| 59 | Copy-Item .\license.txt -Destination $RELEASE_DIST | 75 | $MSVC_BUILD_ZIP = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", "" |
| 60 | Copy-Item .\README.md -Destination $RELEASE_DIST | 76 | $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", "" |
| 61 | 77 | $MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", "" | |
| 62 | 7z a -tzip $MSVC_BUILD_NAME $RELEASE_DIST\* | 78 | |
| 63 | 7z a $MSVC_SEVENZIP $RELEASE_DIST | 79 | # set the build names as env vars so the artifacts can upload them |
| 80 | $env:BUILD_ZIP = $MSVC_BUILD_ZIP | ||
| 81 | $env:BUILD_SYMBOLS = $MSVC_BUILD_PDB | ||
| 82 | $env:BUILD_UPDATE = $MSVC_SEVENZIP | ||
| 83 | |||
| 84 | 7z a -tzip $MSVC_BUILD_PDB .\msvc_build\bin\release\*.pdb | ||
| 85 | rm .\msvc_build\bin\release\*.pdb | ||
| 86 | |||
| 87 | mkdir $RELEASE_DIST | ||
| 88 | Copy-Item .\msvc_build\bin\release\* -Destination $RELEASE_DIST -Recurse | ||
| 89 | Copy-Item .\license.txt -Destination $RELEASE_DIST | ||
| 90 | Copy-Item .\README.md -Destination $RELEASE_DIST | ||
| 91 | 7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\* | ||
| 92 | 7z a $MSVC_SEVENZIP $RELEASE_DIST | ||
| 93 | } else { | ||
| 94 | $MINGW_BUILD_ZIP = "citra-windows-mingw-$GITDATE-$GITREV.zip" -replace " ", "" | ||
| 95 | $MINGW_SEVENZIP = "citra-windows-mingw-$GITDATE-$GITREV.7z" -replace " ", "" | ||
| 96 | # not going to bother adding separate debug symbols for mingw, so just upload a README for it | ||
| 97 | # if someone wants to add them, change mingw to compile with -g and use objdump and strip to separate the symbols from the binary | ||
| 98 | $MINGW_NO_DEBUG_SYMBOLS = "README_No_Debug_Symbols.txt" | ||
| 99 | Set-Content -Path $MINGW_NO_DEBUG_SYMBOLS -Value "This is a workaround for Appveyor since msvc has debug symbols but mingw doesnt" -Force | ||
| 100 | |||
| 101 | # store the build information in env vars so we can use them as artifacts | ||
| 102 | $env:BUILD_ZIP = $MINGW_BUILD_ZIP | ||
| 103 | $env:BUILD_SYMBOLS = $MINGW_NO_DEBUG_SYMBOLS | ||
| 104 | $env:BUILD_UPDATE = $MINGW_SEVENZIP | ||
| 105 | |||
| 106 | $CMAKE_SOURCE_DIR = "$env:APPVEYOR_BUILD_FOLDER" | ||
| 107 | $CMAKE_BINARY_DIR = "$CMAKE_SOURCE_DIR/mingw_build" | ||
| 108 | $RELEASE_DIST = $RELEASE_DIST + "-mingw" | ||
| 109 | |||
| 110 | mkdir $RELEASE_DIST | ||
| 111 | mkdir $RELEASE_DIST/platforms | ||
| 112 | |||
| 113 | # copy the compiled binaries and other release files to the release folder | ||
| 114 | Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "citra*.exe" | Copy-Item -destination $RELEASE_DIST | ||
| 115 | # copy the libcurl dll | ||
| 116 | Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "libcurl.dll" | Copy-Item -destination $RELEASE_DIST | ||
| 117 | Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST | ||
| 118 | Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST | ||
| 119 | # copy all the dll dependencies to the release folder | ||
| 120 | # hardcoded list because we don't build static and determining the list of dlls from the binary is a pain. | ||
| 121 | $MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll", | ||
| 122 | # QT dll dependencies | ||
| 123 | "libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll", | ||
| 124 | "libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll", | ||
| 125 | "libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre16-*.dll","libpng16-*.dll", | ||
| 126 | # Runtime/Other dependencies | ||
| 127 | "libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll" | ||
| 128 | foreach ($file in $MingwDLLs) { | ||
| 129 | Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST" | ||
| 130 | } | ||
| 131 | # the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!) | ||
| 132 | # so we can remove them by hardcoding another list of extra dlls to remove | ||
| 133 | $DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll" | ||
| 134 | foreach ($file in $DebugDLLs) { | ||
| 135 | Remove-Item -path "$RELEASE_DIST/$file" | ||
| 136 | } | ||
| 137 | |||
| 138 | # copy the qt windows plugin dll to platforms | ||
| 139 | Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/platforms/qwindows.dll" -force -destination "$RELEASE_DIST/platforms" | ||
| 140 | |||
| 141 | 7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\* | ||
| 142 | 7z a $MINGW_SEVENZIP $RELEASE_DIST | ||
| 143 | } | ||
| 64 | 144 | ||
| 65 | test_script: | 145 | test_script: |
| 66 | - cd build && ctest -VV -C Release && cd .. | 146 | - cd %BUILD_TYPE%_build |
| 147 | - ps: | | ||
| 148 | if ($env:BUILD_TYPE -eq 'msvc') { | ||
| 149 | ctest -VV -C Release | ||
| 150 | } else { | ||
| 151 | C:\msys64\usr\bin\bash.exe -lc "ctest -VV -C Release" | ||
| 152 | } | ||
| 153 | - cd .. | ||
| 67 | 154 | ||
| 68 | artifacts: | 155 | artifacts: |
| 69 | - path: $(MSVC_BUILD_NAME) | 156 | - path: $(BUILD_ZIP) |
| 70 | name: msvcbuild | 157 | name: build |
| 71 | type: zip | ||
| 72 | - path: $(MSVC_BUILD_PDB) | ||
| 73 | name: msvcdebug | ||
| 74 | type: zip | 158 | type: zip |
| 75 | - path: $(MSVC_SEVENZIP) | 159 | - path: $(BUILD_SYMBOLS) |
| 76 | name: msvcupdate | 160 | name: debugsymbols |
| 161 | - path: $(BUILD_UPDATE) | ||
| 162 | name: update | ||
| 77 | 163 | ||
| 78 | deploy: | 164 | deploy: |
| 79 | provider: GitHub | 165 | provider: GitHub |
| 80 | release: $(appveyor_repo_tag_name) | 166 | release: $(appveyor_repo_tag_name) |
| 81 | auth_token: | 167 | auth_token: |
| 82 | secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1" | 168 | secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1" |
| 83 | artifact: msvcupdate,msvcbuild | 169 | artifact: update,build |
| 84 | draft: false | 170 | draft: false |
| 85 | prerelease: false | 171 | prerelease: false |
| 86 | on: | 172 | on: |
diff --git a/dist/citra.manifest b/dist/citra.manifest new file mode 100644 index 000000000..fd30b656f --- /dev/null +++ b/dist/citra.manifest | |||
| @@ -0,0 +1,24 @@ | |||
| 1 | <?xml version="1.0" encoding="UTF-8" standalone="yes"?> | ||
| 2 | <assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0"> | ||
| 3 | <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
| 4 | <security> | ||
| 5 | <requestedPrivileges> | ||
| 6 | <requestedExecutionLevel level="asInvoker" uiAccess="false"/> | ||
| 7 | </requestedPrivileges> | ||
| 8 | </security> | ||
| 9 | </trustInfo> | ||
| 10 | <application xmlns="urn:schemas-microsoft-com:asm.v3"> | ||
| 11 | <windowsSettings> | ||
| 12 | <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware> | ||
| 13 | <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware> | ||
| 14 | </windowsSettings> | ||
| 15 | </application> | ||
| 16 | <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> | ||
| 17 | <application> | ||
| 18 | <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> | ||
| 19 | <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> | ||
| 20 | <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> | ||
| 21 | <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> | ||
| 22 | </application> | ||
| 23 | </compatibility> | ||
| 24 | </assembly> \ No newline at end of file | ||
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index 8e4bcf21f..4a4ba1101 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt | |||
| @@ -1,5 +1,8 @@ | |||
| 1 | # Definitions for all external bundled libraries | 1 | # Definitions for all external bundled libraries |
| 2 | 2 | ||
| 3 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules) | ||
| 4 | include(DownloadExternals) | ||
| 5 | |||
| 3 | # Catch | 6 | # Catch |
| 4 | add_library(catch-single-include INTERFACE) | 7 | add_library(catch-single-include INTERFACE) |
| 5 | target_include_directories(catch-single-include INTERFACE catch/single_include) | 8 | target_include_directories(catch-single-include INTERFACE catch/single_include) |
| @@ -54,9 +57,21 @@ add_subdirectory(enet) | |||
| 54 | target_include_directories(enet INTERFACE ./enet/include) | 57 | target_include_directories(enet INTERFACE ./enet/include) |
| 55 | 58 | ||
| 56 | if (ENABLE_WEB_SERVICE) | 59 | if (ENABLE_WEB_SERVICE) |
| 60 | # msys installed curl is configured to use openssl, but that isn't portable | ||
| 61 | # since it relies on having the bundled certs install in the home folder for SSL | ||
| 62 | # by default on mingw, download the precompiled curl thats linked against windows native ssl | ||
| 63 | if (MINGW AND CITRA_USE_BUNDLED_CURL) | ||
| 64 | download_bundled_external("curl/" "curl-7_55_1" CURL_PREFIX) | ||
| 65 | set(CURL_PREFIX "${CMAKE_BINARY_DIR}/externals/curl-7_55_1") | ||
| 66 | set(CURL_FOUND YES) | ||
| 67 | set(CURL_INCLUDE_DIR "${CURL_PREFIX}/include" CACHE PATH "Path to curl headers") | ||
| 68 | set(CURL_LIBRARY "${CURL_PREFIX}/lib/libcurldll.a" CACHE PATH "Path to curl library") | ||
| 69 | set(CURL_DLL_DIR "${CURL_PREFIX}/lib/" CACHE PATH "Path to curl.dll") | ||
| 70 | set(USE_SYSTEM_CURL ON CACHE BOOL "") | ||
| 71 | endif() | ||
| 57 | # CPR | 72 | # CPR |
| 58 | option(BUILD_TESTING OFF) | 73 | set(BUILD_TESTING OFF CACHE BOOL "") |
| 59 | option(BUILD_CPR_TESTS OFF) | 74 | set(BUILD_CPR_TESTS OFF CACHE BOOL "") |
| 60 | add_subdirectory(cpr) | 75 | add_subdirectory(cpr) |
| 61 | target_include_directories(cpr INTERFACE ./cpr/include) | 76 | target_include_directories(cpr INTERFACE ./cpr/include) |
| 62 | 77 | ||
diff --git a/src/audio_core/hle/source.cpp b/src/audio_core/hle/source.cpp index 92484c526..de4e88cae 100644 --- a/src/audio_core/hle/source.cpp +++ b/src/audio_core/hle/source.cpp | |||
| @@ -244,17 +244,27 @@ void Source::GenerateFrame() { | |||
| 244 | break; | 244 | break; |
| 245 | } | 245 | } |
| 246 | 246 | ||
| 247 | const size_t size_to_copy = | 247 | switch (state.interpolation_mode) { |
| 248 | std::min(state.current_buffer.size(), current_frame.size() - frame_position); | 248 | case InterpolationMode::None: |
| 249 | 249 | AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier, | |
| 250 | std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy, | 250 | current_frame, frame_position); |
| 251 | current_frame.begin() + frame_position); | 251 | break; |
| 252 | state.current_buffer.erase(state.current_buffer.begin(), | 252 | case InterpolationMode::Linear: |
| 253 | state.current_buffer.begin() + size_to_copy); | 253 | AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier, |
| 254 | 254 | current_frame, frame_position); | |
| 255 | frame_position += size_to_copy; | 255 | break; |
| 256 | state.next_sample_number += static_cast<u32>(size_to_copy); | 256 | case InterpolationMode::Polyphase: |
| 257 | // TODO(merry): Implement polyphase interpolation | ||
| 258 | LOG_DEBUG(Audio_DSP, "Polyphase interpolation unimplemented; falling back to linear"); | ||
| 259 | AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier, | ||
| 260 | current_frame, frame_position); | ||
| 261 | break; | ||
| 262 | default: | ||
| 263 | UNIMPLEMENTED(); | ||
| 264 | break; | ||
| 265 | } | ||
| 257 | } | 266 | } |
| 267 | state.next_sample_number += frame_position; | ||
| 258 | 268 | ||
| 259 | state.filters.ProcessFrame(current_frame); | 269 | state.filters.ProcessFrame(current_frame); |
| 260 | } | 270 | } |
| @@ -305,25 +315,6 @@ bool Source::DequeueBuffer() { | |||
| 305 | return true; | 315 | return true; |
| 306 | } | 316 | } |
| 307 | 317 | ||
| 308 | switch (state.interpolation_mode) { | ||
| 309 | case InterpolationMode::None: | ||
| 310 | state.current_buffer = | ||
| 311 | AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier); | ||
| 312 | break; | ||
| 313 | case InterpolationMode::Linear: | ||
| 314 | state.current_buffer = | ||
| 315 | AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier); | ||
| 316 | break; | ||
| 317 | case InterpolationMode::Polyphase: | ||
| 318 | // TODO(merry): Implement polyphase interpolation | ||
| 319 | state.current_buffer = | ||
| 320 | AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier); | ||
| 321 | break; | ||
| 322 | default: | ||
| 323 | UNIMPLEMENTED(); | ||
| 324 | break; | ||
| 325 | } | ||
| 326 | |||
| 327 | // the first playthrough starts at play_position, loops start at the beginning of the buffer | 318 | // the first playthrough starts at play_position, loops start at the beginning of the buffer |
| 328 | state.current_sample_number = (!buf.has_played) ? buf.play_position : 0; | 319 | state.current_sample_number = (!buf.has_played) ? buf.play_position : 0; |
| 329 | state.next_sample_number = state.current_sample_number; | 320 | state.next_sample_number = state.current_sample_number; |
diff --git a/src/audio_core/interpolate.cpp b/src/audio_core/interpolate.cpp index 8a5d4181a..16e68bc5c 100644 --- a/src/audio_core/interpolate.cpp +++ b/src/audio_core/interpolate.cpp | |||
| @@ -13,74 +13,64 @@ namespace AudioInterp { | |||
| 13 | constexpr u64 scale_factor = 1 << 24; | 13 | constexpr u64 scale_factor = 1 << 24; |
| 14 | constexpr u64 scale_mask = scale_factor - 1; | 14 | constexpr u64 scale_mask = scale_factor - 1; |
| 15 | 15 | ||
| 16 | /// Here we step over the input in steps of rate_multiplier, until we consume all of the input. | 16 | /// Here we step over the input in steps of rate, until we consume all of the input. |
| 17 | /// Three adjacent samples are passed to fn each step. | 17 | /// Three adjacent samples are passed to fn each step. |
| 18 | template <typename Function> | 18 | template <typename Function> |
| 19 | static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, | 19 | static void StepOverSamples(State& state, StereoBuffer16& input, float rate, |
| 20 | float rate_multiplier, Function fn) { | 20 | DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) { |
| 21 | ASSERT(rate_multiplier > 0); | 21 | ASSERT(rate > 0); |
| 22 | 22 | ||
| 23 | if (input.size() < 2) | 23 | if (input.empty()) |
| 24 | return {}; | 24 | return; |
| 25 | 25 | ||
| 26 | StereoBuffer16 output; | 26 | input.insert(input.begin(), {state.xn2, state.xn1}); |
| 27 | output.reserve(static_cast<size_t>(input.size() / rate_multiplier)); | ||
| 28 | 27 | ||
| 29 | u64 step_size = static_cast<u64>(rate_multiplier * scale_factor); | 28 | const u64 step_size = static_cast<u64>(rate * scale_factor); |
| 29 | u64 fposition = state.fposition; | ||
| 30 | size_t inputi = 0; | ||
| 30 | 31 | ||
| 31 | u64 fposition = 0; | 32 | while (outputi < output.size()) { |
| 32 | const u64 max_fposition = input.size() * scale_factor; | 33 | inputi = static_cast<size_t>(fposition / scale_factor); |
| 33 | 34 | ||
| 34 | while (fposition < 1 * scale_factor) { | 35 | if (inputi + 2 >= input.size()) { |
| 35 | u64 fraction = fposition & scale_mask; | 36 | inputi = input.size() - 2; |
| 36 | 37 | break; | |
| 37 | output.push_back(fn(fraction, state.xn2, state.xn1, input[0])); | 38 | } |
| 38 | |||
| 39 | fposition += step_size; | ||
| 40 | } | ||
| 41 | |||
| 42 | while (fposition < 2 * scale_factor) { | ||
| 43 | u64 fraction = fposition & scale_mask; | ||
| 44 | |||
| 45 | output.push_back(fn(fraction, state.xn1, input[0], input[1])); | ||
| 46 | |||
| 47 | fposition += step_size; | ||
| 48 | } | ||
| 49 | 39 | ||
| 50 | while (fposition < max_fposition) { | ||
| 51 | u64 fraction = fposition & scale_mask; | 40 | u64 fraction = fposition & scale_mask; |
| 52 | 41 | output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]); | |
| 53 | size_t index = static_cast<size_t>(fposition / scale_factor); | ||
| 54 | output.push_back(fn(fraction, input[index - 2], input[index - 1], input[index])); | ||
| 55 | 42 | ||
| 56 | fposition += step_size; | 43 | fposition += step_size; |
| 57 | } | 44 | } |
| 58 | 45 | ||
| 59 | state.xn2 = input[input.size() - 2]; | 46 | state.xn2 = input[inputi]; |
| 60 | state.xn1 = input[input.size() - 1]; | 47 | state.xn1 = input[inputi + 1]; |
| 48 | state.fposition = fposition - inputi * scale_factor; | ||
| 61 | 49 | ||
| 62 | return output; | 50 | input.erase(input.begin(), input.begin() + inputi + 2); |
| 63 | } | 51 | } |
| 64 | 52 | ||
| 65 | StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) { | 53 | void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, |
| 66 | return StepOverSamples( | 54 | size_t& outputi) { |
| 67 | state, input, rate_multiplier, | 55 | StepOverSamples( |
| 56 | state, input, rate, output, outputi, | ||
| 68 | [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; }); | 57 | [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; }); |
| 69 | } | 58 | } |
| 70 | 59 | ||
| 71 | StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) { | 60 | void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, |
| 61 | size_t& outputi) { | ||
| 72 | // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware. | 62 | // Note on accuracy: Some values that this produces are +/- 1 from the actual firmware. |
| 73 | return StepOverSamples(state, input, rate_multiplier, | 63 | StepOverSamples(state, input, rate, output, outputi, |
| 74 | [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { | 64 | [](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { |
| 75 | // This is a saturated subtraction. (Verified by black-box fuzzing.) | 65 | // This is a saturated subtraction. (Verified by black-box fuzzing.) |
| 76 | s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767); | 66 | s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767); |
| 77 | s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767); | 67 | s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767); |
| 78 | 68 | ||
| 79 | return std::array<s16, 2>{ | 69 | return std::array<s16, 2>{ |
| 80 | static_cast<s16>(x0[0] + fraction * delta0 / scale_factor), | 70 | static_cast<s16>(x0[0] + fraction * delta0 / scale_factor), |
| 81 | static_cast<s16>(x0[1] + fraction * delta1 / scale_factor), | 71 | static_cast<s16>(x0[1] + fraction * delta1 / scale_factor), |
| 82 | }; | 72 | }; |
| 83 | }); | 73 | }); |
| 84 | } | 74 | } |
| 85 | 75 | ||
| 86 | } // namespace AudioInterp | 76 | } // namespace AudioInterp |
diff --git a/src/audio_core/interpolate.h b/src/audio_core/interpolate.h index 19a7b66cb..59f59bc14 100644 --- a/src/audio_core/interpolate.h +++ b/src/audio_core/interpolate.h | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <vector> | 8 | #include <vector> |
| 9 | #include "audio_core/hle/common.h" | ||
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 10 | 11 | ||
| 11 | namespace AudioInterp { | 12 | namespace AudioInterp { |
| @@ -14,31 +15,35 @@ namespace AudioInterp { | |||
| 14 | using StereoBuffer16 = std::vector<std::array<s16, 2>>; | 15 | using StereoBuffer16 = std::vector<std::array<s16, 2>>; |
| 15 | 16 | ||
| 16 | struct State { | 17 | struct State { |
| 17 | // Two historical samples. | 18 | /// Two historical samples. |
| 18 | std::array<s16, 2> xn1 = {}; ///< x[n-1] | 19 | std::array<s16, 2> xn1 = {}; ///< x[n-1] |
| 19 | std::array<s16, 2> xn2 = {}; ///< x[n-2] | 20 | std::array<s16, 2> xn2 = {}; ///< x[n-2] |
| 21 | /// Current fractional position. | ||
| 22 | u64 fposition = 0; | ||
| 20 | }; | 23 | }; |
| 21 | 24 | ||
| 22 | /** | 25 | /** |
| 23 | * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay. | 26 | * No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay. |
| 24 | * @param state Interpolation state. | 27 | * @param state Interpolation state. |
| 25 | * @param input Input buffer. | 28 | * @param input Input buffer. |
| 26 | * @param rate_multiplier Stretch factor. Must be a positive non-zero value. | 29 | * @param rate Stretch factor. Must be a positive non-zero value. |
| 27 | * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 | 30 | * rate > 1.0 performs decimation and rate < 1.0 performs upsampling. |
| 28 | * performs upsampling. | 31 | * @param output The resampled audio buffer. |
| 29 | * @return The resampled audio buffer. | 32 | * @param outputi The index of output to start writing to. |
| 30 | */ | 33 | */ |
| 31 | StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier); | 34 | void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, |
| 35 | size_t& outputi); | ||
| 32 | 36 | ||
| 33 | /** | 37 | /** |
| 34 | * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. | 38 | * Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay. |
| 35 | * @param state Interpolation state. | 39 | * @param state Interpolation state. |
| 36 | * @param input Input buffer. | 40 | * @param input Input buffer. |
| 37 | * @param rate_multiplier Stretch factor. Must be a positive non-zero value. | 41 | * @param rate Stretch factor. Must be a positive non-zero value. |
| 38 | * rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0 | 42 | * rate > 1.0 performs decimation and rate < 1.0 performs upsampling. |
| 39 | * performs upsampling. | 43 | * @param output The resampled audio buffer. |
| 40 | * @return The resampled audio buffer. | 44 | * @param outputi The index of output to start writing to. |
| 41 | */ | 45 | */ |
| 42 | StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier); | 46 | void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output, |
| 47 | size_t& outputi); | ||
| 43 | 48 | ||
| 44 | } // namespace AudioInterp | 49 | } // namespace AudioInterp |
diff --git a/src/citra/citra.rc b/src/citra/citra.rc index fea603004..c490ef302 100644 --- a/src/citra/citra.rc +++ b/src/citra/citra.rc | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | #include "winresrc.h" | ||
| 1 | ///////////////////////////////////////////////////////////////////////////// | 2 | ///////////////////////////////////////////////////////////////////////////// |
| 2 | // | 3 | // |
| 3 | // Icon | 4 | // Icon |
| @@ -7,3 +8,10 @@ | |||
| 7 | // remains consistent on all systems. | 8 | // remains consistent on all systems. |
| 8 | CITRA_ICON ICON "../../dist/citra.ico" | 9 | CITRA_ICON ICON "../../dist/citra.ico" |
| 9 | 10 | ||
| 11 | |||
| 12 | ///////////////////////////////////////////////////////////////////////////// | ||
| 13 | // | ||
| 14 | // RT_MANIFEST | ||
| 15 | // | ||
| 16 | |||
| 17 | 1 RT_MANIFEST "../../dist/citra.manifest" | ||
diff --git a/src/citra/config.cpp b/src/citra/config.cpp index 3869b6b5d..a48ef08c7 100644 --- a/src/citra/config.cpp +++ b/src/citra/config.cpp | |||
| @@ -78,6 +78,8 @@ void Config::ReadValues() { | |||
| 78 | 78 | ||
| 79 | Settings::values.motion_device = sdl2_config->Get( | 79 | Settings::values.motion_device = sdl2_config->Get( |
| 80 | "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); | 80 | "Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01"); |
| 81 | Settings::values.touch_device = | ||
| 82 | sdl2_config->Get("Controls", "touch_device", "engine:emu_window"); | ||
| 81 | 83 | ||
| 82 | // Core | 84 | // Core |
| 83 | Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); | 85 | Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true); |
diff --git a/src/citra/default_ini.h b/src/citra/default_ini.h index ea02a788d..4b13a2e1b 100644 --- a/src/citra/default_ini.h +++ b/src/citra/default_ini.h | |||
| @@ -62,6 +62,10 @@ c_stick= | |||
| 62 | # - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) | 62 | # - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) |
| 63 | motion_device= | 63 | motion_device= |
| 64 | 64 | ||
| 65 | # for touch input, the following devices are available: | ||
| 66 | # - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required | ||
| 67 | touch_device= | ||
| 68 | |||
| 65 | [Core] | 69 | [Core] |
| 66 | # Whether to use the Just-In-Time (JIT) compiler for CPU emulation | 70 | # Whether to use the Just-In-Time (JIT) compiler for CPU emulation |
| 67 | # 0: Interpreter (slow), 1 (default): JIT (fast) | 71 | # 0: Interpreter (slow), 1 (default): JIT (fast) |
diff --git a/src/citra_qt/citra-qt.rc b/src/citra_qt/citra-qt.rc index fea603004..a48a9440d 100644 --- a/src/citra_qt/citra-qt.rc +++ b/src/citra_qt/citra-qt.rc | |||
| @@ -1,3 +1,4 @@ | |||
| 1 | #include "winresrc.h" | ||
| 1 | ///////////////////////////////////////////////////////////////////////////// | 2 | ///////////////////////////////////////////////////////////////////////////// |
| 2 | // | 3 | // |
| 3 | // Icon | 4 | // Icon |
| @@ -5,5 +6,14 @@ | |||
| 5 | 6 | ||
| 6 | // Icon with lowest ID value placed first to ensure application icon | 7 | // Icon with lowest ID value placed first to ensure application icon |
| 7 | // remains consistent on all systems. | 8 | // remains consistent on all systems. |
| 8 | CITRA_ICON ICON "../../dist/citra.ico" | 9 | // QT requires that the default application icon is named IDI_ICON1 |
| 9 | 10 | ||
| 11 | IDI_ICON1 ICON "../../dist/citra.ico" | ||
| 12 | |||
| 13 | |||
| 14 | ///////////////////////////////////////////////////////////////////////////// | ||
| 15 | // | ||
| 16 | // RT_MANIFEST | ||
| 17 | // | ||
| 18 | |||
| 19 | 1 RT_MANIFEST "../../dist/citra.manifest" | ||
diff --git a/src/citra_qt/configuration/config.cpp b/src/citra_qt/configuration/config.cpp index e2dceaa4c..ef114aad3 100644 --- a/src/citra_qt/configuration/config.cpp +++ b/src/citra_qt/configuration/config.cpp | |||
| @@ -61,6 +61,8 @@ void Config::ReadValues() { | |||
| 61 | qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") | 61 | qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01") |
| 62 | .toString() | 62 | .toString() |
| 63 | .toStdString(); | 63 | .toStdString(); |
| 64 | Settings::values.touch_device = | ||
| 65 | qt_config->value("touch_device", "engine:emu_window").toString().toStdString(); | ||
| 64 | 66 | ||
| 65 | qt_config->endGroup(); | 67 | qt_config->endGroup(); |
| 66 | 68 | ||
| @@ -213,6 +215,7 @@ void Config::SaveValues() { | |||
| 213 | QString::fromStdString(Settings::values.analogs[i])); | 215 | QString::fromStdString(Settings::values.analogs[i])); |
| 214 | } | 216 | } |
| 215 | qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device)); | 217 | qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device)); |
| 218 | qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device)); | ||
| 216 | qt_config->endGroup(); | 219 | qt_config->endGroup(); |
| 217 | 220 | ||
| 218 | qt_config->beginGroup("Core"); | 221 | qt_config->beginGroup("Core"); |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 89578024f..cd1a8de2d 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -146,6 +146,7 @@ set(SRCS | |||
| 146 | hle/service/nwm/nwm_tst.cpp | 146 | hle/service/nwm/nwm_tst.cpp |
| 147 | hle/service/nwm/nwm_uds.cpp | 147 | hle/service/nwm/nwm_uds.cpp |
| 148 | hle/service/nwm/uds_beacon.cpp | 148 | hle/service/nwm/uds_beacon.cpp |
| 149 | hle/service/nwm/uds_connection.cpp | ||
| 149 | hle/service/nwm/uds_data.cpp | 150 | hle/service/nwm/uds_data.cpp |
| 150 | hle/service/pm_app.cpp | 151 | hle/service/pm_app.cpp |
| 151 | hle/service/ptm/ptm.cpp | 152 | hle/service/ptm/ptm.cpp |
| @@ -346,6 +347,7 @@ set(HEADERS | |||
| 346 | hle/service/nwm/nwm_tst.h | 347 | hle/service/nwm/nwm_tst.h |
| 347 | hle/service/nwm/nwm_uds.h | 348 | hle/service/nwm/nwm_uds.h |
| 348 | hle/service/nwm/uds_beacon.h | 349 | hle/service/nwm/uds_beacon.h |
| 350 | hle/service/nwm/uds_connection.h | ||
| 349 | hle/service/nwm/uds_data.h | 351 | hle/service/nwm/uds_data.h |
| 350 | hle/service/pm_app.h | 352 | hle/service/pm_app.h |
| 351 | hle/service/ptm/ptm.h | 353 | hle/service/ptm/ptm.h |
diff --git a/src/core/arm/dynarmic/arm_dynarmic.cpp b/src/core/arm/dynarmic/arm_dynarmic.cpp index 0a0b91590..34c5aa381 100644 --- a/src/core/arm/dynarmic/arm_dynarmic.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic.cpp | |||
| @@ -56,7 +56,9 @@ static Dynarmic::UserCallbacks GetUserCallbacks( | |||
| 56 | user_callbacks.memory.Write16 = &Memory::Write16; | 56 | user_callbacks.memory.Write16 = &Memory::Write16; |
| 57 | user_callbacks.memory.Write32 = &Memory::Write32; | 57 | user_callbacks.memory.Write32 = &Memory::Write32; |
| 58 | user_callbacks.memory.Write64 = &Memory::Write64; | 58 | user_callbacks.memory.Write64 = &Memory::Write64; |
| 59 | user_callbacks.page_table = Memory::GetCurrentPageTablePointers(); | 59 | // TODO(Subv): Re-add the page table pointers once dynarmic supports switching page tables at |
| 60 | // runtime. | ||
| 61 | user_callbacks.page_table = nullptr; | ||
| 60 | user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state); | 62 | user_callbacks.coprocessors[15] = std::make_shared<DynarmicCP15>(interpeter_state); |
| 61 | return user_callbacks; | 63 | return user_callbacks; |
| 62 | } | 64 | } |
diff --git a/src/core/core.cpp b/src/core/core.cpp index 5332318cf..59b8768e7 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -137,7 +137,6 @@ void System::Reschedule() { | |||
| 137 | } | 137 | } |
| 138 | 138 | ||
| 139 | System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { | 139 | System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { |
| 140 | Memory::InitMemoryMap(); | ||
| 141 | LOG_DEBUG(HW_Memory, "initialized OK"); | 140 | LOG_DEBUG(HW_Memory, "initialized OK"); |
| 142 | 141 | ||
| 143 | if (Settings::values.use_cpu_jit) { | 142 | if (Settings::values.use_cpu_jit) { |
diff --git a/src/core/frontend/emu_window.cpp b/src/core/frontend/emu_window.cpp index 54fa5c7fa..e67394177 100644 --- a/src/core/frontend/emu_window.cpp +++ b/src/core/frontend/emu_window.cpp | |||
| @@ -2,14 +2,55 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | ||
| 6 | #include <cmath> | 5 | #include <cmath> |
| 7 | #include "common/assert.h" | 6 | #include <mutex> |
| 8 | #include "core/3ds.h" | ||
| 9 | #include "core/core.h" | ||
| 10 | #include "core/frontend/emu_window.h" | 7 | #include "core/frontend/emu_window.h" |
| 8 | #include "core/frontend/input.h" | ||
| 11 | #include "core/settings.h" | 9 | #include "core/settings.h" |
| 12 | 10 | ||
| 11 | class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>, | ||
| 12 | public std::enable_shared_from_this<TouchState> { | ||
| 13 | public: | ||
| 14 | std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override { | ||
| 15 | return std::make_unique<Device>(shared_from_this()); | ||
| 16 | } | ||
| 17 | |||
| 18 | std::mutex mutex; | ||
| 19 | |||
| 20 | bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false | ||
| 21 | |||
| 22 | float touch_x = 0.0f; ///< Touchpad X-position | ||
| 23 | float touch_y = 0.0f; ///< Touchpad Y-position | ||
| 24 | |||
| 25 | private: | ||
| 26 | class Device : public Input::TouchDevice { | ||
| 27 | public: | ||
| 28 | explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {} | ||
| 29 | std::tuple<float, float, bool> GetStatus() const override { | ||
| 30 | if (auto state = touch_state.lock()) { | ||
| 31 | std::lock_guard<std::mutex> guard(state->mutex); | ||
| 32 | return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed); | ||
| 33 | } | ||
| 34 | return std::make_tuple(0.0f, 0.0f, false); | ||
| 35 | } | ||
| 36 | |||
| 37 | private: | ||
| 38 | std::weak_ptr<TouchState> touch_state; | ||
| 39 | }; | ||
| 40 | }; | ||
| 41 | |||
| 42 | EmuWindow::EmuWindow() { | ||
| 43 | // TODO: Find a better place to set this. | ||
| 44 | config.min_client_area_size = std::make_pair(400u, 480u); | ||
| 45 | active_config = config; | ||
| 46 | touch_state = std::make_shared<TouchState>(); | ||
| 47 | Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state); | ||
| 48 | } | ||
| 49 | |||
| 50 | EmuWindow::~EmuWindow() { | ||
| 51 | Input::UnregisterFactory<Input::TouchDevice>("emu_window"); | ||
| 52 | } | ||
| 53 | |||
| 13 | /** | 54 | /** |
| 14 | * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout | 55 | * Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout |
| 15 | * @param layout FramebufferLayout object describing the framebuffer size and screen positions | 56 | * @param layout FramebufferLayout object describing the framebuffer size and screen positions |
| @@ -38,22 +79,26 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) { | |||
| 38 | if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) | 79 | if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) |
| 39 | return; | 80 | return; |
| 40 | 81 | ||
| 41 | touch_x = Core::kScreenBottomWidth * (framebuffer_x - framebuffer_layout.bottom_screen.left) / | 82 | std::lock_guard<std::mutex> guard(touch_state->mutex); |
| 42 | (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left); | 83 | touch_state->touch_x = |
| 43 | touch_y = Core::kScreenBottomHeight * (framebuffer_y - framebuffer_layout.bottom_screen.top) / | 84 | static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left) / |
| 44 | (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top); | 85 | (framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left); |
| 86 | touch_state->touch_y = | ||
| 87 | static_cast<float>(framebuffer_y - framebuffer_layout.bottom_screen.top) / | ||
| 88 | (framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top); | ||
| 45 | 89 | ||
| 46 | touch_pressed = true; | 90 | touch_state->touch_pressed = true; |
| 47 | } | 91 | } |
| 48 | 92 | ||
| 49 | void EmuWindow::TouchReleased() { | 93 | void EmuWindow::TouchReleased() { |
| 50 | touch_pressed = false; | 94 | std::lock_guard<std::mutex> guard(touch_state->mutex); |
| 51 | touch_x = 0; | 95 | touch_state->touch_pressed = false; |
| 52 | touch_y = 0; | 96 | touch_state->touch_x = 0; |
| 97 | touch_state->touch_y = 0; | ||
| 53 | } | 98 | } |
| 54 | 99 | ||
| 55 | void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { | 100 | void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { |
| 56 | if (!touch_pressed) | 101 | if (!touch_state->touch_pressed) |
| 57 | return; | 102 | return; |
| 58 | 103 | ||
| 59 | if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) | 104 | if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y)) |
diff --git a/src/core/frontend/emu_window.h b/src/core/frontend/emu_window.h index 7bdee251c..c10dee51b 100644 --- a/src/core/frontend/emu_window.h +++ b/src/core/frontend/emu_window.h | |||
| @@ -4,11 +4,10 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <mutex> | 7 | #include <memory> |
| 8 | #include <tuple> | 8 | #include <tuple> |
| 9 | #include <utility> | 9 | #include <utility> |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "common/math_util.h" | ||
| 12 | #include "core/frontend/framebuffer_layout.h" | 11 | #include "core/frontend/framebuffer_layout.h" |
| 13 | 12 | ||
| 14 | /** | 13 | /** |
| @@ -69,17 +68,6 @@ public: | |||
| 69 | void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); | 68 | void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y); |
| 70 | 69 | ||
| 71 | /** | 70 | /** |
| 72 | * Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed). | ||
| 73 | * @note This should be called by the core emu thread to get a state set by the window thread. | ||
| 74 | * @todo Fix this function to be thread-safe. | ||
| 75 | * @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and | ||
| 76 | * `pressed` is true if the touch screen is currently being pressed | ||
| 77 | */ | ||
| 78 | std::tuple<u16, u16, bool> GetTouchState() const { | ||
| 79 | return std::make_tuple(touch_x, touch_y, touch_pressed); | ||
| 80 | } | ||
| 81 | |||
| 82 | /** | ||
| 83 | * Returns currently active configuration. | 71 | * Returns currently active configuration. |
| 84 | * @note Accesses to the returned object need not be consistent because it may be modified in | 72 | * @note Accesses to the returned object need not be consistent because it may be modified in |
| 85 | * another thread | 73 | * another thread |
| @@ -113,15 +101,8 @@ public: | |||
| 113 | void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); | 101 | void UpdateCurrentFramebufferLayout(unsigned width, unsigned height); |
| 114 | 102 | ||
| 115 | protected: | 103 | protected: |
| 116 | EmuWindow() { | 104 | EmuWindow(); |
| 117 | // TODO: Find a better place to set this. | 105 | virtual ~EmuWindow(); |
| 118 | config.min_client_area_size = std::make_pair(400u, 480u); | ||
| 119 | active_config = config; | ||
| 120 | touch_x = 0; | ||
| 121 | touch_y = 0; | ||
| 122 | touch_pressed = false; | ||
| 123 | } | ||
| 124 | virtual ~EmuWindow() {} | ||
| 125 | 106 | ||
| 126 | /** | 107 | /** |
| 127 | * Processes any pending configuration changes from the last SetConfig call. | 108 | * Processes any pending configuration changes from the last SetConfig call. |
| @@ -177,10 +158,8 @@ private: | |||
| 177 | /// ProcessConfigurationChanges) | 158 | /// ProcessConfigurationChanges) |
| 178 | WindowConfig active_config; ///< Internal active configuration | 159 | WindowConfig active_config; ///< Internal active configuration |
| 179 | 160 | ||
| 180 | bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false | 161 | class TouchState; |
| 181 | 162 | std::shared_ptr<TouchState> touch_state; | |
| 182 | u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320) | ||
| 183 | u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240) | ||
| 184 | 163 | ||
| 185 | /** | 164 | /** |
| 186 | * Clip the provided coordinates to be inside the touchscreen area. | 165 | * Clip the provided coordinates to be inside the touchscreen area. |
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 5916a901d..8c256beb5 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h | |||
| @@ -126,4 +126,10 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>; | |||
| 126 | */ | 126 | */ |
| 127 | using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>; | 127 | using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>; |
| 128 | 128 | ||
| 129 | /** | ||
| 130 | * A touch device is an input device that returns a tuple of two floats and a bool. The floats are | ||
| 131 | * x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed. | ||
| 132 | */ | ||
| 133 | using TouchDevice = InputDevice<std::tuple<float, float, bool>>; | ||
| 134 | |||
| 129 | } // namespace Input | 135 | } // namespace Input |
diff --git a/src/core/hle/applets/mii_selector.cpp b/src/core/hle/applets/mii_selector.cpp index 705859f1e..f225c23a5 100644 --- a/src/core/hle/applets/mii_selector.cpp +++ b/src/core/hle/applets/mii_selector.cpp | |||
| @@ -66,7 +66,7 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa | |||
| 66 | // continue. | 66 | // continue. |
| 67 | MiiResult result; | 67 | MiiResult result; |
| 68 | memset(&result, 0, sizeof(result)); | 68 | memset(&result, 0, sizeof(result)); |
| 69 | result.result_code = 0; | 69 | result.return_code = 0; |
| 70 | 70 | ||
| 71 | // Let the application know that we're closing | 71 | // Let the application know that we're closing |
| 72 | Service::APT::MessageParameter message; | 72 | Service::APT::MessageParameter message; |
| @@ -82,5 +82,5 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa | |||
| 82 | } | 82 | } |
| 83 | 83 | ||
| 84 | void MiiSelector::Update() {} | 84 | void MiiSelector::Update() {} |
| 85 | } | 85 | } // namespace Applets |
| 86 | } // namespace | 86 | } // namespace HLE |
diff --git a/src/core/hle/applets/mii_selector.h b/src/core/hle/applets/mii_selector.h index ec00e29d2..136ce8948 100644 --- a/src/core/hle/applets/mii_selector.h +++ b/src/core/hle/applets/mii_selector.h | |||
| @@ -16,51 +16,46 @@ namespace HLE { | |||
| 16 | namespace Applets { | 16 | namespace Applets { |
| 17 | 17 | ||
| 18 | struct MiiConfig { | 18 | struct MiiConfig { |
| 19 | u8 unk_000; | 19 | u8 enable_cancel_button; |
| 20 | u8 unk_001; | 20 | u8 enable_guest_mii; |
| 21 | u8 unk_002; | 21 | u8 show_on_top_screen; |
| 22 | u8 unk_003; | 22 | INSERT_PADDING_BYTES(5); |
| 23 | u8 unk_004; | 23 | u16 title[0x40]; |
| 24 | INSERT_PADDING_BYTES(4); | ||
| 25 | u8 show_guest_miis; | ||
| 24 | INSERT_PADDING_BYTES(3); | 26 | INSERT_PADDING_BYTES(3); |
| 25 | u16 unk_008; | 27 | u32 initially_selected_mii_index; |
| 26 | INSERT_PADDING_BYTES(0x82); | 28 | u8 guest_mii_whitelist[6]; |
| 27 | u8 unk_08C; | 29 | u8 user_mii_whitelist[0x64]; |
| 28 | INSERT_PADDING_BYTES(3); | ||
| 29 | u16 unk_090; | ||
| 30 | INSERT_PADDING_BYTES(2); | 30 | INSERT_PADDING_BYTES(2); |
| 31 | u32 unk_094; | 31 | u32 magic_value; |
| 32 | u16 unk_098; | ||
| 33 | u8 unk_09A[0x64]; | ||
| 34 | u8 unk_0FE; | ||
| 35 | u8 unk_0FF; | ||
| 36 | u32 unk_100; | ||
| 37 | }; | 32 | }; |
| 38 | |||
| 39 | static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size"); | 33 | static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size"); |
| 40 | #define ASSERT_REG_POSITION(field_name, position) \ | 34 | #define ASSERT_REG_POSITION(field_name, position) \ |
| 41 | static_assert(offsetof(MiiConfig, field_name) == position, \ | 35 | static_assert(offsetof(MiiConfig, field_name) == position, \ |
| 42 | "Field " #field_name " has invalid position") | 36 | "Field " #field_name " has invalid position") |
| 43 | ASSERT_REG_POSITION(unk_008, 0x08); | 37 | ASSERT_REG_POSITION(title, 0x08); |
| 44 | ASSERT_REG_POSITION(unk_08C, 0x8C); | 38 | ASSERT_REG_POSITION(show_guest_miis, 0x8C); |
| 45 | ASSERT_REG_POSITION(unk_090, 0x90); | 39 | ASSERT_REG_POSITION(initially_selected_mii_index, 0x90); |
| 46 | ASSERT_REG_POSITION(unk_094, 0x94); | 40 | ASSERT_REG_POSITION(guest_mii_whitelist, 0x94); |
| 47 | ASSERT_REG_POSITION(unk_0FE, 0xFE); | ||
| 48 | #undef ASSERT_REG_POSITION | 41 | #undef ASSERT_REG_POSITION |
| 49 | 42 | ||
| 50 | struct MiiResult { | 43 | struct MiiResult { |
| 51 | u32 result_code; | 44 | u32 return_code; |
| 52 | u8 unk_04; | 45 | u32 is_guest_mii_selected; |
| 53 | INSERT_PADDING_BYTES(7); | 46 | u32 selected_guest_mii_index; |
| 54 | u8 unk_0C[0x60]; | 47 | // TODO(mailwl): expand to Mii Format structure: https://www.3dbrew.org/wiki/Mii |
| 55 | u8 unk_6C[0x16]; | 48 | u8 selected_mii_data[0x5C]; |
| 56 | INSERT_PADDING_BYTES(2); | 49 | INSERT_PADDING_BYTES(2); |
| 50 | u16 mii_data_checksum; | ||
| 51 | u16 guest_mii_name[0xC]; | ||
| 57 | }; | 52 | }; |
| 58 | static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size"); | 53 | static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size"); |
| 59 | #define ASSERT_REG_POSITION(field_name, position) \ | 54 | #define ASSERT_REG_POSITION(field_name, position) \ |
| 60 | static_assert(offsetof(MiiResult, field_name) == position, \ | 55 | static_assert(offsetof(MiiResult, field_name) == position, \ |
| 61 | "Field " #field_name " has invalid position") | 56 | "Field " #field_name " has invalid position") |
| 62 | ASSERT_REG_POSITION(unk_0C, 0x0C); | 57 | ASSERT_REG_POSITION(selected_mii_data, 0x0C); |
| 63 | ASSERT_REG_POSITION(unk_6C, 0x6C); | 58 | ASSERT_REG_POSITION(guest_mii_name, 0x6C); |
| 64 | #undef ASSERT_REG_POSITION | 59 | #undef ASSERT_REG_POSITION |
| 65 | 60 | ||
| 66 | class MiiSelector final : public Applet { | 61 | class MiiSelector final : public Applet { |
| @@ -79,5 +74,5 @@ private: | |||
| 79 | 74 | ||
| 80 | MiiConfig config; | 75 | MiiConfig config; |
| 81 | }; | 76 | }; |
| 82 | } | 77 | } // namespace Applets |
| 83 | } // namespace | 78 | } // namespace HLE |
diff --git a/src/core/hle/kernel/memory.cpp b/src/core/hle/kernel/memory.cpp index 496d07cb5..7f27e9655 100644 --- a/src/core/hle/kernel/memory.cpp +++ b/src/core/hle/kernel/memory.cpp | |||
| @@ -8,7 +8,6 @@ | |||
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <utility> | 9 | #include <utility> |
| 10 | #include <vector> | 10 | #include <vector> |
| 11 | #include "audio_core/audio_core.h" | ||
| 12 | #include "common/assert.h" | 11 | #include "common/assert.h" |
| 13 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 14 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| @@ -24,7 +23,7 @@ | |||
| 24 | 23 | ||
| 25 | namespace Kernel { | 24 | namespace Kernel { |
| 26 | 25 | ||
| 27 | static MemoryRegionInfo memory_regions[3]; | 26 | MemoryRegionInfo memory_regions[3]; |
| 28 | 27 | ||
| 29 | /// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each system | 28 | /// Size of the APPLICATION, SYSTEM and BASE memory regions (respectively) for each system |
| 30 | /// memory configuration type. | 29 | /// memory configuration type. |
| @@ -96,9 +95,6 @@ MemoryRegionInfo* GetMemoryRegion(MemoryRegion region) { | |||
| 96 | } | 95 | } |
| 97 | } | 96 | } |
| 98 | 97 | ||
| 99 | std::array<u8, Memory::VRAM_SIZE> vram; | ||
| 100 | std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; | ||
| 101 | |||
| 102 | void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) { | 98 | void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) { |
| 103 | using namespace Memory; | 99 | using namespace Memory; |
| 104 | 100 | ||
| @@ -143,30 +139,14 @@ void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mappin | |||
| 143 | return; | 139 | return; |
| 144 | } | 140 | } |
| 145 | 141 | ||
| 146 | // TODO(yuriks): Use GetPhysicalPointer when that becomes independent of the virtual | 142 | u8* target_pointer = Memory::GetPhysicalPointer(area->paddr_base + offset_into_region); |
| 147 | // mappings. | ||
| 148 | u8* target_pointer = nullptr; | ||
| 149 | switch (area->paddr_base) { | ||
| 150 | case VRAM_PADDR: | ||
| 151 | target_pointer = vram.data(); | ||
| 152 | break; | ||
| 153 | case DSP_RAM_PADDR: | ||
| 154 | target_pointer = AudioCore::GetDspMemory().data(); | ||
| 155 | break; | ||
| 156 | case N3DS_EXTRA_RAM_PADDR: | ||
| 157 | target_pointer = n3ds_extra_ram.data(); | ||
| 158 | break; | ||
| 159 | default: | ||
| 160 | UNREACHABLE(); | ||
| 161 | } | ||
| 162 | 143 | ||
| 163 | // TODO(yuriks): This flag seems to have some other effect, but it's unknown what | 144 | // TODO(yuriks): This flag seems to have some other effect, but it's unknown what |
| 164 | MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO; | 145 | MemoryState memory_state = mapping.unk_flag ? MemoryState::Static : MemoryState::IO; |
| 165 | 146 | ||
| 166 | auto vma = address_space | 147 | auto vma = |
| 167 | .MapBackingMemory(mapping.address, target_pointer + offset_into_region, | 148 | address_space.MapBackingMemory(mapping.address, target_pointer, mapping.size, memory_state) |
| 168 | mapping.size, memory_state) | 149 | .Unwrap(); |
| 169 | .Unwrap(); | ||
| 170 | address_space.Reprotect(vma, | 150 | address_space.Reprotect(vma, |
| 171 | mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite); | 151 | mapping.read_only ? VMAPermission::Read : VMAPermission::ReadWrite); |
| 172 | } | 152 | } |
diff --git a/src/core/hle/kernel/memory.h b/src/core/hle/kernel/memory.h index 08c1a9989..da6bb3563 100644 --- a/src/core/hle/kernel/memory.h +++ b/src/core/hle/kernel/memory.h | |||
| @@ -26,4 +26,6 @@ MemoryRegionInfo* GetMemoryRegion(MemoryRegion region); | |||
| 26 | 26 | ||
| 27 | void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); | 27 | void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); |
| 28 | void MapSharedPages(VMManager& address_space); | 28 | void MapSharedPages(VMManager& address_space); |
| 29 | |||
| 30 | extern MemoryRegionInfo memory_regions[3]; | ||
| 29 | } // namespace Kernel | 31 | } // namespace Kernel |
diff --git a/src/core/hle/kernel/thread.cpp b/src/core/hle/kernel/thread.cpp index b957c45dd..324415a36 100644 --- a/src/core/hle/kernel/thread.cpp +++ b/src/core/hle/kernel/thread.cpp | |||
| @@ -171,6 +171,8 @@ static void SwitchContext(Thread* new_thread) { | |||
| 171 | // Cancel any outstanding wakeup events for this thread | 171 | // Cancel any outstanding wakeup events for this thread |
| 172 | CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); | 172 | CoreTiming::UnscheduleEvent(ThreadWakeupEventType, new_thread->callback_handle); |
| 173 | 173 | ||
| 174 | auto previous_process = Kernel::g_current_process; | ||
| 175 | |||
| 174 | current_thread = new_thread; | 176 | current_thread = new_thread; |
| 175 | 177 | ||
| 176 | ready_queue.remove(new_thread->current_priority, new_thread); | 178 | ready_queue.remove(new_thread->current_priority, new_thread); |
| @@ -178,8 +180,18 @@ static void SwitchContext(Thread* new_thread) { | |||
| 178 | 180 | ||
| 179 | Core::CPU().LoadContext(new_thread->context); | 181 | Core::CPU().LoadContext(new_thread->context); |
| 180 | Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); | 182 | Core::CPU().SetCP15Register(CP15_THREAD_URO, new_thread->GetTLSAddress()); |
| 183 | |||
| 184 | if (previous_process != current_thread->owner_process) { | ||
| 185 | Kernel::g_current_process = current_thread->owner_process; | ||
| 186 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | ||
| 187 | // We have switched processes and thus, page tables, clear the instruction cache so we | ||
| 188 | // don't keep stale data from the previous process. | ||
| 189 | Core::CPU().ClearInstructionCache(); | ||
| 190 | } | ||
| 181 | } else { | 191 | } else { |
| 182 | current_thread = nullptr; | 192 | current_thread = nullptr; |
| 193 | // Note: We do not reset the current process and current page table when idling because | ||
| 194 | // technically we haven't changed processes, our threads are just paused. | ||
| 183 | } | 195 | } |
| 184 | } | 196 | } |
| 185 | 197 | ||
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp index cef1f7fa8..7a007c065 100644 --- a/src/core/hle/kernel/vm_manager.cpp +++ b/src/core/hle/kernel/vm_manager.cpp | |||
| @@ -56,6 +56,10 @@ void VMManager::Reset() { | |||
| 56 | initial_vma.size = MAX_ADDRESS; | 56 | initial_vma.size = MAX_ADDRESS; |
| 57 | vma_map.emplace(initial_vma.base, initial_vma); | 57 | vma_map.emplace(initial_vma.base, initial_vma); |
| 58 | 58 | ||
| 59 | page_table.pointers.fill(nullptr); | ||
| 60 | page_table.attributes.fill(Memory::PageType::Unmapped); | ||
| 61 | page_table.cached_res_count.fill(0); | ||
| 62 | |||
| 59 | UpdatePageTableForVMA(initial_vma); | 63 | UpdatePageTableForVMA(initial_vma); |
| 60 | } | 64 | } |
| 61 | 65 | ||
| @@ -328,16 +332,17 @@ VMManager::VMAIter VMManager::MergeAdjacent(VMAIter iter) { | |||
| 328 | void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { | 332 | void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { |
| 329 | switch (vma.type) { | 333 | switch (vma.type) { |
| 330 | case VMAType::Free: | 334 | case VMAType::Free: |
| 331 | Memory::UnmapRegion(vma.base, vma.size); | 335 | Memory::UnmapRegion(page_table, vma.base, vma.size); |
| 332 | break; | 336 | break; |
| 333 | case VMAType::AllocatedMemoryBlock: | 337 | case VMAType::AllocatedMemoryBlock: |
| 334 | Memory::MapMemoryRegion(vma.base, vma.size, vma.backing_block->data() + vma.offset); | 338 | Memory::MapMemoryRegion(page_table, vma.base, vma.size, |
| 339 | vma.backing_block->data() + vma.offset); | ||
| 335 | break; | 340 | break; |
| 336 | case VMAType::BackingMemory: | 341 | case VMAType::BackingMemory: |
| 337 | Memory::MapMemoryRegion(vma.base, vma.size, vma.backing_memory); | 342 | Memory::MapMemoryRegion(page_table, vma.base, vma.size, vma.backing_memory); |
| 338 | break; | 343 | break; |
| 339 | case VMAType::MMIO: | 344 | case VMAType::MMIO: |
| 340 | Memory::MapIoRegion(vma.base, vma.size, vma.mmio_handler); | 345 | Memory::MapIoRegion(page_table, vma.base, vma.size, vma.mmio_handler); |
| 341 | break; | 346 | break; |
| 342 | } | 347 | } |
| 343 | } | 348 | } |
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h index 38e0d74d0..1302527bb 100644 --- a/src/core/hle/kernel/vm_manager.h +++ b/src/core/hle/kernel/vm_manager.h | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include <vector> | 9 | #include <vector> |
| 10 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "core/hle/result.h" | 11 | #include "core/hle/result.h" |
| 12 | #include "core/memory.h" | ||
| 12 | #include "core/mmio.h" | 13 | #include "core/mmio.h" |
| 13 | 14 | ||
| 14 | namespace Kernel { | 15 | namespace Kernel { |
| @@ -102,7 +103,6 @@ struct VirtualMemoryArea { | |||
| 102 | * - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/ | 103 | * - http://duartes.org/gustavo/blog/post/page-cache-the-affair-between-memory-and-files/ |
| 103 | */ | 104 | */ |
| 104 | class VMManager final { | 105 | class VMManager final { |
| 105 | // TODO(yuriks): Make page tables switchable to support multiple VMManagers | ||
| 106 | public: | 106 | public: |
| 107 | /** | 107 | /** |
| 108 | * The maximum amount of address space managed by the kernel. Addresses above this are never | 108 | * The maximum amount of address space managed by the kernel. Addresses above this are never |
| @@ -184,6 +184,10 @@ public: | |||
| 184 | /// Dumps the address space layout to the log, for debugging | 184 | /// Dumps the address space layout to the log, for debugging |
| 185 | void LogLayout(Log::Level log_level) const; | 185 | void LogLayout(Log::Level log_level) const; |
| 186 | 186 | ||
| 187 | /// Each VMManager has its own page table, which is set as the main one when the owning process | ||
| 188 | /// is scheduled. | ||
| 189 | Memory::PageTable page_table; | ||
| 190 | |||
| 187 | private: | 191 | private: |
| 188 | using VMAIter = decltype(vma_map)::iterator; | 192 | using VMAIter = decltype(vma_map)::iterator; |
| 189 | 193 | ||
diff --git a/src/core/hle/lock.cpp b/src/core/hle/lock.cpp index 082f689c8..1c24c7ce9 100644 --- a/src/core/hle/lock.cpp +++ b/src/core/hle/lock.cpp | |||
| @@ -7,5 +7,5 @@ | |||
| 7 | #include <core/hle/lock.h> | 7 | #include <core/hle/lock.h> |
| 8 | 8 | ||
| 9 | namespace HLE { | 9 | namespace HLE { |
| 10 | std::mutex g_hle_lock; | 10 | std::recursive_mutex g_hle_lock; |
| 11 | } | 11 | } |
diff --git a/src/core/hle/lock.h b/src/core/hle/lock.h index 8265621e1..5c99fe996 100644 --- a/src/core/hle/lock.h +++ b/src/core/hle/lock.h | |||
| @@ -14,5 +14,5 @@ namespace HLE { | |||
| 14 | * to the emulated memory is not protected by this mutex, and should be avoided in any threads other | 14 | * to the emulated memory is not protected by this mutex, and should be avoided in any threads other |
| 15 | * than the CPU thread. | 15 | * than the CPU thread. |
| 16 | */ | 16 | */ |
| 17 | extern std::mutex g_hle_lock; | 17 | extern std::recursive_mutex g_hle_lock; |
| 18 | } // namespace HLE | 18 | } // namespace HLE |
diff --git a/src/core/hle/service/apt/apt.cpp b/src/core/hle/service/apt/apt.cpp index 58d94768c..8c0ba73f2 100644 --- a/src/core/hle/service/apt/apt.cpp +++ b/src/core/hle/service/apt/apt.cpp | |||
| @@ -19,6 +19,7 @@ | |||
| 19 | #include "core/hle/service/apt/apt_s.h" | 19 | #include "core/hle/service/apt/apt_s.h" |
| 20 | #include "core/hle/service/apt/apt_u.h" | 20 | #include "core/hle/service/apt/apt_u.h" |
| 21 | #include "core/hle/service/apt/bcfnt/bcfnt.h" | 21 | #include "core/hle/service/apt/bcfnt/bcfnt.h" |
| 22 | #include "core/hle/service/cfg/cfg.h" | ||
| 22 | #include "core/hle/service/fs/archive.h" | 23 | #include "core/hle/service/fs/archive.h" |
| 23 | #include "core/hle/service/ptm/ptm.h" | 24 | #include "core/hle/service/ptm/ptm.h" |
| 24 | #include "core/hle/service/service.h" | 25 | #include "core/hle/service/service.h" |
| @@ -198,6 +199,143 @@ void Initialize(Service::Interface* self) { | |||
| 198 | Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap()); | 199 | Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap()); |
| 199 | } | 200 | } |
| 200 | 201 | ||
| 202 | static u32 DecompressLZ11(const u8* in, u8* out) { | ||
| 203 | u32_le decompressed_size; | ||
| 204 | memcpy(&decompressed_size, in, sizeof(u32)); | ||
| 205 | in += 4; | ||
| 206 | |||
| 207 | u8 type = decompressed_size & 0xFF; | ||
| 208 | ASSERT(type == 0x11); | ||
| 209 | decompressed_size >>= 8; | ||
| 210 | |||
| 211 | u32 current_out_size = 0; | ||
| 212 | u8 flags = 0, mask = 1; | ||
| 213 | while (current_out_size < decompressed_size) { | ||
| 214 | if (mask == 1) { | ||
| 215 | flags = *(in++); | ||
| 216 | mask = 0x80; | ||
| 217 | } else { | ||
| 218 | mask >>= 1; | ||
| 219 | } | ||
| 220 | |||
| 221 | if (flags & mask) { | ||
| 222 | u8 byte1 = *(in++); | ||
| 223 | u32 length = byte1 >> 4; | ||
| 224 | u32 offset; | ||
| 225 | if (length == 0) { | ||
| 226 | u8 byte2 = *(in++); | ||
| 227 | u8 byte3 = *(in++); | ||
| 228 | length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11; | ||
| 229 | offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1; | ||
| 230 | } else if (length == 1) { | ||
| 231 | u8 byte2 = *(in++); | ||
| 232 | u8 byte3 = *(in++); | ||
| 233 | u8 byte4 = *(in++); | ||
| 234 | length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111; | ||
| 235 | offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1; | ||
| 236 | } else { | ||
| 237 | u8 byte2 = *(in++); | ||
| 238 | length = (byte1 >> 4) + 0x1; | ||
| 239 | offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1; | ||
| 240 | } | ||
| 241 | |||
| 242 | for (u32 i = 0; i < length; i++) { | ||
| 243 | *out = *(out - offset); | ||
| 244 | ++out; | ||
| 245 | } | ||
| 246 | |||
| 247 | current_out_size += length; | ||
| 248 | } else { | ||
| 249 | *(out++) = *(in++); | ||
| 250 | current_out_size++; | ||
| 251 | } | ||
| 252 | } | ||
| 253 | return decompressed_size; | ||
| 254 | } | ||
| 255 | |||
| 256 | static bool LoadSharedFont() { | ||
| 257 | u8 font_region_code; | ||
| 258 | switch (CFG::GetRegionValue()) { | ||
| 259 | case 4: // CHN | ||
| 260 | font_region_code = 2; | ||
| 261 | break; | ||
| 262 | case 5: // KOR | ||
| 263 | font_region_code = 3; | ||
| 264 | break; | ||
| 265 | case 6: // TWN | ||
| 266 | font_region_code = 4; | ||
| 267 | break; | ||
| 268 | default: // JPN/EUR/USA | ||
| 269 | font_region_code = 1; | ||
| 270 | break; | ||
| 271 | } | ||
| 272 | |||
| 273 | const u64_le shared_font_archive_id_low = 0x0004009b00014002 | ((font_region_code - 1) << 8); | ||
| 274 | const u64_le shared_font_archive_id_high = 0x00000001ffffff00; | ||
| 275 | std::vector<u8> shared_font_archive_id(16); | ||
| 276 | std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64)); | ||
| 277 | std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64)); | ||
| 278 | FileSys::Path archive_path(shared_font_archive_id); | ||
| 279 | auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path); | ||
| 280 | if (archive_result.Failed()) | ||
| 281 | return false; | ||
| 282 | |||
| 283 | std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS | ||
| 284 | FileSys::Path file_path(romfs_path); | ||
| 285 | FileSys::Mode open_mode = {}; | ||
| 286 | open_mode.read_flag.Assign(1); | ||
| 287 | auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode); | ||
| 288 | if (file_result.Failed()) | ||
| 289 | return false; | ||
| 290 | |||
| 291 | auto romfs = std::move(file_result).Unwrap(); | ||
| 292 | std::vector<u8> romfs_buffer(romfs->backend->GetSize()); | ||
| 293 | romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data()); | ||
| 294 | romfs->backend->Close(); | ||
| 295 | |||
| 296 | const char16_t* file_name[4] = {u"cbf_std.bcfnt.lz", u"cbf_zh-Hans-CN.bcfnt.lz", | ||
| 297 | u"cbf_ko-Hang-KR.bcfnt.lz", u"cbf_zh-Hant-TW.bcfnt.lz"}; | ||
| 298 | const u8* font_file = | ||
| 299 | RomFS::GetFilePointer(romfs_buffer.data(), {file_name[font_region_code - 1]}); | ||
| 300 | if (font_file == nullptr) | ||
| 301 | return false; | ||
| 302 | |||
| 303 | struct { | ||
| 304 | u32_le status; | ||
| 305 | u32_le region; | ||
| 306 | u32_le decompressed_size; | ||
| 307 | INSERT_PADDING_WORDS(0x1D); | ||
| 308 | } shared_font_header{}; | ||
| 309 | static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size"); | ||
| 310 | |||
| 311 | shared_font_header.status = 2; // successfully loaded | ||
| 312 | shared_font_header.region = font_region_code; | ||
| 313 | shared_font_header.decompressed_size = | ||
| 314 | DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80)); | ||
| 315 | std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header)); | ||
| 316 | *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU" | ||
| 317 | |||
| 318 | return true; | ||
| 319 | } | ||
| 320 | |||
| 321 | static bool LoadLegacySharedFont() { | ||
| 322 | // This is the legacy method to load shared font. | ||
| 323 | // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header | ||
| 324 | // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided | ||
| 325 | // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file | ||
| 326 | // "shared_font.bin" in the Citra "sysdata" directory. | ||
| 327 | std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; | ||
| 328 | |||
| 329 | FileUtil::CreateFullPath(filepath); // Create path if not already created | ||
| 330 | FileUtil::IOFile file(filepath, "rb"); | ||
| 331 | if (file.IsOpen()) { | ||
| 332 | file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize()); | ||
| 333 | return true; | ||
| 334 | } | ||
| 335 | |||
| 336 | return false; | ||
| 337 | } | ||
| 338 | |||
| 201 | void GetSharedFont(Service::Interface* self) { | 339 | void GetSharedFont(Service::Interface* self) { |
| 202 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000 | 340 | IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000 |
| 203 | IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); | 341 | IPC::RequestBuilder rb = rp.MakeBuilder(2, 2); |
| @@ -206,11 +344,20 @@ void GetSharedFont(Service::Interface* self) { | |||
| 206 | Core::Telemetry().AddField(Telemetry::FieldType::Session, "RequiresSharedFont", true); | 344 | Core::Telemetry().AddField(Telemetry::FieldType::Session, "RequiresSharedFont", true); |
| 207 | 345 | ||
| 208 | if (!shared_font_loaded) { | 346 | if (!shared_font_loaded) { |
| 209 | LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); | 347 | // On real 3DS, font loading happens on booting. However, we load it on demand to coordinate |
| 210 | rb.Push<u32>(-1); // TODO: Find the right error code | 348 | // with CFG region auto configuration, which happens later than APT initialization. |
| 211 | rb.Skip(1 + 2, true); | 349 | if (LoadSharedFont()) { |
| 212 | Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont); | 350 | shared_font_loaded = true; |
| 213 | return; | 351 | } else if (LoadLegacySharedFont()) { |
| 352 | LOG_WARNING(Service_APT, "Loaded shared font by legacy method"); | ||
| 353 | shared_font_loaded = true; | ||
| 354 | } else { | ||
| 355 | LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds"); | ||
| 356 | rb.Push<u32>(-1); // TODO: Find the right error code | ||
| 357 | rb.Skip(1 + 2, true); | ||
| 358 | Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont); | ||
| 359 | return; | ||
| 360 | } | ||
| 214 | } | 361 | } |
| 215 | 362 | ||
| 216 | // The shared font has to be relocated to the new address before being passed to the | 363 | // The shared font has to be relocated to the new address before being passed to the |
| @@ -863,125 +1010,6 @@ void CheckNew3DS(Service::Interface* self) { | |||
| 863 | LOG_WARNING(Service_APT, "(STUBBED) called"); | 1010 | LOG_WARNING(Service_APT, "(STUBBED) called"); |
| 864 | } | 1011 | } |
| 865 | 1012 | ||
| 866 | static u32 DecompressLZ11(const u8* in, u8* out) { | ||
| 867 | u32_le decompressed_size; | ||
| 868 | memcpy(&decompressed_size, in, sizeof(u32)); | ||
| 869 | in += 4; | ||
| 870 | |||
| 871 | u8 type = decompressed_size & 0xFF; | ||
| 872 | ASSERT(type == 0x11); | ||
| 873 | decompressed_size >>= 8; | ||
| 874 | |||
| 875 | u32 current_out_size = 0; | ||
| 876 | u8 flags = 0, mask = 1; | ||
| 877 | while (current_out_size < decompressed_size) { | ||
| 878 | if (mask == 1) { | ||
| 879 | flags = *(in++); | ||
| 880 | mask = 0x80; | ||
| 881 | } else { | ||
| 882 | mask >>= 1; | ||
| 883 | } | ||
| 884 | |||
| 885 | if (flags & mask) { | ||
| 886 | u8 byte1 = *(in++); | ||
| 887 | u32 length = byte1 >> 4; | ||
| 888 | u32 offset; | ||
| 889 | if (length == 0) { | ||
| 890 | u8 byte2 = *(in++); | ||
| 891 | u8 byte3 = *(in++); | ||
| 892 | length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11; | ||
| 893 | offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1; | ||
| 894 | } else if (length == 1) { | ||
| 895 | u8 byte2 = *(in++); | ||
| 896 | u8 byte3 = *(in++); | ||
| 897 | u8 byte4 = *(in++); | ||
| 898 | length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111; | ||
| 899 | offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1; | ||
| 900 | } else { | ||
| 901 | u8 byte2 = *(in++); | ||
| 902 | length = (byte1 >> 4) + 0x1; | ||
| 903 | offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1; | ||
| 904 | } | ||
| 905 | |||
| 906 | for (u32 i = 0; i < length; i++) { | ||
| 907 | *out = *(out - offset); | ||
| 908 | ++out; | ||
| 909 | } | ||
| 910 | |||
| 911 | current_out_size += length; | ||
| 912 | } else { | ||
| 913 | *(out++) = *(in++); | ||
| 914 | current_out_size++; | ||
| 915 | } | ||
| 916 | } | ||
| 917 | return decompressed_size; | ||
| 918 | } | ||
| 919 | |||
| 920 | static bool LoadSharedFont() { | ||
| 921 | // TODO (wwylele): load different font archive for region CHN/KOR/TWN | ||
| 922 | const u64_le shared_font_archive_id_low = 0x0004009b00014002; | ||
| 923 | const u64_le shared_font_archive_id_high = 0x00000001ffffff00; | ||
| 924 | std::vector<u8> shared_font_archive_id(16); | ||
| 925 | std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64)); | ||
| 926 | std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64)); | ||
| 927 | FileSys::Path archive_path(shared_font_archive_id); | ||
| 928 | auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path); | ||
| 929 | if (archive_result.Failed()) | ||
| 930 | return false; | ||
| 931 | |||
| 932 | std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS | ||
| 933 | FileSys::Path file_path(romfs_path); | ||
| 934 | FileSys::Mode open_mode = {}; | ||
| 935 | open_mode.read_flag.Assign(1); | ||
| 936 | auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode); | ||
| 937 | if (file_result.Failed()) | ||
| 938 | return false; | ||
| 939 | |||
| 940 | auto romfs = std::move(file_result).Unwrap(); | ||
| 941 | std::vector<u8> romfs_buffer(romfs->backend->GetSize()); | ||
| 942 | romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data()); | ||
| 943 | romfs->backend->Close(); | ||
| 944 | |||
| 945 | const u8* font_file = RomFS::GetFilePointer(romfs_buffer.data(), {u"cbf_std.bcfnt.lz"}); | ||
| 946 | if (font_file == nullptr) | ||
| 947 | return false; | ||
| 948 | |||
| 949 | struct { | ||
| 950 | u32_le status; | ||
| 951 | u32_le region; | ||
| 952 | u32_le decompressed_size; | ||
| 953 | INSERT_PADDING_WORDS(0x1D); | ||
| 954 | } shared_font_header{}; | ||
| 955 | static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size"); | ||
| 956 | |||
| 957 | shared_font_header.status = 2; // successfully loaded | ||
| 958 | shared_font_header.region = 1; // region JPN/EUR/USA | ||
| 959 | shared_font_header.decompressed_size = | ||
| 960 | DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80)); | ||
| 961 | std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header)); | ||
| 962 | *shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU" | ||
| 963 | |||
| 964 | return true; | ||
| 965 | } | ||
| 966 | |||
| 967 | static bool LoadLegacySharedFont() { | ||
| 968 | // This is the legacy method to load shared font. | ||
| 969 | // The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header | ||
| 970 | // generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided | ||
| 971 | // a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file | ||
| 972 | // "shared_font.bin" in the Citra "sysdata" directory. | ||
| 973 | std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT; | ||
| 974 | |||
| 975 | FileUtil::CreateFullPath(filepath); // Create path if not already created | ||
| 976 | FileUtil::IOFile file(filepath, "rb"); | ||
| 977 | if (file.IsOpen()) { | ||
| 978 | file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize()); | ||
| 979 | return true; | ||
| 980 | } | ||
| 981 | |||
| 982 | return false; | ||
| 983 | } | ||
| 984 | |||
| 985 | void Init() { | 1013 | void Init() { |
| 986 | AddService(new APT_A_Interface); | 1014 | AddService(new APT_A_Interface); |
| 987 | AddService(new APT_S_Interface); | 1015 | AddService(new APT_S_Interface); |
| @@ -995,16 +1023,6 @@ void Init() { | |||
| 995 | MemoryPermission::ReadWrite, MemoryPermission::Read, 0, | 1023 | MemoryPermission::ReadWrite, MemoryPermission::Read, 0, |
| 996 | Kernel::MemoryRegion::SYSTEM, "APT:SharedFont"); | 1024 | Kernel::MemoryRegion::SYSTEM, "APT:SharedFont"); |
| 997 | 1025 | ||
| 998 | if (LoadSharedFont()) { | ||
| 999 | shared_font_loaded = true; | ||
| 1000 | } else if (LoadLegacySharedFont()) { | ||
| 1001 | LOG_WARNING(Service_APT, "Loaded shared font by legacy method"); | ||
| 1002 | shared_font_loaded = true; | ||
| 1003 | } else { | ||
| 1004 | LOG_WARNING(Service_APT, "Unable to load shared font"); | ||
| 1005 | shared_font_loaded = false; | ||
| 1006 | } | ||
| 1007 | |||
| 1008 | lock = Kernel::Mutex::Create(false, "APT_U:Lock"); | 1026 | lock = Kernel::Mutex::Create(false, "APT_U:Lock"); |
| 1009 | 1027 | ||
| 1010 | cpu_percent = 0; | 1028 | cpu_percent = 0; |
diff --git a/src/core/hle/service/cfg/cfg.cpp b/src/core/hle/service/cfg/cfg.cpp index 3dbeb27cc..f26a1f65f 100644 --- a/src/core/hle/service/cfg/cfg.cpp +++ b/src/core/hle/service/cfg/cfg.cpp | |||
| @@ -168,7 +168,7 @@ void GetCountryCodeID(Service::Interface* self) { | |||
| 168 | cmd_buff[2] = country_code_id; | 168 | cmd_buff[2] = country_code_id; |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | static u32 GetRegionValue() { | 171 | u32 GetRegionValue() { |
| 172 | if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) | 172 | if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT) |
| 173 | return preferred_region_code; | 173 | return preferred_region_code; |
| 174 | 174 | ||
diff --git a/src/core/hle/service/cfg/cfg.h b/src/core/hle/service/cfg/cfg.h index 1659ebf32..282b6936b 100644 --- a/src/core/hle/service/cfg/cfg.h +++ b/src/core/hle/service/cfg/cfg.h | |||
| @@ -101,6 +101,8 @@ void GetCountryCodeString(Service::Interface* self); | |||
| 101 | */ | 101 | */ |
| 102 | void GetCountryCodeID(Service::Interface* self); | 102 | void GetCountryCodeID(Service::Interface* self); |
| 103 | 103 | ||
| 104 | u32 GetRegionValue(); | ||
| 105 | |||
| 104 | /** | 106 | /** |
| 105 | * CFG::SecureInfoGetRegion service function | 107 | * CFG::SecureInfoGetRegion service function |
| 106 | * Inputs: | 108 | * Inputs: |
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index 31f34a7ae..aa5d821f9 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp | |||
| @@ -7,9 +7,9 @@ | |||
| 7 | #include <cmath> | 7 | #include <cmath> |
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include "common/logging/log.h" | 9 | #include "common/logging/log.h" |
| 10 | #include "core/3ds.h" | ||
| 10 | #include "core/core.h" | 11 | #include "core/core.h" |
| 11 | #include "core/core_timing.h" | 12 | #include "core/core_timing.h" |
| 12 | #include "core/frontend/emu_window.h" | ||
| 13 | #include "core/frontend/input.h" | 13 | #include "core/frontend/input.h" |
| 14 | #include "core/hle/ipc.h" | 14 | #include "core/hle/ipc.h" |
| 15 | #include "core/hle/kernel/event.h" | 15 | #include "core/hle/kernel/event.h" |
| @@ -19,7 +19,6 @@ | |||
| 19 | #include "core/hle/service/hid/hid_spvr.h" | 19 | #include "core/hle/service/hid/hid_spvr.h" |
| 20 | #include "core/hle/service/hid/hid_user.h" | 20 | #include "core/hle/service/hid/hid_user.h" |
| 21 | #include "core/hle/service/service.h" | 21 | #include "core/hle/service/service.h" |
| 22 | #include "video_core/video_core.h" | ||
| 23 | 22 | ||
| 24 | namespace Service { | 23 | namespace Service { |
| 25 | namespace HID { | 24 | namespace HID { |
| @@ -59,6 +58,7 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton:: | |||
| 59 | buttons; | 58 | buttons; |
| 60 | static std::unique_ptr<Input::AnalogDevice> circle_pad; | 59 | static std::unique_ptr<Input::AnalogDevice> circle_pad; |
| 61 | static std::unique_ptr<Input::MotionDevice> motion_device; | 60 | static std::unique_ptr<Input::MotionDevice> motion_device; |
| 61 | static std::unique_ptr<Input::TouchDevice> touch_device; | ||
| 62 | 62 | ||
| 63 | DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { | 63 | DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { |
| 64 | // 30 degree and 60 degree are angular thresholds for directions | 64 | // 30 degree and 60 degree are angular thresholds for directions |
| @@ -96,6 +96,7 @@ static void LoadInputDevices() { | |||
| 96 | circle_pad = Input::CreateDevice<Input::AnalogDevice>( | 96 | circle_pad = Input::CreateDevice<Input::AnalogDevice>( |
| 97 | Settings::values.analogs[Settings::NativeAnalog::CirclePad]); | 97 | Settings::values.analogs[Settings::NativeAnalog::CirclePad]); |
| 98 | motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device); | 98 | motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device); |
| 99 | touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device); | ||
| 99 | } | 100 | } |
| 100 | 101 | ||
| 101 | static void UnloadInputDevices() { | 102 | static void UnloadInputDevices() { |
| @@ -104,6 +105,7 @@ static void UnloadInputDevices() { | |||
| 104 | } | 105 | } |
| 105 | circle_pad.reset(); | 106 | circle_pad.reset(); |
| 106 | motion_device.reset(); | 107 | motion_device.reset(); |
| 108 | touch_device.reset(); | ||
| 107 | } | 109 | } |
| 108 | 110 | ||
| 109 | static void UpdatePadCallback(u64 userdata, int cycles_late) { | 111 | static void UpdatePadCallback(u64 userdata, int cycles_late) { |
| @@ -172,8 +174,10 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) { | |||
| 172 | // Get the current touch entry | 174 | // Get the current touch entry |
| 173 | TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; | 175 | TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index]; |
| 174 | bool pressed = false; | 176 | bool pressed = false; |
| 175 | 177 | float x, y; | |
| 176 | std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState(); | 178 | std::tie(x, y, pressed) = touch_device->GetStatus(); |
| 179 | touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth); | ||
| 180 | touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight); | ||
| 177 | touch_entry.valid.Assign(pressed ? 1 : 0); | 181 | touch_entry.valid.Assign(pressed ? 1 : 0); |
| 178 | 182 | ||
| 179 | // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which | 183 | // TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which |
diff --git a/src/core/hle/service/nwm/nwm_uds.cpp b/src/core/hle/service/nwm/nwm_uds.cpp index 6dbdff044..893bbb1e7 100644 --- a/src/core/hle/service/nwm/nwm_uds.cpp +++ b/src/core/hle/service/nwm/nwm_uds.cpp | |||
| @@ -4,6 +4,7 @@ | |||
| 4 | 4 | ||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <cstring> | 6 | #include <cstring> |
| 7 | #include <mutex> | ||
| 7 | #include <unordered_map> | 8 | #include <unordered_map> |
| 8 | #include <vector> | 9 | #include <vector> |
| 9 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| @@ -15,8 +16,10 @@ | |||
| 15 | #include "core/hle/result.h" | 16 | #include "core/hle/result.h" |
| 16 | #include "core/hle/service/nwm/nwm_uds.h" | 17 | #include "core/hle/service/nwm/nwm_uds.h" |
| 17 | #include "core/hle/service/nwm/uds_beacon.h" | 18 | #include "core/hle/service/nwm/uds_beacon.h" |
| 19 | #include "core/hle/service/nwm/uds_connection.h" | ||
| 18 | #include "core/hle/service/nwm/uds_data.h" | 20 | #include "core/hle/service/nwm/uds_data.h" |
| 19 | #include "core/memory.h" | 21 | #include "core/memory.h" |
| 22 | #include "network/network.h" | ||
| 20 | 23 | ||
| 21 | namespace Service { | 24 | namespace Service { |
| 22 | namespace NWM { | 25 | namespace NWM { |
| @@ -51,6 +54,135 @@ static NetworkInfo network_info; | |||
| 51 | // Event that will generate and send the 802.11 beacon frames. | 54 | // Event that will generate and send the 802.11 beacon frames. |
| 52 | static int beacon_broadcast_event; | 55 | static int beacon_broadcast_event; |
| 53 | 56 | ||
| 57 | // Mutex to synchronize access to the list of received beacons between the emulation thread and the | ||
| 58 | // network thread. | ||
| 59 | static std::mutex beacon_mutex; | ||
| 60 | |||
| 61 | // Number of beacons to store before we start dropping the old ones. | ||
| 62 | // TODO(Subv): Find a more accurate value for this limit. | ||
| 63 | constexpr size_t MaxBeaconFrames = 15; | ||
| 64 | |||
| 65 | // List of the last <MaxBeaconFrames> beacons received from the network. | ||
| 66 | static std::deque<Network::WifiPacket> received_beacons; | ||
| 67 | |||
| 68 | /** | ||
| 69 | * Returns a list of received 802.11 beacon frames from the specified sender since the last call. | ||
| 70 | */ | ||
| 71 | std::deque<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) { | ||
| 72 | std::lock_guard<std::mutex> lock(beacon_mutex); | ||
| 73 | // TODO(Subv): Filter by sender. | ||
| 74 | return std::move(received_beacons); | ||
| 75 | } | ||
| 76 | |||
| 77 | /// Sends a WifiPacket to the room we're currently connected to. | ||
| 78 | void SendPacket(Network::WifiPacket& packet) { | ||
| 79 | // TODO(Subv): Implement. | ||
| 80 | } | ||
| 81 | |||
| 82 | // Inserts the received beacon frame in the beacon queue and removes any older beacons if the size | ||
| 83 | // limit is exceeded. | ||
| 84 | void HandleBeaconFrame(const Network::WifiPacket& packet) { | ||
| 85 | std::lock_guard<std::mutex> lock(beacon_mutex); | ||
| 86 | |||
| 87 | received_beacons.emplace_back(packet); | ||
| 88 | |||
| 89 | // Discard old beacons if the buffer is full. | ||
| 90 | if (received_beacons.size() > MaxBeaconFrames) | ||
| 91 | received_beacons.pop_front(); | ||
| 92 | } | ||
| 93 | |||
| 94 | /* | ||
| 95 | * Returns an available index in the nodes array for the | ||
| 96 | * currently-hosted UDS network. | ||
| 97 | */ | ||
| 98 | static u16 GetNextAvailableNodeId() { | ||
| 99 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost), | ||
| 100 | "Can not accept clients if we're not hosting a network"); | ||
| 101 | |||
| 102 | for (u16 index = 0; index < connection_status.max_nodes; ++index) { | ||
| 103 | if ((connection_status.node_bitmask & (1 << index)) == 0) | ||
| 104 | return index; | ||
| 105 | } | ||
| 106 | |||
| 107 | // Any connection attempts to an already full network should have been refused. | ||
| 108 | ASSERT_MSG(false, "No available connection slots in the network"); | ||
| 109 | } | ||
| 110 | |||
| 111 | /* | ||
| 112 | * Start a connection sequence with an UDS server. The sequence starts by sending an 802.11 | ||
| 113 | * authentication frame with SEQ1. | ||
| 114 | */ | ||
| 115 | void StartConnectionSequence(const MacAddress& server) { | ||
| 116 | ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected)); | ||
| 117 | |||
| 118 | // TODO(Subv): Handle timeout. | ||
| 119 | |||
| 120 | // Send an authentication frame with SEQ1 | ||
| 121 | using Network::WifiPacket; | ||
| 122 | WifiPacket auth_request; | ||
| 123 | auth_request.channel = network_channel; | ||
| 124 | auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1); | ||
| 125 | auth_request.destination_address = server; | ||
| 126 | auth_request.type = WifiPacket::PacketType::Authentication; | ||
| 127 | |||
| 128 | SendPacket(auth_request); | ||
| 129 | } | ||
| 130 | |||
| 131 | /// Sends an Association Response frame to the specified mac address | ||
| 132 | void SendAssociationResponseFrame(const MacAddress& address) { | ||
| 133 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost)); | ||
| 134 | |||
| 135 | using Network::WifiPacket; | ||
| 136 | WifiPacket assoc_response; | ||
| 137 | assoc_response.channel = network_channel; | ||
| 138 | // TODO(Subv): This will cause multiple clients to end up with the same association id, but | ||
| 139 | // we're not using that for anything. | ||
| 140 | u16 association_id = 1; | ||
| 141 | assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id, | ||
| 142 | network_info.network_id); | ||
| 143 | assoc_response.destination_address = address; | ||
| 144 | assoc_response.type = WifiPacket::PacketType::AssociationResponse; | ||
| 145 | |||
| 146 | SendPacket(assoc_response); | ||
| 147 | } | ||
| 148 | |||
| 149 | /* | ||
| 150 | * Handles the authentication request frame and sends the authentication response and association | ||
| 151 | * response frames. Once an Authentication frame with SEQ1 is received by the server, it responds | ||
| 152 | * with an Authentication frame containing SEQ2, and immediately sends an Association response frame | ||
| 153 | * containing the details of the access point and the assigned association id for the new client. | ||
| 154 | */ | ||
| 155 | void HandleAuthenticationFrame(const Network::WifiPacket& packet) { | ||
| 156 | // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior | ||
| 157 | if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) { | ||
| 158 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost)); | ||
| 159 | |||
| 160 | // Respond with an authentication response frame with SEQ2 | ||
| 161 | using Network::WifiPacket; | ||
| 162 | WifiPacket auth_request; | ||
| 163 | auth_request.channel = network_channel; | ||
| 164 | auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2); | ||
| 165 | auth_request.destination_address = packet.transmitter_address; | ||
| 166 | auth_request.type = WifiPacket::PacketType::Authentication; | ||
| 167 | |||
| 168 | SendPacket(auth_request); | ||
| 169 | |||
| 170 | SendAssociationResponseFrame(packet.transmitter_address); | ||
| 171 | } | ||
| 172 | } | ||
| 173 | |||
| 174 | /// Callback to parse and handle a received wifi packet. | ||
| 175 | void OnWifiPacketReceived(const Network::WifiPacket& packet) { | ||
| 176 | switch (packet.type) { | ||
| 177 | case Network::WifiPacket::PacketType::Beacon: | ||
| 178 | HandleBeaconFrame(packet); | ||
| 179 | break; | ||
| 180 | case Network::WifiPacket::PacketType::Authentication: | ||
| 181 | HandleAuthenticationFrame(packet); | ||
| 182 | break; | ||
| 183 | } | ||
| 184 | } | ||
| 185 | |||
| 54 | /** | 186 | /** |
| 55 | * NWM_UDS::Shutdown service function | 187 | * NWM_UDS::Shutdown service function |
| 56 | * Inputs: | 188 | * Inputs: |
| @@ -111,8 +243,7 @@ static void RecvBeaconBroadcastData(Interface* self) { | |||
| 111 | u32 total_size = sizeof(BeaconDataReplyHeader); | 243 | u32 total_size = sizeof(BeaconDataReplyHeader); |
| 112 | 244 | ||
| 113 | // Retrieve all beacon frames that were received from the desired mac address. | 245 | // Retrieve all beacon frames that were received from the desired mac address. |
| 114 | std::deque<WifiPacket> beacons = | 246 | auto beacons = GetReceivedBeacons(mac_address); |
| 115 | GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address); | ||
| 116 | 247 | ||
| 117 | BeaconDataReplyHeader data_reply_header{}; | 248 | BeaconDataReplyHeader data_reply_header{}; |
| 118 | data_reply_header.total_entries = beacons.size(); | 249 | data_reply_header.total_entries = beacons.size(); |
| @@ -193,6 +324,9 @@ static void InitializeWithVersion(Interface* self) { | |||
| 193 | rb.Push(RESULT_SUCCESS); | 324 | rb.Push(RESULT_SUCCESS); |
| 194 | rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap()); | 325 | rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap()); |
| 195 | 326 | ||
| 327 | // TODO(Subv): Connect the OnWifiPacketReceived function to the wifi packet received callback of | ||
| 328 | // the room we're currently in. | ||
| 329 | |||
| 196 | LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X", | 330 | LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X", |
| 197 | sharedmem_size, version, sharedmem_handle); | 331 | sharedmem_size, version, sharedmem_handle); |
| 198 | } | 332 | } |
| @@ -610,32 +744,23 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) { | |||
| 610 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) | 744 | if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost)) |
| 611 | return; | 745 | return; |
| 612 | 746 | ||
| 613 | // TODO(Subv): Actually send the beacon. | ||
| 614 | std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info); | 747 | std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info); |
| 615 | 748 | ||
| 749 | using Network::WifiPacket; | ||
| 750 | WifiPacket packet; | ||
| 751 | packet.type = WifiPacket::PacketType::Beacon; | ||
| 752 | packet.data = std::move(frame); | ||
| 753 | packet.destination_address = Network::BroadcastMac; | ||
| 754 | packet.channel = network_channel; | ||
| 755 | |||
| 756 | SendPacket(packet); | ||
| 757 | |||
| 616 | // Start broadcasting the network, send a beacon frame every 102.4ms. | 758 | // Start broadcasting the network, send a beacon frame every 102.4ms. |
| 617 | CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, | 759 | CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late, |
| 618 | beacon_broadcast_event, 0); | 760 | beacon_broadcast_event, 0); |
| 619 | } | 761 | } |
| 620 | 762 | ||
| 621 | /* | 763 | /* |
| 622 | * Returns an available index in the nodes array for the | ||
| 623 | * currently-hosted UDS network. | ||
| 624 | */ | ||
| 625 | static u32 GetNextAvailableNodeId() { | ||
| 626 | ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost), | ||
| 627 | "Can not accept clients if we're not hosting a network"); | ||
| 628 | |||
| 629 | for (unsigned index = 0; index < connection_status.max_nodes; ++index) { | ||
| 630 | if ((connection_status.node_bitmask & (1 << index)) == 0) | ||
| 631 | return index; | ||
| 632 | } | ||
| 633 | |||
| 634 | // Any connection attempts to an already full network should have been refused. | ||
| 635 | ASSERT_MSG(false, "No available connection slots in the network"); | ||
| 636 | } | ||
| 637 | |||
| 638 | /* | ||
| 639 | * Called when a client connects to an UDS network we're hosting, | 764 | * Called when a client connects to an UDS network we're hosting, |
| 640 | * updates the connection status and signals the update event. | 765 | * updates the connection status and signals the update event. |
| 641 | * @param network_node_id Network Node Id of the connecting client. | 766 | * @param network_node_id Network Node Id of the connecting client. |
diff --git a/src/core/hle/service/nwm/nwm_uds.h b/src/core/hle/service/nwm/nwm_uds.h index 141f49f9c..f1caaf974 100644 --- a/src/core/hle/service/nwm/nwm_uds.h +++ b/src/core/hle/service/nwm/nwm_uds.h | |||
| @@ -42,6 +42,7 @@ using NodeList = std::vector<NodeInfo>; | |||
| 42 | enum class NetworkStatus { | 42 | enum class NetworkStatus { |
| 43 | NotConnected = 3, | 43 | NotConnected = 3, |
| 44 | ConnectedAsHost = 6, | 44 | ConnectedAsHost = 6, |
| 45 | Connecting = 7, | ||
| 45 | ConnectedAsClient = 9, | 46 | ConnectedAsClient = 9, |
| 46 | ConnectedAsSpectator = 10, | 47 | ConnectedAsSpectator = 10, |
| 47 | }; | 48 | }; |
| @@ -85,6 +86,17 @@ static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wron | |||
| 85 | static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); | 86 | static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); |
| 86 | static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); | 87 | static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); |
| 87 | 88 | ||
| 89 | /// Additional block tag ids in the Beacon and Association Response frames | ||
| 90 | enum class TagId : u8 { | ||
| 91 | SSID = 0, | ||
| 92 | SupportedRates = 1, | ||
| 93 | DSParameterSet = 2, | ||
| 94 | TrafficIndicationMap = 5, | ||
| 95 | CountryInformation = 7, | ||
| 96 | ERPInformation = 42, | ||
| 97 | VendorSpecific = 221 | ||
| 98 | }; | ||
| 99 | |||
| 88 | class NWM_UDS final : public Interface { | 100 | class NWM_UDS final : public Interface { |
| 89 | public: | 101 | public: |
| 90 | NWM_UDS(); | 102 | NWM_UDS(); |
diff --git a/src/core/hle/service/nwm/uds_beacon.cpp b/src/core/hle/service/nwm/uds_beacon.cpp index 6332b404c..552eaf65e 100644 --- a/src/core/hle/service/nwm/uds_beacon.cpp +++ b/src/core/hle/service/nwm/uds_beacon.cpp | |||
| @@ -325,8 +325,5 @@ std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeL | |||
| 325 | return buffer; | 325 | return buffer; |
| 326 | } | 326 | } |
| 327 | 327 | ||
| 328 | std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) { | ||
| 329 | return {}; | ||
| 330 | } | ||
| 331 | } // namespace NWM | 328 | } // namespace NWM |
| 332 | } // namespace Service | 329 | } // namespace Service |
diff --git a/src/core/hle/service/nwm/uds_beacon.h b/src/core/hle/service/nwm/uds_beacon.h index caacf4c6f..50cc76da2 100644 --- a/src/core/hle/service/nwm/uds_beacon.h +++ b/src/core/hle/service/nwm/uds_beacon.h | |||
| @@ -17,17 +17,6 @@ namespace NWM { | |||
| 17 | using MacAddress = std::array<u8, 6>; | 17 | using MacAddress = std::array<u8, 6>; |
| 18 | constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; | 18 | constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; |
| 19 | 19 | ||
| 20 | /// Additional block tag ids in the Beacon frames | ||
| 21 | enum class TagId : u8 { | ||
| 22 | SSID = 0, | ||
| 23 | SupportedRates = 1, | ||
| 24 | DSParameterSet = 2, | ||
| 25 | TrafficIndicationMap = 5, | ||
| 26 | CountryInformation = 7, | ||
| 27 | ERPInformation = 42, | ||
| 28 | VendorSpecific = 221 | ||
| 29 | }; | ||
| 30 | |||
| 31 | /** | 20 | /** |
| 32 | * Internal vendor-specific tag ids as stored inside | 21 | * Internal vendor-specific tag ids as stored inside |
| 33 | * VendorSpecific blocks in the Beacon frames. | 22 | * VendorSpecific blocks in the Beacon frames. |
| @@ -135,20 +124,6 @@ struct BeaconData { | |||
| 135 | 124 | ||
| 136 | static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size."); | 125 | static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size."); |
| 137 | 126 | ||
| 138 | /// Information about a received WiFi packet. | ||
| 139 | /// Acts as our own 802.11 header. | ||
| 140 | struct WifiPacket { | ||
| 141 | enum class PacketType { Beacon, Data }; | ||
| 142 | |||
| 143 | PacketType type; ///< The type of 802.11 frame, Beacon / Data. | ||
| 144 | |||
| 145 | /// Raw 802.11 frame data, starting at the management frame header for management frames. | ||
| 146 | std::vector<u8> data; | ||
| 147 | MacAddress transmitter_address; ///< Mac address of the transmitter. | ||
| 148 | MacAddress destination_address; ///< Mac address of the receiver. | ||
| 149 | u8 channel; ///< WiFi channel where this frame was transmitted. | ||
| 150 | }; | ||
| 151 | |||
| 152 | /** | 127 | /** |
| 153 | * Decrypts the beacon data buffer for the network described by `network_info`. | 128 | * Decrypts the beacon data buffer for the network described by `network_info`. |
| 154 | */ | 129 | */ |
| @@ -161,10 +136,5 @@ void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer) | |||
| 161 | */ | 136 | */ |
| 162 | std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes); | 137 | std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes); |
| 163 | 138 | ||
| 164 | /** | ||
| 165 | * Returns a list of received 802.11 frames from the specified sender | ||
| 166 | * matching the type since the last call. | ||
| 167 | */ | ||
| 168 | std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender); | ||
| 169 | } // namespace NWM | 139 | } // namespace NWM |
| 170 | } // namespace Service | 140 | } // namespace Service |
diff --git a/src/core/hle/service/nwm/uds_connection.cpp b/src/core/hle/service/nwm/uds_connection.cpp new file mode 100644 index 000000000..c8a76ec2a --- /dev/null +++ b/src/core/hle/service/nwm/uds_connection.cpp | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "core/hle/service/nwm/nwm_uds.h" | ||
| 6 | #include "core/hle/service/nwm/uds_connection.h" | ||
| 7 | #include "fmt/format.h" | ||
| 8 | |||
| 9 | namespace Service { | ||
| 10 | namespace NWM { | ||
| 11 | |||
| 12 | // Note: These values were taken from a packet capture of an o3DS XL | ||
| 13 | // broadcasting a Super Smash Bros. 4 lobby. | ||
| 14 | constexpr u16 DefaultExtraCapabilities = 0x0431; | ||
| 15 | |||
| 16 | std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq) { | ||
| 17 | AuthenticationFrame frame{}; | ||
| 18 | frame.auth_seq = static_cast<u16>(seq); | ||
| 19 | |||
| 20 | std::vector<u8> data(sizeof(frame)); | ||
| 21 | std::memcpy(data.data(), &frame, sizeof(frame)); | ||
| 22 | |||
| 23 | return data; | ||
| 24 | } | ||
| 25 | |||
| 26 | AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body) { | ||
| 27 | AuthenticationFrame frame; | ||
| 28 | std::memcpy(&frame, body.data(), sizeof(frame)); | ||
| 29 | |||
| 30 | return static_cast<AuthenticationSeq>(frame.auth_seq); | ||
| 31 | } | ||
| 32 | |||
| 33 | /** | ||
| 34 | * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte character representation of the | ||
| 35 | * specified network id as the SSID value. | ||
| 36 | * @param network_id The network id to use. | ||
| 37 | * @returns A buffer with the SSID tag. | ||
| 38 | */ | ||
| 39 | static std::vector<u8> GenerateSSIDTag(u32 network_id) { | ||
| 40 | constexpr u8 SSIDSize = 8; | ||
| 41 | |||
| 42 | struct { | ||
| 43 | u8 id = static_cast<u8>(TagId::SSID); | ||
| 44 | u8 size = SSIDSize; | ||
| 45 | } tag_header; | ||
| 46 | |||
| 47 | std::vector<u8> buffer(sizeof(tag_header) + SSIDSize); | ||
| 48 | |||
| 49 | std::memcpy(buffer.data(), &tag_header, sizeof(tag_header)); | ||
| 50 | |||
| 51 | std::string network_name = fmt::format("{0:08X}", network_id); | ||
| 52 | |||
| 53 | std::memcpy(buffer.data() + sizeof(tag_header), network_name.c_str(), SSIDSize); | ||
| 54 | |||
| 55 | return buffer; | ||
| 56 | } | ||
| 57 | |||
| 58 | std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id) { | ||
| 59 | AssociationResponseFrame frame{}; | ||
| 60 | frame.capabilities = DefaultExtraCapabilities; | ||
| 61 | frame.status_code = static_cast<u16>(status); | ||
| 62 | // The association id is ORed with this magic value (0xC000) | ||
| 63 | constexpr u16 AssociationIdMagic = 0xC000; | ||
| 64 | frame.assoc_id = association_id | AssociationIdMagic; | ||
| 65 | |||
| 66 | std::vector<u8> data(sizeof(frame)); | ||
| 67 | std::memcpy(data.data(), &frame, sizeof(frame)); | ||
| 68 | |||
| 69 | auto ssid_tag = GenerateSSIDTag(network_id); | ||
| 70 | data.insert(data.end(), ssid_tag.begin(), ssid_tag.end()); | ||
| 71 | |||
| 72 | // TODO(Subv): Add the SupportedRates tag. | ||
| 73 | // TODO(Subv): Add the DSParameterSet tag. | ||
| 74 | // TODO(Subv): Add the ERPInformation tag. | ||
| 75 | return data; | ||
| 76 | } | ||
| 77 | |||
| 78 | } // namespace NWM | ||
| 79 | } // namespace Service | ||
diff --git a/src/core/hle/service/nwm/uds_connection.h b/src/core/hle/service/nwm/uds_connection.h new file mode 100644 index 000000000..73f55a4fd --- /dev/null +++ b/src/core/hle/service/nwm/uds_connection.h | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <vector> | ||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "common/swap.h" | ||
| 10 | #include "core/hle/service/service.h" | ||
| 11 | |||
| 12 | namespace Service { | ||
| 13 | namespace NWM { | ||
| 14 | |||
| 15 | /// Sequence number of the 802.11 authentication frames. | ||
| 16 | enum class AuthenticationSeq : u16 { SEQ1 = 1, SEQ2 = 2 }; | ||
| 17 | |||
| 18 | enum class AuthAlgorithm : u16 { OpenSystem = 0 }; | ||
| 19 | |||
| 20 | enum class AuthStatus : u16 { Successful = 0 }; | ||
| 21 | |||
| 22 | enum class AssocStatus : u16 { Successful = 0 }; | ||
| 23 | |||
| 24 | struct AuthenticationFrame { | ||
| 25 | u16_le auth_algorithm = static_cast<u16>(AuthAlgorithm::OpenSystem); | ||
| 26 | u16_le auth_seq; | ||
| 27 | u16_le status_code = static_cast<u16>(AuthStatus::Successful); | ||
| 28 | }; | ||
| 29 | |||
| 30 | static_assert(sizeof(AuthenticationFrame) == 6, "AuthenticationFrame has wrong size"); | ||
| 31 | |||
| 32 | struct AssociationResponseFrame { | ||
| 33 | u16_le capabilities; | ||
| 34 | u16_le status_code; | ||
| 35 | u16_le assoc_id; | ||
| 36 | }; | ||
| 37 | |||
| 38 | static_assert(sizeof(AssociationResponseFrame) == 6, "AssociationResponseFrame has wrong size"); | ||
| 39 | |||
| 40 | /// Generates an 802.11 authentication frame, starting at the frame body. | ||
| 41 | std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq); | ||
| 42 | |||
| 43 | /// Returns the sequence number from the body of an Authentication frame. | ||
| 44 | AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body); | ||
| 45 | |||
| 46 | /// Generates an 802.11 association response frame with the specified status, association id and | ||
| 47 | /// network id, starting at the frame body. | ||
| 48 | std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id); | ||
| 49 | |||
| 50 | } // namespace NWM | ||
| 51 | } // namespace Service | ||
diff --git a/src/core/hle/svc.cpp b/src/core/hle/svc.cpp index b98938cb4..dfc36748c 100644 --- a/src/core/hle/svc.cpp +++ b/src/core/hle/svc.cpp | |||
| @@ -1334,7 +1334,7 @@ void CallSVC(u32 immediate) { | |||
| 1334 | MICROPROFILE_SCOPE(Kernel_SVC); | 1334 | MICROPROFILE_SCOPE(Kernel_SVC); |
| 1335 | 1335 | ||
| 1336 | // Lock the global kernel mutex when we enter the kernel HLE. | 1336 | // Lock the global kernel mutex when we enter the kernel HLE. |
| 1337 | std::lock_guard<std::mutex> lock(HLE::g_hle_lock); | 1337 | std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); |
| 1338 | 1338 | ||
| 1339 | const FunctionDef* info = GetSVCInfo(immediate); | 1339 | const FunctionDef* info = GetSVCInfo(immediate); |
| 1340 | if (info) { | 1340 | if (info) { |
diff --git a/src/core/loader/3dsx.cpp b/src/core/loader/3dsx.cpp index 74e336487..69cdc0867 100644 --- a/src/core/loader/3dsx.cpp +++ b/src/core/loader/3dsx.cpp | |||
| @@ -270,6 +270,7 @@ ResultStatus AppLoader_THREEDSX::Load() { | |||
| 270 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); | 270 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); |
| 271 | Kernel::g_current_process->svc_access_mask.set(); | 271 | Kernel::g_current_process->svc_access_mask.set(); |
| 272 | Kernel::g_current_process->address_mappings = default_address_mappings; | 272 | Kernel::g_current_process->address_mappings = default_address_mappings; |
| 273 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | ||
| 273 | 274 | ||
| 274 | // Attach the default resource limit (APPLICATION) to the process | 275 | // Attach the default resource limit (APPLICATION) to the process |
| 275 | Kernel::g_current_process->resource_limit = | 276 | Kernel::g_current_process->resource_limit = |
diff --git a/src/core/loader/elf.cpp b/src/core/loader/elf.cpp index cfcde9167..2f27606a1 100644 --- a/src/core/loader/elf.cpp +++ b/src/core/loader/elf.cpp | |||
| @@ -397,6 +397,7 @@ ResultStatus AppLoader_ELF::Load() { | |||
| 397 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); | 397 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); |
| 398 | Kernel::g_current_process->svc_access_mask.set(); | 398 | Kernel::g_current_process->svc_access_mask.set(); |
| 399 | Kernel::g_current_process->address_mappings = default_address_mappings; | 399 | Kernel::g_current_process->address_mappings = default_address_mappings; |
| 400 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | ||
| 400 | 401 | ||
| 401 | // Attach the default resource limit (APPLICATION) to the process | 402 | // Attach the default resource limit (APPLICATION) to the process |
| 402 | Kernel::g_current_process->resource_limit = | 403 | Kernel::g_current_process->resource_limit = |
diff --git a/src/core/loader/ncch.cpp b/src/core/loader/ncch.cpp index 7aff7f29b..79ea50147 100644 --- a/src/core/loader/ncch.cpp +++ b/src/core/loader/ncch.cpp | |||
| @@ -172,6 +172,7 @@ ResultStatus AppLoader_NCCH::LoadExec() { | |||
| 172 | codeset->memory = std::make_shared<std::vector<u8>>(std::move(code)); | 172 | codeset->memory = std::make_shared<std::vector<u8>>(std::move(code)); |
| 173 | 173 | ||
| 174 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); | 174 | Kernel::g_current_process = Kernel::Process::Create(std::move(codeset)); |
| 175 | Memory::current_page_table = &Kernel::g_current_process->vm_manager.page_table; | ||
| 175 | 176 | ||
| 176 | // Attach a resource limit to the process based on the resource limit category | 177 | // Attach a resource limit to the process based on the resource limit category |
| 177 | Kernel::g_current_process->resource_limit = | 178 | Kernel::g_current_process->resource_limit = |
diff --git a/src/core/memory.cpp b/src/core/memory.cpp index a3c5f4a9d..68a6b1ac2 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp | |||
| @@ -4,83 +4,31 @@ | |||
| 4 | 4 | ||
| 5 | #include <array> | 5 | #include <array> |
| 6 | #include <cstring> | 6 | #include <cstring> |
| 7 | #include "audio_core/audio_core.h" | ||
| 7 | #include "common/assert.h" | 8 | #include "common/assert.h" |
| 8 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 9 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 10 | #include "common/swap.h" | 11 | #include "common/swap.h" |
| 12 | #include "core/hle/kernel/memory.h" | ||
| 11 | #include "core/hle/kernel/process.h" | 13 | #include "core/hle/kernel/process.h" |
| 12 | #include "core/hle/lock.h" | 14 | #include "core/hle/lock.h" |
| 13 | #include "core/memory.h" | 15 | #include "core/memory.h" |
| 14 | #include "core/memory_setup.h" | 16 | #include "core/memory_setup.h" |
| 15 | #include "core/mmio.h" | ||
| 16 | #include "video_core/renderer_base.h" | 17 | #include "video_core/renderer_base.h" |
| 17 | #include "video_core/video_core.h" | 18 | #include "video_core/video_core.h" |
| 18 | 19 | ||
| 19 | namespace Memory { | 20 | namespace Memory { |
| 20 | 21 | ||
| 21 | enum class PageType { | 22 | static std::array<u8, Memory::VRAM_SIZE> vram; |
| 22 | /// Page is unmapped and should cause an access error. | 23 | static std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram; |
| 23 | Unmapped, | ||
| 24 | /// Page is mapped to regular memory. This is the only type you can get pointers to. | ||
| 25 | Memory, | ||
| 26 | /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and | ||
| 27 | /// invalidation | ||
| 28 | RasterizerCachedMemory, | ||
| 29 | /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. | ||
| 30 | Special, | ||
| 31 | /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and | ||
| 32 | /// invalidation | ||
| 33 | RasterizerCachedSpecial, | ||
| 34 | }; | ||
| 35 | |||
| 36 | struct SpecialRegion { | ||
| 37 | VAddr base; | ||
| 38 | u32 size; | ||
| 39 | MMIORegionPointer handler; | ||
| 40 | }; | ||
| 41 | 24 | ||
| 42 | /** | 25 | PageTable* current_page_table = nullptr; |
| 43 | * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely | ||
| 44 | * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and | ||
| 45 | * fetching requirements when accessing. In the usual case of an access to regular memory, it only | ||
| 46 | * requires an indexed fetch and a check for NULL. | ||
| 47 | */ | ||
| 48 | struct PageTable { | ||
| 49 | /** | ||
| 50 | * Array of memory pointers backing each page. An entry can only be non-null if the | ||
| 51 | * corresponding entry in the `attributes` array is of type `Memory`. | ||
| 52 | */ | ||
| 53 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES> pointers; | ||
| 54 | |||
| 55 | /** | ||
| 56 | * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of | ||
| 57 | * type `Special`. | ||
| 58 | */ | ||
| 59 | std::vector<SpecialRegion> special_regions; | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Array of fine grained page attributes. If it is set to any value other than `Memory`, then | ||
| 63 | * the corresponding entry in `pointers` MUST be set to null. | ||
| 64 | */ | ||
| 65 | std::array<PageType, PAGE_TABLE_NUM_ENTRIES> attributes; | ||
| 66 | |||
| 67 | /** | ||
| 68 | * Indicates the number of externally cached resources touching a page that should be | ||
| 69 | * flushed before the memory is accessed | ||
| 70 | */ | ||
| 71 | std::array<u8, PAGE_TABLE_NUM_ENTRIES> cached_res_count; | ||
| 72 | }; | ||
| 73 | |||
| 74 | /// Singular page table used for the singleton process | ||
| 75 | static PageTable main_page_table; | ||
| 76 | /// Currently active page table | ||
| 77 | static PageTable* current_page_table = &main_page_table; | ||
| 78 | 26 | ||
| 79 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers() { | 27 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers() { |
| 80 | return ¤t_page_table->pointers; | 28 | return ¤t_page_table->pointers; |
| 81 | } | 29 | } |
| 82 | 30 | ||
| 83 | static void MapPages(u32 base, u32 size, u8* memory, PageType type) { | 31 | static void MapPages(PageTable& page_table, u32 base, u32 size, u8* memory, PageType type) { |
| 84 | LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE, | 32 | LOG_DEBUG(HW_Memory, "Mapping %p onto %08X-%08X", memory, base * PAGE_SIZE, |
| 85 | (base + size) * PAGE_SIZE); | 33 | (base + size) * PAGE_SIZE); |
| 86 | 34 | ||
| @@ -91,9 +39,9 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { | |||
| 91 | while (base != end) { | 39 | while (base != end) { |
| 92 | ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base); | 40 | ASSERT_MSG(base < PAGE_TABLE_NUM_ENTRIES, "out of range mapping at %08X", base); |
| 93 | 41 | ||
| 94 | current_page_table->attributes[base] = type; | 42 | page_table.attributes[base] = type; |
| 95 | current_page_table->pointers[base] = memory; | 43 | page_table.pointers[base] = memory; |
| 96 | current_page_table->cached_res_count[base] = 0; | 44 | page_table.cached_res_count[base] = 0; |
| 97 | 45 | ||
| 98 | base += 1; | 46 | base += 1; |
| 99 | if (memory != nullptr) | 47 | if (memory != nullptr) |
| @@ -101,30 +49,24 @@ static void MapPages(u32 base, u32 size, u8* memory, PageType type) { | |||
| 101 | } | 49 | } |
| 102 | } | 50 | } |
| 103 | 51 | ||
| 104 | void InitMemoryMap() { | 52 | void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target) { |
| 105 | main_page_table.pointers.fill(nullptr); | ||
| 106 | main_page_table.attributes.fill(PageType::Unmapped); | ||
| 107 | main_page_table.cached_res_count.fill(0); | ||
| 108 | } | ||
| 109 | |||
| 110 | void MapMemoryRegion(VAddr base, u32 size, u8* target) { | ||
| 111 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); | 53 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); |
| 112 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); | 54 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); |
| 113 | MapPages(base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); | 55 | MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, PageType::Memory); |
| 114 | } | 56 | } |
| 115 | 57 | ||
| 116 | void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler) { | 58 | void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler) { |
| 117 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); | 59 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); |
| 118 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); | 60 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); |
| 119 | MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); | 61 | MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Special); |
| 120 | 62 | ||
| 121 | current_page_table->special_regions.emplace_back(SpecialRegion{base, size, mmio_handler}); | 63 | page_table.special_regions.emplace_back(SpecialRegion{base, size, mmio_handler}); |
| 122 | } | 64 | } |
| 123 | 65 | ||
| 124 | void UnmapRegion(VAddr base, u32 size) { | 66 | void UnmapRegion(PageTable& page_table, VAddr base, u32 size) { |
| 125 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); | 67 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: %08X", size); |
| 126 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); | 68 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: %08X", base); |
| 127 | MapPages(base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); | 69 | MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, nullptr, PageType::Unmapped); |
| 128 | } | 70 | } |
| 129 | 71 | ||
| 130 | /** | 72 | /** |
| @@ -183,7 +125,7 @@ T Read(const VAddr vaddr) { | |||
| 183 | } | 125 | } |
| 184 | 126 | ||
| 185 | // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state | 127 | // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state |
| 186 | std::lock_guard<std::mutex> lock(HLE::g_hle_lock); | 128 | std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); |
| 187 | 129 | ||
| 188 | PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; | 130 | PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; |
| 189 | switch (type) { | 131 | switch (type) { |
| @@ -224,7 +166,7 @@ void Write(const VAddr vaddr, const T data) { | |||
| 224 | } | 166 | } |
| 225 | 167 | ||
| 226 | // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state | 168 | // The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state |
| 227 | std::lock_guard<std::mutex> lock(HLE::g_hle_lock); | 169 | std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock); |
| 228 | 170 | ||
| 229 | PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; | 171 | PageType type = current_page_table->attributes[vaddr >> PAGE_BITS]; |
| 230 | switch (type) { | 172 | switch (type) { |
| @@ -273,8 +215,7 @@ bool IsValidVirtualAddress(const VAddr vaddr) { | |||
| 273 | } | 215 | } |
| 274 | 216 | ||
| 275 | bool IsValidPhysicalAddress(const PAddr paddr) { | 217 | bool IsValidPhysicalAddress(const PAddr paddr) { |
| 276 | boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(paddr); | 218 | return GetPhysicalPointer(paddr) != nullptr; |
| 277 | return vaddr && IsValidVirtualAddress(*vaddr); | ||
| 278 | } | 219 | } |
| 279 | 220 | ||
| 280 | u8* GetPointer(const VAddr vaddr) { | 221 | u8* GetPointer(const VAddr vaddr) { |
| @@ -306,9 +247,63 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) { | |||
| 306 | } | 247 | } |
| 307 | 248 | ||
| 308 | u8* GetPhysicalPointer(PAddr address) { | 249 | u8* GetPhysicalPointer(PAddr address) { |
| 309 | // TODO(Subv): This call should not go through the application's memory mapping. | 250 | struct MemoryArea { |
| 310 | boost::optional<VAddr> vaddr = PhysicalToVirtualAddress(address); | 251 | PAddr paddr_base; |
| 311 | return vaddr ? GetPointer(*vaddr) : nullptr; | 252 | u32 size; |
| 253 | }; | ||
| 254 | |||
| 255 | static constexpr MemoryArea memory_areas[] = { | ||
| 256 | {VRAM_PADDR, VRAM_SIZE}, | ||
| 257 | {IO_AREA_PADDR, IO_AREA_SIZE}, | ||
| 258 | {DSP_RAM_PADDR, DSP_RAM_SIZE}, | ||
| 259 | {FCRAM_PADDR, FCRAM_N3DS_SIZE}, | ||
| 260 | {N3DS_EXTRA_RAM_PADDR, N3DS_EXTRA_RAM_SIZE}, | ||
| 261 | }; | ||
| 262 | |||
| 263 | const auto area = | ||
| 264 | std::find_if(std::begin(memory_areas), std::end(memory_areas), [&](const auto& area) { | ||
| 265 | return address >= area.paddr_base && address < area.paddr_base + area.size; | ||
| 266 | }); | ||
| 267 | |||
| 268 | if (area == std::end(memory_areas)) { | ||
| 269 | LOG_ERROR(HW_Memory, "unknown GetPhysicalPointer @ 0x%08X", address); | ||
| 270 | return nullptr; | ||
| 271 | } | ||
| 272 | |||
| 273 | if (area->paddr_base == IO_AREA_PADDR) { | ||
| 274 | LOG_ERROR(HW_Memory, "MMIO mappings are not supported yet. phys_addr=0x%08X", address); | ||
| 275 | return nullptr; | ||
| 276 | } | ||
| 277 | |||
| 278 | u32 offset_into_region = address - area->paddr_base; | ||
| 279 | |||
| 280 | u8* target_pointer = nullptr; | ||
| 281 | switch (area->paddr_base) { | ||
| 282 | case VRAM_PADDR: | ||
| 283 | target_pointer = vram.data() + offset_into_region; | ||
| 284 | break; | ||
| 285 | case DSP_RAM_PADDR: | ||
| 286 | target_pointer = AudioCore::GetDspMemory().data() + offset_into_region; | ||
| 287 | break; | ||
| 288 | case FCRAM_PADDR: | ||
| 289 | for (const auto& region : Kernel::memory_regions) { | ||
| 290 | if (offset_into_region >= region.base && | ||
| 291 | offset_into_region < region.base + region.size) { | ||
| 292 | target_pointer = | ||
| 293 | region.linear_heap_memory->data() + offset_into_region - region.base; | ||
| 294 | break; | ||
| 295 | } | ||
| 296 | } | ||
| 297 | ASSERT_MSG(target_pointer != nullptr, "Invalid FCRAM address"); | ||
| 298 | break; | ||
| 299 | case N3DS_EXTRA_RAM_PADDR: | ||
| 300 | target_pointer = n3ds_extra_ram.data() + offset_into_region; | ||
| 301 | break; | ||
| 302 | default: | ||
| 303 | UNREACHABLE(); | ||
| 304 | } | ||
| 305 | |||
| 306 | return target_pointer; | ||
| 312 | } | 307 | } |
| 313 | 308 | ||
| 314 | void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { | 309 | void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { |
diff --git a/src/core/memory.h b/src/core/memory.h index c8c56babd..b228a48c2 100644 --- a/src/core/memory.h +++ b/src/core/memory.h | |||
| @@ -7,8 +7,10 @@ | |||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <cstddef> | 8 | #include <cstddef> |
| 9 | #include <string> | 9 | #include <string> |
| 10 | #include <vector> | ||
| 10 | #include <boost/optional.hpp> | 11 | #include <boost/optional.hpp> |
| 11 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 13 | #include "core/mmio.h" | ||
| 12 | 14 | ||
| 13 | namespace Memory { | 15 | namespace Memory { |
| 14 | 16 | ||
| @@ -21,6 +23,59 @@ const u32 PAGE_MASK = PAGE_SIZE - 1; | |||
| 21 | const int PAGE_BITS = 12; | 23 | const int PAGE_BITS = 12; |
| 22 | const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS); | 24 | const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS); |
| 23 | 25 | ||
| 26 | enum class PageType { | ||
| 27 | /// Page is unmapped and should cause an access error. | ||
| 28 | Unmapped, | ||
| 29 | /// Page is mapped to regular memory. This is the only type you can get pointers to. | ||
| 30 | Memory, | ||
| 31 | /// Page is mapped to regular memory, but also needs to check for rasterizer cache flushing and | ||
| 32 | /// invalidation | ||
| 33 | RasterizerCachedMemory, | ||
| 34 | /// Page is mapped to a I/O region. Writing and reading to this page is handled by functions. | ||
| 35 | Special, | ||
| 36 | /// Page is mapped to a I/O region, but also needs to check for rasterizer cache flushing and | ||
| 37 | /// invalidation | ||
| 38 | RasterizerCachedSpecial, | ||
| 39 | }; | ||
| 40 | |||
| 41 | struct SpecialRegion { | ||
| 42 | VAddr base; | ||
| 43 | u32 size; | ||
| 44 | MMIORegionPointer handler; | ||
| 45 | }; | ||
| 46 | |||
| 47 | /** | ||
| 48 | * A (reasonably) fast way of allowing switchable and remappable process address spaces. It loosely | ||
| 49 | * mimics the way a real CPU page table works, but instead is optimized for minimal decoding and | ||
| 50 | * fetching requirements when accessing. In the usual case of an access to regular memory, it only | ||
| 51 | * requires an indexed fetch and a check for NULL. | ||
| 52 | */ | ||
| 53 | struct PageTable { | ||
| 54 | /** | ||
| 55 | * Array of memory pointers backing each page. An entry can only be non-null if the | ||
| 56 | * corresponding entry in the `attributes` array is of type `Memory`. | ||
| 57 | */ | ||
| 58 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES> pointers; | ||
| 59 | |||
| 60 | /** | ||
| 61 | * Contains MMIO handlers that back memory regions whose entries in the `attribute` array is of | ||
| 62 | * type `Special`. | ||
| 63 | */ | ||
| 64 | std::vector<SpecialRegion> special_regions; | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Array of fine grained page attributes. If it is set to any value other than `Memory`, then | ||
| 68 | * the corresponding entry in `pointers` MUST be set to null. | ||
| 69 | */ | ||
| 70 | std::array<PageType, PAGE_TABLE_NUM_ENTRIES> attributes; | ||
| 71 | |||
| 72 | /** | ||
| 73 | * Indicates the number of externally cached resources touching a page that should be | ||
| 74 | * flushed before the memory is accessed | ||
| 75 | */ | ||
| 76 | std::array<u8, PAGE_TABLE_NUM_ENTRIES> cached_res_count; | ||
| 77 | }; | ||
| 78 | |||
| 24 | /// Physical memory regions as seen from the ARM11 | 79 | /// Physical memory regions as seen from the ARM11 |
| 25 | enum : PAddr { | 80 | enum : PAddr { |
| 26 | /// IO register area | 81 | /// IO register area |
| @@ -126,6 +181,9 @@ enum : VAddr { | |||
| 126 | NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, | 181 | NEW_LINEAR_HEAP_VADDR_END = NEW_LINEAR_HEAP_VADDR + NEW_LINEAR_HEAP_SIZE, |
| 127 | }; | 182 | }; |
| 128 | 183 | ||
| 184 | /// Currently active page table | ||
| 185 | extern PageTable* current_page_table; | ||
| 186 | |||
| 129 | bool IsValidVirtualAddress(const VAddr addr); | 187 | bool IsValidVirtualAddress(const VAddr addr); |
| 130 | bool IsValidPhysicalAddress(const PAddr addr); | 188 | bool IsValidPhysicalAddress(const PAddr addr); |
| 131 | 189 | ||
| @@ -169,8 +227,6 @@ boost::optional<VAddr> PhysicalToVirtualAddress(PAddr addr); | |||
| 169 | 227 | ||
| 170 | /** | 228 | /** |
| 171 | * Gets a pointer to the memory region beginning at the specified physical address. | 229 | * Gets a pointer to the memory region beginning at the specified physical address. |
| 172 | * | ||
| 173 | * @note This is currently implemented using PhysicalToVirtualAddress(). | ||
| 174 | */ | 230 | */ |
| 175 | u8* GetPhysicalPointer(PAddr address); | 231 | u8* GetPhysicalPointer(PAddr address); |
| 176 | 232 | ||
| @@ -209,4 +265,4 @@ void RasterizerFlushVirtualRegion(VAddr start, u32 size, FlushMode mode); | |||
| 209 | * retrieve the current page table for that purpose. | 265 | * retrieve the current page table for that purpose. |
| 210 | */ | 266 | */ |
| 211 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers(); | 267 | std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers(); |
| 212 | } | 268 | } // namespace Memory |
diff --git a/src/core/memory_setup.h b/src/core/memory_setup.h index 3fdf3a87d..c58baa50b 100644 --- a/src/core/memory_setup.h +++ b/src/core/memory_setup.h | |||
| @@ -9,24 +9,24 @@ | |||
| 9 | 9 | ||
| 10 | namespace Memory { | 10 | namespace Memory { |
| 11 | 11 | ||
| 12 | void InitMemoryMap(); | ||
| 13 | |||
| 14 | /** | 12 | /** |
| 15 | * Maps an allocated buffer onto a region of the emulated process address space. | 13 | * Maps an allocated buffer onto a region of the emulated process address space. |
| 16 | * | 14 | * |
| 15 | * @param page_table The page table of the emulated process. | ||
| 17 | * @param base The address to start mapping at. Must be page-aligned. | 16 | * @param base The address to start mapping at. Must be page-aligned. |
| 18 | * @param size The amount of bytes to map. Must be page-aligned. | 17 | * @param size The amount of bytes to map. Must be page-aligned. |
| 19 | * @param target Buffer with the memory backing the mapping. Must be of length at least `size`. | 18 | * @param target Buffer with the memory backing the mapping. Must be of length at least `size`. |
| 20 | */ | 19 | */ |
| 21 | void MapMemoryRegion(VAddr base, u32 size, u8* target); | 20 | void MapMemoryRegion(PageTable& page_table, VAddr base, u32 size, u8* target); |
| 22 | 21 | ||
| 23 | /** | 22 | /** |
| 24 | * Maps a region of the emulated process address space as a IO region. | 23 | * Maps a region of the emulated process address space as a IO region. |
| 24 | * @param page_table The page table of the emulated process. | ||
| 25 | * @param base The address to start mapping at. Must be page-aligned. | 25 | * @param base The address to start mapping at. Must be page-aligned. |
| 26 | * @param size The amount of bytes to map. Must be page-aligned. | 26 | * @param size The amount of bytes to map. Must be page-aligned. |
| 27 | * @param mmio_handler The handler that backs the mapping. | 27 | * @param mmio_handler The handler that backs the mapping. |
| 28 | */ | 28 | */ |
| 29 | void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler); | 29 | void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler); |
| 30 | 30 | ||
| 31 | void UnmapRegion(VAddr base, u32 size); | 31 | void UnmapRegion(PageTable& page_table, VAddr base, u32 size); |
| 32 | } | 32 | } |
diff --git a/src/core/settings.h b/src/core/settings.h index bf8014c5a..024f14666 100644 --- a/src/core/settings.h +++ b/src/core/settings.h | |||
| @@ -81,6 +81,7 @@ struct Values { | |||
| 81 | std::array<std::string, NativeButton::NumButtons> buttons; | 81 | std::array<std::string, NativeButton::NumButtons> buttons; |
| 82 | std::array<std::string, NativeAnalog::NumAnalogs> analogs; | 82 | std::array<std::string, NativeAnalog::NumAnalogs> analogs; |
| 83 | std::string motion_device; | 83 | std::string motion_device; |
| 84 | std::string touch_device; | ||
| 84 | 85 | ||
| 85 | // Core | 86 | // Core |
| 86 | bool use_cpu_jit; | 87 | bool use_cpu_jit; |
diff --git a/src/tests/core/arm/arm_test_common.cpp b/src/tests/core/arm/arm_test_common.cpp index 1df6c5677..8384ce744 100644 --- a/src/tests/core/arm/arm_test_common.cpp +++ b/src/tests/core/arm/arm_test_common.cpp | |||
| @@ -3,20 +3,30 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include "core/core.h" | 5 | #include "core/core.h" |
| 6 | #include "core/memory.h" | ||
| 6 | #include "core/memory_setup.h" | 7 | #include "core/memory_setup.h" |
| 7 | #include "tests/core/arm/arm_test_common.h" | 8 | #include "tests/core/arm/arm_test_common.h" |
| 8 | 9 | ||
| 9 | namespace ArmTests { | 10 | namespace ArmTests { |
| 10 | 11 | ||
| 12 | static Memory::PageTable page_table; | ||
| 13 | |||
| 11 | TestEnvironment::TestEnvironment(bool mutable_memory_) | 14 | TestEnvironment::TestEnvironment(bool mutable_memory_) |
| 12 | : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { | 15 | : mutable_memory(mutable_memory_), test_memory(std::make_shared<TestMemory>(this)) { |
| 13 | Memory::MapIoRegion(0x00000000, 0x80000000, test_memory); | 16 | |
| 14 | Memory::MapIoRegion(0x80000000, 0x80000000, test_memory); | 17 | page_table.pointers.fill(nullptr); |
| 18 | page_table.attributes.fill(Memory::PageType::Unmapped); | ||
| 19 | page_table.cached_res_count.fill(0); | ||
| 20 | |||
| 21 | Memory::MapIoRegion(page_table, 0x00000000, 0x80000000, test_memory); | ||
| 22 | Memory::MapIoRegion(page_table, 0x80000000, 0x80000000, test_memory); | ||
| 23 | |||
| 24 | Memory::current_page_table = &page_table; | ||
| 15 | } | 25 | } |
| 16 | 26 | ||
| 17 | TestEnvironment::~TestEnvironment() { | 27 | TestEnvironment::~TestEnvironment() { |
| 18 | Memory::UnmapRegion(0x80000000, 0x80000000); | 28 | Memory::UnmapRegion(page_table, 0x80000000, 0x80000000); |
| 19 | Memory::UnmapRegion(0x00000000, 0x80000000); | 29 | Memory::UnmapRegion(page_table, 0x00000000, 0x80000000); |
| 20 | } | 30 | } |
| 21 | 31 | ||
| 22 | void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) { | 32 | void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) { |
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index cffa4c952..82f47d8a9 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | set(SRCS | 1 | set(SRCS |
| 2 | command_processor.cpp | 2 | command_processor.cpp |
| 3 | debug_utils/debug_utils.cpp | 3 | debug_utils/debug_utils.cpp |
| 4 | geometry_pipeline.cpp | ||
| 4 | pica.cpp | 5 | pica.cpp |
| 5 | primitive_assembly.cpp | 6 | primitive_assembly.cpp |
| 6 | regs.cpp | 7 | regs.cpp |
| @@ -29,6 +30,7 @@ set(SRCS | |||
| 29 | set(HEADERS | 30 | set(HEADERS |
| 30 | command_processor.h | 31 | command_processor.h |
| 31 | debug_utils/debug_utils.h | 32 | debug_utils/debug_utils.h |
| 33 | geometry_pipeline.h | ||
| 32 | gpu_debugger.h | 34 | gpu_debugger.h |
| 33 | pica.h | 35 | pica.h |
| 34 | pica_state.h | 36 | pica_state.h |
diff --git a/src/video_core/command_processor.cpp b/src/video_core/command_processor.cpp index f98ca3302..fb65a3a0a 100644 --- a/src/video_core/command_processor.cpp +++ b/src/video_core/command_processor.cpp | |||
| @@ -161,6 +161,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 161 | 161 | ||
| 162 | case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index): | 162 | case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index): |
| 163 | g_state.immediate.current_attribute = 0; | 163 | g_state.immediate.current_attribute = 0; |
| 164 | g_state.immediate.reset_geometry_pipeline = true; | ||
| 164 | default_attr_counter = 0; | 165 | default_attr_counter = 0; |
| 165 | break; | 166 | break; |
| 166 | 167 | ||
| @@ -234,16 +235,14 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 234 | shader_engine->Run(g_state.vs, shader_unit); | 235 | shader_engine->Run(g_state.vs, shader_unit); |
| 235 | shader_unit.WriteOutput(regs.vs, output); | 236 | shader_unit.WriteOutput(regs.vs, output); |
| 236 | 237 | ||
| 237 | // Send to renderer | 238 | // Send to geometry pipeline |
| 238 | using Pica::Shader::OutputVertex; | 239 | if (g_state.immediate.reset_geometry_pipeline) { |
| 239 | auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1, | 240 | g_state.geometry_pipeline.Reconfigure(); |
| 240 | const OutputVertex& v2) { | 241 | g_state.immediate.reset_geometry_pipeline = false; |
| 241 | VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); | 242 | } |
| 242 | }; | 243 | ASSERT(!g_state.geometry_pipeline.NeedIndexInput()); |
| 243 | 244 | g_state.geometry_pipeline.Setup(shader_engine); | |
| 244 | g_state.primitive_assembler.SubmitVertex( | 245 | g_state.geometry_pipeline.SubmitVertex(output); |
| 245 | Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output), | ||
| 246 | AddTriangle); | ||
| 247 | } | 246 | } |
| 248 | } | 247 | } |
| 249 | } | 248 | } |
| @@ -321,8 +320,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 321 | // The size has been tuned for optimal balance between hit-rate and the cost of lookup | 320 | // The size has been tuned for optimal balance between hit-rate and the cost of lookup |
| 322 | const size_t VERTEX_CACHE_SIZE = 32; | 321 | const size_t VERTEX_CACHE_SIZE = 32; |
| 323 | std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids; | 322 | std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids; |
| 324 | std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache; | 323 | std::array<Shader::AttributeBuffer, VERTEX_CACHE_SIZE> vertex_cache; |
| 325 | Shader::OutputVertex output_vertex; | 324 | Shader::AttributeBuffer vs_output; |
| 326 | 325 | ||
| 327 | unsigned int vertex_cache_pos = 0; | 326 | unsigned int vertex_cache_pos = 0; |
| 328 | vertex_cache_ids.fill(-1); | 327 | vertex_cache_ids.fill(-1); |
| @@ -332,6 +331,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 332 | 331 | ||
| 333 | shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); | 332 | shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset); |
| 334 | 333 | ||
| 334 | g_state.geometry_pipeline.Reconfigure(); | ||
| 335 | g_state.geometry_pipeline.Setup(shader_engine); | ||
| 336 | if (g_state.geometry_pipeline.NeedIndexInput()) | ||
| 337 | ASSERT(is_indexed); | ||
| 338 | |||
| 335 | for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) { | 339 | for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) { |
| 336 | // Indexed rendering doesn't use the start offset | 340 | // Indexed rendering doesn't use the start offset |
| 337 | unsigned int vertex = | 341 | unsigned int vertex = |
| @@ -345,6 +349,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 345 | bool vertex_cache_hit = false; | 349 | bool vertex_cache_hit = false; |
| 346 | 350 | ||
| 347 | if (is_indexed) { | 351 | if (is_indexed) { |
| 352 | if (g_state.geometry_pipeline.NeedIndexInput()) { | ||
| 353 | g_state.geometry_pipeline.SubmitIndex(vertex); | ||
| 354 | continue; | ||
| 355 | } | ||
| 356 | |||
| 348 | if (g_debug_context && Pica::g_debug_context->recorder) { | 357 | if (g_debug_context && Pica::g_debug_context->recorder) { |
| 349 | int size = index_u16 ? 2 : 1; | 358 | int size = index_u16 ? 2 : 1; |
| 350 | memory_accesses.AddAccess(base_address + index_info.offset + size * index, | 359 | memory_accesses.AddAccess(base_address + index_info.offset + size * index, |
| @@ -353,7 +362,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 353 | 362 | ||
| 354 | for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) { | 363 | for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) { |
| 355 | if (vertex == vertex_cache_ids[i]) { | 364 | if (vertex == vertex_cache_ids[i]) { |
| 356 | output_vertex = vertex_cache[i]; | 365 | vs_output = vertex_cache[i]; |
| 357 | vertex_cache_hit = true; | 366 | vertex_cache_hit = true; |
| 358 | break; | 367 | break; |
| 359 | } | 368 | } |
| @@ -362,7 +371,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 362 | 371 | ||
| 363 | if (!vertex_cache_hit) { | 372 | if (!vertex_cache_hit) { |
| 364 | // Initialize data for the current vertex | 373 | // Initialize data for the current vertex |
| 365 | Shader::AttributeBuffer input, output{}; | 374 | Shader::AttributeBuffer input; |
| 366 | loader.LoadVertex(base_address, index, vertex, input, memory_accesses); | 375 | loader.LoadVertex(base_address, index, vertex, input, memory_accesses); |
| 367 | 376 | ||
| 368 | // Send to vertex shader | 377 | // Send to vertex shader |
| @@ -371,26 +380,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) { | |||
| 371 | (void*)&input); | 380 | (void*)&input); |
| 372 | shader_unit.LoadInput(regs.vs, input); | 381 | shader_unit.LoadInput(regs.vs, input); |
| 373 | shader_engine->Run(g_state.vs, shader_unit); | 382 | shader_engine->Run(g_state.vs, shader_unit); |
| 374 | shader_unit.WriteOutput(regs.vs, output); | 383 | shader_unit.WriteOutput(regs.vs, vs_output); |
| 375 | |||
| 376 | // Retrieve vertex from register data | ||
| 377 | output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output); | ||
| 378 | 384 | ||
| 379 | if (is_indexed) { | 385 | if (is_indexed) { |
| 380 | vertex_cache[vertex_cache_pos] = output_vertex; | 386 | vertex_cache[vertex_cache_pos] = vs_output; |
| 381 | vertex_cache_ids[vertex_cache_pos] = vertex; | 387 | vertex_cache_ids[vertex_cache_pos] = vertex; |
| 382 | vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE; | 388 | vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE; |
| 383 | } | 389 | } |
| 384 | } | 390 | } |
| 385 | 391 | ||
| 386 | // Send to renderer | 392 | // Send to geometry pipeline |
| 387 | using Pica::Shader::OutputVertex; | 393 | g_state.geometry_pipeline.SubmitVertex(vs_output); |
| 388 | auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1, | ||
| 389 | const OutputVertex& v2) { | ||
| 390 | VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); | ||
| 391 | }; | ||
| 392 | |||
| 393 | primitive_assembler.SubmitVertex(output_vertex, AddTriangle); | ||
| 394 | } | 394 | } |
| 395 | 395 | ||
| 396 | for (auto& range : memory_accesses.ranges) { | 396 | for (auto& range : memory_accesses.ranges) { |
diff --git a/src/video_core/geometry_pipeline.cpp b/src/video_core/geometry_pipeline.cpp new file mode 100644 index 000000000..b146e2ecb --- /dev/null +++ b/src/video_core/geometry_pipeline.cpp | |||
| @@ -0,0 +1,274 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include "video_core/geometry_pipeline.h" | ||
| 6 | #include "video_core/pica_state.h" | ||
| 7 | #include "video_core/regs.h" | ||
| 8 | #include "video_core/renderer_base.h" | ||
| 9 | #include "video_core/video_core.h" | ||
| 10 | |||
| 11 | namespace Pica { | ||
| 12 | |||
| 13 | /// An attribute buffering interface for different pipeline modes | ||
| 14 | class GeometryPipelineBackend { | ||
| 15 | public: | ||
| 16 | virtual ~GeometryPipelineBackend() = default; | ||
| 17 | |||
| 18 | /// Checks if there is no incomplete data transfer | ||
| 19 | virtual bool IsEmpty() const = 0; | ||
| 20 | |||
| 21 | /// Checks if the pipeline needs a direct input from index buffer | ||
| 22 | virtual bool NeedIndexInput() const = 0; | ||
| 23 | |||
| 24 | /// Submits an index from index buffer | ||
| 25 | virtual void SubmitIndex(unsigned int val) = 0; | ||
| 26 | |||
| 27 | /** | ||
| 28 | * Submits vertex attributes | ||
| 29 | * @param input attributes of a vertex output from vertex shader | ||
| 30 | * @return if the buffer is full and the geometry shader should be invoked | ||
| 31 | */ | ||
| 32 | virtual bool SubmitVertex(const Shader::AttributeBuffer& input) = 0; | ||
| 33 | }; | ||
| 34 | |||
| 35 | // In the Point mode, vertex attributes are sent to the input registers in the geometry shader unit. | ||
| 36 | // The size of vertex shader outputs and geometry shader inputs are constants. Geometry shader is | ||
| 37 | // invoked upon inputs buffer filled up by vertex shader outputs. For example, if we have a geometry | ||
| 38 | // shader that takes 6 inputs, and the vertex shader outputs 2 attributes, it would take 3 vertices | ||
| 39 | // for one geometry shader invocation. | ||
| 40 | // TODO: what happens when the input size is not divisible by the output size? | ||
| 41 | class GeometryPipeline_Point : public GeometryPipelineBackend { | ||
| 42 | public: | ||
| 43 | GeometryPipeline_Point(const Regs& regs, Shader::GSUnitState& unit) : regs(regs), unit(unit) { | ||
| 44 | ASSERT(regs.pipeline.variable_primitive == 0); | ||
| 45 | ASSERT(regs.gs.input_to_uniform == 0); | ||
| 46 | vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1; | ||
| 47 | size_t gs_input_num = regs.gs.max_input_attribute_index + 1; | ||
| 48 | ASSERT(gs_input_num % vs_output_num == 0); | ||
| 49 | buffer_cur = attribute_buffer.attr; | ||
| 50 | buffer_end = attribute_buffer.attr + gs_input_num; | ||
| 51 | } | ||
| 52 | |||
| 53 | bool IsEmpty() const override { | ||
| 54 | return buffer_cur == attribute_buffer.attr; | ||
| 55 | } | ||
| 56 | |||
| 57 | bool NeedIndexInput() const override { | ||
| 58 | return false; | ||
| 59 | } | ||
| 60 | |||
| 61 | void SubmitIndex(unsigned int val) override { | ||
| 62 | UNREACHABLE(); | ||
| 63 | } | ||
| 64 | |||
| 65 | bool SubmitVertex(const Shader::AttributeBuffer& input) override { | ||
| 66 | buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur); | ||
| 67 | if (buffer_cur == buffer_end) { | ||
| 68 | buffer_cur = attribute_buffer.attr; | ||
| 69 | unit.LoadInput(regs.gs, attribute_buffer); | ||
| 70 | return true; | ||
| 71 | } | ||
| 72 | return false; | ||
| 73 | } | ||
| 74 | |||
| 75 | private: | ||
| 76 | const Regs& regs; | ||
| 77 | Shader::GSUnitState& unit; | ||
| 78 | Shader::AttributeBuffer attribute_buffer; | ||
| 79 | Math::Vec4<float24>* buffer_cur; | ||
| 80 | Math::Vec4<float24>* buffer_end; | ||
| 81 | unsigned int vs_output_num; | ||
| 82 | }; | ||
| 83 | |||
| 84 | // In VariablePrimitive mode, vertex attributes are buffered into the uniform registers in the | ||
| 85 | // geometry shader unit. The number of vertex is variable, which is specified by the first index | ||
| 86 | // value in the batch. This mode is usually used for subdivision. | ||
| 87 | class GeometryPipeline_VariablePrimitive : public GeometryPipelineBackend { | ||
| 88 | public: | ||
| 89 | GeometryPipeline_VariablePrimitive(const Regs& regs, Shader::ShaderSetup& setup) | ||
| 90 | : regs(regs), setup(setup) { | ||
| 91 | ASSERT(regs.pipeline.variable_primitive == 1); | ||
| 92 | ASSERT(regs.gs.input_to_uniform == 1); | ||
| 93 | vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1; | ||
| 94 | } | ||
| 95 | |||
| 96 | bool IsEmpty() const override { | ||
| 97 | return need_index; | ||
| 98 | } | ||
| 99 | |||
| 100 | bool NeedIndexInput() const override { | ||
| 101 | return need_index; | ||
| 102 | } | ||
| 103 | |||
| 104 | void SubmitIndex(unsigned int val) override { | ||
| 105 | DEBUG_ASSERT(need_index); | ||
| 106 | |||
| 107 | // The number of vertex input is put to the uniform register | ||
| 108 | float24 vertex_num = float24::FromFloat32(val); | ||
| 109 | setup.uniforms.f[0] = Math::MakeVec(vertex_num, vertex_num, vertex_num, vertex_num); | ||
| 110 | |||
| 111 | // The second uniform register and so on are used for receiving input vertices | ||
| 112 | buffer_cur = setup.uniforms.f + 1; | ||
| 113 | |||
| 114 | main_vertex_num = regs.pipeline.variable_vertex_main_num_minus_1 + 1; | ||
| 115 | total_vertex_num = val; | ||
| 116 | need_index = false; | ||
| 117 | } | ||
| 118 | |||
| 119 | bool SubmitVertex(const Shader::AttributeBuffer& input) override { | ||
| 120 | DEBUG_ASSERT(!need_index); | ||
| 121 | if (main_vertex_num != 0) { | ||
| 122 | // For main vertices, receive all attributes | ||
| 123 | buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur); | ||
| 124 | --main_vertex_num; | ||
| 125 | } else { | ||
| 126 | // For other vertices, only receive the first attribute (usually the position) | ||
| 127 | *(buffer_cur++) = input.attr[0]; | ||
| 128 | } | ||
| 129 | --total_vertex_num; | ||
| 130 | |||
| 131 | if (total_vertex_num == 0) { | ||
| 132 | need_index = true; | ||
| 133 | return true; | ||
| 134 | } | ||
| 135 | |||
| 136 | return false; | ||
| 137 | } | ||
| 138 | |||
| 139 | private: | ||
| 140 | bool need_index = true; | ||
| 141 | const Regs& regs; | ||
| 142 | Shader::ShaderSetup& setup; | ||
| 143 | unsigned int main_vertex_num; | ||
| 144 | unsigned int total_vertex_num; | ||
| 145 | Math::Vec4<float24>* buffer_cur; | ||
| 146 | unsigned int vs_output_num; | ||
| 147 | }; | ||
| 148 | |||
| 149 | // In FixedPrimitive mode, vertex attributes are buffered into the uniform registers in the geometry | ||
| 150 | // shader unit. The number of vertex per shader invocation is constant. This is usually used for | ||
| 151 | // particle system. | ||
| 152 | class GeometryPipeline_FixedPrimitive : public GeometryPipelineBackend { | ||
| 153 | public: | ||
| 154 | GeometryPipeline_FixedPrimitive(const Regs& regs, Shader::ShaderSetup& setup) | ||
| 155 | : regs(regs), setup(setup) { | ||
| 156 | ASSERT(regs.pipeline.variable_primitive == 0); | ||
| 157 | ASSERT(regs.gs.input_to_uniform == 1); | ||
| 158 | vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1; | ||
| 159 | ASSERT(vs_output_num == regs.pipeline.gs_config.stride_minus_1 + 1); | ||
| 160 | size_t vertex_num = regs.pipeline.gs_config.fixed_vertex_num_minus_1 + 1; | ||
| 161 | buffer_cur = buffer_begin = setup.uniforms.f + regs.pipeline.gs_config.start_index; | ||
| 162 | buffer_end = buffer_begin + vs_output_num * vertex_num; | ||
| 163 | } | ||
| 164 | |||
| 165 | bool IsEmpty() const override { | ||
| 166 | return buffer_cur == buffer_begin; | ||
| 167 | } | ||
| 168 | |||
| 169 | bool NeedIndexInput() const override { | ||
| 170 | return false; | ||
| 171 | } | ||
| 172 | |||
| 173 | void SubmitIndex(unsigned int val) override { | ||
| 174 | UNREACHABLE(); | ||
| 175 | } | ||
| 176 | |||
| 177 | bool SubmitVertex(const Shader::AttributeBuffer& input) override { | ||
| 178 | buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur); | ||
| 179 | if (buffer_cur == buffer_end) { | ||
| 180 | buffer_cur = buffer_begin; | ||
| 181 | return true; | ||
| 182 | } | ||
| 183 | return false; | ||
| 184 | } | ||
| 185 | |||
| 186 | private: | ||
| 187 | const Regs& regs; | ||
| 188 | Shader::ShaderSetup& setup; | ||
| 189 | Math::Vec4<float24>* buffer_begin; | ||
| 190 | Math::Vec4<float24>* buffer_cur; | ||
| 191 | Math::Vec4<float24>* buffer_end; | ||
| 192 | unsigned int vs_output_num; | ||
| 193 | }; | ||
| 194 | |||
| 195 | GeometryPipeline::GeometryPipeline(State& state) : state(state) {} | ||
| 196 | |||
| 197 | GeometryPipeline::~GeometryPipeline() = default; | ||
| 198 | |||
| 199 | void GeometryPipeline::SetVertexHandler(Shader::VertexHandler vertex_handler) { | ||
| 200 | this->vertex_handler = vertex_handler; | ||
| 201 | } | ||
| 202 | |||
| 203 | void GeometryPipeline::Setup(Shader::ShaderEngine* shader_engine) { | ||
| 204 | if (!backend) | ||
| 205 | return; | ||
| 206 | |||
| 207 | this->shader_engine = shader_engine; | ||
| 208 | shader_engine->SetupBatch(state.gs, state.regs.gs.main_offset); | ||
| 209 | } | ||
| 210 | |||
| 211 | void GeometryPipeline::Reconfigure() { | ||
| 212 | ASSERT(!backend || backend->IsEmpty()); | ||
| 213 | |||
| 214 | if (state.regs.pipeline.use_gs == PipelineRegs::UseGS::No) { | ||
| 215 | backend = nullptr; | ||
| 216 | return; | ||
| 217 | } | ||
| 218 | |||
| 219 | ASSERT(state.regs.pipeline.use_gs == PipelineRegs::UseGS::Yes); | ||
| 220 | |||
| 221 | // The following assumes that when geometry shader is in use, the shader unit 3 is configured as | ||
| 222 | // a geometry shader unit. | ||
| 223 | // TODO: what happens if this is not true? | ||
| 224 | ASSERT(state.regs.pipeline.gs_unit_exclusive_configuration == 1); | ||
| 225 | ASSERT(state.regs.gs.shader_mode == ShaderRegs::ShaderMode::GS); | ||
| 226 | |||
| 227 | state.gs_unit.ConfigOutput(state.regs.gs); | ||
| 228 | |||
| 229 | ASSERT(state.regs.pipeline.vs_outmap_total_minus_1_a == | ||
| 230 | state.regs.pipeline.vs_outmap_total_minus_1_b); | ||
| 231 | |||
| 232 | switch (state.regs.pipeline.gs_config.mode) { | ||
| 233 | case PipelineRegs::GSMode::Point: | ||
| 234 | backend = std::make_unique<GeometryPipeline_Point>(state.regs, state.gs_unit); | ||
| 235 | break; | ||
| 236 | case PipelineRegs::GSMode::VariablePrimitive: | ||
| 237 | backend = std::make_unique<GeometryPipeline_VariablePrimitive>(state.regs, state.gs); | ||
| 238 | break; | ||
| 239 | case PipelineRegs::GSMode::FixedPrimitive: | ||
| 240 | backend = std::make_unique<GeometryPipeline_FixedPrimitive>(state.regs, state.gs); | ||
| 241 | break; | ||
| 242 | default: | ||
| 243 | UNREACHABLE(); | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | bool GeometryPipeline::NeedIndexInput() const { | ||
| 248 | if (!backend) | ||
| 249 | return false; | ||
| 250 | return backend->NeedIndexInput(); | ||
| 251 | } | ||
| 252 | |||
| 253 | void GeometryPipeline::SubmitIndex(unsigned int val) { | ||
| 254 | backend->SubmitIndex(val); | ||
| 255 | } | ||
| 256 | |||
| 257 | void GeometryPipeline::SubmitVertex(const Shader::AttributeBuffer& input) { | ||
| 258 | if (!backend) { | ||
| 259 | // No backend means the geometry shader is disabled, so we send the vertex shader output | ||
| 260 | // directly to the primitive assembler. | ||
| 261 | vertex_handler(input); | ||
| 262 | } else { | ||
| 263 | if (backend->SubmitVertex(input)) { | ||
| 264 | shader_engine->Run(state.gs, state.gs_unit); | ||
| 265 | |||
| 266 | // The uniform b15 is set to true after every geometry shader invocation. This is useful | ||
| 267 | // for the shader to know if this is the first invocation in a batch, if the program set | ||
| 268 | // b15 to false first. | ||
| 269 | state.gs.uniforms.b[15] = true; | ||
| 270 | } | ||
| 271 | } | ||
| 272 | } | ||
| 273 | |||
| 274 | } // namespace Pica | ||
diff --git a/src/video_core/geometry_pipeline.h b/src/video_core/geometry_pipeline.h new file mode 100644 index 000000000..91fdd3192 --- /dev/null +++ b/src/video_core/geometry_pipeline.h | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | // Copyright 2017 Citra Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include "video_core/shader/shader.h" | ||
| 9 | |||
| 10 | namespace Pica { | ||
| 11 | |||
| 12 | struct State; | ||
| 13 | |||
| 14 | class GeometryPipelineBackend; | ||
| 15 | |||
| 16 | /// A pipeline receiving from vertex shader and sending to geometry shader and primitive assembler | ||
| 17 | class GeometryPipeline { | ||
| 18 | public: | ||
| 19 | explicit GeometryPipeline(State& state); | ||
| 20 | ~GeometryPipeline(); | ||
| 21 | |||
| 22 | /// Sets the handler for receiving vertex outputs from vertex shader | ||
| 23 | void SetVertexHandler(Shader::VertexHandler vertex_handler); | ||
| 24 | |||
| 25 | /** | ||
| 26 | * Setup the geometry shader unit if it is in use | ||
| 27 | * @param shader_engine the shader engine for the geometry shader to run | ||
| 28 | */ | ||
| 29 | void Setup(Shader::ShaderEngine* shader_engine); | ||
| 30 | |||
| 31 | /// Reconfigures the pipeline according to current register settings | ||
| 32 | void Reconfigure(); | ||
| 33 | |||
| 34 | /// Checks if the pipeline needs a direct input from index buffer | ||
| 35 | bool NeedIndexInput() const; | ||
| 36 | |||
| 37 | /// Submits an index from index buffer. Call this only when NeedIndexInput returns true | ||
| 38 | void SubmitIndex(unsigned int val); | ||
| 39 | |||
| 40 | /// Submits vertex attributes output from vertex shader | ||
| 41 | void SubmitVertex(const Shader::AttributeBuffer& input); | ||
| 42 | |||
| 43 | private: | ||
| 44 | Shader::VertexHandler vertex_handler; | ||
| 45 | Shader::ShaderEngine* shader_engine; | ||
| 46 | std::unique_ptr<GeometryPipelineBackend> backend; | ||
| 47 | State& state; | ||
| 48 | }; | ||
| 49 | } // namespace Pica | ||
diff --git a/src/video_core/pica.cpp b/src/video_core/pica.cpp index b95148a6a..218e06883 100644 --- a/src/video_core/pica.cpp +++ b/src/video_core/pica.cpp | |||
| @@ -3,9 +3,11 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <cstring> | 5 | #include <cstring> |
| 6 | #include "video_core/geometry_pipeline.h" | ||
| 6 | #include "video_core/pica.h" | 7 | #include "video_core/pica.h" |
| 7 | #include "video_core/pica_state.h" | 8 | #include "video_core/pica_state.h" |
| 8 | #include "video_core/regs_pipeline.h" | 9 | #include "video_core/renderer_base.h" |
| 10 | #include "video_core/video_core.h" | ||
| 9 | 11 | ||
| 10 | namespace Pica { | 12 | namespace Pica { |
| 11 | 13 | ||
| @@ -24,6 +26,23 @@ void Zero(T& o) { | |||
| 24 | memset(&o, 0, sizeof(o)); | 26 | memset(&o, 0, sizeof(o)); |
| 25 | } | 27 | } |
| 26 | 28 | ||
| 29 | State::State() : geometry_pipeline(*this) { | ||
| 30 | auto SubmitVertex = [this](const Shader::AttributeBuffer& vertex) { | ||
| 31 | using Pica::Shader::OutputVertex; | ||
| 32 | auto AddTriangle = [this](const OutputVertex& v0, const OutputVertex& v1, | ||
| 33 | const OutputVertex& v2) { | ||
| 34 | VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2); | ||
| 35 | }; | ||
| 36 | primitive_assembler.SubmitVertex( | ||
| 37 | Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, vertex), AddTriangle); | ||
| 38 | }; | ||
| 39 | |||
| 40 | auto SetWinding = [this]() { primitive_assembler.SetWinding(); }; | ||
| 41 | |||
| 42 | g_state.gs_unit.SetVertexHandler(SubmitVertex, SetWinding); | ||
| 43 | g_state.geometry_pipeline.SetVertexHandler(SubmitVertex); | ||
| 44 | } | ||
| 45 | |||
| 27 | void State::Reset() { | 46 | void State::Reset() { |
| 28 | Zero(regs); | 47 | Zero(regs); |
| 29 | Zero(vs); | 48 | Zero(vs); |
diff --git a/src/video_core/pica_state.h b/src/video_core/pica_state.h index 864a2c9e6..c6634a0bc 100644 --- a/src/video_core/pica_state.h +++ b/src/video_core/pica_state.h | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include "common/bit_field.h" | 8 | #include "common/bit_field.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/vector_math.h" | 10 | #include "common/vector_math.h" |
| 11 | #include "video_core/geometry_pipeline.h" | ||
| 11 | #include "video_core/primitive_assembly.h" | 12 | #include "video_core/primitive_assembly.h" |
| 12 | #include "video_core/regs.h" | 13 | #include "video_core/regs.h" |
| 13 | #include "video_core/shader/shader.h" | 14 | #include "video_core/shader/shader.h" |
| @@ -16,6 +17,7 @@ namespace Pica { | |||
| 16 | 17 | ||
| 17 | /// Struct used to describe current Pica state | 18 | /// Struct used to describe current Pica state |
| 18 | struct State { | 19 | struct State { |
| 20 | State(); | ||
| 19 | void Reset(); | 21 | void Reset(); |
| 20 | 22 | ||
| 21 | /// Pica registers | 23 | /// Pica registers |
| @@ -137,8 +139,17 @@ struct State { | |||
| 137 | Shader::AttributeBuffer input_vertex; | 139 | Shader::AttributeBuffer input_vertex; |
| 138 | // Index of the next attribute to be loaded into `input_vertex`. | 140 | // Index of the next attribute to be loaded into `input_vertex`. |
| 139 | u32 current_attribute = 0; | 141 | u32 current_attribute = 0; |
| 142 | // Indicates the immediate mode just started and the geometry pipeline needs to reconfigure | ||
| 143 | bool reset_geometry_pipeline = true; | ||
| 140 | } immediate; | 144 | } immediate; |
| 141 | 145 | ||
| 146 | // the geometry shader needs to be kept in the global state because some shaders relie on | ||
| 147 | // preserved register value across shader invocation. | ||
| 148 | // TODO: also bring the three vertex shader units here and implement the shader scheduler. | ||
| 149 | Shader::GSUnitState gs_unit; | ||
| 150 | |||
| 151 | GeometryPipeline geometry_pipeline; | ||
| 152 | |||
| 142 | // This is constructed with a dummy triangle topology | 153 | // This is constructed with a dummy triangle topology |
| 143 | PrimitiveAssembler<Shader::OutputVertex> primitive_assembler; | 154 | PrimitiveAssembler<Shader::OutputVertex> primitive_assembler; |
| 144 | }; | 155 | }; |
diff --git a/src/video_core/primitive_assembly.cpp b/src/video_core/primitive_assembly.cpp index acd2ac5e2..9c3dd4cab 100644 --- a/src/video_core/primitive_assembly.cpp +++ b/src/video_core/primitive_assembly.cpp | |||
| @@ -17,15 +17,18 @@ template <typename VertexType> | |||
| 17 | void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx, | 17 | void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx, |
| 18 | TriangleHandler triangle_handler) { | 18 | TriangleHandler triangle_handler) { |
| 19 | switch (topology) { | 19 | switch (topology) { |
| 20 | // TODO: Figure out what's different with TriangleTopology::Shader. | ||
| 21 | case PipelineRegs::TriangleTopology::List: | 20 | case PipelineRegs::TriangleTopology::List: |
| 22 | case PipelineRegs::TriangleTopology::Shader: | 21 | case PipelineRegs::TriangleTopology::Shader: |
| 23 | if (buffer_index < 2) { | 22 | if (buffer_index < 2) { |
| 24 | buffer[buffer_index++] = vtx; | 23 | buffer[buffer_index++] = vtx; |
| 25 | } else { | 24 | } else { |
| 26 | buffer_index = 0; | 25 | buffer_index = 0; |
| 27 | 26 | if (topology == PipelineRegs::TriangleTopology::Shader && winding) { | |
| 28 | triangle_handler(buffer[0], buffer[1], vtx); | 27 | triangle_handler(buffer[1], buffer[0], vtx); |
| 28 | winding = false; | ||
| 29 | } else { | ||
| 30 | triangle_handler(buffer[0], buffer[1], vtx); | ||
| 31 | } | ||
| 29 | } | 32 | } |
| 30 | break; | 33 | break; |
| 31 | 34 | ||
| @@ -51,9 +54,15 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx, | |||
| 51 | } | 54 | } |
| 52 | 55 | ||
| 53 | template <typename VertexType> | 56 | template <typename VertexType> |
| 57 | void PrimitiveAssembler<VertexType>::SetWinding() { | ||
| 58 | winding = true; | ||
| 59 | } | ||
| 60 | |||
| 61 | template <typename VertexType> | ||
| 54 | void PrimitiveAssembler<VertexType>::Reset() { | 62 | void PrimitiveAssembler<VertexType>::Reset() { |
| 55 | buffer_index = 0; | 63 | buffer_index = 0; |
| 56 | strip_ready = false; | 64 | strip_ready = false; |
| 65 | winding = false; | ||
| 57 | } | 66 | } |
| 58 | 67 | ||
| 59 | template <typename VertexType> | 68 | template <typename VertexType> |
diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h index e8eccdf27..12de8e3b9 100644 --- a/src/video_core/primitive_assembly.h +++ b/src/video_core/primitive_assembly.h | |||
| @@ -30,6 +30,12 @@ struct PrimitiveAssembler { | |||
| 30 | void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler); | 30 | void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler); |
| 31 | 31 | ||
| 32 | /** | 32 | /** |
| 33 | * Invert the vertex order of the next triangle. Called by geometry shader emitter. | ||
| 34 | * This only takes effect for TriangleTopology::Shader. | ||
| 35 | */ | ||
| 36 | void SetWinding(); | ||
| 37 | |||
| 38 | /** | ||
| 33 | * Resets the internal state of the PrimitiveAssembler. | 39 | * Resets the internal state of the PrimitiveAssembler. |
| 34 | */ | 40 | */ |
| 35 | void Reset(); | 41 | void Reset(); |
| @@ -45,6 +51,7 @@ private: | |||
| 45 | int buffer_index; | 51 | int buffer_index; |
| 46 | VertexType buffer[2]; | 52 | VertexType buffer[2]; |
| 47 | bool strip_ready = false; | 53 | bool strip_ready = false; |
| 54 | bool winding = false; | ||
| 48 | }; | 55 | }; |
| 49 | 56 | ||
| 50 | } // namespace | 57 | } // namespace |
diff --git a/src/video_core/regs_pipeline.h b/src/video_core/regs_pipeline.h index 8b6369297..e78c3e331 100644 --- a/src/video_core/regs_pipeline.h +++ b/src/video_core/regs_pipeline.h | |||
| @@ -147,7 +147,15 @@ struct PipelineRegs { | |||
| 147 | // Number of vertices to render | 147 | // Number of vertices to render |
| 148 | u32 num_vertices; | 148 | u32 num_vertices; |
| 149 | 149 | ||
| 150 | INSERT_PADDING_WORDS(0x1); | 150 | enum class UseGS : u32 { |
| 151 | No = 0, | ||
| 152 | Yes = 2, | ||
| 153 | }; | ||
| 154 | |||
| 155 | union { | ||
| 156 | BitField<0, 2, UseGS> use_gs; | ||
| 157 | BitField<31, 1, u32> variable_primitive; | ||
| 158 | }; | ||
| 151 | 159 | ||
| 152 | // The index of the first vertex to render | 160 | // The index of the first vertex to render |
| 153 | u32 vertex_offset; | 161 | u32 vertex_offset; |
| @@ -218,7 +226,29 @@ struct PipelineRegs { | |||
| 218 | 226 | ||
| 219 | GPUMode gpu_mode; | 227 | GPUMode gpu_mode; |
| 220 | 228 | ||
| 221 | INSERT_PADDING_WORDS(0x18); | 229 | INSERT_PADDING_WORDS(0x4); |
| 230 | BitField<0, 4, u32> vs_outmap_total_minus_1_a; | ||
| 231 | INSERT_PADDING_WORDS(0x6); | ||
| 232 | BitField<0, 4, u32> vs_outmap_total_minus_1_b; | ||
| 233 | |||
| 234 | enum class GSMode : u32 { | ||
| 235 | Point = 0, | ||
| 236 | VariablePrimitive = 1, | ||
| 237 | FixedPrimitive = 2, | ||
| 238 | }; | ||
| 239 | |||
| 240 | union { | ||
| 241 | BitField<0, 8, GSMode> mode; | ||
| 242 | BitField<8, 4, u32> fixed_vertex_num_minus_1; | ||
| 243 | BitField<12, 4, u32> stride_minus_1; | ||
| 244 | BitField<16, 4, u32> start_index; | ||
| 245 | } gs_config; | ||
| 246 | |||
| 247 | INSERT_PADDING_WORDS(0x1); | ||
| 248 | |||
| 249 | u32 variable_vertex_main_num_minus_1; | ||
| 250 | |||
| 251 | INSERT_PADDING_WORDS(0x9); | ||
| 222 | 252 | ||
| 223 | enum class TriangleTopology : u32 { | 253 | enum class TriangleTopology : u32 { |
| 224 | List = 0, | 254 | List = 0, |
diff --git a/src/video_core/regs_rasterizer.h b/src/video_core/regs_rasterizer.h index 2874fd127..4fef00d76 100644 --- a/src/video_core/regs_rasterizer.h +++ b/src/video_core/regs_rasterizer.h | |||
| @@ -5,10 +5,10 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | |||
| 9 | #include "common/bit_field.h" | 8 | #include "common/bit_field.h" |
| 10 | #include "common/common_funcs.h" | 9 | #include "common/common_funcs.h" |
| 11 | #include "common/common_types.h" | 10 | #include "common/common_types.h" |
| 11 | #include "video_core/pica_types.h" | ||
| 12 | 12 | ||
| 13 | namespace Pica { | 13 | namespace Pica { |
| 14 | 14 | ||
| @@ -31,7 +31,17 @@ struct RasterizerRegs { | |||
| 31 | 31 | ||
| 32 | BitField<0, 24, u32> viewport_size_y; | 32 | BitField<0, 24, u32> viewport_size_y; |
| 33 | 33 | ||
| 34 | INSERT_PADDING_WORDS(0x9); | 34 | INSERT_PADDING_WORDS(0x3); |
| 35 | |||
| 36 | BitField<0, 1, u32> clip_enable; | ||
| 37 | BitField<0, 24, u32> clip_coef[4]; // float24 | ||
| 38 | |||
| 39 | Math::Vec4<float24> GetClipCoef() const { | ||
| 40 | return {float24::FromRaw(clip_coef[0]), float24::FromRaw(clip_coef[1]), | ||
| 41 | float24::FromRaw(clip_coef[2]), float24::FromRaw(clip_coef[3])}; | ||
| 42 | } | ||
| 43 | |||
| 44 | INSERT_PADDING_WORDS(0x1); | ||
| 35 | 45 | ||
| 36 | BitField<0, 24, u32> viewport_depth_range; // float24 | 46 | BitField<0, 24, u32> viewport_depth_range; // float24 |
| 37 | BitField<0, 24, u32> viewport_depth_near_plane; // float24 | 47 | BitField<0, 24, u32> viewport_depth_near_plane; // float24 |
diff --git a/src/video_core/regs_shader.h b/src/video_core/regs_shader.h index ddb1ee451..c15d4d162 100644 --- a/src/video_core/regs_shader.h +++ b/src/video_core/regs_shader.h | |||
| @@ -24,9 +24,16 @@ struct ShaderRegs { | |||
| 24 | 24 | ||
| 25 | INSERT_PADDING_WORDS(0x4); | 25 | INSERT_PADDING_WORDS(0x4); |
| 26 | 26 | ||
| 27 | enum ShaderMode { | ||
| 28 | GS = 0x08, | ||
| 29 | VS = 0xA0, | ||
| 30 | }; | ||
| 31 | |||
| 27 | union { | 32 | union { |
| 28 | // Number of input attributes to shader unit - 1 | 33 | // Number of input attributes to shader unit - 1 |
| 29 | BitField<0, 4, u32> max_input_attribute_index; | 34 | BitField<0, 4, u32> max_input_attribute_index; |
| 35 | BitField<8, 8, u32> input_to_uniform; | ||
| 36 | BitField<24, 8, ShaderMode> shader_mode; | ||
| 30 | }; | 37 | }; |
| 31 | 38 | ||
| 32 | // Offset to shader program entry point (in words) | 39 | // Offset to shader program entry point (in words) |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index aa95ef21d..7b0cd1b66 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -169,6 +169,8 @@ RasterizerOpenGL::RasterizerOpenGL() : shader_dirty(true) { | |||
| 169 | glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, proctex_diff_lut_buffer.handle); | 169 | glTexBuffer(GL_TEXTURE_BUFFER, GL_RGBA32F, proctex_diff_lut_buffer.handle); |
| 170 | 170 | ||
| 171 | // Sync fixed function OpenGL state | 171 | // Sync fixed function OpenGL state |
| 172 | SyncClipEnabled(); | ||
| 173 | SyncClipCoef(); | ||
| 172 | SyncCullMode(); | 174 | SyncCullMode(); |
| 173 | SyncBlendEnabled(); | 175 | SyncBlendEnabled(); |
| 174 | SyncBlendFuncs(); | 176 | SyncBlendFuncs(); |
| @@ -401,6 +403,18 @@ void RasterizerOpenGL::NotifyPicaRegisterChanged(u32 id) { | |||
| 401 | SyncCullMode(); | 403 | SyncCullMode(); |
| 402 | break; | 404 | break; |
| 403 | 405 | ||
| 406 | // Clipping plane | ||
| 407 | case PICA_REG_INDEX(rasterizer.clip_enable): | ||
| 408 | SyncClipEnabled(); | ||
| 409 | break; | ||
| 410 | |||
| 411 | case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[0], 0x48): | ||
| 412 | case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[1], 0x49): | ||
| 413 | case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[2], 0x4a): | ||
| 414 | case PICA_REG_INDEX_WORKAROUND(rasterizer.clip_coef[3], 0x4b): | ||
| 415 | SyncClipCoef(); | ||
| 416 | break; | ||
| 417 | |||
| 404 | // Depth modifiers | 418 | // Depth modifiers |
| 405 | case PICA_REG_INDEX(rasterizer.viewport_depth_range): | 419 | case PICA_REG_INDEX(rasterizer.viewport_depth_range): |
| 406 | SyncDepthScale(); | 420 | SyncDepthScale(); |
| @@ -1280,6 +1294,20 @@ void RasterizerOpenGL::SetShader() { | |||
| 1280 | } | 1294 | } |
| 1281 | } | 1295 | } |
| 1282 | 1296 | ||
| 1297 | void RasterizerOpenGL::SyncClipEnabled() { | ||
| 1298 | state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0; | ||
| 1299 | } | ||
| 1300 | |||
| 1301 | void RasterizerOpenGL::SyncClipCoef() { | ||
| 1302 | const auto raw_clip_coef = Pica::g_state.regs.rasterizer.GetClipCoef(); | ||
| 1303 | const GLvec4 new_clip_coef = {raw_clip_coef.x.ToFloat32(), raw_clip_coef.y.ToFloat32(), | ||
| 1304 | raw_clip_coef.z.ToFloat32(), raw_clip_coef.w.ToFloat32()}; | ||
| 1305 | if (new_clip_coef != uniform_block_data.data.clip_coef) { | ||
| 1306 | uniform_block_data.data.clip_coef = new_clip_coef; | ||
| 1307 | uniform_block_data.dirty = true; | ||
| 1308 | } | ||
| 1309 | } | ||
| 1310 | |||
| 1283 | void RasterizerOpenGL::SyncCullMode() { | 1311 | void RasterizerOpenGL::SyncCullMode() { |
| 1284 | const auto& regs = Pica::g_state.regs; | 1312 | const auto& regs = Pica::g_state.regs; |
| 1285 | 1313 | ||
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 78e218efe..46c62961c 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h | |||
| @@ -151,14 +151,21 @@ private: | |||
| 151 | LightSrc light_src[8]; | 151 | LightSrc light_src[8]; |
| 152 | alignas(16) GLvec4 const_color[6]; // A vec4 color for each of the six tev stages | 152 | alignas(16) GLvec4 const_color[6]; // A vec4 color for each of the six tev stages |
| 153 | alignas(16) GLvec4 tev_combiner_buffer_color; | 153 | alignas(16) GLvec4 tev_combiner_buffer_color; |
| 154 | alignas(16) GLvec4 clip_coef; | ||
| 154 | }; | 155 | }; |
| 155 | 156 | ||
| 156 | static_assert( | 157 | static_assert( |
| 157 | sizeof(UniformData) == 0x460, | 158 | sizeof(UniformData) == 0x470, |
| 158 | "The size of the UniformData structure has changed, update the structure in the shader"); | 159 | "The size of the UniformData structure has changed, update the structure in the shader"); |
| 159 | static_assert(sizeof(UniformData) < 16384, | 160 | static_assert(sizeof(UniformData) < 16384, |
| 160 | "UniformData structure must be less than 16kb as per the OpenGL spec"); | 161 | "UniformData structure must be less than 16kb as per the OpenGL spec"); |
| 161 | 162 | ||
| 163 | /// Syncs the clip enabled status to match the PICA register | ||
| 164 | void SyncClipEnabled(); | ||
| 165 | |||
| 166 | /// Syncs the clip coefficients to match the PICA register | ||
| 167 | void SyncClipCoef(); | ||
| 168 | |||
| 162 | /// Sets the OpenGL shader in accordance with the current PICA register state | 169 | /// Sets the OpenGL shader in accordance with the current PICA register state |
| 163 | void SetShader(); | 170 | void SetShader(); |
| 164 | 171 | ||
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp index 015e69da9..9fe183944 100644 --- a/src/video_core/renderer_opengl/gl_shader_gen.cpp +++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include "common/assert.h" | 8 | #include "common/assert.h" |
| 9 | #include "common/bit_field.h" | 9 | #include "common/bit_field.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "core/core.h" | ||
| 11 | #include "video_core/regs_framebuffer.h" | 12 | #include "video_core/regs_framebuffer.h" |
| 12 | #include "video_core/regs_lighting.h" | 13 | #include "video_core/regs_lighting.h" |
| 13 | #include "video_core/regs_rasterizer.h" | 14 | #include "video_core/regs_rasterizer.h" |
| @@ -24,6 +25,42 @@ using TevStageConfig = TexturingRegs::TevStageConfig; | |||
| 24 | 25 | ||
| 25 | namespace GLShader { | 26 | namespace GLShader { |
| 26 | 27 | ||
| 28 | static const std::string UniformBlockDef = R"( | ||
| 29 | #define NUM_TEV_STAGES 6 | ||
| 30 | #define NUM_LIGHTS 8 | ||
| 31 | |||
| 32 | struct LightSrc { | ||
| 33 | vec3 specular_0; | ||
| 34 | vec3 specular_1; | ||
| 35 | vec3 diffuse; | ||
| 36 | vec3 ambient; | ||
| 37 | vec3 position; | ||
| 38 | vec3 spot_direction; | ||
| 39 | float dist_atten_bias; | ||
| 40 | float dist_atten_scale; | ||
| 41 | }; | ||
| 42 | |||
| 43 | layout (std140) uniform shader_data { | ||
| 44 | vec2 framebuffer_scale; | ||
| 45 | int alphatest_ref; | ||
| 46 | float depth_scale; | ||
| 47 | float depth_offset; | ||
| 48 | int scissor_x1; | ||
| 49 | int scissor_y1; | ||
| 50 | int scissor_x2; | ||
| 51 | int scissor_y2; | ||
| 52 | vec3 fog_color; | ||
| 53 | vec2 proctex_noise_f; | ||
| 54 | vec2 proctex_noise_a; | ||
| 55 | vec2 proctex_noise_p; | ||
| 56 | vec3 lighting_global_ambient; | ||
| 57 | LightSrc light_src[NUM_LIGHTS]; | ||
| 58 | vec4 const_color[NUM_TEV_STAGES]; | ||
| 59 | vec4 tev_combiner_buffer_color; | ||
| 60 | vec4 clip_coef; | ||
| 61 | }; | ||
| 62 | )"; | ||
| 63 | |||
| 27 | PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { | 64 | PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { |
| 28 | PicaShaderConfig res; | 65 | PicaShaderConfig res; |
| 29 | 66 | ||
| @@ -594,8 +631,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { | |||
| 594 | // Note: even if the normal vector is modified by normal map, which is not the | 631 | // Note: even if the normal vector is modified by normal map, which is not the |
| 595 | // normal of the tangent plane anymore, the half angle vector is still projected | 632 | // normal of the tangent plane anymore, the half angle vector is still projected |
| 596 | // using the modified normal vector. | 633 | // using the modified normal vector. |
| 597 | std::string half_angle_proj = "normalize(half_vector) - normal / dot(normal, " | 634 | std::string half_angle_proj = |
| 598 | "normal) * dot(normal, normalize(half_vector))"; | 635 | "normalize(half_vector) - normal * dot(normal, normalize(half_vector))"; |
| 599 | // Note: the half angle vector projection is confirmed not normalized before the dot | 636 | // Note: the half angle vector projection is confirmed not normalized before the dot |
| 600 | // product. The result is in fact not cos(phi) as the name suggested. | 637 | // product. The result is in fact not cos(phi) as the name suggested. |
| 601 | index = "dot(" + half_angle_proj + ", tangent)"; | 638 | index = "dot(" + half_angle_proj + ", tangent)"; |
| @@ -750,7 +787,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { | |||
| 750 | } | 787 | } |
| 751 | 788 | ||
| 752 | // Fresnel | 789 | // Fresnel |
| 753 | if (lighting.lut_fr.enable && | 790 | // Note: only the last entry in the light slots applies the Fresnel factor |
| 791 | if (light_index == lighting.src_num - 1 && lighting.lut_fr.enable && | ||
| 754 | LightingRegs::IsLightingSamplerSupported(lighting.config, | 792 | LightingRegs::IsLightingSamplerSupported(lighting.config, |
| 755 | LightingRegs::LightingSampler::Fresnel)) { | 793 | LightingRegs::LightingSampler::Fresnel)) { |
| 756 | // Lookup fresnel LUT value | 794 | // Lookup fresnel LUT value |
| @@ -759,17 +797,17 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) { | |||
| 759 | lighting.lut_fr.type, lighting.lut_fr.abs_input); | 797 | lighting.lut_fr.type, lighting.lut_fr.abs_input); |
| 760 | value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")"; | 798 | value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")"; |
| 761 | 799 | ||
| 762 | // Enabled for difffuse lighting alpha component | 800 | // Enabled for diffuse lighting alpha component |
| 763 | if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha || | 801 | if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha || |
| 764 | lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { | 802 | lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { |
| 765 | out += "diffuse_sum.a *= " + value + ";\n"; | 803 | out += "diffuse_sum.a = " + value + ";\n"; |
| 766 | } | 804 | } |
| 767 | 805 | ||
| 768 | // Enabled for the specular lighting alpha component | 806 | // Enabled for the specular lighting alpha component |
| 769 | if (lighting.fresnel_selector == | 807 | if (lighting.fresnel_selector == |
| 770 | LightingRegs::LightingFresnelSelector::SecondaryAlpha || | 808 | LightingRegs::LightingFresnelSelector::SecondaryAlpha || |
| 771 | lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { | 809 | lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { |
| 772 | out += "specular_sum.a *= " + value + ";\n"; | 810 | out += "specular_sum.a = " + value + ";\n"; |
| 773 | } | 811 | } |
| 774 | } | 812 | } |
| 775 | 813 | ||
| @@ -1008,8 +1046,6 @@ std::string GenerateFragmentShader(const PicaShaderConfig& config) { | |||
| 1008 | 1046 | ||
| 1009 | std::string out = R"( | 1047 | std::string out = R"( |
| 1010 | #version 330 core | 1048 | #version 330 core |
| 1011 | #define NUM_TEV_STAGES 6 | ||
| 1012 | #define NUM_LIGHTS 8 | ||
| 1013 | 1049 | ||
| 1014 | in vec4 primary_color; | 1050 | in vec4 primary_color; |
| 1015 | in vec2 texcoord[3]; | 1051 | in vec2 texcoord[3]; |
| @@ -1021,36 +1057,6 @@ in vec4 gl_FragCoord; | |||
| 1021 | 1057 | ||
| 1022 | out vec4 color; | 1058 | out vec4 color; |
| 1023 | 1059 | ||
| 1024 | struct LightSrc { | ||
| 1025 | vec3 specular_0; | ||
| 1026 | vec3 specular_1; | ||
| 1027 | vec3 diffuse; | ||
| 1028 | vec3 ambient; | ||
| 1029 | vec3 position; | ||
| 1030 | vec3 spot_direction; | ||
| 1031 | float dist_atten_bias; | ||
| 1032 | float dist_atten_scale; | ||
| 1033 | }; | ||
| 1034 | |||
| 1035 | layout (std140) uniform shader_data { | ||
| 1036 | vec2 framebuffer_scale; | ||
| 1037 | int alphatest_ref; | ||
| 1038 | float depth_scale; | ||
| 1039 | float depth_offset; | ||
| 1040 | int scissor_x1; | ||
| 1041 | int scissor_y1; | ||
| 1042 | int scissor_x2; | ||
| 1043 | int scissor_y2; | ||
| 1044 | vec3 fog_color; | ||
| 1045 | vec2 proctex_noise_f; | ||
| 1046 | vec2 proctex_noise_a; | ||
| 1047 | vec2 proctex_noise_p; | ||
| 1048 | vec3 lighting_global_ambient; | ||
| 1049 | LightSrc light_src[NUM_LIGHTS]; | ||
| 1050 | vec4 const_color[NUM_TEV_STAGES]; | ||
| 1051 | vec4 tev_combiner_buffer_color; | ||
| 1052 | }; | ||
| 1053 | |||
| 1054 | uniform sampler2D tex[3]; | 1060 | uniform sampler2D tex[3]; |
| 1055 | uniform samplerBuffer lighting_lut; | 1061 | uniform samplerBuffer lighting_lut; |
| 1056 | uniform samplerBuffer fog_lut; | 1062 | uniform samplerBuffer fog_lut; |
| @@ -1059,7 +1065,11 @@ uniform samplerBuffer proctex_color_map; | |||
| 1059 | uniform samplerBuffer proctex_alpha_map; | 1065 | uniform samplerBuffer proctex_alpha_map; |
| 1060 | uniform samplerBuffer proctex_lut; | 1066 | uniform samplerBuffer proctex_lut; |
| 1061 | uniform samplerBuffer proctex_diff_lut; | 1067 | uniform samplerBuffer proctex_diff_lut; |
| 1068 | )"; | ||
| 1069 | |||
| 1070 | out += UniformBlockDef; | ||
| 1062 | 1071 | ||
| 1072 | out += R"( | ||
| 1063 | // Rotate the vector v by the quaternion q | 1073 | // Rotate the vector v by the quaternion q |
| 1064 | vec3 quaternion_rotate(vec4 q, vec3 v) { | 1074 | vec3 quaternion_rotate(vec4 q, vec3 v) { |
| 1065 | return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); | 1075 | return v + 2.0 * cross(q.xyz, cross(q.xyz, v) + q.w * v); |
| @@ -1155,6 +1165,11 @@ vec4 secondary_fragment_color = vec4(0.0); | |||
| 1155 | 1165 | ||
| 1156 | // Blend the fog | 1166 | // Blend the fog |
| 1157 | out += "last_tex_env_out.rgb = mix(fog_color.rgb, last_tex_env_out.rgb, fog_factor);\n"; | 1167 | out += "last_tex_env_out.rgb = mix(fog_color.rgb, last_tex_env_out.rgb, fog_factor);\n"; |
| 1168 | } else if (state.fog_mode == TexturingRegs::FogMode::Gas) { | ||
| 1169 | Core::Telemetry().AddField(Telemetry::FieldType::Session, "VideoCore_Pica_UseGasMode", | ||
| 1170 | true); | ||
| 1171 | LOG_CRITICAL(Render_OpenGL, "Unimplemented gas mode"); | ||
| 1172 | UNIMPLEMENTED(); | ||
| 1158 | } | 1173 | } |
| 1159 | 1174 | ||
| 1160 | out += "gl_FragDepth = depth;\n"; | 1175 | out += "gl_FragDepth = depth;\n"; |
| @@ -1190,6 +1205,12 @@ out float texcoord0_w; | |||
| 1190 | out vec4 normquat; | 1205 | out vec4 normquat; |
| 1191 | out vec3 view; | 1206 | out vec3 view; |
| 1192 | 1207 | ||
| 1208 | )"; | ||
| 1209 | |||
| 1210 | out += UniformBlockDef; | ||
| 1211 | |||
| 1212 | out += R"( | ||
| 1213 | |||
| 1193 | void main() { | 1214 | void main() { |
| 1194 | primary_color = vert_color; | 1215 | primary_color = vert_color; |
| 1195 | texcoord[0] = vert_texcoord0; | 1216 | texcoord[0] = vert_texcoord0; |
| @@ -1200,7 +1221,7 @@ void main() { | |||
| 1200 | view = vert_view; | 1221 | view = vert_view; |
| 1201 | gl_Position = vert_position; | 1222 | gl_Position = vert_position; |
| 1202 | gl_ClipDistance[0] = -vert_position.z; // fixed PICA clipping plane z <= 0 | 1223 | gl_ClipDistance[0] = -vert_position.z; // fixed PICA clipping plane z <= 0 |
| 1203 | // TODO (wwylele): calculate gl_ClipDistance[1] from user-defined clipping plane | 1224 | gl_ClipDistance[1] = dot(clip_coef, vert_position); |
| 1204 | } | 1225 | } |
| 1205 | )"; | 1226 | )"; |
| 1206 | 1227 | ||
diff --git a/src/video_core/shader/shader.cpp b/src/video_core/shader/shader.cpp index 67ed19ba8..e9063e616 100644 --- a/src/video_core/shader/shader.cpp +++ b/src/video_core/shader/shader.cpp | |||
| @@ -21,7 +21,8 @@ namespace Pica { | |||
| 21 | 21 | ||
| 22 | namespace Shader { | 22 | namespace Shader { |
| 23 | 23 | ||
| 24 | OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& input) { | 24 | OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, |
| 25 | const AttributeBuffer& input) { | ||
| 25 | // Setup output data | 26 | // Setup output data |
| 26 | union { | 27 | union { |
| 27 | OutputVertex ret{}; | 28 | OutputVertex ret{}; |
| @@ -82,6 +83,44 @@ void UnitState::WriteOutput(const ShaderRegs& config, AttributeBuffer& output) { | |||
| 82 | } | 83 | } |
| 83 | } | 84 | } |
| 84 | 85 | ||
| 86 | UnitState::UnitState(GSEmitter* emitter) : emitter_ptr(emitter) {} | ||
| 87 | |||
| 88 | GSEmitter::GSEmitter() { | ||
| 89 | handlers = new Handlers; | ||
| 90 | } | ||
| 91 | |||
| 92 | GSEmitter::~GSEmitter() { | ||
| 93 | delete handlers; | ||
| 94 | } | ||
| 95 | |||
| 96 | void GSEmitter::Emit(Math::Vec4<float24> (&vertex)[16]) { | ||
| 97 | ASSERT(vertex_id < 3); | ||
| 98 | std::copy(std::begin(vertex), std::end(vertex), buffer[vertex_id].begin()); | ||
| 99 | if (prim_emit) { | ||
| 100 | if (winding) | ||
| 101 | handlers->winding_setter(); | ||
| 102 | for (size_t i = 0; i < buffer.size(); ++i) { | ||
| 103 | AttributeBuffer output; | ||
| 104 | unsigned int output_i = 0; | ||
| 105 | for (unsigned int reg : Common::BitSet<u32>(output_mask)) { | ||
| 106 | output.attr[output_i++] = buffer[i][reg]; | ||
| 107 | } | ||
| 108 | handlers->vertex_handler(output); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | } | ||
| 112 | |||
| 113 | GSUnitState::GSUnitState() : UnitState(&emitter) {} | ||
| 114 | |||
| 115 | void GSUnitState::SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter) { | ||
| 116 | emitter.handlers->vertex_handler = std::move(vertex_handler); | ||
| 117 | emitter.handlers->winding_setter = std::move(winding_setter); | ||
| 118 | } | ||
| 119 | |||
| 120 | void GSUnitState::ConfigOutput(const ShaderRegs& config) { | ||
| 121 | emitter.output_mask = config.output_mask; | ||
| 122 | } | ||
| 123 | |||
| 85 | MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); | 124 | MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); |
| 86 | 125 | ||
| 87 | #ifdef ARCHITECTURE_x86_64 | 126 | #ifdef ARCHITECTURE_x86_64 |
diff --git a/src/video_core/shader/shader.h b/src/video_core/shader/shader.h index e156f6aef..a3789da01 100644 --- a/src/video_core/shader/shader.h +++ b/src/video_core/shader/shader.h | |||
| @@ -6,6 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <cstddef> | 8 | #include <cstddef> |
| 9 | #include <functional> | ||
| 9 | #include <type_traits> | 10 | #include <type_traits> |
| 10 | #include <nihstro/shader_bytecode.h> | 11 | #include <nihstro/shader_bytecode.h> |
| 11 | #include "common/assert.h" | 12 | #include "common/assert.h" |
| @@ -31,6 +32,12 @@ struct AttributeBuffer { | |||
| 31 | alignas(16) Math::Vec4<float24> attr[16]; | 32 | alignas(16) Math::Vec4<float24> attr[16]; |
| 32 | }; | 33 | }; |
| 33 | 34 | ||
| 35 | /// Handler type for receiving vertex outputs from vertex shader or geometry shader | ||
| 36 | using VertexHandler = std::function<void(const AttributeBuffer&)>; | ||
| 37 | |||
| 38 | /// Handler type for signaling to invert the vertex order of the next triangle | ||
| 39 | using WindingSetter = std::function<void()>; | ||
| 40 | |||
| 34 | struct OutputVertex { | 41 | struct OutputVertex { |
| 35 | Math::Vec4<float24> pos; | 42 | Math::Vec4<float24> pos; |
| 36 | Math::Vec4<float24> quat; | 43 | Math::Vec4<float24> quat; |
| @@ -43,7 +50,8 @@ struct OutputVertex { | |||
| 43 | INSERT_PADDING_WORDS(1); | 50 | INSERT_PADDING_WORDS(1); |
| 44 | Math::Vec2<float24> tc2; | 51 | Math::Vec2<float24> tc2; |
| 45 | 52 | ||
| 46 | static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& output); | 53 | static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, |
| 54 | const AttributeBuffer& output); | ||
| 47 | }; | 55 | }; |
| 48 | #define ASSERT_POS(var, pos) \ | 56 | #define ASSERT_POS(var, pos) \ |
| 49 | static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong " \ | 57 | static_assert(offsetof(OutputVertex, var) == pos * sizeof(float24), "Semantic at wrong " \ |
| @@ -61,12 +69,36 @@ static_assert(std::is_pod<OutputVertex>::value, "Structure is not POD"); | |||
| 61 | static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size"); | 69 | static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size"); |
| 62 | 70 | ||
| 63 | /** | 71 | /** |
| 72 | * This structure contains state information for primitive emitting in geometry shader. | ||
| 73 | */ | ||
| 74 | struct GSEmitter { | ||
| 75 | std::array<std::array<Math::Vec4<float24>, 16>, 3> buffer; | ||
| 76 | u8 vertex_id; | ||
| 77 | bool prim_emit; | ||
| 78 | bool winding; | ||
| 79 | u32 output_mask; | ||
| 80 | |||
| 81 | // Function objects are hidden behind a raw pointer to make the structure standard layout type, | ||
| 82 | // for JIT to use offsetof to access other members. | ||
| 83 | struct Handlers { | ||
| 84 | VertexHandler vertex_handler; | ||
| 85 | WindingSetter winding_setter; | ||
| 86 | } * handlers; | ||
| 87 | |||
| 88 | GSEmitter(); | ||
| 89 | ~GSEmitter(); | ||
| 90 | void Emit(Math::Vec4<float24> (&vertex)[16]); | ||
| 91 | }; | ||
| 92 | static_assert(std::is_standard_layout<GSEmitter>::value, "GSEmitter is not standard layout type"); | ||
| 93 | |||
| 94 | /** | ||
| 64 | * This structure contains the state information that needs to be unique for a shader unit. The 3DS | 95 | * This structure contains the state information that needs to be unique for a shader unit. The 3DS |
| 65 | * has four shader units that process shaders in parallel. At the present, Citra only implements a | 96 | * has four shader units that process shaders in parallel. At the present, Citra only implements a |
| 66 | * single shader unit that processes all shaders serially. Putting the state information in a struct | 97 | * single shader unit that processes all shaders serially. Putting the state information in a struct |
| 67 | * here will make it easier for us to parallelize the shader processing later. | 98 | * here will make it easier for us to parallelize the shader processing later. |
| 68 | */ | 99 | */ |
| 69 | struct UnitState { | 100 | struct UnitState { |
| 101 | explicit UnitState(GSEmitter* emitter = nullptr); | ||
| 70 | struct Registers { | 102 | struct Registers { |
| 71 | // The registers are accessed by the shader JIT using SSE instructions, and are therefore | 103 | // The registers are accessed by the shader JIT using SSE instructions, and are therefore |
| 72 | // required to be 16-byte aligned. | 104 | // required to be 16-byte aligned. |
| @@ -82,6 +114,8 @@ struct UnitState { | |||
| 82 | // TODO: How many bits do these actually have? | 114 | // TODO: How many bits do these actually have? |
| 83 | s32 address_registers[3]; | 115 | s32 address_registers[3]; |
| 84 | 116 | ||
| 117 | GSEmitter* emitter_ptr; | ||
| 118 | |||
| 85 | static size_t InputOffset(const SourceRegister& reg) { | 119 | static size_t InputOffset(const SourceRegister& reg) { |
| 86 | switch (reg.GetRegisterType()) { | 120 | switch (reg.GetRegisterType()) { |
| 87 | case RegisterType::Input: | 121 | case RegisterType::Input: |
| @@ -125,6 +159,19 @@ struct UnitState { | |||
| 125 | void WriteOutput(const ShaderRegs& config, AttributeBuffer& output); | 159 | void WriteOutput(const ShaderRegs& config, AttributeBuffer& output); |
| 126 | }; | 160 | }; |
| 127 | 161 | ||
| 162 | /** | ||
| 163 | * This is an extended shader unit state that represents the special unit that can run both vertex | ||
| 164 | * shader and geometry shader. It contains an additional primitive emitter and utilities for | ||
| 165 | * geometry shader. | ||
| 166 | */ | ||
| 167 | struct GSUnitState : public UnitState { | ||
| 168 | GSUnitState(); | ||
| 169 | void SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter); | ||
| 170 | void ConfigOutput(const ShaderRegs& config); | ||
| 171 | |||
| 172 | GSEmitter emitter; | ||
| 173 | }; | ||
| 174 | |||
| 128 | struct ShaderSetup { | 175 | struct ShaderSetup { |
| 129 | struct { | 176 | struct { |
| 130 | // The float uniforms are accessed by the shader JIT using SSE instructions, and are | 177 | // The float uniforms are accessed by the shader JIT using SSE instructions, and are |
diff --git a/src/video_core/shader/shader_interpreter.cpp b/src/video_core/shader/shader_interpreter.cpp index 206c0978a..9d4da4904 100644 --- a/src/video_core/shader/shader_interpreter.cpp +++ b/src/video_core/shader/shader_interpreter.cpp | |||
| @@ -636,6 +636,22 @@ static void RunInterpreter(const ShaderSetup& setup, UnitState& state, DebugData | |||
| 636 | break; | 636 | break; |
| 637 | } | 637 | } |
| 638 | 638 | ||
| 639 | case OpCode::Id::EMIT: { | ||
| 640 | GSEmitter* emitter = state.emitter_ptr; | ||
| 641 | ASSERT_MSG(emitter, "Execute EMIT on VS"); | ||
| 642 | emitter->Emit(state.registers.output); | ||
| 643 | break; | ||
| 644 | } | ||
| 645 | |||
| 646 | case OpCode::Id::SETEMIT: { | ||
| 647 | GSEmitter* emitter = state.emitter_ptr; | ||
| 648 | ASSERT_MSG(emitter, "Execute SETEMIT on VS"); | ||
| 649 | emitter->vertex_id = instr.setemit.vertex_id; | ||
| 650 | emitter->prim_emit = instr.setemit.prim_emit != 0; | ||
| 651 | emitter->winding = instr.setemit.winding != 0; | ||
| 652 | break; | ||
| 653 | } | ||
| 654 | |||
| 639 | default: | 655 | default: |
| 640 | LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x", | 656 | LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x", |
| 641 | (int)instr.opcode.Value().EffectiveOpCode(), | 657 | (int)instr.opcode.Value().EffectiveOpCode(), |
diff --git a/src/video_core/shader/shader_jit_x64_compiler.cpp b/src/video_core/shader/shader_jit_x64_compiler.cpp index 42a57aab1..1b31623bd 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.cpp +++ b/src/video_core/shader/shader_jit_x64_compiler.cpp | |||
| @@ -75,8 +75,8 @@ const JitFunction instr_table[64] = { | |||
| 75 | &JitShader::Compile_IF, // ifu | 75 | &JitShader::Compile_IF, // ifu |
| 76 | &JitShader::Compile_IF, // ifc | 76 | &JitShader::Compile_IF, // ifc |
| 77 | &JitShader::Compile_LOOP, // loop | 77 | &JitShader::Compile_LOOP, // loop |
| 78 | nullptr, // emit | 78 | &JitShader::Compile_EMIT, // emit |
| 79 | nullptr, // sete | 79 | &JitShader::Compile_SETE, // sete |
| 80 | &JitShader::Compile_JMP, // jmpc | 80 | &JitShader::Compile_JMP, // jmpc |
| 81 | &JitShader::Compile_JMP, // jmpu | 81 | &JitShader::Compile_JMP, // jmpu |
| 82 | &JitShader::Compile_CMP, // cmp | 82 | &JitShader::Compile_CMP, // cmp |
| @@ -772,6 +772,51 @@ void JitShader::Compile_JMP(Instruction instr) { | |||
| 772 | } | 772 | } |
| 773 | } | 773 | } |
| 774 | 774 | ||
| 775 | static void Emit(GSEmitter* emitter, Math::Vec4<float24> (*output)[16]) { | ||
| 776 | emitter->Emit(*output); | ||
| 777 | } | ||
| 778 | |||
| 779 | void JitShader::Compile_EMIT(Instruction instr) { | ||
| 780 | Label have_emitter, end; | ||
| 781 | mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]); | ||
| 782 | test(rax, rax); | ||
| 783 | jnz(have_emitter); | ||
| 784 | |||
| 785 | ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); | ||
| 786 | mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute EMIT on VS")); | ||
| 787 | CallFarFunction(*this, LogCritical); | ||
| 788 | ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); | ||
| 789 | jmp(end); | ||
| 790 | |||
| 791 | L(have_emitter); | ||
| 792 | ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); | ||
| 793 | mov(ABI_PARAM1, rax); | ||
| 794 | mov(ABI_PARAM2, STATE); | ||
| 795 | add(ABI_PARAM2, static_cast<Xbyak::uint32>(offsetof(UnitState, registers.output))); | ||
| 796 | CallFarFunction(*this, Emit); | ||
| 797 | ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); | ||
| 798 | L(end); | ||
| 799 | } | ||
| 800 | |||
| 801 | void JitShader::Compile_SETE(Instruction instr) { | ||
| 802 | Label have_emitter, end; | ||
| 803 | mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]); | ||
| 804 | test(rax, rax); | ||
| 805 | jnz(have_emitter); | ||
| 806 | |||
| 807 | ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); | ||
| 808 | mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute SETEMIT on VS")); | ||
| 809 | CallFarFunction(*this, LogCritical); | ||
| 810 | ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0); | ||
| 811 | jmp(end); | ||
| 812 | |||
| 813 | L(have_emitter); | ||
| 814 | mov(byte[rax + offsetof(GSEmitter, vertex_id)], instr.setemit.vertex_id); | ||
| 815 | mov(byte[rax + offsetof(GSEmitter, prim_emit)], instr.setemit.prim_emit); | ||
| 816 | mov(byte[rax + offsetof(GSEmitter, winding)], instr.setemit.winding); | ||
| 817 | L(end); | ||
| 818 | } | ||
| 819 | |||
| 775 | void JitShader::Compile_Block(unsigned end) { | 820 | void JitShader::Compile_Block(unsigned end) { |
| 776 | while (program_counter < end) { | 821 | while (program_counter < end) { |
| 777 | Compile_NextInstr(); | 822 | Compile_NextInstr(); |
diff --git a/src/video_core/shader/shader_jit_x64_compiler.h b/src/video_core/shader/shader_jit_x64_compiler.h index 31af0ca48..4aee56b1d 100644 --- a/src/video_core/shader/shader_jit_x64_compiler.h +++ b/src/video_core/shader/shader_jit_x64_compiler.h | |||
| @@ -66,6 +66,8 @@ public: | |||
| 66 | void Compile_JMP(Instruction instr); | 66 | void Compile_JMP(Instruction instr); |
| 67 | void Compile_CMP(Instruction instr); | 67 | void Compile_CMP(Instruction instr); |
| 68 | void Compile_MAD(Instruction instr); | 68 | void Compile_MAD(Instruction instr); |
| 69 | void Compile_EMIT(Instruction instr); | ||
| 70 | void Compile_SETE(Instruction instr); | ||
| 69 | 71 | ||
| 70 | private: | 72 | private: |
| 71 | void Compile_Block(unsigned end); | 73 | void Compile_Block(unsigned end); |
diff --git a/src/video_core/swrasterizer/clipper.cpp b/src/video_core/swrasterizer/clipper.cpp index cdbc71502..a52129eb7 100644 --- a/src/video_core/swrasterizer/clipper.cpp +++ b/src/video_core/swrasterizer/clipper.cpp | |||
| @@ -31,7 +31,7 @@ public: | |||
| 31 | : coeffs(coeffs), bias(bias) {} | 31 | : coeffs(coeffs), bias(bias) {} |
| 32 | 32 | ||
| 33 | bool IsInside(const Vertex& vertex) const { | 33 | bool IsInside(const Vertex& vertex) const { |
| 34 | return Math::Dot(vertex.pos + bias, coeffs) <= float24::FromFloat32(0); | 34 | return Math::Dot(vertex.pos + bias, coeffs) >= float24::FromFloat32(0); |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | bool IsOutSide(const Vertex& vertex) const { | 37 | bool IsOutSide(const Vertex& vertex) const { |
| @@ -116,19 +116,18 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu | |||
| 116 | static const float24 f0 = float24::FromFloat32(0.0); | 116 | static const float24 f0 = float24::FromFloat32(0.0); |
| 117 | static const float24 f1 = float24::FromFloat32(1.0); | 117 | static const float24 f1 = float24::FromFloat32(1.0); |
| 118 | static const std::array<ClippingEdge, 7> clipping_edges = {{ | 118 | static const std::array<ClippingEdge, 7> clipping_edges = {{ |
| 119 | {Math::MakeVec(f1, f0, f0, -f1)}, // x = +w | 119 | {Math::MakeVec(-f1, f0, f0, f1)}, // x = +w |
| 120 | {Math::MakeVec(-f1, f0, f0, -f1)}, // x = -w | 120 | {Math::MakeVec(f1, f0, f0, f1)}, // x = -w |
| 121 | {Math::MakeVec(f0, f1, f0, -f1)}, // y = +w | 121 | {Math::MakeVec(f0, -f1, f0, f1)}, // y = +w |
| 122 | {Math::MakeVec(f0, -f1, f0, -f1)}, // y = -w | 122 | {Math::MakeVec(f0, f1, f0, f1)}, // y = -w |
| 123 | {Math::MakeVec(f0, f0, f1, f0)}, // z = 0 | 123 | {Math::MakeVec(f0, f0, -f1, f0)}, // z = 0 |
| 124 | {Math::MakeVec(f0, f0, -f1, -f1)}, // z = -w | 124 | {Math::MakeVec(f0, f0, f1, f1)}, // z = -w |
| 125 | {Math::MakeVec(f0, f0, f0, -f1), Math::Vec4<float24>(f0, f0, f0, EPSILON)}, // w = EPSILON | 125 | {Math::MakeVec(f0, f0, f0, f1), Math::Vec4<float24>(f0, f0, f0, EPSILON)}, // w = EPSILON |
| 126 | }}; | 126 | }}; |
| 127 | 127 | ||
| 128 | // Simple implementation of the Sutherland-Hodgman clipping algorithm. | 128 | // Simple implementation of the Sutherland-Hodgman clipping algorithm. |
| 129 | // TODO: Make this less inefficient (currently lots of useless buffering overhead happens here) | 129 | // TODO: Make this less inefficient (currently lots of useless buffering overhead happens here) |
| 130 | for (auto edge : clipping_edges) { | 130 | auto Clip = [&](const ClippingEdge& edge) { |
| 131 | |||
| 132 | std::swap(input_list, output_list); | 131 | std::swap(input_list, output_list); |
| 133 | output_list->clear(); | 132 | output_list->clear(); |
| 134 | 133 | ||
| @@ -147,12 +146,24 @@ void ProcessTriangle(const OutputVertex& v0, const OutputVertex& v1, const Outpu | |||
| 147 | } | 146 | } |
| 148 | reference_vertex = &vertex; | 147 | reference_vertex = &vertex; |
| 149 | } | 148 | } |
| 149 | }; | ||
| 150 | |||
| 151 | for (auto edge : clipping_edges) { | ||
| 152 | Clip(edge); | ||
| 150 | 153 | ||
| 151 | // Need to have at least a full triangle to continue... | 154 | // Need to have at least a full triangle to continue... |
| 152 | if (output_list->size() < 3) | 155 | if (output_list->size() < 3) |
| 153 | return; | 156 | return; |
| 154 | } | 157 | } |
| 155 | 158 | ||
| 159 | if (g_state.regs.rasterizer.clip_enable) { | ||
| 160 | ClippingEdge custom_edge{g_state.regs.rasterizer.GetClipCoef()}; | ||
| 161 | Clip(custom_edge); | ||
| 162 | |||
| 163 | if (output_list->size() < 3) | ||
| 164 | return; | ||
| 165 | } | ||
| 166 | |||
| 156 | InitScreenCoordinates((*output_list)[0]); | 167 | InitScreenCoordinates((*output_list)[0]); |
| 157 | InitScreenCoordinates((*output_list)[1]); | 168 | InitScreenCoordinates((*output_list)[1]); |
| 158 | 169 | ||
diff --git a/src/video_core/swrasterizer/lighting.cpp b/src/video_core/swrasterizer/lighting.cpp index 39a3e396d..5fa748611 100644 --- a/src/video_core/swrasterizer/lighting.cpp +++ b/src/video_core/swrasterizer/lighting.cpp | |||
| @@ -22,18 +22,37 @@ static float LookupLightingLut(const Pica::State::Lighting& lighting, size_t lut | |||
| 22 | 22 | ||
| 23 | std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( | 23 | std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( |
| 24 | const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, | 24 | const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, |
| 25 | const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view) { | 25 | const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view, |
| 26 | const Math::Vec4<u8> (&texture_color)[4]) { | ||
| 26 | 27 | ||
| 27 | // TODO(Subv): Bump mapping | 28 | Math::Vec3<float> surface_normal; |
| 28 | Math::Vec3<float> surface_normal = {0.0f, 0.0f, 1.0f}; | 29 | Math::Vec3<float> surface_tangent; |
| 29 | 30 | ||
| 30 | if (lighting.config0.bump_mode != LightingRegs::LightingBumpMode::None) { | 31 | if (lighting.config0.bump_mode != LightingRegs::LightingBumpMode::None) { |
| 31 | LOG_CRITICAL(HW_GPU, "unimplemented bump mapping"); | 32 | Math::Vec3<float> perturbation = |
| 32 | UNIMPLEMENTED(); | 33 | texture_color[lighting.config0.bump_selector].xyz().Cast<float>() / 127.5f - |
| 34 | Math::MakeVec(1.0f, 1.0f, 1.0f); | ||
| 35 | if (lighting.config0.bump_mode == LightingRegs::LightingBumpMode::NormalMap) { | ||
| 36 | if (!lighting.config0.disable_bump_renorm) { | ||
| 37 | const float z_square = 1 - perturbation.xy().Length2(); | ||
| 38 | perturbation.z = std::sqrt(std::max(z_square, 0.0f)); | ||
| 39 | } | ||
| 40 | surface_normal = perturbation; | ||
| 41 | surface_tangent = Math::MakeVec(1.0f, 0.0f, 0.0f); | ||
| 42 | } else if (lighting.config0.bump_mode == LightingRegs::LightingBumpMode::TangentMap) { | ||
| 43 | surface_normal = Math::MakeVec(0.0f, 0.0f, 1.0f); | ||
| 44 | surface_tangent = perturbation; | ||
| 45 | } else { | ||
| 46 | LOG_ERROR(HW_GPU, "Unknown bump mode %u", lighting.config0.bump_mode.Value()); | ||
| 47 | } | ||
| 48 | } else { | ||
| 49 | surface_normal = Math::MakeVec(0.0f, 0.0f, 1.0f); | ||
| 50 | surface_tangent = Math::MakeVec(1.0f, 0.0f, 0.0f); | ||
| 33 | } | 51 | } |
| 34 | 52 | ||
| 35 | // Use the normalized the quaternion when performing the rotation | 53 | // Use the normalized the quaternion when performing the rotation |
| 36 | auto normal = Math::QuaternionRotate(normquat, surface_normal); | 54 | auto normal = Math::QuaternionRotate(normquat, surface_normal); |
| 55 | auto tangent = Math::QuaternionRotate(normquat, surface_tangent); | ||
| 37 | 56 | ||
| 38 | Math::Vec4<float> diffuse_sum = {0.0f, 0.0f, 0.0f, 1.0f}; | 57 | Math::Vec4<float> diffuse_sum = {0.0f, 0.0f, 0.0f, 1.0f}; |
| 39 | Math::Vec4<float> specular_sum = {0.0f, 0.0f, 0.0f, 1.0f}; | 58 | Math::Vec4<float> specular_sum = {0.0f, 0.0f, 0.0f, 1.0f}; |
| @@ -102,6 +121,16 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( | |||
| 102 | result = Math::Dot(light_vector, spot_dir.Cast<float>() / 2047.0f); | 121 | result = Math::Dot(light_vector, spot_dir.Cast<float>() / 2047.0f); |
| 103 | break; | 122 | break; |
| 104 | } | 123 | } |
| 124 | case LightingRegs::LightingLutInput::CP: | ||
| 125 | if (lighting.config0.config == LightingRegs::LightingConfig::Config7) { | ||
| 126 | const Math::Vec3<float> norm_half_vector = half_vector.Normalized(); | ||
| 127 | const Math::Vec3<float> half_vector_proj = | ||
| 128 | norm_half_vector - normal * Math::Dot(normal, norm_half_vector); | ||
| 129 | result = Math::Dot(half_vector_proj, tangent); | ||
| 130 | } else { | ||
| 131 | result = 0.0f; | ||
| 132 | } | ||
| 133 | break; | ||
| 105 | default: | 134 | default: |
| 106 | LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %u\n", static_cast<u32>(input)); | 135 | LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %u\n", static_cast<u32>(input)); |
| 107 | UNIMPLEMENTED(); | 136 | UNIMPLEMENTED(); |
| @@ -201,7 +230,8 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( | |||
| 201 | d1_lut_value * refl_value * light_config.specular_1.ToVec3f(); | 230 | d1_lut_value * refl_value * light_config.specular_1.ToVec3f(); |
| 202 | 231 | ||
| 203 | // Fresnel | 232 | // Fresnel |
| 204 | if (lighting.config1.disable_lut_fr == 0 && | 233 | // Note: only the last entry in the light slots applies the Fresnel factor |
| 234 | if (light_index == lighting.max_light_index && lighting.config1.disable_lut_fr == 0 && | ||
| 205 | LightingRegs::IsLightingSamplerSupported(lighting.config0.config, | 235 | LightingRegs::IsLightingSamplerSupported(lighting.config0.config, |
| 206 | LightingRegs::LightingSampler::Fresnel)) { | 236 | LightingRegs::LightingSampler::Fresnel)) { |
| 207 | 237 | ||
| @@ -213,14 +243,14 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( | |||
| 213 | if (lighting.config0.fresnel_selector == | 243 | if (lighting.config0.fresnel_selector == |
| 214 | LightingRegs::LightingFresnelSelector::PrimaryAlpha || | 244 | LightingRegs::LightingFresnelSelector::PrimaryAlpha || |
| 215 | lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { | 245 | lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { |
| 216 | diffuse_sum.a() *= lut_value; | 246 | diffuse_sum.a() = lut_value; |
| 217 | } | 247 | } |
| 218 | 248 | ||
| 219 | // Enabled for the specular lighting alpha component | 249 | // Enabled for the specular lighting alpha component |
| 220 | if (lighting.config0.fresnel_selector == | 250 | if (lighting.config0.fresnel_selector == |
| 221 | LightingRegs::LightingFresnelSelector::SecondaryAlpha || | 251 | LightingRegs::LightingFresnelSelector::SecondaryAlpha || |
| 222 | lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { | 252 | lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) { |
| 223 | specular_sum.a() *= lut_value; | 253 | specular_sum.a() = lut_value; |
| 224 | } | 254 | } |
| 225 | } | 255 | } |
| 226 | 256 | ||
diff --git a/src/video_core/swrasterizer/lighting.h b/src/video_core/swrasterizer/lighting.h index 438dca926..d807a3d94 100644 --- a/src/video_core/swrasterizer/lighting.h +++ b/src/video_core/swrasterizer/lighting.h | |||
| @@ -13,6 +13,7 @@ namespace Pica { | |||
| 13 | 13 | ||
| 14 | std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( | 14 | std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( |
| 15 | const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, | 15 | const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state, |
| 16 | const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view); | 16 | const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view, |
| 17 | const Math::Vec4<u8> (&texture_color)[4]); | ||
| 17 | 18 | ||
| 18 | } // namespace Pica | 19 | } // namespace Pica |
diff --git a/src/video_core/swrasterizer/rasterizer.cpp b/src/video_core/swrasterizer/rasterizer.cpp index fdc1df199..862135614 100644 --- a/src/video_core/swrasterizer/rasterizer.cpp +++ b/src/video_core/swrasterizer/rasterizer.cpp | |||
| @@ -437,8 +437,8 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve | |||
| 437 | GetInterpolatedAttribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(), | 437 | GetInterpolatedAttribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(), |
| 438 | GetInterpolatedAttribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(), | 438 | GetInterpolatedAttribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(), |
| 439 | }; | 439 | }; |
| 440 | std::tie(primary_fragment_color, secondary_fragment_color) = | 440 | std::tie(primary_fragment_color, secondary_fragment_color) = ComputeFragmentsColors( |
| 441 | ComputeFragmentsColors(g_state.regs.lighting, g_state.lighting, normquat, view); | 441 | g_state.regs.lighting, g_state.lighting, normquat, view, texture_color); |
| 442 | } | 442 | } |
| 443 | 443 | ||
| 444 | for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); | 444 | for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size(); |