resoveld merge conflicts

This commit is contained in:
B3n30 2017-09-15 20:46:10 +02:00
commit 69a36fbaec
58 changed files with 1515 additions and 482 deletions

View File

@ -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

View File

@ -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.")

View 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()

View File

@ -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

View File

@ -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
View 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>

View File

@ -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)

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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"

View File

@ -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);

View File

@ -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)

View File

@ -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"

View File

@ -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");

View File

@ -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

View File

@ -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))

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -7,5 +7,5 @@
#include <core/hle/lock.h>
namespace HLE {
std::mutex g_hle_lock;
std::recursive_mutex g_hle_lock;
}

View File

@ -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

View File

@ -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;

View File

@ -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;

View File

@ -101,6 +101,8 @@ void GetCountryCodeString(Service::Interface* self);
*/
void GetCountryCodeID(Service::Interface* self);
u32 GetRegionValue();
/**
* CFG::SecureInfoGetRegion service function
* Inputs:

View File

@ -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

View File

@ -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.

View File

@ -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();

View File

@ -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

View File

@ -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

View 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

View 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

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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) {

View 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

View 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

View File

@ -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);

View File

@ -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;
};

View File

@ -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>

View File

@ -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

View File

@ -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);

View File

@ -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,

View File

@ -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)

View File

@ -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";

View File

@ -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

View File

@ -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

View File

@ -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(),

View File

@ -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();

View File

@ -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);

View File

@ -352,6 +352,8 @@ u8 LogicOp(u8 src, u8 dest, FramebufferRegs::LogicOp op) {
case FramebufferRegs::LogicOp::OrInverted:
return ~src | dest;
}
UNREACHABLE();
};
} // namespace Rasterizer

View File

@ -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;
}
}

View File

@ -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

View File

@ -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();

View File

@ -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]) {