summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-x.travis-build.sh2
-rw-r--r--CMakeLists.txt32
-rw-r--r--CMakeModules/DownloadExternals.cmake18
-rw-r--r--appveyor.yml166
-rw-r--r--dist/citra.manifest24
-rw-r--r--externals/CMakeLists.txt19
-rw-r--r--src/audio_core/hle/source.cpp49
-rw-r--r--src/audio_core/interpolate.cpp86
-rw-r--r--src/audio_core/interpolate.h27
-rw-r--r--src/citra/citra.rc8
-rw-r--r--src/citra/config.cpp2
-rw-r--r--src/citra/default_ini.h4
-rw-r--r--src/citra_qt/citra-qt.rc12
-rw-r--r--src/citra_qt/configuration/config.cpp3
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic.cpp4
-rw-r--r--src/core/core.cpp1
-rw-r--r--src/core/frontend/emu_window.cpp71
-rw-r--r--src/core/frontend/emu_window.h31
-rw-r--r--src/core/frontend/input.h6
-rw-r--r--src/core/hle/applets/mii_selector.cpp6
-rw-r--r--src/core/hle/applets/mii_selector.h57
-rw-r--r--src/core/hle/kernel/memory.cpp30
-rw-r--r--src/core/hle/kernel/memory.h2
-rw-r--r--src/core/hle/kernel/thread.cpp12
-rw-r--r--src/core/hle/kernel/vm_manager.cpp13
-rw-r--r--src/core/hle/kernel/vm_manager.h6
-rw-r--r--src/core/hle/lock.cpp2
-rw-r--r--src/core/hle/lock.h2
-rw-r--r--src/core/hle/service/apt/apt.cpp286
-rw-r--r--src/core/hle/service/cfg/cfg.cpp2
-rw-r--r--src/core/hle/service/cfg/cfg.h2
-rw-r--r--src/core/hle/service/hid/hid.cpp12
-rw-r--r--src/core/hle/service/nwm/nwm_uds.cpp165
-rw-r--r--src/core/hle/service/nwm/nwm_uds.h12
-rw-r--r--src/core/hle/service/nwm/uds_beacon.cpp3
-rw-r--r--src/core/hle/service/nwm/uds_beacon.h30
-rw-r--r--src/core/hle/service/nwm/uds_connection.cpp79
-rw-r--r--src/core/hle/service/nwm/uds_connection.h51
-rw-r--r--src/core/hle/svc.cpp2
-rw-r--r--src/core/loader/3dsx.cpp1
-rw-r--r--src/core/loader/elf.cpp1
-rw-r--r--src/core/loader/ncch.cpp1
-rw-r--r--src/core/memory.cpp157
-rw-r--r--src/core/memory.h62
-rw-r--r--src/core/memory_setup.h10
-rw-r--r--src/core/settings.h1
-rw-r--r--src/tests/core/arm/arm_test_common.cpp18
-rw-r--r--src/video_core/CMakeLists.txt2
-rw-r--r--src/video_core/command_processor.cpp54
-rw-r--r--src/video_core/geometry_pipeline.cpp274
-rw-r--r--src/video_core/geometry_pipeline.h49
-rw-r--r--src/video_core/pica.cpp21
-rw-r--r--src/video_core/pica_state.h11
-rw-r--r--src/video_core/primitive_assembly.cpp15
-rw-r--r--src/video_core/primitive_assembly.h7
-rw-r--r--src/video_core/regs_pipeline.h34
-rw-r--r--src/video_core/regs_rasterizer.h14
-rw-r--r--src/video_core/regs_shader.h7
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp28
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h9
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp99
-rw-r--r--src/video_core/shader/shader.cpp41
-rw-r--r--src/video_core/shader/shader.h49
-rw-r--r--src/video_core/shader/shader_interpreter.cpp16
-rw-r--r--src/video_core/shader/shader_jit_x64_compiler.cpp49
-rw-r--r--src/video_core/shader/shader_jit_x64_compiler.h2
-rw-r--r--src/video_core/swrasterizer/clipper.cpp31
-rw-r--r--src/video_core/swrasterizer/lighting.cpp46
-rw-r--r--src/video_core/swrasterizer/lighting.h3
-rw-r--r--src/video_core/swrasterizer/rasterizer.cpp4
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 @@
2cmake_minimum_required(VERSION 3.6) 2cmake_minimum_required(VERSION 3.6)
3list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules") 3list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
4list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules") 4list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
5include(DownloadExternals)
5 6
6project(citra) 7project(citra)
7 8
@@ -12,6 +13,15 @@ option(ENABLE_QT "Enable the Qt frontend" ON)
12option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF) 13option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
13 14
14option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) 15option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
16option(CITRA_USE_BUNDLED_CURL "FOR MINGW ONLY: Download curl configured against winssl instead of openssl" OFF)
17if (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)
20endif()
21if (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)
24endif()
15 25
16if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit) 26if(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)
134endif() 144endif()
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
158function(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)
170endfunction()
171
172find_package(PNG QUIET) 164find_package(PNG QUIET)
173if (NOT PNG_FOUND) 165if (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
6function(download_bundled_external remote_path lib_name prefix_var)
7set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}")
8if (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")
15endif()
16message(STATUS "Using bundled binaries at ${prefix}")
17set(${prefix_var} "${prefix}" PARENT_SCOPE)
18endfunction() \ 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
8os: Visual Studio 2017 8os: Visual Studio 2017
9 9
10environment:
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
10platform: 19platform:
11 - x64 20 - x64
12 21
@@ -15,72 +24,149 @@ configuration:
15 24
16install: 25install:
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
19before_build: 39before_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
25build: 51build_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
29after_build: 60after_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
65test_script: 145test_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
68artifacts: 155artifacts:
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
78deploy: 164deploy:
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
3set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
4include(DownloadExternals)
5
3# Catch 6# Catch
4add_library(catch-single-include INTERFACE) 7add_library(catch-single-include INTERFACE)
5target_include_directories(catch-single-include INTERFACE catch/single_include) 8target_include_directories(catch-single-include INTERFACE catch/single_include)
@@ -54,9 +57,21 @@ add_subdirectory(enet)
54target_include_directories(enet INTERFACE ./enet/include) 57target_include_directories(enet INTERFACE ./enet/include)
55 58
56if (ENABLE_WEB_SERVICE) 59if (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 {
13constexpr u64 scale_factor = 1 << 24; 13constexpr u64 scale_factor = 1 << 24;
14constexpr u64 scale_mask = scale_factor - 1; 14constexpr 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.
18template <typename Function> 18template <typename Function>
19static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input, 19static 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
65StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) { 53void 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
71StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) { 60void 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
11namespace AudioInterp { 12namespace AudioInterp {
@@ -14,31 +15,35 @@ namespace AudioInterp {
14using StereoBuffer16 = std::vector<std::array<s16, 2>>; 15using StereoBuffer16 = std::vector<std::array<s16, 2>>;
15 16
16struct State { 17struct 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 */
31StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier); 34void 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 */
42StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier); 46void 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.
8CITRA_ICON ICON "../../dist/citra.ico" 9CITRA_ICON ICON "../../dist/citra.ico"
9 10
11
12/////////////////////////////////////////////////////////////////////////////
13//
14// RT_MANIFEST
15//
16
171 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)
63motion_device= 63motion_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
67touch_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.
8CITRA_ICON ICON "../../dist/citra.ico" 9// QT requires that the default application icon is named IDI_ICON1
9 10
11IDI_ICON1 ICON "../../dist/citra.ico"
12
13
14/////////////////////////////////////////////////////////////////////////////
15//
16// RT_MANIFEST
17//
18
191 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
139System::ResultStatus System::Init(EmuWindow* emu_window, u32 system_mode) { 139System::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
11class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
12 public std::enable_shared_from_this<TouchState> {
13public:
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
25private:
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
42EmuWindow::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
50EmuWindow::~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
49void EmuWindow::TouchReleased() { 93void 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
55void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) { 100void 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
115protected: 103protected:
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 */
127using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>; 127using 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 */
133using 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
84void MiiSelector::Update() {} 84void 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 {
16namespace Applets { 16namespace Applets {
17 17
18struct MiiConfig { 18struct 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
39static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size"); 33static_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")
43ASSERT_REG_POSITION(unk_008, 0x08); 37ASSERT_REG_POSITION(title, 0x08);
44ASSERT_REG_POSITION(unk_08C, 0x8C); 38ASSERT_REG_POSITION(show_guest_miis, 0x8C);
45ASSERT_REG_POSITION(unk_090, 0x90); 39ASSERT_REG_POSITION(initially_selected_mii_index, 0x90);
46ASSERT_REG_POSITION(unk_094, 0x94); 40ASSERT_REG_POSITION(guest_mii_whitelist, 0x94);
47ASSERT_REG_POSITION(unk_0FE, 0xFE);
48#undef ASSERT_REG_POSITION 41#undef ASSERT_REG_POSITION
49 42
50struct MiiResult { 43struct 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};
58static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size"); 53static_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")
62ASSERT_REG_POSITION(unk_0C, 0x0C); 57ASSERT_REG_POSITION(selected_mii_data, 0x0C);
63ASSERT_REG_POSITION(unk_6C, 0x6C); 58ASSERT_REG_POSITION(guest_mii_name, 0x6C);
64#undef ASSERT_REG_POSITION 59#undef ASSERT_REG_POSITION
65 60
66class MiiSelector final : public Applet { 61class 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
25namespace Kernel { 24namespace Kernel {
26 25
27static MemoryRegionInfo memory_regions[3]; 26MemoryRegionInfo 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
99std::array<u8, Memory::VRAM_SIZE> vram;
100std::array<u8, Memory::N3DS_EXTRA_RAM_SIZE> n3ds_extra_ram;
101
102void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping) { 98void 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
27void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping); 27void HandleSpecialMapping(VMManager& address_space, const AddressMapping& mapping);
28void MapSharedPages(VMManager& address_space); 28void MapSharedPages(VMManager& address_space);
29
30extern 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) {
328void VMManager::UpdatePageTableForVMA(const VirtualMemoryArea& vma) { 332void 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
14namespace Kernel { 15namespace 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 */
104class VMManager final { 105class VMManager final {
105 // TODO(yuriks): Make page tables switchable to support multiple VMManagers
106public: 106public:
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
187private: 191private:
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
9namespace HLE { 9namespace HLE {
10std::mutex g_hle_lock; 10std::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 */
17extern std::mutex g_hle_lock; 17extern 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
202static 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
256static 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
321static 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
201void GetSharedFont(Service::Interface* self) { 339void 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
866static 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
920static 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
967static 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
985void Init() { 1013void 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
171static u32 GetRegionValue() { 171u32 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 */
102void GetCountryCodeID(Service::Interface* self); 102void GetCountryCodeID(Service::Interface* self);
103 103
104u32 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
24namespace Service { 23namespace Service {
25namespace HID { 24namespace HID {
@@ -59,6 +58,7 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::
59 buttons; 58 buttons;
60static std::unique_ptr<Input::AnalogDevice> circle_pad; 59static std::unique_ptr<Input::AnalogDevice> circle_pad;
61static std::unique_ptr<Input::MotionDevice> motion_device; 60static std::unique_ptr<Input::MotionDevice> motion_device;
61static std::unique_ptr<Input::TouchDevice> touch_device;
62 62
63DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) { 63DirectionState 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
101static void UnloadInputDevices() { 102static 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
109static void UpdatePadCallback(u64 userdata, int cycles_late) { 111static 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
21namespace Service { 24namespace Service {
22namespace NWM { 25namespace 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.
52static int beacon_broadcast_event; 55static 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.
59static 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.
63constexpr size_t MaxBeaconFrames = 15;
64
65// List of the last <MaxBeaconFrames> beacons received from the network.
66static 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 */
71std::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.
78void 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.
84void 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 */
98static 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 */
115void 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
132void 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 */
155void 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.
175void 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 */
625static 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>;
42enum class NetworkStatus { 42enum 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
85static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset."); 86static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset.");
86static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size."); 87static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size.");
87 88
89/// Additional block tag ids in the Beacon and Association Response frames
90enum 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
88class NWM_UDS final : public Interface { 100class NWM_UDS final : public Interface {
89public: 101public:
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
328std::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 {
17using MacAddress = std::array<u8, 6>; 17using MacAddress = std::array<u8, 6>;
18constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32}; 18constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};
19 19
20/// Additional block tag ids in the Beacon frames
21enum 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
136static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size."); 125static_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.
140struct 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 */
162std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes); 137std::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 */
168std::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
9namespace Service {
10namespace NWM {
11
12// Note: These values were taken from a packet capture of an o3DS XL
13// broadcasting a Super Smash Bros. 4 lobby.
14constexpr u16 DefaultExtraCapabilities = 0x0431;
15
16std::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
26AuthenticationSeq 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 */
39static 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
58std::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
12namespace Service {
13namespace NWM {
14
15/// Sequence number of the 802.11 authentication frames.
16enum class AuthenticationSeq : u16 { SEQ1 = 1, SEQ2 = 2 };
17
18enum class AuthAlgorithm : u16 { OpenSystem = 0 };
19
20enum class AuthStatus : u16 { Successful = 0 };
21
22enum class AssocStatus : u16 { Successful = 0 };
23
24struct 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
30static_assert(sizeof(AuthenticationFrame) == 6, "AuthenticationFrame has wrong size");
31
32struct AssociationResponseFrame {
33 u16_le capabilities;
34 u16_le status_code;
35 u16_le assoc_id;
36};
37
38static_assert(sizeof(AssociationResponseFrame) == 6, "AssociationResponseFrame has wrong size");
39
40/// Generates an 802.11 authentication frame, starting at the frame body.
41std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq);
42
43/// Returns the sequence number from the body of an Authentication frame.
44AuthenticationSeq 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.
48std::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
19namespace Memory { 20namespace Memory {
20 21
21enum class PageType { 22static std::array<u8, Memory::VRAM_SIZE> vram;
22 /// Page is unmapped and should cause an access error. 23static 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
36struct SpecialRegion {
37 VAddr base;
38 u32 size;
39 MMIORegionPointer handler;
40};
41 24
42/** 25PageTable* 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 */
48struct 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
75static PageTable main_page_table;
76/// Currently active page table
77static PageTable* current_page_table = &main_page_table;
78 26
79std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers() { 27std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers() {
80 return &current_page_table->pointers; 28 return &current_page_table->pointers;
81} 29}
82 30
83static void MapPages(u32 base, u32 size, u8* memory, PageType type) { 31static 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
104void InitMemoryMap() { 52void 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
110void 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
116void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler) { 58void 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
124void UnmapRegion(VAddr base, u32 size) { 66void 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
275bool IsValidPhysicalAddress(const PAddr paddr) { 217bool 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
280u8* GetPointer(const VAddr vaddr) { 221u8* GetPointer(const VAddr vaddr) {
@@ -306,9 +247,63 @@ std::string ReadCString(VAddr vaddr, std::size_t max_length) {
306} 247}
307 248
308u8* GetPhysicalPointer(PAddr address) { 249u8* 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
314void RasterizerMarkRegionCached(PAddr start, u32 size, int count_delta) { 309void 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
13namespace Memory { 15namespace Memory {
14 16
@@ -21,6 +23,59 @@ const u32 PAGE_MASK = PAGE_SIZE - 1;
21const int PAGE_BITS = 12; 23const int PAGE_BITS = 12;
22const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS); 24const size_t PAGE_TABLE_NUM_ENTRIES = 1 << (32 - PAGE_BITS);
23 25
26enum 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
41struct 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 */
53struct 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
25enum : PAddr { 80enum : 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
185extern PageTable* current_page_table;
186
129bool IsValidVirtualAddress(const VAddr addr); 187bool IsValidVirtualAddress(const VAddr addr);
130bool IsValidPhysicalAddress(const PAddr addr); 188bool 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 */
175u8* GetPhysicalPointer(PAddr address); 231u8* 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 */
211std::array<u8*, PAGE_TABLE_NUM_ENTRIES>* GetCurrentPageTablePointers(); 267std::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
10namespace Memory { 10namespace Memory {
11 11
12void 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 */
21void MapMemoryRegion(VAddr base, u32 size, u8* target); 20void 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 */
29void MapIoRegion(VAddr base, u32 size, MMIORegionPointer mmio_handler); 29void MapIoRegion(PageTable& page_table, VAddr base, u32 size, MMIORegionPointer mmio_handler);
30 30
31void UnmapRegion(VAddr base, u32 size); 31void 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
9namespace ArmTests { 10namespace ArmTests {
10 11
12static Memory::PageTable page_table;
13
11TestEnvironment::TestEnvironment(bool mutable_memory_) 14TestEnvironment::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
17TestEnvironment::~TestEnvironment() { 27TestEnvironment::~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
22void TestEnvironment::SetMemory64(VAddr vaddr, u64 value) { 32void 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 @@
1set(SRCS 1set(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
29set(HEADERS 30set(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
11namespace Pica {
12
13/// An attribute buffering interface for different pipeline modes
14class GeometryPipelineBackend {
15public:
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?
41class GeometryPipeline_Point : public GeometryPipelineBackend {
42public:
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
75private:
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.
87class GeometryPipeline_VariablePrimitive : public GeometryPipelineBackend {
88public:
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
139private:
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.
152class GeometryPipeline_FixedPrimitive : public GeometryPipelineBackend {
153public:
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
186private:
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
195GeometryPipeline::GeometryPipeline(State& state) : state(state) {}
196
197GeometryPipeline::~GeometryPipeline() = default;
198
199void GeometryPipeline::SetVertexHandler(Shader::VertexHandler vertex_handler) {
200 this->vertex_handler = vertex_handler;
201}
202
203void 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
211void 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
247bool GeometryPipeline::NeedIndexInput() const {
248 if (!backend)
249 return false;
250 return backend->NeedIndexInput();
251}
252
253void GeometryPipeline::SubmitIndex(unsigned int val) {
254 backend->SubmitIndex(val);
255}
256
257void 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
10namespace Pica {
11
12struct State;
13
14class GeometryPipelineBackend;
15
16/// A pipeline receiving from vertex shader and sending to geometry shader and primitive assembler
17class GeometryPipeline {
18public:
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
43private:
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
10namespace Pica { 12namespace 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
29State::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
27void State::Reset() { 46void 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
18struct State { 19struct 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>
17void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx, 17void 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
53template <typename VertexType> 56template <typename VertexType>
57void PrimitiveAssembler<VertexType>::SetWinding() {
58 winding = true;
59}
60
61template <typename VertexType>
54void PrimitiveAssembler<VertexType>::Reset() { 62void 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
59template <typename VertexType> 68template <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
13namespace Pica { 13namespace 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
1297void RasterizerOpenGL::SyncClipEnabled() {
1298 state.clip_distance[1] = Pica::g_state.regs.rasterizer.clip_enable != 0;
1299}
1300
1301void 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
1283void RasterizerOpenGL::SyncCullMode() { 1311void 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
25namespace GLShader { 26namespace GLShader {
26 27
28static const std::string UniformBlockDef = R"(
29#define NUM_TEV_STAGES 6
30#define NUM_LIGHTS 8
31
32struct 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
43layout (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
27PicaShaderConfig PicaShaderConfig::BuildFromRegs(const Pica::Regs& regs) { 64PicaShaderConfig 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
1014in vec4 primary_color; 1050in vec4 primary_color;
1015in vec2 texcoord[3]; 1051in vec2 texcoord[3];
@@ -1021,36 +1057,6 @@ in vec4 gl_FragCoord;
1021 1057
1022out vec4 color; 1058out vec4 color;
1023 1059
1024struct 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
1035layout (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
1054uniform sampler2D tex[3]; 1060uniform sampler2D tex[3];
1055uniform samplerBuffer lighting_lut; 1061uniform samplerBuffer lighting_lut;
1056uniform samplerBuffer fog_lut; 1062uniform samplerBuffer fog_lut;
@@ -1059,7 +1065,11 @@ uniform samplerBuffer proctex_color_map;
1059uniform samplerBuffer proctex_alpha_map; 1065uniform samplerBuffer proctex_alpha_map;
1060uniform samplerBuffer proctex_lut; 1066uniform samplerBuffer proctex_lut;
1061uniform samplerBuffer proctex_diff_lut; 1067uniform 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
1064vec3 quaternion_rotate(vec4 q, vec3 v) { 1074vec3 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;
1190out vec4 normquat; 1205out vec4 normquat;
1191out vec3 view; 1206out vec3 view;
1192 1207
1208)";
1209
1210 out += UniformBlockDef;
1211
1212 out += R"(
1213
1193void main() { 1214void 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
22namespace Shader { 22namespace Shader {
23 23
24OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& input) { 24OutputVertex 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
86UnitState::UnitState(GSEmitter* emitter) : emitter_ptr(emitter) {}
87
88GSEmitter::GSEmitter() {
89 handlers = new Handlers;
90}
91
92GSEmitter::~GSEmitter() {
93 delete handlers;
94}
95
96void 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
113GSUnitState::GSUnitState() : UnitState(&emitter) {}
114
115void 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
120void GSUnitState::ConfigOutput(const ShaderRegs& config) {
121 emitter.output_mask = config.output_mask;
122}
123
85MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240)); 124MICROPROFILE_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
36using VertexHandler = std::function<void(const AttributeBuffer&)>;
37
38/// Handler type for signaling to invert the vertex order of the next triangle
39using WindingSetter = std::function<void()>;
40
34struct OutputVertex { 41struct 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");
61static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size"); 69static_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 */
74struct 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};
92static_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 */
69struct UnitState { 100struct 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 */
167struct 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
128struct ShaderSetup { 175struct 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
775static void Emit(GSEmitter* emitter, Math::Vec4<float24> (*output)[16]) {
776 emitter->Emit(*output);
777}
778
779void 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
801void 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
775void JitShader::Compile_Block(unsigned end) { 820void 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
70private: 72private:
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
23std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( 23std::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
14std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors( 14std::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();