summaryrefslogtreecommitdiffstats
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/citra/citra.rc8
-rw-r--r--src/citra_qt/citra-qt.rc12
-rw-r--r--src/core/CMakeLists.txt2
-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/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/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_shader.h7
-rw-r--r--src/video_core/renderer_opengl/gl_shader_gen.cpp9
-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/lighting.cpp7
34 files changed, 1138 insertions, 195 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
export Qt5_DIR=$(brew --prefix)/opt/qt5
mkdir build && cd build
- cmake .. -GXcode
+ cmake .. -DUSE_SYSTEM_CURL=ON -GXcode
xcodebuild -configuration Release
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 @@
cmake_minimum_required(VERSION 3.6)
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/CMakeModules")
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/externals/cmake-modules")
+include(DownloadExternals)
project(citra)
@@ -12,6 +13,15 @@ option(ENABLE_QT "Enable the Qt frontend" ON)
option(CITRA_USE_BUNDLED_QT "Download bundled Qt binaries" OFF)
option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
+option(CITRA_USE_BUNDLED_CURL "FOR MINGW ONLY: Download curl configured against winssl instead of openssl" OFF)
+if (ENABLE_WEB_SERVICE AND CITRA_USE_BUNDLED_CURL AND WINDOWS AND MSVC)
+ message("Turning off use bundled curl as msvc can compile curl on cpr")
+ SET(CITRA_USE_BUNDLED_CURL OFF CACHE BOOL "" FORCE)
+endif()
+if (ENABLE_WEB_SERVICE AND NOT CITRA_USE_BUNDLED_CURL AND MINGW)
+ message(AUTHOR_WARNING "Turning on CITRA_USE_BUNDLED_CURL. Override it only if you know what you are doing.")
+ SET(CITRA_USE_BUNDLED_CURL ON CACHE BOOL "" FORCE)
+endif()
if(NOT EXISTS ${CMAKE_SOURCE_DIR}/.git/hooks/pre-commit)
message(STATUS "Copying pre-commit hook")
@@ -129,8 +139,8 @@ else()
set(CMAKE_C_FLAGS_RELEASE "/O2 /GS- /MD" CACHE STRING "" FORCE)
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE}" CACHE STRING "" FORCE)
- set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG" CACHE STRING "" FORCE)
- set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
+ set(CMAKE_EXE_LINKER_FLAGS_DEBUG "/DEBUG /MANIFEST:NO" CACHE STRING "" FORCE)
+ set(CMAKE_EXE_LINKER_FLAGS_RELEASE "/DEBUG /MANIFEST:NO /INCREMENTAL:NO /OPT:REF,ICF" CACHE STRING "" FORCE)
endif()
# Set file offset size to 64 bits.
@@ -151,24 +161,6 @@ set_property(DIRECTORY APPEND PROPERTY
# System imported libraries
# ======================
-# This function downloads a binary library package from our external repo.
-# Params:
-# remote_path: path to the file to download, relative to the remote repository root
-# prefix_var: name of a variable which will be set with the path to the extracted contents
-function(download_bundled_external remote_path lib_name prefix_var)
- set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}")
- if (NOT EXISTS "${prefix}")
- message(STATUS "Downloading binaries for ${lib_name}...")
- file(DOWNLOAD
- https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z
- "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS)
- execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z"
- WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
- endif()
- message(STATUS "Using bundled binaries at ${prefix}")
- set(${prefix_var} "${prefix}" PARENT_SCOPE)
-endfunction()
-
find_package(PNG QUIET)
if (NOT PNG_FOUND)
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 @@
+
+# This function downloads a binary library package from our external repo.
+# Params:
+# remote_path: path to the file to download, relative to the remote repository root
+# prefix_var: name of a variable which will be set with the path to the extracted contents
+function(download_bundled_external remote_path lib_name prefix_var)
+set(prefix "${CMAKE_BINARY_DIR}/externals/${lib_name}")
+if (NOT EXISTS "${prefix}")
+ message(STATUS "Downloading binaries for ${lib_name}...")
+ file(DOWNLOAD
+ https://github.com/citra-emu/ext-windows-bin/raw/master/${remote_path}${lib_name}.7z
+ "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z" SHOW_PROGRESS)
+ execute_process(COMMAND ${CMAKE_COMMAND} -E tar xf "${CMAKE_BINARY_DIR}/externals/${lib_name}.7z"
+ WORKING_DIRECTORY "${CMAKE_BINARY_DIR}/externals")
+endif()
+message(STATUS "Using bundled binaries at ${prefix}")
+set(${prefix_var} "${prefix}" PARENT_SCOPE)
+endfunction() \ No newline at end of file
diff --git a/appveyor.yml b/appveyor.yml
index 94e9969f5..5524eb576 100644
--- a/appveyor.yml
+++ b/appveyor.yml
@@ -7,6 +7,15 @@ cache:
os: Visual Studio 2017
+environment:
+ # Tell msys2 to add mingw64 to the path
+ MSYSTEM: MINGW64
+ # Tell msys2 to inherit the current directory when starting the shell
+ CHERE_INVOKING: 1
+ matrix:
+ - BUILD_TYPE: mingw
+ - BUILD_TYPE: msvc
+
platform:
- x64
@@ -15,72 +24,149 @@ configuration:
install:
- git submodule update --init --recursive
+ - ps: |
+ if ($env:BUILD_TYPE -eq 'mingw') {
+ $dependencies = "mingw64/mingw-w64-x86_64-cmake",
+ "mingw64/mingw-w64-x86_64-qt5",
+ "mingw64/mingw-w64-x86_64-curl",
+ "mingw64/mingw-w64-x86_64-SDL2"
+ # redirect err to null to prevent warnings from becoming errors
+ # workaround to prevent pacman from failing due to cyclical dependencies
+ C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S mingw64/mingw-w64-x86_64-freetype mingw64/mingw-w64-x86_64-fontconfig" 2> $null
+ C:\msys64\usr\bin\bash -lc "pacman --noconfirm -S $dependencies" 2> $null
+ }
before_build:
- - mkdir build
- - cd build
- - cmake -G "Visual Studio 15 2017 Win64" -DCITRA_USE_BUNDLED_QT=1 -DCITRA_USE_BUNDLED_SDL2=1 -DCMAKE_USE_OPENSSL=0 ..
+ - mkdir %BUILD_TYPE%_build
+ - cd %BUILD_TYPE%_build
+ - ps: |
+ if ($env:BUILD_TYPE -eq 'msvc') {
+ # redirect stderr and change the exit code to prevent powershell from cancelling the build if cmake prints a warning
+ 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'
+ } else {
+ 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"
+ }
- cd ..
-build:
- project: build/citra.sln
- parallel: true
+build_script:
+ - ps: |
+ if ($env:BUILD_TYPE -eq 'msvc') {
+ # https://www.appveyor.com/docs/build-phase
+ msbuild msvc_build/citra.sln /maxcpucount /logger:"C:\Program Files\AppVeyor\BuildAgent\Appveyor.MSBuildLogger.dll"
+ } else {
+ C:\msys64\usr\bin\bash.exe -lc 'mingw32-make -C mingw_build/ 2>&1'
+ }
after_build:
- ps: |
$GITDATE = $(git show -s --date=short --format='%ad') -replace "-",""
$GITREV = $(git show -s --format='%h')
- $GIT_LONG_HASH = $(git rev-parse HEAD)
- # Where are these spaces coming from? Regardless, let's remove them
- $MSVC_BUILD_NAME = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
- $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
- $MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
- $BINTRAY_VERSION = "nightly-$GIT_LONG_HASH" -replace " ", ""
-
- # set the build names as env vars so the artifacts can upload them
- $env:MSVC_BUILD_NAME = $MSVC_BUILD_NAME
- $env:MSVC_BUILD_PDB = $MSVC_BUILD_PDB
- $env:MSVC_SEVENZIP = $MSVC_SEVENZIP
- $env:GITREV = $GITREV
-
- 7z a -tzip $MSVC_BUILD_PDB .\build\bin\release\*.pdb
- rm .\build\bin\release\*.pdb
# Find out which kind of release we are producing by tag name
if ($env:APPVEYOR_REPO_TAG_NAME) {
- $RELEASE_DIST, $RELEASE_VERSION = $env:APPVEYOR_REPO_TAG_NAME.split('-')
+ $RELEASE_DIST, $RELEASE_VERSION = $env:APPVEYOR_REPO_TAG_NAME.split('-')
} else {
- # There is no repo tag - make assumptions
- $RELEASE_DIST = "head"
+ # There is no repo tag - make assumptions
+ $RELEASE_DIST = "head"
}
- mkdir $RELEASE_DIST
- Copy-Item .\build\bin\release\* -Destination $RELEASE_DIST -Recurse
- Copy-Item .\license.txt -Destination $RELEASE_DIST
- Copy-Item .\README.md -Destination $RELEASE_DIST
-
- 7z a -tzip $MSVC_BUILD_NAME $RELEASE_DIST\*
- 7z a $MSVC_SEVENZIP $RELEASE_DIST
+ if ($env:BUILD_TYPE -eq 'msvc') {
+ # Where are these spaces coming from? Regardless, let's remove them
+ $MSVC_BUILD_ZIP = "citra-windows-msvc-$GITDATE-$GITREV.zip" -replace " ", ""
+ $MSVC_BUILD_PDB = "citra-windows-msvc-$GITDATE-$GITREV-debugsymbols.zip" -replace " ", ""
+ $MSVC_SEVENZIP = "citra-windows-msvc-$GITDATE-$GITREV.7z" -replace " ", ""
+
+ # set the build names as env vars so the artifacts can upload them
+ $env:BUILD_ZIP = $MSVC_BUILD_ZIP
+ $env:BUILD_SYMBOLS = $MSVC_BUILD_PDB
+ $env:BUILD_UPDATE = $MSVC_SEVENZIP
+
+ 7z a -tzip $MSVC_BUILD_PDB .\msvc_build\bin\release\*.pdb
+ rm .\msvc_build\bin\release\*.pdb
+
+ mkdir $RELEASE_DIST
+ Copy-Item .\msvc_build\bin\release\* -Destination $RELEASE_DIST -Recurse
+ Copy-Item .\license.txt -Destination $RELEASE_DIST
+ Copy-Item .\README.md -Destination $RELEASE_DIST
+ 7z a -tzip $MSVC_BUILD_ZIP $RELEASE_DIST\*
+ 7z a $MSVC_SEVENZIP $RELEASE_DIST
+ } else {
+ $MINGW_BUILD_ZIP = "citra-windows-mingw-$GITDATE-$GITREV.zip" -replace " ", ""
+ $MINGW_SEVENZIP = "citra-windows-mingw-$GITDATE-$GITREV.7z" -replace " ", ""
+ # not going to bother adding separate debug symbols for mingw, so just upload a README for it
+ # if someone wants to add them, change mingw to compile with -g and use objdump and strip to separate the symbols from the binary
+ $MINGW_NO_DEBUG_SYMBOLS = "README_No_Debug_Symbols.txt"
+ Set-Content -Path $MINGW_NO_DEBUG_SYMBOLS -Value "This is a workaround for Appveyor since msvc has debug symbols but mingw doesnt" -Force
+
+ # store the build information in env vars so we can use them as artifacts
+ $env:BUILD_ZIP = $MINGW_BUILD_ZIP
+ $env:BUILD_SYMBOLS = $MINGW_NO_DEBUG_SYMBOLS
+ $env:BUILD_UPDATE = $MINGW_SEVENZIP
+
+ $CMAKE_SOURCE_DIR = "$env:APPVEYOR_BUILD_FOLDER"
+ $CMAKE_BINARY_DIR = "$CMAKE_SOURCE_DIR/mingw_build"
+ $RELEASE_DIST = $RELEASE_DIST + "-mingw"
+
+ mkdir $RELEASE_DIST
+ mkdir $RELEASE_DIST/platforms
+
+ # copy the compiled binaries and other release files to the release folder
+ Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "citra*.exe" | Copy-Item -destination $RELEASE_DIST
+ # copy the libcurl dll
+ Get-ChildItem "$CMAKE_BINARY_DIR" -Recurse -Filter "libcurl.dll" | Copy-Item -destination $RELEASE_DIST
+ Copy-Item -path "$CMAKE_SOURCE_DIR/license.txt" -destination $RELEASE_DIST
+ Copy-Item -path "$CMAKE_SOURCE_DIR/README.md" -destination $RELEASE_DIST
+ # copy all the dll dependencies to the release folder
+ # hardcoded list because we don't build static and determining the list of dlls from the binary is a pain.
+ $MingwDLLs = "Qt5Core.dll","Qt5Widgets.dll","Qt5Gui.dll","Qt5OpenGL.dll",
+ # QT dll dependencies
+ "libbz2-*.dll","libicudt*.dll","libicuin*.dll","libicuuc*.dll","libffi-*.dll",
+ "libfreetype-*.dll","libglib-*.dll","libgobject-*.dll","libgraphite2.dll","libiconv-*.dll",
+ "libharfbuzz-*.dll","libintl-*.dll","libpcre-*.dll","libpcre16-*.dll","libpng16-*.dll",
+ # Runtime/Other dependencies
+ "libgcc_s_seh-*.dll","libstdc++-*.dll","libwinpthread-*.dll","SDL2.dll","zlib1.dll"
+ foreach ($file in $MingwDLLs) {
+ Copy-Item -path "C:/msys64/mingw64/bin/$file" -force -destination "$RELEASE_DIST"
+ }
+ # the above list copies a few extra debug dlls that aren't needed (thanks globbing patterns!)
+ # so we can remove them by hardcoding another list of extra dlls to remove
+ $DebugDLLs = "libicudtd*.dll","libicuind*.dll","libicuucd*.dll"
+ foreach ($file in $DebugDLLs) {
+ Remove-Item -path "$RELEASE_DIST/$file"
+ }
+
+ # copy the qt windows plugin dll to platforms
+ Copy-Item -path "C:/msys64/mingw64/share/qt5/plugins/platforms/qwindows.dll" -force -destination "$RELEASE_DIST/platforms"
+
+ 7z a -tzip $MINGW_BUILD_ZIP $RELEASE_DIST\*
+ 7z a $MINGW_SEVENZIP $RELEASE_DIST
+ }
test_script:
- - cd build && ctest -VV -C Release && cd ..
+ - cd %BUILD_TYPE%_build
+ - ps: |
+ if ($env:BUILD_TYPE -eq 'msvc') {
+ ctest -VV -C Release
+ } else {
+ C:\msys64\usr\bin\bash.exe -lc "ctest -VV -C Release"
+ }
+ - cd ..
artifacts:
- - path: $(MSVC_BUILD_NAME)
- name: msvcbuild
- type: zip
- - path: $(MSVC_BUILD_PDB)
- name: msvcdebug
+ - path: $(BUILD_ZIP)
+ name: build
type: zip
- - path: $(MSVC_SEVENZIP)
- name: msvcupdate
+ - path: $(BUILD_SYMBOLS)
+ name: debugsymbols
+ - path: $(BUILD_UPDATE)
+ name: update
deploy:
provider: GitHub
release: $(appveyor_repo_tag_name)
auth_token:
secure: "dbpsMC/MgPKWFNJCXpQl4cR8FYhepkPLjgNp/pRMktZ8oLKTqPYErfreaIxb/4P1"
- artifact: msvcupdate,msvcbuild
+ artifact: update,build
draft: false
prerelease: false
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 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0">
+ <trustInfo xmlns="urn:schemas-microsoft-com:asm.v3">
+ <security>
+ <requestedPrivileges>
+ <requestedExecutionLevel level="asInvoker" uiAccess="false"/>
+ </requestedPrivileges>
+ </security>
+ </trustInfo>
+ <application xmlns="urn:schemas-microsoft-com:asm.v3">
+ <windowsSettings>
+ <dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">True/PM</dpiAware>
+ <longPathAware xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">true</longPathAware>
+ </windowsSettings>
+ </application>
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+ </application>
+ </compatibility>
+</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 @@
# Definitions for all external bundled libraries
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/CMakeModules)
+include(DownloadExternals)
+
# Catch
add_library(catch-single-include INTERFACE)
target_include_directories(catch-single-include INTERFACE catch/single_include)
@@ -54,9 +57,21 @@ add_subdirectory(enet)
target_include_directories(enet INTERFACE ./enet/include)
if (ENABLE_WEB_SERVICE)
+ # msys installed curl is configured to use openssl, but that isn't portable
+ # since it relies on having the bundled certs install in the home folder for SSL
+ # by default on mingw, download the precompiled curl thats linked against windows native ssl
+ if (MINGW AND CITRA_USE_BUNDLED_CURL)
+ download_bundled_external("curl/" "curl-7_55_1" CURL_PREFIX)
+ set(CURL_PREFIX "${CMAKE_BINARY_DIR}/externals/curl-7_55_1")
+ set(CURL_FOUND YES)
+ set(CURL_INCLUDE_DIR "${CURL_PREFIX}/include" CACHE PATH "Path to curl headers")
+ set(CURL_LIBRARY "${CURL_PREFIX}/lib/libcurldll.a" CACHE PATH "Path to curl library")
+ set(CURL_DLL_DIR "${CURL_PREFIX}/lib/" CACHE PATH "Path to curl.dll")
+ set(USE_SYSTEM_CURL ON CACHE BOOL "")
+ endif()
# CPR
- option(BUILD_TESTING OFF)
- option(BUILD_CPR_TESTS OFF)
+ set(BUILD_TESTING OFF CACHE BOOL "")
+ set(BUILD_CPR_TESTS OFF CACHE BOOL "")
add_subdirectory(cpr)
target_include_directories(cpr INTERFACE ./cpr/include)
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 @@
+#include "winresrc.h"
/////////////////////////////////////////////////////////////////////////////
//
// Icon
@@ -7,3 +8,10 @@
// remains consistent on all systems.
CITRA_ICON ICON "../../dist/citra.ico"
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+1 RT_MANIFEST "../../dist/citra.manifest"
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 @@
+#include "winresrc.h"
/////////////////////////////////////////////////////////////////////////////
//
// Icon
@@ -5,5 +6,14 @@
// Icon with lowest ID value placed first to ensure application icon
// remains consistent on all systems.
-CITRA_ICON ICON "../../dist/citra.ico"
+// QT requires that the default application icon is named IDI_ICON1
+IDI_ICON1 ICON "../../dist/citra.ico"
+
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// RT_MANIFEST
+//
+
+1 RT_MANIFEST "../../dist/citra.manifest"
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 662030782..78dec8600 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -145,6 +145,7 @@ set(SRCS
hle/service/nwm/nwm_tst.cpp
hle/service/nwm/nwm_uds.cpp
hle/service/nwm/uds_beacon.cpp
+ hle/service/nwm/uds_connection.cpp
hle/service/nwm/uds_data.cpp
hle/service/pm_app.cpp
hle/service/ptm/ptm.cpp
@@ -344,6 +345,7 @@ set(HEADERS
hle/service/nwm/nwm_tst.h
hle/service/nwm/nwm_uds.h
hle/service/nwm/uds_beacon.h
+ hle/service/nwm/uds_connection.h
hle/service/nwm/uds_data.h
hle/service/pm_app.h
hle/service/ptm/ptm.h
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
// continue.
MiiResult result;
memset(&result, 0, sizeof(result));
- result.result_code = 0;
+ result.return_code = 0;
// Let the application know that we're closing
Service::APT::MessageParameter message;
@@ -82,5 +82,5 @@ ResultCode MiiSelector::StartImpl(const Service::APT::AppletStartupParameter& pa
}
void MiiSelector::Update() {}
-}
-} // namespace
+} // namespace Applets
+} // 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 {
namespace Applets {
struct MiiConfig {
- u8 unk_000;
- u8 unk_001;
- u8 unk_002;
- u8 unk_003;
- u8 unk_004;
+ u8 enable_cancel_button;
+ u8 enable_guest_mii;
+ u8 show_on_top_screen;
+ INSERT_PADDING_BYTES(5);
+ u16 title[0x40];
+ INSERT_PADDING_BYTES(4);
+ u8 show_guest_miis;
INSERT_PADDING_BYTES(3);
- u16 unk_008;
- INSERT_PADDING_BYTES(0x82);
- u8 unk_08C;
- INSERT_PADDING_BYTES(3);
- u16 unk_090;
+ u32 initially_selected_mii_index;
+ u8 guest_mii_whitelist[6];
+ u8 user_mii_whitelist[0x64];
INSERT_PADDING_BYTES(2);
- u32 unk_094;
- u16 unk_098;
- u8 unk_09A[0x64];
- u8 unk_0FE;
- u8 unk_0FF;
- u32 unk_100;
+ u32 magic_value;
};
-
static_assert(sizeof(MiiConfig) == 0x104, "MiiConfig structure has incorrect size");
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(MiiConfig, field_name) == position, \
"Field " #field_name " has invalid position")
-ASSERT_REG_POSITION(unk_008, 0x08);
-ASSERT_REG_POSITION(unk_08C, 0x8C);
-ASSERT_REG_POSITION(unk_090, 0x90);
-ASSERT_REG_POSITION(unk_094, 0x94);
-ASSERT_REG_POSITION(unk_0FE, 0xFE);
+ASSERT_REG_POSITION(title, 0x08);
+ASSERT_REG_POSITION(show_guest_miis, 0x8C);
+ASSERT_REG_POSITION(initially_selected_mii_index, 0x90);
+ASSERT_REG_POSITION(guest_mii_whitelist, 0x94);
#undef ASSERT_REG_POSITION
struct MiiResult {
- u32 result_code;
- u8 unk_04;
- INSERT_PADDING_BYTES(7);
- u8 unk_0C[0x60];
- u8 unk_6C[0x16];
+ u32 return_code;
+ u32 is_guest_mii_selected;
+ u32 selected_guest_mii_index;
+ // TODO(mailwl): expand to Mii Format structure: https://www.3dbrew.org/wiki/Mii
+ u8 selected_mii_data[0x5C];
INSERT_PADDING_BYTES(2);
+ u16 mii_data_checksum;
+ u16 guest_mii_name[0xC];
};
static_assert(sizeof(MiiResult) == 0x84, "MiiResult structure has incorrect size");
#define ASSERT_REG_POSITION(field_name, position) \
static_assert(offsetof(MiiResult, field_name) == position, \
"Field " #field_name " has invalid position")
-ASSERT_REG_POSITION(unk_0C, 0x0C);
-ASSERT_REG_POSITION(unk_6C, 0x6C);
+ASSERT_REG_POSITION(selected_mii_data, 0x0C);
+ASSERT_REG_POSITION(guest_mii_name, 0x6C);
#undef ASSERT_REG_POSITION
class MiiSelector final : public Applet {
@@ -79,5 +74,5 @@ private:
MiiConfig config;
};
-}
-} // namespace
+} // namespace Applets
+} // namespace HLE
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 @@
#include <array>
#include <cstring>
+#include <mutex>
#include <unordered_map>
#include <vector>
#include "common/common_types.h"
@@ -15,8 +16,10 @@
#include "core/hle/result.h"
#include "core/hle/service/nwm/nwm_uds.h"
#include "core/hle/service/nwm/uds_beacon.h"
+#include "core/hle/service/nwm/uds_connection.h"
#include "core/hle/service/nwm/uds_data.h"
#include "core/memory.h"
+#include "network/network.h"
namespace Service {
namespace NWM {
@@ -51,6 +54,135 @@ static NetworkInfo network_info;
// Event that will generate and send the 802.11 beacon frames.
static int beacon_broadcast_event;
+// Mutex to synchronize access to the list of received beacons between the emulation thread and the
+// network thread.
+static std::mutex beacon_mutex;
+
+// Number of beacons to store before we start dropping the old ones.
+// TODO(Subv): Find a more accurate value for this limit.
+constexpr size_t MaxBeaconFrames = 15;
+
+// List of the last <MaxBeaconFrames> beacons received from the network.
+static std::deque<Network::WifiPacket> received_beacons;
+
+/**
+ * Returns a list of received 802.11 beacon frames from the specified sender since the last call.
+ */
+std::deque<Network::WifiPacket> GetReceivedBeacons(const MacAddress& sender) {
+ std::lock_guard<std::mutex> lock(beacon_mutex);
+ // TODO(Subv): Filter by sender.
+ return std::move(received_beacons);
+}
+
+/// Sends a WifiPacket to the room we're currently connected to.
+void SendPacket(Network::WifiPacket& packet) {
+ // TODO(Subv): Implement.
+}
+
+// Inserts the received beacon frame in the beacon queue and removes any older beacons if the size
+// limit is exceeded.
+void HandleBeaconFrame(const Network::WifiPacket& packet) {
+ std::lock_guard<std::mutex> lock(beacon_mutex);
+
+ received_beacons.emplace_back(packet);
+
+ // Discard old beacons if the buffer is full.
+ if (received_beacons.size() > MaxBeaconFrames)
+ received_beacons.pop_front();
+}
+
+/*
+ * Returns an available index in the nodes array for the
+ * currently-hosted UDS network.
+ */
+static u16 GetNextAvailableNodeId() {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
+ "Can not accept clients if we're not hosting a network");
+
+ for (u16 index = 0; index < connection_status.max_nodes; ++index) {
+ if ((connection_status.node_bitmask & (1 << index)) == 0)
+ return index;
+ }
+
+ // Any connection attempts to an already full network should have been refused.
+ ASSERT_MSG(false, "No available connection slots in the network");
+}
+
+/*
+ * Start a connection sequence with an UDS server. The sequence starts by sending an 802.11
+ * authentication frame with SEQ1.
+ */
+void StartConnectionSequence(const MacAddress& server) {
+ ASSERT(connection_status.status == static_cast<u32>(NetworkStatus::NotConnected));
+
+ // TODO(Subv): Handle timeout.
+
+ // Send an authentication frame with SEQ1
+ using Network::WifiPacket;
+ WifiPacket auth_request;
+ auth_request.channel = network_channel;
+ auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ1);
+ auth_request.destination_address = server;
+ auth_request.type = WifiPacket::PacketType::Authentication;
+
+ SendPacket(auth_request);
+}
+
+/// Sends an Association Response frame to the specified mac address
+void SendAssociationResponseFrame(const MacAddress& address) {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost));
+
+ using Network::WifiPacket;
+ WifiPacket assoc_response;
+ assoc_response.channel = network_channel;
+ // TODO(Subv): This will cause multiple clients to end up with the same association id, but
+ // we're not using that for anything.
+ u16 association_id = 1;
+ assoc_response.data = GenerateAssocResponseFrame(AssocStatus::Successful, association_id,
+ network_info.network_id);
+ assoc_response.destination_address = address;
+ assoc_response.type = WifiPacket::PacketType::AssociationResponse;
+
+ SendPacket(assoc_response);
+}
+
+/*
+ * Handles the authentication request frame and sends the authentication response and association
+ * response frames. Once an Authentication frame with SEQ1 is received by the server, it responds
+ * with an Authentication frame containing SEQ2, and immediately sends an Association response frame
+ * containing the details of the access point and the assigned association id for the new client.
+ */
+void HandleAuthenticationFrame(const Network::WifiPacket& packet) {
+ // Only the SEQ1 auth frame is handled here, the SEQ2 frame doesn't need any special behavior
+ if (GetAuthenticationSeqNumber(packet.data) == AuthenticationSeq::SEQ1) {
+ ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost));
+
+ // Respond with an authentication response frame with SEQ2
+ using Network::WifiPacket;
+ WifiPacket auth_request;
+ auth_request.channel = network_channel;
+ auth_request.data = GenerateAuthenticationFrame(AuthenticationSeq::SEQ2);
+ auth_request.destination_address = packet.transmitter_address;
+ auth_request.type = WifiPacket::PacketType::Authentication;
+
+ SendPacket(auth_request);
+
+ SendAssociationResponseFrame(packet.transmitter_address);
+ }
+}
+
+/// Callback to parse and handle a received wifi packet.
+void OnWifiPacketReceived(const Network::WifiPacket& packet) {
+ switch (packet.type) {
+ case Network::WifiPacket::PacketType::Beacon:
+ HandleBeaconFrame(packet);
+ break;
+ case Network::WifiPacket::PacketType::Authentication:
+ HandleAuthenticationFrame(packet);
+ break;
+ }
+}
+
/**
* NWM_UDS::Shutdown service function
* Inputs:
@@ -111,8 +243,7 @@ static void RecvBeaconBroadcastData(Interface* self) {
u32 total_size = sizeof(BeaconDataReplyHeader);
// Retrieve all beacon frames that were received from the desired mac address.
- std::deque<WifiPacket> beacons =
- GetReceivedPackets(WifiPacket::PacketType::Beacon, mac_address);
+ auto beacons = GetReceivedBeacons(mac_address);
BeaconDataReplyHeader data_reply_header{};
data_reply_header.total_entries = beacons.size();
@@ -193,6 +324,9 @@ static void InitializeWithVersion(Interface* self) {
rb.Push(RESULT_SUCCESS);
rb.PushCopyHandles(Kernel::g_handle_table.Create(connection_status_event).Unwrap());
+ // TODO(Subv): Connect the OnWifiPacketReceived function to the wifi packet received callback of
+ // the room we're currently in.
+
LOG_DEBUG(Service_NWM, "called sharedmem_size=0x%08X, version=0x%08X, sharedmem_handle=0x%08X",
sharedmem_size, version, sharedmem_handle);
}
@@ -610,32 +744,23 @@ static void BeaconBroadcastCallback(u64 userdata, int cycles_late) {
if (connection_status.status != static_cast<u32>(NetworkStatus::ConnectedAsHost))
return;
- // TODO(Subv): Actually send the beacon.
std::vector<u8> frame = GenerateBeaconFrame(network_info, node_info);
+ using Network::WifiPacket;
+ WifiPacket packet;
+ packet.type = WifiPacket::PacketType::Beacon;
+ packet.data = std::move(frame);
+ packet.destination_address = Network::BroadcastMac;
+ packet.channel = network_channel;
+
+ SendPacket(packet);
+
// Start broadcasting the network, send a beacon frame every 102.4ms.
CoreTiming::ScheduleEvent(msToCycles(DefaultBeaconInterval * MillisecondsPerTU) - cycles_late,
beacon_broadcast_event, 0);
}
/*
- * Returns an available index in the nodes array for the
- * currently-hosted UDS network.
- */
-static u32 GetNextAvailableNodeId() {
- ASSERT_MSG(connection_status.status == static_cast<u32>(NetworkStatus::ConnectedAsHost),
- "Can not accept clients if we're not hosting a network");
-
- for (unsigned index = 0; index < connection_status.max_nodes; ++index) {
- if ((connection_status.node_bitmask & (1 << index)) == 0)
- return index;
- }
-
- // Any connection attempts to an already full network should have been refused.
- ASSERT_MSG(false, "No available connection slots in the network");
-}
-
-/*
* Called when a client connects to an UDS network we're hosting,
* updates the connection status and signals the update event.
* @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>;
enum class NetworkStatus {
NotConnected = 3,
ConnectedAsHost = 6,
+ Connecting = 7,
ConnectedAsClient = 9,
ConnectedAsSpectator = 10,
};
@@ -85,6 +86,17 @@ static_assert(offsetof(NetworkInfo, oui_value) == 0xC, "oui_value is at the wron
static_assert(offsetof(NetworkInfo, wlan_comm_id) == 0x10, "wlancommid is at the wrong offset.");
static_assert(sizeof(NetworkInfo) == 0x108, "NetworkInfo has incorrect size.");
+/// Additional block tag ids in the Beacon and Association Response frames
+enum class TagId : u8 {
+ SSID = 0,
+ SupportedRates = 1,
+ DSParameterSet = 2,
+ TrafficIndicationMap = 5,
+ CountryInformation = 7,
+ ERPInformation = 42,
+ VendorSpecific = 221
+};
+
class NWM_UDS final : public Interface {
public:
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
return buffer;
}
-std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender) {
- return {};
-}
} // namespace NWM
} // 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 {
using MacAddress = std::array<u8, 6>;
constexpr std::array<u8, 3> NintendoOUI = {0x00, 0x1F, 0x32};
-/// Additional block tag ids in the Beacon frames
-enum class TagId : u8 {
- SSID = 0,
- SupportedRates = 1,
- DSParameterSet = 2,
- TrafficIndicationMap = 5,
- CountryInformation = 7,
- ERPInformation = 42,
- VendorSpecific = 221
-};
-
/**
* Internal vendor-specific tag ids as stored inside
* VendorSpecific blocks in the Beacon frames.
@@ -135,20 +124,6 @@ struct BeaconData {
static_assert(sizeof(BeaconData) == 0x12, "BeaconData has incorrect size.");
-/// Information about a received WiFi packet.
-/// Acts as our own 802.11 header.
-struct WifiPacket {
- enum class PacketType { Beacon, Data };
-
- PacketType type; ///< The type of 802.11 frame, Beacon / Data.
-
- /// Raw 802.11 frame data, starting at the management frame header for management frames.
- std::vector<u8> data;
- MacAddress transmitter_address; ///< Mac address of the transmitter.
- MacAddress destination_address; ///< Mac address of the receiver.
- u8 channel; ///< WiFi channel where this frame was transmitted.
-};
-
/**
* Decrypts the beacon data buffer for the network described by `network_info`.
*/
@@ -161,10 +136,5 @@ void DecryptBeaconData(const NetworkInfo& network_info, std::vector<u8>& buffer)
*/
std::vector<u8> GenerateBeaconFrame(const NetworkInfo& network_info, const NodeList& nodes);
-/**
- * Returns a list of received 802.11 frames from the specified sender
- * matching the type since the last call.
- */
-std::deque<WifiPacket> GetReceivedPackets(WifiPacket::PacketType type, const MacAddress& sender);
} // namespace NWM
} // 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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "core/hle/service/nwm/nwm_uds.h"
+#include "core/hle/service/nwm/uds_connection.h"
+#include "fmt/format.h"
+
+namespace Service {
+namespace NWM {
+
+// Note: These values were taken from a packet capture of an o3DS XL
+// broadcasting a Super Smash Bros. 4 lobby.
+constexpr u16 DefaultExtraCapabilities = 0x0431;
+
+std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq) {
+ AuthenticationFrame frame{};
+ frame.auth_seq = static_cast<u16>(seq);
+
+ std::vector<u8> data(sizeof(frame));
+ std::memcpy(data.data(), &frame, sizeof(frame));
+
+ return data;
+}
+
+AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body) {
+ AuthenticationFrame frame;
+ std::memcpy(&frame, body.data(), sizeof(frame));
+
+ return static_cast<AuthenticationSeq>(frame.auth_seq);
+}
+
+/**
+ * Generates an SSID tag of an 802.11 Beacon frame with an 8-byte character representation of the
+ * specified network id as the SSID value.
+ * @param network_id The network id to use.
+ * @returns A buffer with the SSID tag.
+ */
+static std::vector<u8> GenerateSSIDTag(u32 network_id) {
+ constexpr u8 SSIDSize = 8;
+
+ struct {
+ u8 id = static_cast<u8>(TagId::SSID);
+ u8 size = SSIDSize;
+ } tag_header;
+
+ std::vector<u8> buffer(sizeof(tag_header) + SSIDSize);
+
+ std::memcpy(buffer.data(), &tag_header, sizeof(tag_header));
+
+ std::string network_name = fmt::format("{0:08X}", network_id);
+
+ std::memcpy(buffer.data() + sizeof(tag_header), network_name.c_str(), SSIDSize);
+
+ return buffer;
+}
+
+std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id) {
+ AssociationResponseFrame frame{};
+ frame.capabilities = DefaultExtraCapabilities;
+ frame.status_code = static_cast<u16>(status);
+ // The association id is ORed with this magic value (0xC000)
+ constexpr u16 AssociationIdMagic = 0xC000;
+ frame.assoc_id = association_id | AssociationIdMagic;
+
+ std::vector<u8> data(sizeof(frame));
+ std::memcpy(data.data(), &frame, sizeof(frame));
+
+ auto ssid_tag = GenerateSSIDTag(network_id);
+ data.insert(data.end(), ssid_tag.begin(), ssid_tag.end());
+
+ // TODO(Subv): Add the SupportedRates tag.
+ // TODO(Subv): Add the DSParameterSet tag.
+ // TODO(Subv): Add the ERPInformation tag.
+ return data;
+}
+
+} // namespace NWM
+} // 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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <vector>
+#include "common/common_types.h"
+#include "common/swap.h"
+#include "core/hle/service/service.h"
+
+namespace Service {
+namespace NWM {
+
+/// Sequence number of the 802.11 authentication frames.
+enum class AuthenticationSeq : u16 { SEQ1 = 1, SEQ2 = 2 };
+
+enum class AuthAlgorithm : u16 { OpenSystem = 0 };
+
+enum class AuthStatus : u16 { Successful = 0 };
+
+enum class AssocStatus : u16 { Successful = 0 };
+
+struct AuthenticationFrame {
+ u16_le auth_algorithm = static_cast<u16>(AuthAlgorithm::OpenSystem);
+ u16_le auth_seq;
+ u16_le status_code = static_cast<u16>(AuthStatus::Successful);
+};
+
+static_assert(sizeof(AuthenticationFrame) == 6, "AuthenticationFrame has wrong size");
+
+struct AssociationResponseFrame {
+ u16_le capabilities;
+ u16_le status_code;
+ u16_le assoc_id;
+};
+
+static_assert(sizeof(AssociationResponseFrame) == 6, "AssociationResponseFrame has wrong size");
+
+/// Generates an 802.11 authentication frame, starting at the frame body.
+std::vector<u8> GenerateAuthenticationFrame(AuthenticationSeq seq);
+
+/// Returns the sequence number from the body of an Authentication frame.
+AuthenticationSeq GetAuthenticationSeqNumber(const std::vector<u8>& body);
+
+/// Generates an 802.11 association response frame with the specified status, association id and
+/// network id, starting at the frame body.
+std::vector<u8> GenerateAssocResponseFrame(AssocStatus status, u16 association_id, u32 network_id);
+
+} // namespace NWM
+} // namespace Service
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 @@
set(SRCS
command_processor.cpp
debug_utils/debug_utils.cpp
+ geometry_pipeline.cpp
pica.cpp
primitive_assembly.cpp
regs.cpp
@@ -29,6 +30,7 @@ set(SRCS
set(HEADERS
command_processor.h
debug_utils/debug_utils.h
+ geometry_pipeline.h
gpu_debugger.h
pica.h
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) {
case PICA_REG_INDEX(pipeline.vs_default_attributes_setup.index):
g_state.immediate.current_attribute = 0;
+ g_state.immediate.reset_geometry_pipeline = true;
default_attr_counter = 0;
break;
@@ -234,16 +235,14 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
shader_engine->Run(g_state.vs, shader_unit);
shader_unit.WriteOutput(regs.vs, output);
- // Send to renderer
- using Pica::Shader::OutputVertex;
- auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
- const OutputVertex& v2) {
- VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
- };
-
- g_state.primitive_assembler.SubmitVertex(
- Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output),
- AddTriangle);
+ // Send to geometry pipeline
+ if (g_state.immediate.reset_geometry_pipeline) {
+ g_state.geometry_pipeline.Reconfigure();
+ g_state.immediate.reset_geometry_pipeline = false;
+ }
+ ASSERT(!g_state.geometry_pipeline.NeedIndexInput());
+ g_state.geometry_pipeline.Setup(shader_engine);
+ g_state.geometry_pipeline.SubmitVertex(output);
}
}
}
@@ -321,8 +320,8 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
// The size has been tuned for optimal balance between hit-rate and the cost of lookup
const size_t VERTEX_CACHE_SIZE = 32;
std::array<u16, VERTEX_CACHE_SIZE> vertex_cache_ids;
- std::array<Shader::OutputVertex, VERTEX_CACHE_SIZE> vertex_cache;
- Shader::OutputVertex output_vertex;
+ std::array<Shader::AttributeBuffer, VERTEX_CACHE_SIZE> vertex_cache;
+ Shader::AttributeBuffer vs_output;
unsigned int vertex_cache_pos = 0;
vertex_cache_ids.fill(-1);
@@ -332,6 +331,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
shader_engine->SetupBatch(g_state.vs, regs.vs.main_offset);
+ g_state.geometry_pipeline.Reconfigure();
+ g_state.geometry_pipeline.Setup(shader_engine);
+ if (g_state.geometry_pipeline.NeedIndexInput())
+ ASSERT(is_indexed);
+
for (unsigned int index = 0; index < regs.pipeline.num_vertices; ++index) {
// Indexed rendering doesn't use the start offset
unsigned int vertex =
@@ -345,6 +349,11 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
bool vertex_cache_hit = false;
if (is_indexed) {
+ if (g_state.geometry_pipeline.NeedIndexInput()) {
+ g_state.geometry_pipeline.SubmitIndex(vertex);
+ continue;
+ }
+
if (g_debug_context && Pica::g_debug_context->recorder) {
int size = index_u16 ? 2 : 1;
memory_accesses.AddAccess(base_address + index_info.offset + size * index,
@@ -353,7 +362,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
for (unsigned int i = 0; i < VERTEX_CACHE_SIZE; ++i) {
if (vertex == vertex_cache_ids[i]) {
- output_vertex = vertex_cache[i];
+ vs_output = vertex_cache[i];
vertex_cache_hit = true;
break;
}
@@ -362,7 +371,7 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
if (!vertex_cache_hit) {
// Initialize data for the current vertex
- Shader::AttributeBuffer input, output{};
+ Shader::AttributeBuffer input;
loader.LoadVertex(base_address, index, vertex, input, memory_accesses);
// Send to vertex shader
@@ -371,26 +380,17 @@ static void WritePicaReg(u32 id, u32 value, u32 mask) {
(void*)&input);
shader_unit.LoadInput(regs.vs, input);
shader_engine->Run(g_state.vs, shader_unit);
- shader_unit.WriteOutput(regs.vs, output);
-
- // Retrieve vertex from register data
- output_vertex = Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, output);
+ shader_unit.WriteOutput(regs.vs, vs_output);
if (is_indexed) {
- vertex_cache[vertex_cache_pos] = output_vertex;
+ vertex_cache[vertex_cache_pos] = vs_output;
vertex_cache_ids[vertex_cache_pos] = vertex;
vertex_cache_pos = (vertex_cache_pos + 1) % VERTEX_CACHE_SIZE;
}
}
- // Send to renderer
- using Pica::Shader::OutputVertex;
- auto AddTriangle = [](const OutputVertex& v0, const OutputVertex& v1,
- const OutputVertex& v2) {
- VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
- };
-
- primitive_assembler.SubmitVertex(output_vertex, AddTriangle);
+ // Send to geometry pipeline
+ g_state.geometry_pipeline.SubmitVertex(vs_output);
}
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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include "video_core/geometry_pipeline.h"
+#include "video_core/pica_state.h"
+#include "video_core/regs.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
+
+namespace Pica {
+
+/// An attribute buffering interface for different pipeline modes
+class GeometryPipelineBackend {
+public:
+ virtual ~GeometryPipelineBackend() = default;
+
+ /// Checks if there is no incomplete data transfer
+ virtual bool IsEmpty() const = 0;
+
+ /// Checks if the pipeline needs a direct input from index buffer
+ virtual bool NeedIndexInput() const = 0;
+
+ /// Submits an index from index buffer
+ virtual void SubmitIndex(unsigned int val) = 0;
+
+ /**
+ * Submits vertex attributes
+ * @param input attributes of a vertex output from vertex shader
+ * @return if the buffer is full and the geometry shader should be invoked
+ */
+ virtual bool SubmitVertex(const Shader::AttributeBuffer& input) = 0;
+};
+
+// In the Point mode, vertex attributes are sent to the input registers in the geometry shader unit.
+// The size of vertex shader outputs and geometry shader inputs are constants. Geometry shader is
+// invoked upon inputs buffer filled up by vertex shader outputs. For example, if we have a geometry
+// shader that takes 6 inputs, and the vertex shader outputs 2 attributes, it would take 3 vertices
+// for one geometry shader invocation.
+// TODO: what happens when the input size is not divisible by the output size?
+class GeometryPipeline_Point : public GeometryPipelineBackend {
+public:
+ GeometryPipeline_Point(const Regs& regs, Shader::GSUnitState& unit) : regs(regs), unit(unit) {
+ ASSERT(regs.pipeline.variable_primitive == 0);
+ ASSERT(regs.gs.input_to_uniform == 0);
+ vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
+ size_t gs_input_num = regs.gs.max_input_attribute_index + 1;
+ ASSERT(gs_input_num % vs_output_num == 0);
+ buffer_cur = attribute_buffer.attr;
+ buffer_end = attribute_buffer.attr + gs_input_num;
+ }
+
+ bool IsEmpty() const override {
+ return buffer_cur == attribute_buffer.attr;
+ }
+
+ bool NeedIndexInput() const override {
+ return false;
+ }
+
+ void SubmitIndex(unsigned int val) override {
+ UNREACHABLE();
+ }
+
+ bool SubmitVertex(const Shader::AttributeBuffer& input) override {
+ buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
+ if (buffer_cur == buffer_end) {
+ buffer_cur = attribute_buffer.attr;
+ unit.LoadInput(regs.gs, attribute_buffer);
+ return true;
+ }
+ return false;
+ }
+
+private:
+ const Regs& regs;
+ Shader::GSUnitState& unit;
+ Shader::AttributeBuffer attribute_buffer;
+ Math::Vec4<float24>* buffer_cur;
+ Math::Vec4<float24>* buffer_end;
+ unsigned int vs_output_num;
+};
+
+// In VariablePrimitive mode, vertex attributes are buffered into the uniform registers in the
+// geometry shader unit. The number of vertex is variable, which is specified by the first index
+// value in the batch. This mode is usually used for subdivision.
+class GeometryPipeline_VariablePrimitive : public GeometryPipelineBackend {
+public:
+ GeometryPipeline_VariablePrimitive(const Regs& regs, Shader::ShaderSetup& setup)
+ : regs(regs), setup(setup) {
+ ASSERT(regs.pipeline.variable_primitive == 1);
+ ASSERT(regs.gs.input_to_uniform == 1);
+ vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
+ }
+
+ bool IsEmpty() const override {
+ return need_index;
+ }
+
+ bool NeedIndexInput() const override {
+ return need_index;
+ }
+
+ void SubmitIndex(unsigned int val) override {
+ DEBUG_ASSERT(need_index);
+
+ // The number of vertex input is put to the uniform register
+ float24 vertex_num = float24::FromFloat32(val);
+ setup.uniforms.f[0] = Math::MakeVec(vertex_num, vertex_num, vertex_num, vertex_num);
+
+ // The second uniform register and so on are used for receiving input vertices
+ buffer_cur = setup.uniforms.f + 1;
+
+ main_vertex_num = regs.pipeline.variable_vertex_main_num_minus_1 + 1;
+ total_vertex_num = val;
+ need_index = false;
+ }
+
+ bool SubmitVertex(const Shader::AttributeBuffer& input) override {
+ DEBUG_ASSERT(!need_index);
+ if (main_vertex_num != 0) {
+ // For main vertices, receive all attributes
+ buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
+ --main_vertex_num;
+ } else {
+ // For other vertices, only receive the first attribute (usually the position)
+ *(buffer_cur++) = input.attr[0];
+ }
+ --total_vertex_num;
+
+ if (total_vertex_num == 0) {
+ need_index = true;
+ return true;
+ }
+
+ return false;
+ }
+
+private:
+ bool need_index = true;
+ const Regs& regs;
+ Shader::ShaderSetup& setup;
+ unsigned int main_vertex_num;
+ unsigned int total_vertex_num;
+ Math::Vec4<float24>* buffer_cur;
+ unsigned int vs_output_num;
+};
+
+// In FixedPrimitive mode, vertex attributes are buffered into the uniform registers in the geometry
+// shader unit. The number of vertex per shader invocation is constant. This is usually used for
+// particle system.
+class GeometryPipeline_FixedPrimitive : public GeometryPipelineBackend {
+public:
+ GeometryPipeline_FixedPrimitive(const Regs& regs, Shader::ShaderSetup& setup)
+ : regs(regs), setup(setup) {
+ ASSERT(regs.pipeline.variable_primitive == 0);
+ ASSERT(regs.gs.input_to_uniform == 1);
+ vs_output_num = regs.pipeline.vs_outmap_total_minus_1_a + 1;
+ ASSERT(vs_output_num == regs.pipeline.gs_config.stride_minus_1 + 1);
+ size_t vertex_num = regs.pipeline.gs_config.fixed_vertex_num_minus_1 + 1;
+ buffer_cur = buffer_begin = setup.uniforms.f + regs.pipeline.gs_config.start_index;
+ buffer_end = buffer_begin + vs_output_num * vertex_num;
+ }
+
+ bool IsEmpty() const override {
+ return buffer_cur == buffer_begin;
+ }
+
+ bool NeedIndexInput() const override {
+ return false;
+ }
+
+ void SubmitIndex(unsigned int val) override {
+ UNREACHABLE();
+ }
+
+ bool SubmitVertex(const Shader::AttributeBuffer& input) override {
+ buffer_cur = std::copy(input.attr, input.attr + vs_output_num, buffer_cur);
+ if (buffer_cur == buffer_end) {
+ buffer_cur = buffer_begin;
+ return true;
+ }
+ return false;
+ }
+
+private:
+ const Regs& regs;
+ Shader::ShaderSetup& setup;
+ Math::Vec4<float24>* buffer_begin;
+ Math::Vec4<float24>* buffer_cur;
+ Math::Vec4<float24>* buffer_end;
+ unsigned int vs_output_num;
+};
+
+GeometryPipeline::GeometryPipeline(State& state) : state(state) {}
+
+GeometryPipeline::~GeometryPipeline() = default;
+
+void GeometryPipeline::SetVertexHandler(Shader::VertexHandler vertex_handler) {
+ this->vertex_handler = vertex_handler;
+}
+
+void GeometryPipeline::Setup(Shader::ShaderEngine* shader_engine) {
+ if (!backend)
+ return;
+
+ this->shader_engine = shader_engine;
+ shader_engine->SetupBatch(state.gs, state.regs.gs.main_offset);
+}
+
+void GeometryPipeline::Reconfigure() {
+ ASSERT(!backend || backend->IsEmpty());
+
+ if (state.regs.pipeline.use_gs == PipelineRegs::UseGS::No) {
+ backend = nullptr;
+ return;
+ }
+
+ ASSERT(state.regs.pipeline.use_gs == PipelineRegs::UseGS::Yes);
+
+ // The following assumes that when geometry shader is in use, the shader unit 3 is configured as
+ // a geometry shader unit.
+ // TODO: what happens if this is not true?
+ ASSERT(state.regs.pipeline.gs_unit_exclusive_configuration == 1);
+ ASSERT(state.regs.gs.shader_mode == ShaderRegs::ShaderMode::GS);
+
+ state.gs_unit.ConfigOutput(state.regs.gs);
+
+ ASSERT(state.regs.pipeline.vs_outmap_total_minus_1_a ==
+ state.regs.pipeline.vs_outmap_total_minus_1_b);
+
+ switch (state.regs.pipeline.gs_config.mode) {
+ case PipelineRegs::GSMode::Point:
+ backend = std::make_unique<GeometryPipeline_Point>(state.regs, state.gs_unit);
+ break;
+ case PipelineRegs::GSMode::VariablePrimitive:
+ backend = std::make_unique<GeometryPipeline_VariablePrimitive>(state.regs, state.gs);
+ break;
+ case PipelineRegs::GSMode::FixedPrimitive:
+ backend = std::make_unique<GeometryPipeline_FixedPrimitive>(state.regs, state.gs);
+ break;
+ default:
+ UNREACHABLE();
+ }
+}
+
+bool GeometryPipeline::NeedIndexInput() const {
+ if (!backend)
+ return false;
+ return backend->NeedIndexInput();
+}
+
+void GeometryPipeline::SubmitIndex(unsigned int val) {
+ backend->SubmitIndex(val);
+}
+
+void GeometryPipeline::SubmitVertex(const Shader::AttributeBuffer& input) {
+ if (!backend) {
+ // No backend means the geometry shader is disabled, so we send the vertex shader output
+ // directly to the primitive assembler.
+ vertex_handler(input);
+ } else {
+ if (backend->SubmitVertex(input)) {
+ shader_engine->Run(state.gs, state.gs_unit);
+
+ // The uniform b15 is set to true after every geometry shader invocation. This is useful
+ // for the shader to know if this is the first invocation in a batch, if the program set
+ // b15 to false first.
+ state.gs.uniforms.b[15] = true;
+ }
+ }
+}
+
+} // 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 @@
+// Copyright 2017 Citra Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "video_core/shader/shader.h"
+
+namespace Pica {
+
+struct State;
+
+class GeometryPipelineBackend;
+
+/// A pipeline receiving from vertex shader and sending to geometry shader and primitive assembler
+class GeometryPipeline {
+public:
+ explicit GeometryPipeline(State& state);
+ ~GeometryPipeline();
+
+ /// Sets the handler for receiving vertex outputs from vertex shader
+ void SetVertexHandler(Shader::VertexHandler vertex_handler);
+
+ /**
+ * Setup the geometry shader unit if it is in use
+ * @param shader_engine the shader engine for the geometry shader to run
+ */
+ void Setup(Shader::ShaderEngine* shader_engine);
+
+ /// Reconfigures the pipeline according to current register settings
+ void Reconfigure();
+
+ /// Checks if the pipeline needs a direct input from index buffer
+ bool NeedIndexInput() const;
+
+ /// Submits an index from index buffer. Call this only when NeedIndexInput returns true
+ void SubmitIndex(unsigned int val);
+
+ /// Submits vertex attributes output from vertex shader
+ void SubmitVertex(const Shader::AttributeBuffer& input);
+
+private:
+ Shader::VertexHandler vertex_handler;
+ Shader::ShaderEngine* shader_engine;
+ std::unique_ptr<GeometryPipelineBackend> backend;
+ State& state;
+};
+} // 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 @@
// Refer to the license.txt file included.
#include <cstring>
+#include "video_core/geometry_pipeline.h"
#include "video_core/pica.h"
#include "video_core/pica_state.h"
-#include "video_core/regs_pipeline.h"
+#include "video_core/renderer_base.h"
+#include "video_core/video_core.h"
namespace Pica {
@@ -24,6 +26,23 @@ void Zero(T& o) {
memset(&o, 0, sizeof(o));
}
+State::State() : geometry_pipeline(*this) {
+ auto SubmitVertex = [this](const Shader::AttributeBuffer& vertex) {
+ using Pica::Shader::OutputVertex;
+ auto AddTriangle = [this](const OutputVertex& v0, const OutputVertex& v1,
+ const OutputVertex& v2) {
+ VideoCore::g_renderer->Rasterizer()->AddTriangle(v0, v1, v2);
+ };
+ primitive_assembler.SubmitVertex(
+ Shader::OutputVertex::FromAttributeBuffer(regs.rasterizer, vertex), AddTriangle);
+ };
+
+ auto SetWinding = [this]() { primitive_assembler.SetWinding(); };
+
+ g_state.gs_unit.SetVertexHandler(SubmitVertex, SetWinding);
+ g_state.geometry_pipeline.SetVertexHandler(SubmitVertex);
+}
+
void State::Reset() {
Zero(regs);
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 @@
#include "common/bit_field.h"
#include "common/common_types.h"
#include "common/vector_math.h"
+#include "video_core/geometry_pipeline.h"
#include "video_core/primitive_assembly.h"
#include "video_core/regs.h"
#include "video_core/shader/shader.h"
@@ -16,6 +17,7 @@ namespace Pica {
/// Struct used to describe current Pica state
struct State {
+ State();
void Reset();
/// Pica registers
@@ -137,8 +139,17 @@ struct State {
Shader::AttributeBuffer input_vertex;
// Index of the next attribute to be loaded into `input_vertex`.
u32 current_attribute = 0;
+ // Indicates the immediate mode just started and the geometry pipeline needs to reconfigure
+ bool reset_geometry_pipeline = true;
} immediate;
+ // the geometry shader needs to be kept in the global state because some shaders relie on
+ // preserved register value across shader invocation.
+ // TODO: also bring the three vertex shader units here and implement the shader scheduler.
+ Shader::GSUnitState gs_unit;
+
+ GeometryPipeline geometry_pipeline;
+
// This is constructed with a dummy triangle topology
PrimitiveAssembler<Shader::OutputVertex> primitive_assembler;
};
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>
void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
TriangleHandler triangle_handler) {
switch (topology) {
- // TODO: Figure out what's different with TriangleTopology::Shader.
case PipelineRegs::TriangleTopology::List:
case PipelineRegs::TriangleTopology::Shader:
if (buffer_index < 2) {
buffer[buffer_index++] = vtx;
} else {
buffer_index = 0;
-
- triangle_handler(buffer[0], buffer[1], vtx);
+ if (topology == PipelineRegs::TriangleTopology::Shader && winding) {
+ triangle_handler(buffer[1], buffer[0], vtx);
+ winding = false;
+ } else {
+ triangle_handler(buffer[0], buffer[1], vtx);
+ }
}
break;
@@ -51,9 +54,15 @@ void PrimitiveAssembler<VertexType>::SubmitVertex(const VertexType& vtx,
}
template <typename VertexType>
+void PrimitiveAssembler<VertexType>::SetWinding() {
+ winding = true;
+}
+
+template <typename VertexType>
void PrimitiveAssembler<VertexType>::Reset() {
buffer_index = 0;
strip_ready = false;
+ winding = false;
}
template <typename VertexType>
diff --git a/src/video_core/primitive_assembly.h b/src/video_core/primitive_assembly.h
index e8eccdf27..12de8e3b9 100644
--- a/src/video_core/primitive_assembly.h
+++ b/src/video_core/primitive_assembly.h
@@ -30,6 +30,12 @@ struct PrimitiveAssembler {
void SubmitVertex(const VertexType& vtx, TriangleHandler triangle_handler);
/**
+ * Invert the vertex order of the next triangle. Called by geometry shader emitter.
+ * This only takes effect for TriangleTopology::Shader.
+ */
+ void SetWinding();
+
+ /**
* Resets the internal state of the PrimitiveAssembler.
*/
void Reset();
@@ -45,6 +51,7 @@ private:
int buffer_index;
VertexType buffer[2];
bool strip_ready = false;
+ bool winding = false;
};
} // 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 {
// Number of vertices to render
u32 num_vertices;
- INSERT_PADDING_WORDS(0x1);
+ enum class UseGS : u32 {
+ No = 0,
+ Yes = 2,
+ };
+
+ union {
+ BitField<0, 2, UseGS> use_gs;
+ BitField<31, 1, u32> variable_primitive;
+ };
// The index of the first vertex to render
u32 vertex_offset;
@@ -218,7 +226,29 @@ struct PipelineRegs {
GPUMode gpu_mode;
- INSERT_PADDING_WORDS(0x18);
+ INSERT_PADDING_WORDS(0x4);
+ BitField<0, 4, u32> vs_outmap_total_minus_1_a;
+ INSERT_PADDING_WORDS(0x6);
+ BitField<0, 4, u32> vs_outmap_total_minus_1_b;
+
+ enum class GSMode : u32 {
+ Point = 0,
+ VariablePrimitive = 1,
+ FixedPrimitive = 2,
+ };
+
+ union {
+ BitField<0, 8, GSMode> mode;
+ BitField<8, 4, u32> fixed_vertex_num_minus_1;
+ BitField<12, 4, u32> stride_minus_1;
+ BitField<16, 4, u32> start_index;
+ } gs_config;
+
+ INSERT_PADDING_WORDS(0x1);
+
+ u32 variable_vertex_main_num_minus_1;
+
+ INSERT_PADDING_WORDS(0x9);
enum class TriangleTopology : u32 {
List = 0,
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 {
INSERT_PADDING_WORDS(0x4);
+ enum ShaderMode {
+ GS = 0x08,
+ VS = 0xA0,
+ };
+
union {
// Number of input attributes to shader unit - 1
BitField<0, 4, u32> max_input_attribute_index;
+ BitField<8, 8, u32> input_to_uniform;
+ BitField<24, 8, ShaderMode> shader_mode;
};
// Offset to shader program entry point (in words)
diff --git a/src/video_core/renderer_opengl/gl_shader_gen.cpp b/src/video_core/renderer_opengl/gl_shader_gen.cpp
index c8fc7a0ff..c536e61e1 100644
--- a/src/video_core/renderer_opengl/gl_shader_gen.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_gen.cpp
@@ -751,7 +751,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
}
// Fresnel
- if (lighting.lut_fr.enable &&
+ // Note: only the last entry in the light slots applies the Fresnel factor
+ if (light_index == lighting.src_num - 1 && lighting.lut_fr.enable &&
LightingRegs::IsLightingSamplerSupported(lighting.config,
LightingRegs::LightingSampler::Fresnel)) {
// Lookup fresnel LUT value
@@ -760,17 +761,17 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
lighting.lut_fr.type, lighting.lut_fr.abs_input);
value = "(" + std::to_string(lighting.lut_fr.scale) + " * " + value + ")";
- // Enabled for difffuse lighting alpha component
+ // Enabled for diffuse lighting alpha component
if (lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::PrimaryAlpha ||
lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
- out += "diffuse_sum.a *= " + value + ";\n";
+ out += "diffuse_sum.a = " + value + ";\n";
}
// Enabled for the specular lighting alpha component
if (lighting.fresnel_selector ==
LightingRegs::LightingFresnelSelector::SecondaryAlpha ||
lighting.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
- out += "specular_sum.a *= " + value + ";\n";
+ out += "specular_sum.a = " + value + ";\n";
}
}
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 {
namespace Shader {
-OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& input) {
+OutputVertex OutputVertex::FromAttributeBuffer(const RasterizerRegs& regs,
+ const AttributeBuffer& input) {
// Setup output data
union {
OutputVertex ret{};
@@ -82,6 +83,44 @@ void UnitState::WriteOutput(const ShaderRegs& config, AttributeBuffer& output) {
}
}
+UnitState::UnitState(GSEmitter* emitter) : emitter_ptr(emitter) {}
+
+GSEmitter::GSEmitter() {
+ handlers = new Handlers;
+}
+
+GSEmitter::~GSEmitter() {
+ delete handlers;
+}
+
+void GSEmitter::Emit(Math::Vec4<float24> (&vertex)[16]) {
+ ASSERT(vertex_id < 3);
+ std::copy(std::begin(vertex), std::end(vertex), buffer[vertex_id].begin());
+ if (prim_emit) {
+ if (winding)
+ handlers->winding_setter();
+ for (size_t i = 0; i < buffer.size(); ++i) {
+ AttributeBuffer output;
+ unsigned int output_i = 0;
+ for (unsigned int reg : Common::BitSet<u32>(output_mask)) {
+ output.attr[output_i++] = buffer[i][reg];
+ }
+ handlers->vertex_handler(output);
+ }
+ }
+}
+
+GSUnitState::GSUnitState() : UnitState(&emitter) {}
+
+void GSUnitState::SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter) {
+ emitter.handlers->vertex_handler = std::move(vertex_handler);
+ emitter.handlers->winding_setter = std::move(winding_setter);
+}
+
+void GSUnitState::ConfigOutput(const ShaderRegs& config) {
+ emitter.output_mask = config.output_mask;
+}
+
MICROPROFILE_DEFINE(GPU_Shader, "GPU", "Shader", MP_RGB(50, 50, 240));
#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 @@
#include <array>
#include <cstddef>
+#include <functional>
#include <type_traits>
#include <nihstro/shader_bytecode.h>
#include "common/assert.h"
@@ -31,6 +32,12 @@ struct AttributeBuffer {
alignas(16) Math::Vec4<float24> attr[16];
};
+/// Handler type for receiving vertex outputs from vertex shader or geometry shader
+using VertexHandler = std::function<void(const AttributeBuffer&)>;
+
+/// Handler type for signaling to invert the vertex order of the next triangle
+using WindingSetter = std::function<void()>;
+
struct OutputVertex {
Math::Vec4<float24> pos;
Math::Vec4<float24> quat;
@@ -43,7 +50,8 @@ struct OutputVertex {
INSERT_PADDING_WORDS(1);
Math::Vec2<float24> tc2;
- static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs, AttributeBuffer& output);
+ static OutputVertex FromAttributeBuffer(const RasterizerRegs& regs,
+ const AttributeBuffer& output);
};
#define ASSERT_POS(var, pos) \
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");
static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has invalid size");
/**
+ * This structure contains state information for primitive emitting in geometry shader.
+ */
+struct GSEmitter {
+ std::array<std::array<Math::Vec4<float24>, 16>, 3> buffer;
+ u8 vertex_id;
+ bool prim_emit;
+ bool winding;
+ u32 output_mask;
+
+ // Function objects are hidden behind a raw pointer to make the structure standard layout type,
+ // for JIT to use offsetof to access other members.
+ struct Handlers {
+ VertexHandler vertex_handler;
+ WindingSetter winding_setter;
+ } * handlers;
+
+ GSEmitter();
+ ~GSEmitter();
+ void Emit(Math::Vec4<float24> (&vertex)[16]);
+};
+static_assert(std::is_standard_layout<GSEmitter>::value, "GSEmitter is not standard layout type");
+
+/**
* This structure contains the state information that needs to be unique for a shader unit. The 3DS
* has four shader units that process shaders in parallel. At the present, Citra only implements a
* single shader unit that processes all shaders serially. Putting the state information in a struct
* here will make it easier for us to parallelize the shader processing later.
*/
struct UnitState {
+ explicit UnitState(GSEmitter* emitter = nullptr);
struct Registers {
// The registers are accessed by the shader JIT using SSE instructions, and are therefore
// required to be 16-byte aligned.
@@ -82,6 +114,8 @@ struct UnitState {
// TODO: How many bits do these actually have?
s32 address_registers[3];
+ GSEmitter* emitter_ptr;
+
static size_t InputOffset(const SourceRegister& reg) {
switch (reg.GetRegisterType()) {
case RegisterType::Input:
@@ -125,6 +159,19 @@ struct UnitState {
void WriteOutput(const ShaderRegs& config, AttributeBuffer& output);
};
+/**
+ * This is an extended shader unit state that represents the special unit that can run both vertex
+ * shader and geometry shader. It contains an additional primitive emitter and utilities for
+ * geometry shader.
+ */
+struct GSUnitState : public UnitState {
+ GSUnitState();
+ void SetVertexHandler(VertexHandler vertex_handler, WindingSetter winding_setter);
+ void ConfigOutput(const ShaderRegs& config);
+
+ GSEmitter emitter;
+};
+
struct ShaderSetup {
struct {
// 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
break;
}
+ case OpCode::Id::EMIT: {
+ GSEmitter* emitter = state.emitter_ptr;
+ ASSERT_MSG(emitter, "Execute EMIT on VS");
+ emitter->Emit(state.registers.output);
+ break;
+ }
+
+ case OpCode::Id::SETEMIT: {
+ GSEmitter* emitter = state.emitter_ptr;
+ ASSERT_MSG(emitter, "Execute SETEMIT on VS");
+ emitter->vertex_id = instr.setemit.vertex_id;
+ emitter->prim_emit = instr.setemit.prim_emit != 0;
+ emitter->winding = instr.setemit.winding != 0;
+ break;
+ }
+
default:
LOG_ERROR(HW_GPU, "Unhandled instruction: 0x%02x (%s): 0x%08x",
(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] = {
&JitShader::Compile_IF, // ifu
&JitShader::Compile_IF, // ifc
&JitShader::Compile_LOOP, // loop
- nullptr, // emit
- nullptr, // sete
+ &JitShader::Compile_EMIT, // emit
+ &JitShader::Compile_SETE, // sete
&JitShader::Compile_JMP, // jmpc
&JitShader::Compile_JMP, // jmpu
&JitShader::Compile_CMP, // cmp
@@ -772,6 +772,51 @@ void JitShader::Compile_JMP(Instruction instr) {
}
}
+static void Emit(GSEmitter* emitter, Math::Vec4<float24> (*output)[16]) {
+ emitter->Emit(*output);
+}
+
+void JitShader::Compile_EMIT(Instruction instr) {
+ Label have_emitter, end;
+ mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]);
+ test(rax, rax);
+ jnz(have_emitter);
+
+ ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute EMIT on VS"));
+ CallFarFunction(*this, LogCritical);
+ ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ jmp(end);
+
+ L(have_emitter);
+ ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ mov(ABI_PARAM1, rax);
+ mov(ABI_PARAM2, STATE);
+ add(ABI_PARAM2, static_cast<Xbyak::uint32>(offsetof(UnitState, registers.output)));
+ CallFarFunction(*this, Emit);
+ ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ L(end);
+}
+
+void JitShader::Compile_SETE(Instruction instr) {
+ Label have_emitter, end;
+ mov(rax, qword[STATE + offsetof(UnitState, emitter_ptr)]);
+ test(rax, rax);
+ jnz(have_emitter);
+
+ ABI_PushRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ mov(ABI_PARAM1, reinterpret_cast<size_t>("Execute SETEMIT on VS"));
+ CallFarFunction(*this, LogCritical);
+ ABI_PopRegistersAndAdjustStack(*this, PersistentCallerSavedRegs(), 0);
+ jmp(end);
+
+ L(have_emitter);
+ mov(byte[rax + offsetof(GSEmitter, vertex_id)], instr.setemit.vertex_id);
+ mov(byte[rax + offsetof(GSEmitter, prim_emit)], instr.setemit.prim_emit);
+ mov(byte[rax + offsetof(GSEmitter, winding)], instr.setemit.winding);
+ L(end);
+}
+
void JitShader::Compile_Block(unsigned end) {
while (program_counter < end) {
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:
void Compile_JMP(Instruction instr);
void Compile_CMP(Instruction instr);
void Compile_MAD(Instruction instr);
+ void Compile_EMIT(Instruction instr);
+ void Compile_SETE(Instruction instr);
private:
void Compile_Block(unsigned end);
diff --git a/src/video_core/swrasterizer/lighting.cpp b/src/video_core/swrasterizer/lighting.cpp
index b38964530..5fa748611 100644
--- a/src/video_core/swrasterizer/lighting.cpp
+++ b/src/video_core/swrasterizer/lighting.cpp
@@ -230,7 +230,8 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
d1_lut_value * refl_value * light_config.specular_1.ToVec3f();
// Fresnel
- if (lighting.config1.disable_lut_fr == 0 &&
+ // Note: only the last entry in the light slots applies the Fresnel factor
+ if (light_index == lighting.max_light_index && lighting.config1.disable_lut_fr == 0 &&
LightingRegs::IsLightingSamplerSupported(lighting.config0.config,
LightingRegs::LightingSampler::Fresnel)) {
@@ -242,14 +243,14 @@ std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
if (lighting.config0.fresnel_selector ==
LightingRegs::LightingFresnelSelector::PrimaryAlpha ||
lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
- diffuse_sum.a() *= lut_value;
+ diffuse_sum.a() = lut_value;
}
// Enabled for the specular lighting alpha component
if (lighting.config0.fresnel_selector ==
LightingRegs::LightingFresnelSelector::SecondaryAlpha ||
lighting.config0.fresnel_selector == LightingRegs::LightingFresnelSelector::Both) {
- specular_sum.a() *= lut_value;
+ specular_sum.a() = lut_value;
}
}