mirror of
https://github.com/citra-emu/citra.git
synced 2025-05-10 22:01:06 +00:00
resoveld merge conflicts
This commit is contained in:
commit
69a36fbaec
@ -52,7 +52,7 @@ elif [ "$TRAVIS_OS_NAME" = "osx" ]; then
|
||||
export Qt5_DIR=$(brew --prefix)/opt/qt5
|
||||
|
||||
mkdir build && cd build
|
||||
cmake .. -GXcode -DENABLE_CUSTOM_SYSTEM_ARCHIVES=1
|
||||
cmake .. -GXcode -DUSE_SYSTEM_CURL=ON -DENABLE_CUSTOM_SYSTEM_ARCHIVES=1
|
||||
xcodebuild -configuration Release
|
||||
|
||||
ctest -VV -C Release
|
||||
|
@ -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()
|
||||
|
||||
option(ENABLE_CUSTOM_SYSTEM_ARCHIVES "Enable the creation of custom system archives if missing" OFF)
|
||||
|
||||
@ -131,8 +141,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.
|
||||
@ -153,24 +163,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.")
|
||||
|
18
CMakeModules/DownloadExternals.cmake
Normal file
18
CMakeModules/DownloadExternals.cmake
Normal file
@ -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()
|
@ -17,7 +17,7 @@ For development discussion, please join us @ #citra on freenode.
|
||||
|
||||
Most of the development happens on GitHub. It's also where [our central repository](https://github.com/citra-emu/citra) is hosted.
|
||||
|
||||
If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md), [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should as well contact any of the developers in the forum in order to know about the current state of the emulator.
|
||||
If you want to contribute please take a look at the [Contributor's Guide](CONTRIBUTING.md) and [Developer Information](https://github.com/citra-emu/citra/wiki/Developer-Information). You should as well contact any of the developers in the forum in order to know about the current state of the emulator because the [TODO list](https://docs.google.com/document/d/1SWIop0uBI9IW8VGg97TAtoT_CHNoP42FzYmvG1F4QDA) isn't maintained anymore.
|
||||
|
||||
### Building
|
||||
|
||||
|
164
appveyor.yml
164
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 -DENABLE_CUSTOM_SYSTEM_ARCHIVES=1 ..
|
||||
- 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 -DENABLE_CUSTOM_SYSTEM_ARCHIVES=1 .. 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 -DENABLE_CUSTOM_SYSTEM_ARCHIVES=1 .. 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
|
||||
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 " ", ""
|
||||
|
||||
7z a -tzip $MSVC_BUILD_NAME $RELEASE_DIST\*
|
||||
7z a $MSVC_SEVENZIP $RELEASE_DIST
|
||||
# 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
|
||||
- path: $(BUILD_ZIP)
|
||||
name: build
|
||||
type: zip
|
||||
- path: $(MSVC_BUILD_PDB)
|
||||
name: msvcdebug
|
||||
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:
|
||||
|
24
dist/citra.manifest
vendored
Normal file
24
dist/citra.manifest
vendored
Normal file
@ -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>
|
19
externals/CMakeLists.txt
vendored
19
externals/CMakeLists.txt
vendored
@ -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)
|
||||
|
||||
|
@ -244,17 +244,27 @@ void Source::GenerateFrame() {
|
||||
break;
|
||||
}
|
||||
|
||||
const size_t size_to_copy =
|
||||
std::min(state.current_buffer.size(), current_frame.size() - frame_position);
|
||||
|
||||
std::copy(state.current_buffer.begin(), state.current_buffer.begin() + size_to_copy,
|
||||
current_frame.begin() + frame_position);
|
||||
state.current_buffer.erase(state.current_buffer.begin(),
|
||||
state.current_buffer.begin() + size_to_copy);
|
||||
|
||||
frame_position += size_to_copy;
|
||||
state.next_sample_number += static_cast<u32>(size_to_copy);
|
||||
switch (state.interpolation_mode) {
|
||||
case InterpolationMode::None:
|
||||
AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier,
|
||||
current_frame, frame_position);
|
||||
break;
|
||||
case InterpolationMode::Linear:
|
||||
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
|
||||
current_frame, frame_position);
|
||||
break;
|
||||
case InterpolationMode::Polyphase:
|
||||
// TODO(merry): Implement polyphase interpolation
|
||||
LOG_DEBUG(Audio_DSP, "Polyphase interpolation unimplemented; falling back to linear");
|
||||
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier,
|
||||
current_frame, frame_position);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
break;
|
||||
}
|
||||
}
|
||||
state.next_sample_number += frame_position;
|
||||
|
||||
state.filters.ProcessFrame(current_frame);
|
||||
}
|
||||
@ -305,25 +315,6 @@ bool Source::DequeueBuffer() {
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (state.interpolation_mode) {
|
||||
case InterpolationMode::None:
|
||||
state.current_buffer =
|
||||
AudioInterp::None(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
break;
|
||||
case InterpolationMode::Linear:
|
||||
state.current_buffer =
|
||||
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
break;
|
||||
case InterpolationMode::Polyphase:
|
||||
// TODO(merry): Implement polyphase interpolation
|
||||
state.current_buffer =
|
||||
AudioInterp::Linear(state.interp_state, state.current_buffer, state.rate_multiplier);
|
||||
break;
|
||||
default:
|
||||
UNIMPLEMENTED();
|
||||
break;
|
||||
}
|
||||
|
||||
// the first playthrough starts at play_position, loops start at the beginning of the buffer
|
||||
state.current_sample_number = (!buf.has_played) ? buf.play_position : 0;
|
||||
state.next_sample_number = state.current_sample_number;
|
||||
|
@ -13,74 +13,64 @@ namespace AudioInterp {
|
||||
constexpr u64 scale_factor = 1 << 24;
|
||||
constexpr u64 scale_mask = scale_factor - 1;
|
||||
|
||||
/// Here we step over the input in steps of rate_multiplier, until we consume all of the input.
|
||||
/// Here we step over the input in steps of rate, until we consume all of the input.
|
||||
/// Three adjacent samples are passed to fn each step.
|
||||
template <typename Function>
|
||||
static StereoBuffer16 StepOverSamples(State& state, const StereoBuffer16& input,
|
||||
float rate_multiplier, Function fn) {
|
||||
ASSERT(rate_multiplier > 0);
|
||||
static void StepOverSamples(State& state, StereoBuffer16& input, float rate,
|
||||
DSP::HLE::StereoFrame16& output, size_t& outputi, Function fn) {
|
||||
ASSERT(rate > 0);
|
||||
|
||||
if (input.size() < 2)
|
||||
return {};
|
||||
if (input.empty())
|
||||
return;
|
||||
|
||||
StereoBuffer16 output;
|
||||
output.reserve(static_cast<size_t>(input.size() / rate_multiplier));
|
||||
input.insert(input.begin(), {state.xn2, state.xn1});
|
||||
|
||||
u64 step_size = static_cast<u64>(rate_multiplier * scale_factor);
|
||||
const u64 step_size = static_cast<u64>(rate * scale_factor);
|
||||
u64 fposition = state.fposition;
|
||||
size_t inputi = 0;
|
||||
|
||||
u64 fposition = 0;
|
||||
const u64 max_fposition = input.size() * scale_factor;
|
||||
while (outputi < output.size()) {
|
||||
inputi = static_cast<size_t>(fposition / scale_factor);
|
||||
|
||||
if (inputi + 2 >= input.size()) {
|
||||
inputi = input.size() - 2;
|
||||
break;
|
||||
}
|
||||
|
||||
while (fposition < 1 * scale_factor) {
|
||||
u64 fraction = fposition & scale_mask;
|
||||
|
||||
output.push_back(fn(fraction, state.xn2, state.xn1, input[0]));
|
||||
output[outputi++] = fn(fraction, input[inputi], input[inputi + 1], input[inputi + 2]);
|
||||
|
||||
fposition += step_size;
|
||||
}
|
||||
|
||||
while (fposition < 2 * scale_factor) {
|
||||
u64 fraction = fposition & scale_mask;
|
||||
state.xn2 = input[inputi];
|
||||
state.xn1 = input[inputi + 1];
|
||||
state.fposition = fposition - inputi * scale_factor;
|
||||
|
||||
output.push_back(fn(fraction, state.xn1, input[0], input[1]));
|
||||
|
||||
fposition += step_size;
|
||||
}
|
||||
|
||||
while (fposition < max_fposition) {
|
||||
u64 fraction = fposition & scale_mask;
|
||||
|
||||
size_t index = static_cast<size_t>(fposition / scale_factor);
|
||||
output.push_back(fn(fraction, input[index - 2], input[index - 1], input[index]));
|
||||
|
||||
fposition += step_size;
|
||||
}
|
||||
|
||||
state.xn2 = input[input.size() - 2];
|
||||
state.xn1 = input[input.size() - 1];
|
||||
|
||||
return output;
|
||||
input.erase(input.begin(), input.begin() + inputi + 2);
|
||||
}
|
||||
|
||||
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
||||
return StepOverSamples(
|
||||
state, input, rate_multiplier,
|
||||
void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
|
||||
size_t& outputi) {
|
||||
StepOverSamples(
|
||||
state, input, rate, output, outputi,
|
||||
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) { return x0; });
|
||||
}
|
||||
|
||||
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier) {
|
||||
void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
|
||||
size_t& outputi) {
|
||||
// Note on accuracy: Some values that this produces are +/- 1 from the actual firmware.
|
||||
return StepOverSamples(state, input, rate_multiplier,
|
||||
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
|
||||
// This is a saturated subtraction. (Verified by black-box fuzzing.)
|
||||
s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
|
||||
s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
|
||||
StepOverSamples(state, input, rate, output, outputi,
|
||||
[](u64 fraction, const auto& x0, const auto& x1, const auto& x2) {
|
||||
// This is a saturated subtraction. (Verified by black-box fuzzing.)
|
||||
s64 delta0 = MathUtil::Clamp<s64>(x1[0] - x0[0], -32768, 32767);
|
||||
s64 delta1 = MathUtil::Clamp<s64>(x1[1] - x0[1], -32768, 32767);
|
||||
|
||||
return std::array<s16, 2>{
|
||||
static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
|
||||
static_cast<s16>(x0[1] + fraction * delta1 / scale_factor),
|
||||
};
|
||||
});
|
||||
return std::array<s16, 2>{
|
||||
static_cast<s16>(x0[0] + fraction * delta0 / scale_factor),
|
||||
static_cast<s16>(x0[1] + fraction * delta1 / scale_factor),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace AudioInterp
|
||||
|
@ -6,6 +6,7 @@
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include "audio_core/hle/common.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace AudioInterp {
|
||||
@ -14,31 +15,35 @@ namespace AudioInterp {
|
||||
using StereoBuffer16 = std::vector<std::array<s16, 2>>;
|
||||
|
||||
struct State {
|
||||
// Two historical samples.
|
||||
/// Two historical samples.
|
||||
std::array<s16, 2> xn1 = {}; ///< x[n-1]
|
||||
std::array<s16, 2> xn2 = {}; ///< x[n-2]
|
||||
/// Current fractional position.
|
||||
u64 fposition = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* No interpolation. This is equivalent to a zero-order hold. There is a two-sample predelay.
|
||||
* @param state Interpolation state.
|
||||
* @param input Input buffer.
|
||||
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
|
||||
* performs upsampling.
|
||||
* @return The resampled audio buffer.
|
||||
* @param rate Stretch factor. Must be a positive non-zero value.
|
||||
* rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
|
||||
* @param output The resampled audio buffer.
|
||||
* @param outputi The index of output to start writing to.
|
||||
*/
|
||||
StereoBuffer16 None(State& state, const StereoBuffer16& input, float rate_multiplier);
|
||||
void None(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
|
||||
size_t& outputi);
|
||||
|
||||
/**
|
||||
* Linear interpolation. This is equivalent to a first-order hold. There is a two-sample predelay.
|
||||
* @param state Interpolation state.
|
||||
* @param input Input buffer.
|
||||
* @param rate_multiplier Stretch factor. Must be a positive non-zero value.
|
||||
* rate_multiplier > 1.0 performs decimation and rate_multipler < 1.0
|
||||
* performs upsampling.
|
||||
* @return The resampled audio buffer.
|
||||
* @param rate Stretch factor. Must be a positive non-zero value.
|
||||
* rate > 1.0 performs decimation and rate < 1.0 performs upsampling.
|
||||
* @param output The resampled audio buffer.
|
||||
* @param outputi The index of output to start writing to.
|
||||
*/
|
||||
StereoBuffer16 Linear(State& state, const StereoBuffer16& input, float rate_multiplier);
|
||||
void Linear(State& state, StereoBuffer16& input, float rate, DSP::HLE::StereoFrame16& output,
|
||||
size_t& outputi);
|
||||
|
||||
} // namespace AudioInterp
|
||||
|
@ -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"
|
||||
|
@ -78,6 +78,8 @@ void Config::ReadValues() {
|
||||
|
||||
Settings::values.motion_device = sdl2_config->Get(
|
||||
"Controls", "motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01");
|
||||
Settings::values.touch_device =
|
||||
sdl2_config->Get("Controls", "touch_device", "engine:emu_window");
|
||||
|
||||
// Core
|
||||
Settings::values.use_cpu_jit = sdl2_config->GetBoolean("Core", "use_cpu_jit", true);
|
||||
|
@ -62,6 +62,10 @@ c_stick=
|
||||
# - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01)
|
||||
motion_device=
|
||||
|
||||
# for touch input, the following devices are available:
|
||||
# - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required
|
||||
touch_device=
|
||||
|
||||
[Core]
|
||||
# Whether to use the Just-In-Time (JIT) compiler for CPU emulation
|
||||
# 0: Interpreter (slow), 1 (default): JIT (fast)
|
||||
|
@ -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"
|
||||
|
@ -61,6 +61,8 @@ void Config::ReadValues() {
|
||||
qt_config->value("motion_device", "engine:motion_emu,update_period:100,sensitivity:0.01")
|
||||
.toString()
|
||||
.toStdString();
|
||||
Settings::values.touch_device =
|
||||
qt_config->value("touch_device", "engine:emu_window").toString().toStdString();
|
||||
|
||||
qt_config->endGroup();
|
||||
|
||||
@ -217,6 +219,7 @@ void Config::SaveValues() {
|
||||
QString::fromStdString(Settings::values.analogs[i]));
|
||||
}
|
||||
qt_config->setValue("motion_device", QString::fromStdString(Settings::values.motion_device));
|
||||
qt_config->setValue("touch_device", QString::fromStdString(Settings::values.touch_device));
|
||||
qt_config->endGroup();
|
||||
|
||||
qt_config->beginGroup("Core");
|
||||
|
@ -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
|
||||
|
@ -2,14 +2,55 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include "common/assert.h"
|
||||
#include "core/3ds.h"
|
||||
#include "core/core.h"
|
||||
#include <mutex>
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/settings.h"
|
||||
|
||||
class EmuWindow::TouchState : public Input::Factory<Input::TouchDevice>,
|
||||
public std::enable_shared_from_this<TouchState> {
|
||||
public:
|
||||
std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage&) override {
|
||||
return std::make_unique<Device>(shared_from_this());
|
||||
}
|
||||
|
||||
std::mutex mutex;
|
||||
|
||||
bool touch_pressed = false; ///< True if touchpad area is currently pressed, otherwise false
|
||||
|
||||
float touch_x = 0.0f; ///< Touchpad X-position
|
||||
float touch_y = 0.0f; ///< Touchpad Y-position
|
||||
|
||||
private:
|
||||
class Device : public Input::TouchDevice {
|
||||
public:
|
||||
explicit Device(std::weak_ptr<TouchState>&& touch_state) : touch_state(touch_state) {}
|
||||
std::tuple<float, float, bool> GetStatus() const override {
|
||||
if (auto state = touch_state.lock()) {
|
||||
std::lock_guard<std::mutex> guard(state->mutex);
|
||||
return std::make_tuple(state->touch_x, state->touch_y, state->touch_pressed);
|
||||
}
|
||||
return std::make_tuple(0.0f, 0.0f, false);
|
||||
}
|
||||
|
||||
private:
|
||||
std::weak_ptr<TouchState> touch_state;
|
||||
};
|
||||
};
|
||||
|
||||
EmuWindow::EmuWindow() {
|
||||
// TODO: Find a better place to set this.
|
||||
config.min_client_area_size = std::make_pair(400u, 480u);
|
||||
active_config = config;
|
||||
touch_state = std::make_shared<TouchState>();
|
||||
Input::RegisterFactory<Input::TouchDevice>("emu_window", touch_state);
|
||||
}
|
||||
|
||||
EmuWindow::~EmuWindow() {
|
||||
Input::UnregisterFactory<Input::TouchDevice>("emu_window");
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given x/y coordinates are within the touchpad specified by the framebuffer layout
|
||||
* @param layout FramebufferLayout object describing the framebuffer size and screen positions
|
||||
@ -38,22 +79,26 @@ void EmuWindow::TouchPressed(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
||||
return;
|
||||
|
||||
touch_x = Core::kScreenBottomWidth * (framebuffer_x - framebuffer_layout.bottom_screen.left) /
|
||||
(framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left);
|
||||
touch_y = Core::kScreenBottomHeight * (framebuffer_y - framebuffer_layout.bottom_screen.top) /
|
||||
(framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
|
||||
std::lock_guard<std::mutex> guard(touch_state->mutex);
|
||||
touch_state->touch_x =
|
||||
static_cast<float>(framebuffer_x - framebuffer_layout.bottom_screen.left) /
|
||||
(framebuffer_layout.bottom_screen.right - framebuffer_layout.bottom_screen.left);
|
||||
touch_state->touch_y =
|
||||
static_cast<float>(framebuffer_y - framebuffer_layout.bottom_screen.top) /
|
||||
(framebuffer_layout.bottom_screen.bottom - framebuffer_layout.bottom_screen.top);
|
||||
|
||||
touch_pressed = true;
|
||||
touch_state->touch_pressed = true;
|
||||
}
|
||||
|
||||
void EmuWindow::TouchReleased() {
|
||||
touch_pressed = false;
|
||||
touch_x = 0;
|
||||
touch_y = 0;
|
||||
std::lock_guard<std::mutex> guard(touch_state->mutex);
|
||||
touch_state->touch_pressed = false;
|
||||
touch_state->touch_x = 0;
|
||||
touch_state->touch_y = 0;
|
||||
}
|
||||
|
||||
void EmuWindow::TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y) {
|
||||
if (!touch_pressed)
|
||||
if (!touch_state->touch_pressed)
|
||||
return;
|
||||
|
||||
if (!IsWithinTouchscreen(framebuffer_layout, framebuffer_x, framebuffer_y))
|
||||
|
@ -4,11 +4,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <mutex>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include "common/common_types.h"
|
||||
#include "common/math_util.h"
|
||||
#include "core/frontend/framebuffer_layout.h"
|
||||
|
||||
/**
|
||||
@ -68,17 +67,6 @@ public:
|
||||
*/
|
||||
void TouchMoved(unsigned framebuffer_x, unsigned framebuffer_y);
|
||||
|
||||
/**
|
||||
* Gets the current touch screen state (touch X/Y coordinates and whether or not it is pressed).
|
||||
* @note This should be called by the core emu thread to get a state set by the window thread.
|
||||
* @todo Fix this function to be thread-safe.
|
||||
* @return std::tuple of (x, y, pressed) where `x` and `y` are the touch coordinates and
|
||||
* `pressed` is true if the touch screen is currently being pressed
|
||||
*/
|
||||
std::tuple<u16, u16, bool> GetTouchState() const {
|
||||
return std::make_tuple(touch_x, touch_y, touch_pressed);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns currently active configuration.
|
||||
* @note Accesses to the returned object need not be consistent because it may be modified in
|
||||
@ -113,15 +101,8 @@ public:
|
||||
void UpdateCurrentFramebufferLayout(unsigned width, unsigned height);
|
||||
|
||||
protected:
|
||||
EmuWindow() {
|
||||
// TODO: Find a better place to set this.
|
||||
config.min_client_area_size = std::make_pair(400u, 480u);
|
||||
active_config = config;
|
||||
touch_x = 0;
|
||||
touch_y = 0;
|
||||
touch_pressed = false;
|
||||
}
|
||||
virtual ~EmuWindow() {}
|
||||
EmuWindow();
|
||||
virtual ~EmuWindow();
|
||||
|
||||
/**
|
||||
* Processes any pending configuration changes from the last SetConfig call.
|
||||
@ -177,10 +158,8 @@ private:
|
||||
/// ProcessConfigurationChanges)
|
||||
WindowConfig active_config; ///< Internal active configuration
|
||||
|
||||
bool touch_pressed; ///< True if touchpad area is currently pressed, otherwise false
|
||||
|
||||
u16 touch_x; ///< Touchpad X-position in native 3DS pixel coordinates (0-320)
|
||||
u16 touch_y; ///< Touchpad Y-position in native 3DS pixel coordinates (0-240)
|
||||
class TouchState;
|
||||
std::shared_ptr<TouchState> touch_state;
|
||||
|
||||
/**
|
||||
* Clip the provided coordinates to be inside the touchscreen area.
|
||||
|
@ -126,4 +126,10 @@ using AnalogDevice = InputDevice<std::tuple<float, float>>;
|
||||
*/
|
||||
using MotionDevice = InputDevice<std::tuple<Math::Vec3<float>, Math::Vec3<float>>>;
|
||||
|
||||
/**
|
||||
* A touch device is an input device that returns a tuple of two floats and a bool. The floats are
|
||||
* x and y coordinates in the range 0.0 - 1.0, and the bool indicates whether it is pressed.
|
||||
*/
|
||||
using TouchDevice = InputDevice<std::tuple<float, float, bool>>;
|
||||
|
||||
} // namespace Input
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -7,5 +7,5 @@
|
||||
#include <core/hle/lock.h>
|
||||
|
||||
namespace HLE {
|
||||
std::mutex g_hle_lock;
|
||||
std::recursive_mutex g_hle_lock;
|
||||
}
|
||||
|
@ -14,5 +14,5 @@ namespace HLE {
|
||||
* to the emulated memory is not protected by this mutex, and should be avoided in any threads other
|
||||
* than the CPU thread.
|
||||
*/
|
||||
extern std::mutex g_hle_lock;
|
||||
extern std::recursive_mutex g_hle_lock;
|
||||
} // namespace HLE
|
||||
|
@ -19,6 +19,7 @@
|
||||
#include "core/hle/service/apt/apt_s.h"
|
||||
#include "core/hle/service/apt/apt_u.h"
|
||||
#include "core/hle/service/apt/bcfnt/bcfnt.h"
|
||||
#include "core/hle/service/cfg/cfg.h"
|
||||
#include "core/hle/service/fs/archive.h"
|
||||
#include "core/hle/service/ptm/ptm.h"
|
||||
#include "core/hle/service/service.h"
|
||||
@ -198,6 +199,143 @@ void Initialize(Service::Interface* self) {
|
||||
Kernel::g_handle_table.Create(slot_data->parameter_event).Unwrap());
|
||||
}
|
||||
|
||||
static u32 DecompressLZ11(const u8* in, u8* out) {
|
||||
u32_le decompressed_size;
|
||||
memcpy(&decompressed_size, in, sizeof(u32));
|
||||
in += 4;
|
||||
|
||||
u8 type = decompressed_size & 0xFF;
|
||||
ASSERT(type == 0x11);
|
||||
decompressed_size >>= 8;
|
||||
|
||||
u32 current_out_size = 0;
|
||||
u8 flags = 0, mask = 1;
|
||||
while (current_out_size < decompressed_size) {
|
||||
if (mask == 1) {
|
||||
flags = *(in++);
|
||||
mask = 0x80;
|
||||
} else {
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
if (flags & mask) {
|
||||
u8 byte1 = *(in++);
|
||||
u32 length = byte1 >> 4;
|
||||
u32 offset;
|
||||
if (length == 0) {
|
||||
u8 byte2 = *(in++);
|
||||
u8 byte3 = *(in++);
|
||||
length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11;
|
||||
offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1;
|
||||
} else if (length == 1) {
|
||||
u8 byte2 = *(in++);
|
||||
u8 byte3 = *(in++);
|
||||
u8 byte4 = *(in++);
|
||||
length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111;
|
||||
offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1;
|
||||
} else {
|
||||
u8 byte2 = *(in++);
|
||||
length = (byte1 >> 4) + 0x1;
|
||||
offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < length; i++) {
|
||||
*out = *(out - offset);
|
||||
++out;
|
||||
}
|
||||
|
||||
current_out_size += length;
|
||||
} else {
|
||||
*(out++) = *(in++);
|
||||
current_out_size++;
|
||||
}
|
||||
}
|
||||
return decompressed_size;
|
||||
}
|
||||
|
||||
static bool LoadSharedFont() {
|
||||
u8 font_region_code;
|
||||
switch (CFG::GetRegionValue()) {
|
||||
case 4: // CHN
|
||||
font_region_code = 2;
|
||||
break;
|
||||
case 5: // KOR
|
||||
font_region_code = 3;
|
||||
break;
|
||||
case 6: // TWN
|
||||
font_region_code = 4;
|
||||
break;
|
||||
default: // JPN/EUR/USA
|
||||
font_region_code = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
const u64_le shared_font_archive_id_low = 0x0004009b00014002 | ((font_region_code - 1) << 8);
|
||||
const u64_le shared_font_archive_id_high = 0x00000001ffffff00;
|
||||
std::vector<u8> shared_font_archive_id(16);
|
||||
std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64));
|
||||
std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64));
|
||||
FileSys::Path archive_path(shared_font_archive_id);
|
||||
auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path);
|
||||
if (archive_result.Failed())
|
||||
return false;
|
||||
|
||||
std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS
|
||||
FileSys::Path file_path(romfs_path);
|
||||
FileSys::Mode open_mode = {};
|
||||
open_mode.read_flag.Assign(1);
|
||||
auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode);
|
||||
if (file_result.Failed())
|
||||
return false;
|
||||
|
||||
auto romfs = std::move(file_result).Unwrap();
|
||||
std::vector<u8> romfs_buffer(romfs->backend->GetSize());
|
||||
romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data());
|
||||
romfs->backend->Close();
|
||||
|
||||
const char16_t* file_name[4] = {u"cbf_std.bcfnt.lz", u"cbf_zh-Hans-CN.bcfnt.lz",
|
||||
u"cbf_ko-Hang-KR.bcfnt.lz", u"cbf_zh-Hant-TW.bcfnt.lz"};
|
||||
const u8* font_file =
|
||||
RomFS::GetFilePointer(romfs_buffer.data(), {file_name[font_region_code - 1]});
|
||||
if (font_file == nullptr)
|
||||
return false;
|
||||
|
||||
struct {
|
||||
u32_le status;
|
||||
u32_le region;
|
||||
u32_le decompressed_size;
|
||||
INSERT_PADDING_WORDS(0x1D);
|
||||
} shared_font_header{};
|
||||
static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size");
|
||||
|
||||
shared_font_header.status = 2; // successfully loaded
|
||||
shared_font_header.region = font_region_code;
|
||||
shared_font_header.decompressed_size =
|
||||
DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80));
|
||||
std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header));
|
||||
*shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU"
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadLegacySharedFont() {
|
||||
// This is the legacy method to load shared font.
|
||||
// The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header
|
||||
// generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided
|
||||
// a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file
|
||||
// "shared_font.bin" in the Citra "sysdata" directory.
|
||||
std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT;
|
||||
|
||||
FileUtil::CreateFullPath(filepath); // Create path if not already created
|
||||
FileUtil::IOFile file(filepath, "rb");
|
||||
if (file.IsOpen()) {
|
||||
file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void GetSharedFont(Service::Interface* self) {
|
||||
IPC::RequestParser rp(Kernel::GetCommandBuffer(), 0x44, 0, 0); // 0x00440000
|
||||
IPC::RequestBuilder rb = rp.MakeBuilder(2, 2);
|
||||
@ -206,11 +344,20 @@ void GetSharedFont(Service::Interface* self) {
|
||||
Core::Telemetry().AddField(Telemetry::FieldType::Session, "RequiresSharedFont", true);
|
||||
|
||||
if (!shared_font_loaded) {
|
||||
LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds");
|
||||
rb.Push<u32>(-1); // TODO: Find the right error code
|
||||
rb.Skip(1 + 2, true);
|
||||
Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont);
|
||||
return;
|
||||
// On real 3DS, font loading happens on booting. However, we load it on demand to coordinate
|
||||
// with CFG region auto configuration, which happens later than APT initialization.
|
||||
if (LoadSharedFont()) {
|
||||
shared_font_loaded = true;
|
||||
} else if (LoadLegacySharedFont()) {
|
||||
LOG_WARNING(Service_APT, "Loaded shared font by legacy method");
|
||||
shared_font_loaded = true;
|
||||
} else {
|
||||
LOG_ERROR(Service_APT, "shared font file missing - go dump it from your 3ds");
|
||||
rb.Push<u32>(-1); // TODO: Find the right error code
|
||||
rb.Skip(1 + 2, true);
|
||||
Core::System::GetInstance().SetStatus(Core::System::ResultStatus::ErrorSharedFont);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
LOG_WARNING(Service_APT, "(STUBBED) called");
|
||||
}
|
||||
|
||||
static u32 DecompressLZ11(const u8* in, u8* out) {
|
||||
u32_le decompressed_size;
|
||||
memcpy(&decompressed_size, in, sizeof(u32));
|
||||
in += 4;
|
||||
|
||||
u8 type = decompressed_size & 0xFF;
|
||||
ASSERT(type == 0x11);
|
||||
decompressed_size >>= 8;
|
||||
|
||||
u32 current_out_size = 0;
|
||||
u8 flags = 0, mask = 1;
|
||||
while (current_out_size < decompressed_size) {
|
||||
if (mask == 1) {
|
||||
flags = *(in++);
|
||||
mask = 0x80;
|
||||
} else {
|
||||
mask >>= 1;
|
||||
}
|
||||
|
||||
if (flags & mask) {
|
||||
u8 byte1 = *(in++);
|
||||
u32 length = byte1 >> 4;
|
||||
u32 offset;
|
||||
if (length == 0) {
|
||||
u8 byte2 = *(in++);
|
||||
u8 byte3 = *(in++);
|
||||
length = (((byte1 & 0x0F) << 4) | (byte2 >> 4)) + 0x11;
|
||||
offset = (((byte2 & 0x0F) << 8) | byte3) + 0x1;
|
||||
} else if (length == 1) {
|
||||
u8 byte2 = *(in++);
|
||||
u8 byte3 = *(in++);
|
||||
u8 byte4 = *(in++);
|
||||
length = (((byte1 & 0x0F) << 12) | (byte2 << 4) | (byte3 >> 4)) + 0x111;
|
||||
offset = (((byte3 & 0x0F) << 8) | byte4) + 0x1;
|
||||
} else {
|
||||
u8 byte2 = *(in++);
|
||||
length = (byte1 >> 4) + 0x1;
|
||||
offset = (((byte1 & 0x0F) << 8) | byte2) + 0x1;
|
||||
}
|
||||
|
||||
for (u32 i = 0; i < length; i++) {
|
||||
*out = *(out - offset);
|
||||
++out;
|
||||
}
|
||||
|
||||
current_out_size += length;
|
||||
} else {
|
||||
*(out++) = *(in++);
|
||||
current_out_size++;
|
||||
}
|
||||
}
|
||||
return decompressed_size;
|
||||
}
|
||||
|
||||
static bool LoadSharedFont() {
|
||||
// TODO (wwylele): load different font archive for region CHN/KOR/TWN
|
||||
const u64_le shared_font_archive_id_low = 0x0004009b00014002;
|
||||
const u64_le shared_font_archive_id_high = 0x00000001ffffff00;
|
||||
std::vector<u8> shared_font_archive_id(16);
|
||||
std::memcpy(&shared_font_archive_id[0], &shared_font_archive_id_low, sizeof(u64));
|
||||
std::memcpy(&shared_font_archive_id[8], &shared_font_archive_id_high, sizeof(u64));
|
||||
FileSys::Path archive_path(shared_font_archive_id);
|
||||
auto archive_result = Service::FS::OpenArchive(Service::FS::ArchiveIdCode::NCCH, archive_path);
|
||||
if (archive_result.Failed())
|
||||
return false;
|
||||
|
||||
std::vector<u8> romfs_path(20, 0); // 20-byte all zero path for opening RomFS
|
||||
FileSys::Path file_path(romfs_path);
|
||||
FileSys::Mode open_mode = {};
|
||||
open_mode.read_flag.Assign(1);
|
||||
auto file_result = Service::FS::OpenFileFromArchive(*archive_result, file_path, open_mode);
|
||||
if (file_result.Failed())
|
||||
return false;
|
||||
|
||||
auto romfs = std::move(file_result).Unwrap();
|
||||
std::vector<u8> romfs_buffer(romfs->backend->GetSize());
|
||||
romfs->backend->Read(0, romfs_buffer.size(), romfs_buffer.data());
|
||||
romfs->backend->Close();
|
||||
|
||||
const u8* font_file = RomFS::GetFilePointer(romfs_buffer.data(), {u"cbf_std.bcfnt.lz"});
|
||||
if (font_file == nullptr)
|
||||
return false;
|
||||
|
||||
struct {
|
||||
u32_le status;
|
||||
u32_le region;
|
||||
u32_le decompressed_size;
|
||||
INSERT_PADDING_WORDS(0x1D);
|
||||
} shared_font_header{};
|
||||
static_assert(sizeof(shared_font_header) == 0x80, "shared_font_header has incorrect size");
|
||||
|
||||
shared_font_header.status = 2; // successfully loaded
|
||||
shared_font_header.region = 1; // region JPN/EUR/USA
|
||||
shared_font_header.decompressed_size =
|
||||
DecompressLZ11(font_file, shared_font_mem->GetPointer(0x80));
|
||||
std::memcpy(shared_font_mem->GetPointer(), &shared_font_header, sizeof(shared_font_header));
|
||||
*shared_font_mem->GetPointer(0x83) = 'U'; // Change the magic from "CFNT" to "CFNU"
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool LoadLegacySharedFont() {
|
||||
// This is the legacy method to load shared font.
|
||||
// The expected format is a decrypted, uncompressed BCFNT file with the 0x80 byte header
|
||||
// generated by the APT:U service. The best way to get is by dumping it from RAM. We've provided
|
||||
// a homebrew app to do this: https://github.com/citra-emu/3dsutils. Put the resulting file
|
||||
// "shared_font.bin" in the Citra "sysdata" directory.
|
||||
std::string filepath = FileUtil::GetUserPath(D_SYSDATA_IDX) + SHARED_FONT;
|
||||
|
||||
FileUtil::CreateFullPath(filepath); // Create path if not already created
|
||||
FileUtil::IOFile file(filepath, "rb");
|
||||
if (file.IsOpen()) {
|
||||
file.ReadBytes(shared_font_mem->GetPointer(), file.GetSize());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void Init() {
|
||||
AddService(new APT_A_Interface);
|
||||
AddService(new APT_S_Interface);
|
||||
@ -995,16 +1023,6 @@ void Init() {
|
||||
MemoryPermission::ReadWrite, MemoryPermission::Read, 0,
|
||||
Kernel::MemoryRegion::SYSTEM, "APT:SharedFont");
|
||||
|
||||
if (LoadSharedFont()) {
|
||||
shared_font_loaded = true;
|
||||
} else if (LoadLegacySharedFont()) {
|
||||
LOG_WARNING(Service_APT, "Loaded shared font by legacy method");
|
||||
shared_font_loaded = true;
|
||||
} else {
|
||||
LOG_WARNING(Service_APT, "Unable to load shared font");
|
||||
shared_font_loaded = false;
|
||||
}
|
||||
|
||||
lock = Kernel::Mutex::Create(false, "APT_U:Lock");
|
||||
|
||||
cpu_percent = 0;
|
||||
|
@ -168,7 +168,7 @@ void GetCountryCodeID(Service::Interface* self) {
|
||||
cmd_buff[2] = country_code_id;
|
||||
}
|
||||
|
||||
static u32 GetRegionValue() {
|
||||
u32 GetRegionValue() {
|
||||
if (Settings::values.region_value == Settings::REGION_VALUE_AUTO_SELECT)
|
||||
return preferred_region_code;
|
||||
|
||||
|
@ -101,6 +101,8 @@ void GetCountryCodeString(Service::Interface* self);
|
||||
*/
|
||||
void GetCountryCodeID(Service::Interface* self);
|
||||
|
||||
u32 GetRegionValue();
|
||||
|
||||
/**
|
||||
* CFG::SecureInfoGetRegion service function
|
||||
* Inputs:
|
||||
|
@ -7,9 +7,9 @@
|
||||
#include <cmath>
|
||||
#include <memory>
|
||||
#include "common/logging/log.h"
|
||||
#include "core/3ds.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/frontend/emu_window.h"
|
||||
#include "core/frontend/input.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/event.h"
|
||||
@ -19,7 +19,6 @@
|
||||
#include "core/hle/service/hid/hid_spvr.h"
|
||||
#include "core/hle/service/hid/hid_user.h"
|
||||
#include "core/hle/service/service.h"
|
||||
#include "video_core/video_core.h"
|
||||
|
||||
namespace Service {
|
||||
namespace HID {
|
||||
@ -59,6 +58,7 @@ static std::array<std::unique_ptr<Input::ButtonDevice>, Settings::NativeButton::
|
||||
buttons;
|
||||
static std::unique_ptr<Input::AnalogDevice> circle_pad;
|
||||
static std::unique_ptr<Input::MotionDevice> motion_device;
|
||||
static std::unique_ptr<Input::TouchDevice> touch_device;
|
||||
|
||||
DirectionState GetStickDirectionState(s16 circle_pad_x, s16 circle_pad_y) {
|
||||
// 30 degree and 60 degree are angular thresholds for directions
|
||||
@ -96,6 +96,7 @@ static void LoadInputDevices() {
|
||||
circle_pad = Input::CreateDevice<Input::AnalogDevice>(
|
||||
Settings::values.analogs[Settings::NativeAnalog::CirclePad]);
|
||||
motion_device = Input::CreateDevice<Input::MotionDevice>(Settings::values.motion_device);
|
||||
touch_device = Input::CreateDevice<Input::TouchDevice>(Settings::values.touch_device);
|
||||
}
|
||||
|
||||
static void UnloadInputDevices() {
|
||||
@ -104,6 +105,7 @@ static void UnloadInputDevices() {
|
||||
}
|
||||
circle_pad.reset();
|
||||
motion_device.reset();
|
||||
touch_device.reset();
|
||||
}
|
||||
|
||||
static void UpdatePadCallback(u64 userdata, int cycles_late) {
|
||||
@ -172,8 +174,10 @@ static void UpdatePadCallback(u64 userdata, int cycles_late) {
|
||||
// Get the current touch entry
|
||||
TouchDataEntry& touch_entry = mem->touch.entries[mem->touch.index];
|
||||
bool pressed = false;
|
||||
|
||||
std::tie(touch_entry.x, touch_entry.y, pressed) = VideoCore::g_emu_window->GetTouchState();
|
||||
float x, y;
|
||||
std::tie(x, y, pressed) = touch_device->GetStatus();
|
||||
touch_entry.x = static_cast<u16>(x * Core::kScreenBottomWidth);
|
||||
touch_entry.y = static_cast<u16>(y * Core::kScreenBottomHeight);
|
||||
touch_entry.valid.Assign(pressed ? 1 : 0);
|
||||
|
||||
// TODO(bunnei): We're not doing anything with offset 0xA8 + 0x18 of HID SharedMemory, which
|
||||
|
@ -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,31 +744,22 @@ 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.
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
79
src/core/hle/service/nwm/uds_connection.cpp
Normal file
79
src/core/hle/service/nwm/uds_connection.cpp
Normal file
@ -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
|
51
src/core/hle/service/nwm/uds_connection.h
Normal file
51
src/core/hle/service/nwm/uds_connection.h
Normal file
@ -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
|
@ -1334,7 +1334,7 @@ void CallSVC(u32 immediate) {
|
||||
MICROPROFILE_SCOPE(Kernel_SVC);
|
||||
|
||||
// Lock the global kernel mutex when we enter the kernel HLE.
|
||||
std::lock_guard<std::mutex> lock(HLE::g_hle_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
const FunctionDef* info = GetSVCInfo(immediate);
|
||||
if (info) {
|
||||
|
@ -183,7 +183,7 @@ T Read(const VAddr vaddr) {
|
||||
}
|
||||
|
||||
// The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
|
||||
std::lock_guard<std::mutex> lock(HLE::g_hle_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
@ -224,7 +224,7 @@ void Write(const VAddr vaddr, const T data) {
|
||||
}
|
||||
|
||||
// The memory access might do an MMIO or cached access, so we have to lock the HLE kernel state
|
||||
std::lock_guard<std::mutex> lock(HLE::g_hle_lock);
|
||||
std::lock_guard<std::recursive_mutex> lock(HLE::g_hle_lock);
|
||||
|
||||
PageType type = current_page_table->attributes[vaddr >> PAGE_BITS];
|
||||
switch (type) {
|
||||
|
@ -81,6 +81,7 @@ struct Values {
|
||||
std::array<std::string, NativeButton::NumButtons> buttons;
|
||||
std::array<std::string, NativeAnalog::NumAnalogs> analogs;
|
||||
std::string motion_device;
|
||||
std::string touch_device;
|
||||
|
||||
// Core
|
||||
bool use_cpu_jit;
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
|
274
src/video_core/geometry_pipeline.cpp
Normal file
274
src/video_core/geometry_pipeline.cpp
Normal file
@ -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
|
49
src/video_core/geometry_pipeline.h
Normal file
49
src/video_core/geometry_pipeline.h
Normal file
@ -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
|
@ -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);
|
||||
|
@ -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;
|
||||
};
|
||||
|
@ -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;
|
||||
|
||||
@ -50,10 +53,16 @@ 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>
|
||||
|
@ -29,6 +29,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.
|
||||
*/
|
||||
@ -45,6 +51,7 @@ private:
|
||||
int buffer_index;
|
||||
VertexType buffer[2];
|
||||
bool strip_ready = false;
|
||||
bool winding = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
@ -256,10 +256,9 @@ struct FramebufferRegs {
|
||||
return 3;
|
||||
case DepthFormat::D24S8:
|
||||
return 4;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
ASSERT_MSG(false, "Unknown depth format %u", format);
|
||||
}
|
||||
|
||||
// Returns the number of bits per depth component of the specified depth format
|
||||
@ -270,10 +269,9 @@ struct FramebufferRegs {
|
||||
case DepthFormat::D24:
|
||||
case DepthFormat::D24S8:
|
||||
return 24;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unknown depth format %u", format);
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
ASSERT_MSG(false, "Unknown depth format %u", format);
|
||||
}
|
||||
|
||||
INSERT_PADDING_WORDS(0x20);
|
||||
|
@ -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,
|
||||
|
@ -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)
|
||||
|
@ -8,6 +8,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_field.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "core/core.h"
|
||||
#include "video_core/regs_framebuffer.h"
|
||||
#include "video_core/regs_lighting.h"
|
||||
#include "video_core/regs_rasterizer.h"
|
||||
@ -594,8 +595,8 @@ static void WriteLighting(std::string& out, const PicaShaderConfig& config) {
|
||||
// Note: even if the normal vector is modified by normal map, which is not the
|
||||
// normal of the tangent plane anymore, the half angle vector is still projected
|
||||
// using the modified normal vector.
|
||||
std::string half_angle_proj = "normalize(half_vector) - normal / dot(normal, "
|
||||
"normal) * dot(normal, normalize(half_vector))";
|
||||
std::string half_angle_proj =
|
||||
"normalize(half_vector) - normal * dot(normal, normalize(half_vector))";
|
||||
// Note: the half angle vector projection is confirmed not normalized before the dot
|
||||
// product. The result is in fact not cos(phi) as the name suggested.
|
||||
index = "dot(" + half_angle_proj + ", tangent)";
|
||||
@ -750,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
|
||||
@ -759,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";
|
||||
}
|
||||
}
|
||||
|
||||
@ -1155,6 +1157,11 @@ vec4 secondary_fragment_color = vec4(0.0);
|
||||
|
||||
// Blend the fog
|
||||
out += "last_tex_env_out.rgb = mix(fog_color.rgb, last_tex_env_out.rgb, fog_factor);\n";
|
||||
} else if (state.fog_mode == TexturingRegs::FogMode::Gas) {
|
||||
Core::Telemetry().AddField(Telemetry::FieldType::Session, "VideoCore_Pica_UseGasMode",
|
||||
true);
|
||||
LOG_CRITICAL(Render_OpenGL, "Unimplemented gas mode");
|
||||
UNIMPLEMENTED();
|
||||
}
|
||||
|
||||
out += "gl_FragDepth = depth;\n";
|
||||
|
@ -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
|
||||
|
@ -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 " \
|
||||
@ -60,6 +68,29 @@ ASSERT_POS(tc2, RasterizerRegs::VSOutputAttributes::TEXCOORD2_U);
|
||||
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
|
||||
@ -67,6 +98,7 @@ static_assert(sizeof(OutputVertex) == 24 * sizeof(float), "OutputVertex has inva
|
||||
* 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
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
|
@ -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);
|
||||
|
@ -352,6 +352,8 @@ u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op) {
|
||||
case FramebufferRegs::LogicOp::OrInverted:
|
||||
return ~src | dest;
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
};
|
||||
|
||||
} // namespace Rasterizer
|
||||
|
@ -22,18 +22,37 @@ static float LookupLightingLut(const Pica::State::Lighting& lighting, size_t lut
|
||||
|
||||
std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
|
||||
const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state,
|
||||
const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view) {
|
||||
const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view,
|
||||
const Math::Vec4<u8> (&texture_color)[4]) {
|
||||
|
||||
// TODO(Subv): Bump mapping
|
||||
Math::Vec3<float> surface_normal = {0.0f, 0.0f, 1.0f};
|
||||
Math::Vec3<float> surface_normal;
|
||||
Math::Vec3<float> surface_tangent;
|
||||
|
||||
if (lighting.config0.bump_mode != LightingRegs::LightingBumpMode::None) {
|
||||
LOG_CRITICAL(HW_GPU, "unimplemented bump mapping");
|
||||
UNIMPLEMENTED();
|
||||
Math::Vec3<float> perturbation =
|
||||
texture_color[lighting.config0.bump_selector].xyz().Cast<float>() / 127.5f -
|
||||
Math::MakeVec(1.0f, 1.0f, 1.0f);
|
||||
if (lighting.config0.bump_mode == LightingRegs::LightingBumpMode::NormalMap) {
|
||||
if (!lighting.config0.disable_bump_renorm) {
|
||||
const float z_square = 1 - perturbation.xy().Length2();
|
||||
perturbation.z = std::sqrt(std::max(z_square, 0.0f));
|
||||
}
|
||||
surface_normal = perturbation;
|
||||
surface_tangent = Math::MakeVec(1.0f, 0.0f, 0.0f);
|
||||
} else if (lighting.config0.bump_mode == LightingRegs::LightingBumpMode::TangentMap) {
|
||||
surface_normal = Math::MakeVec(0.0f, 0.0f, 1.0f);
|
||||
surface_tangent = perturbation;
|
||||
} else {
|
||||
LOG_ERROR(HW_GPU, "Unknown bump mode %u", lighting.config0.bump_mode.Value());
|
||||
}
|
||||
} else {
|
||||
surface_normal = Math::MakeVec(0.0f, 0.0f, 1.0f);
|
||||
surface_tangent = Math::MakeVec(1.0f, 0.0f, 0.0f);
|
||||
}
|
||||
|
||||
// Use the normalized the quaternion when performing the rotation
|
||||
auto normal = Math::QuaternionRotate(normquat, surface_normal);
|
||||
auto tangent = Math::QuaternionRotate(normquat, surface_tangent);
|
||||
|
||||
Math::Vec4<float> diffuse_sum = {0.0f, 0.0f, 0.0f, 1.0f};
|
||||
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(
|
||||
result = Math::Dot(light_vector, spot_dir.Cast<float>() / 2047.0f);
|
||||
break;
|
||||
}
|
||||
case LightingRegs::LightingLutInput::CP:
|
||||
if (lighting.config0.config == LightingRegs::LightingConfig::Config7) {
|
||||
const Math::Vec3<float> norm_half_vector = half_vector.Normalized();
|
||||
const Math::Vec3<float> half_vector_proj =
|
||||
norm_half_vector - normal * Math::Dot(normal, norm_half_vector);
|
||||
result = Math::Dot(half_vector_proj, tangent);
|
||||
} else {
|
||||
result = 0.0f;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(HW_GPU, "Unknown lighting LUT input %u\n", static_cast<u32>(input));
|
||||
UNIMPLEMENTED();
|
||||
@ -201,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)) {
|
||||
|
||||
@ -213,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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -13,6 +13,7 @@ namespace Pica {
|
||||
|
||||
std::tuple<Math::Vec4<u8>, Math::Vec4<u8>> ComputeFragmentsColors(
|
||||
const Pica::LightingRegs& lighting, const Pica::State::Lighting& lighting_state,
|
||||
const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view);
|
||||
const Math::Quaternion<float>& normquat, const Math::Vec3<float>& view,
|
||||
const Math::Vec4<u8> (&texture_color)[4]);
|
||||
|
||||
} // namespace Pica
|
||||
|
@ -437,8 +437,8 @@ static void ProcessTriangleInternal(const Vertex& v0, const Vertex& v1, const Ve
|
||||
GetInterpolatedAttribute(v0.view.y, v1.view.y, v2.view.y).ToFloat32(),
|
||||
GetInterpolatedAttribute(v0.view.z, v1.view.z, v2.view.z).ToFloat32(),
|
||||
};
|
||||
std::tie(primary_fragment_color, secondary_fragment_color) =
|
||||
ComputeFragmentsColors(g_state.regs.lighting, g_state.lighting, normquat, view);
|
||||
std::tie(primary_fragment_color, secondary_fragment_color) = ComputeFragmentsColors(
|
||||
g_state.regs.lighting, g_state.lighting, normquat, view, texture_color);
|
||||
}
|
||||
|
||||
for (unsigned tev_stage_index = 0; tev_stage_index < tev_stages.size();
|
||||
|
@ -89,6 +89,8 @@ Math::Vec3<u8> GetColorModifier(TevStageConfig::ColorModifier factor,
|
||||
case ColorModifier::OneMinusSourceBlue:
|
||||
return (Math::Vec3<u8>(255, 255, 255) - values.bbb()).Cast<u8>();
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
};
|
||||
|
||||
u8 GetAlphaModifier(TevStageConfig::AlphaModifier factor, const Math::Vec4<u8>& values) {
|
||||
@ -119,6 +121,8 @@ u8 GetAlphaModifier(TevStageConfig::AlphaModifier factor, const Math::Vec4<u8>&
|
||||
case AlphaModifier::OneMinusSourceBlue:
|
||||
return 255 - values.b();
|
||||
}
|
||||
|
||||
UNREACHABLE();
|
||||
};
|
||||
|
||||
Math::Vec3<u8> ColorCombine(TevStageConfig::Operation op, const Math::Vec3<u8> input[3]) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user