mirror of
https://github.com/RPCS3/rpcs3.git
synced 2025-12-16 04:09:07 +00:00
Compare commits
68 Commits
7d82eebba2
...
974f7eb721
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
974f7eb721 | ||
|
|
cf87f24587 | ||
|
|
12a3818fcf | ||
|
|
2f5843c6cf | ||
|
|
d9da5f26c0 | ||
|
|
1a3e150a62 | ||
|
|
c3db85c68e | ||
|
|
2b0456520e | ||
|
|
0c455d12c9 | ||
|
|
236f783466 | ||
|
|
f739ce7323 | ||
|
|
3c0558c822 | ||
|
|
c38146636a | ||
|
|
26b0f822d8 | ||
|
|
ee9dc44059 | ||
|
|
24745416c5 | ||
|
|
fa4e2d1b42 | ||
|
|
c2284c962b | ||
|
|
757e9a0493 | ||
|
|
f7cda4b2b4 | ||
|
|
c6ef09500a | ||
|
|
26ae6f0902 | ||
|
|
1b01a9274c | ||
|
|
0f1eadcab0 | ||
|
|
1ea3c121fa | ||
|
|
2c6d3dde67 | ||
|
|
a412e34fa6 | ||
|
|
4dc52fde82 | ||
|
|
5688573b3d | ||
|
|
98a12de256 | ||
|
|
43d8518faa | ||
|
|
1e6fe1f4ab | ||
|
|
93f89b8a74 | ||
|
|
8cd241ca10 | ||
|
|
8d2f7ae85f | ||
|
|
81d657a960 | ||
|
|
4183d09a52 | ||
|
|
316e01995b | ||
|
|
f2913e4692 | ||
|
|
d23ea4760b | ||
|
|
26fd0510ab | ||
|
|
e8e2d4b93d | ||
|
|
a7f5514913 | ||
|
|
3bed46b844 | ||
|
|
e1ec2f58bb | ||
|
|
91e19652de | ||
|
|
b244c0fa0f | ||
|
|
f4ba548748 | ||
|
|
856eaac1b6 | ||
|
|
8ff3dda5e8 | ||
|
|
fa40cef0b1 | ||
|
|
65b4bcb027 | ||
|
|
5a8ad219ba | ||
|
|
ed397c5d67 | ||
|
|
13d66f324b | ||
|
|
aaaa6feb5a | ||
|
|
88a54e2d98 | ||
|
|
9d30716aa8 | ||
|
|
e20bae3cd7 | ||
|
|
aff645272f | ||
|
|
27f39d2ac0 | ||
|
|
c840c98e9e | ||
|
|
adcacc1119 | ||
|
|
65458effa6 | ||
|
|
0f1d516d9a | ||
|
|
3f6529fecb | ||
|
|
b76ca8807f | ||
|
|
54206c62b3 |
@ -6,7 +6,9 @@ export HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1
|
|||||||
export HOMEBREW_NO_ENV_HINTS=1
|
export HOMEBREW_NO_ENV_HINTS=1
|
||||||
export HOMEBREW_NO_INSTALL_CLEANUP=1
|
export HOMEBREW_NO_INSTALL_CLEANUP=1
|
||||||
|
|
||||||
brew install -f --overwrite --quiet pipenv googletest ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers
|
brew install -f --overwrite --quiet pipenv googletest opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader
|
||||||
|
brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative
|
||||||
|
|
||||||
brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
|
brew link -f --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
|
||||||
|
|
||||||
# moltenvk based on commit for 1.4.0 release
|
# moltenvk based on commit for 1.4.0 release
|
||||||
@ -56,15 +58,14 @@ export SDL3_DIR="$BREW_PATH/opt/sdl3/lib/cmake/SDL3"
|
|||||||
|
|
||||||
export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH"
|
export PATH="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/bin:$WORKDIR/qt-downloader/$QT_VER/clang_64/bin:$BREW_BIN:$BREW_SBIN:/usr/bin:/bin:/usr/sbin:/sbin:/opt/X11/bin:/Library/Apple/usr/bin:$PATH"
|
||||||
export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib"
|
export LDFLAGS="-L$BREW_PATH/lib $BREW_PATH/opt/ffmpeg@5/lib/libavcodec.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavformat.dylib $BREW_PATH/opt/ffmpeg@5/lib/libavutil.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswscale.dylib $BREW_PATH/opt/ffmpeg@5/lib/libswresample.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++.1.dylib $BREW_PATH/lib/libSDL3.dylib $BREW_PATH/lib/libGLEW.dylib $BREW_PATH/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib -Wl,-rpath,$BREW_PATH/lib"
|
||||||
export CPPFLAGS="-I$BREW_PATH/include -I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000"
|
export CPPFLAGS="-I$BREW_PATH/include -no-pie -D__MAC_OS_X_VERSION_MIN_REQUIRED=140000"
|
||||||
export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000"
|
export CFLAGS="-D__MAC_OS_X_VERSION_MIN_REQUIRED=140000"
|
||||||
export LIBRARY_PATH="$BREW_PATH/lib"
|
export LIBRARY_PATH="$BREW_PATH/lib"
|
||||||
export LD_LIBRARY_PATH="$BREW_PATH/lib"
|
export LD_LIBRARY_PATH="$BREW_PATH/lib"
|
||||||
|
|
||||||
export VULKAN_SDK
|
export VULKAN_SDK
|
||||||
VULKAN_SDK="$BREW_PATH/opt/molten-vk"
|
VULKAN_SDK="$BREW_PATH/opt/molten-vk"
|
||||||
ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true
|
ln -s "$BREW_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib" || true
|
||||||
export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json"
|
|
||||||
|
|
||||||
export LLVM_DIR
|
export LLVM_DIR
|
||||||
LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER"
|
LLVM_DIR="$BREW_PATH/opt/llvm@$LLVM_COMPILER_VER"
|
||||||
|
|||||||
@ -12,7 +12,8 @@ brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER"
|
|||||||
rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \
|
rm /usr/local/bin/{idle3.14,pip3.14,pydoc3.14,python3.14,python3.14-config} && \
|
||||||
rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config}
|
rm /usr/local/bin/{idle3,pip3,pydoc3,python3,python3-config}
|
||||||
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
arch -x86_64 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
|
||||||
arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers
|
arch -x86_64 /usr/local/bin/brew install -f --overwrite --quiet opencv@4 ffmpeg@5 "llvm@$LLVM_COMPILER_VER" glew sdl3 vulkan-headers vulkan-loader
|
||||||
|
arch -x86_64 /usr/local/bin/brew unlink --quiet ffmpeg qtbase qtsvg qtdeclarative
|
||||||
arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
|
arch -x86_64 /usr/local/bin/brew link -f --overwrite --quiet "llvm@$LLVM_COMPILER_VER" ffmpeg@5
|
||||||
|
|
||||||
# moltenvk based on commit for 1.4.0 release
|
# moltenvk based on commit for 1.4.0 release
|
||||||
@ -63,8 +64,7 @@ export LD_LIBRARY_PATH="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER/lib:$BREW_X64
|
|||||||
|
|
||||||
export VULKAN_SDK
|
export VULKAN_SDK
|
||||||
VULKAN_SDK="$BREW_X64_PATH/opt/molten-vk"
|
VULKAN_SDK="$BREW_X64_PATH/opt/molten-vk"
|
||||||
ln -s "$VULKAN_SDK/lib/libMoltenVK.dylib" "$VULKAN_SDK/lib/libvulkan.dylib"
|
ln -s "$BREW_X64_PATH/opt/vulkan-loader/lib/libvulkan.dylib" "$VULKAN_SDK/lib/libvulkan.dylib"
|
||||||
export VK_ICD_FILENAMES="$VULKAN_SDK/share/vulkan/icd.d/MoltenVK_icd.json"
|
|
||||||
|
|
||||||
export LLVM_DIR
|
export LLVM_DIR
|
||||||
LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER"
|
LLVM_DIR="$BREW_X64_PATH/opt/llvm@$LLVM_COMPILER_VER"
|
||||||
|
|||||||
@ -15,8 +15,13 @@ echo "AVVER=$AVVER" >> ../.ci/ci-vars.env
|
|||||||
|
|
||||||
cd bin
|
cd bin
|
||||||
mkdir "rpcs3.app/Contents/lib/" || true
|
mkdir "rpcs3.app/Contents/lib/" || true
|
||||||
|
mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true
|
||||||
|
cp "$(realpath /opt/homebrew/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib"
|
||||||
|
cp "$(realpath /opt/homebrew/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
|
||||||
|
sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
|
||||||
|
|
||||||
cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib"
|
cp "$(realpath /opt/homebrew/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib"
|
||||||
|
cp "$(realpath /opt/homebrew/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib"
|
||||||
cp "$(realpath /opt/homebrew/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib"
|
cp "$(realpath /opt/homebrew/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib"
|
||||||
cp "$(realpath /opt/homebrew/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib"
|
cp "$(realpath /opt/homebrew/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib"
|
||||||
|
|
||||||
|
|||||||
@ -14,10 +14,15 @@ AVVER="${COMM_TAG}-${COMM_COUNT}"
|
|||||||
echo "AVVER=$AVVER" >> ../.ci/ci-vars.env
|
echo "AVVER=$AVVER" >> ../.ci/ci-vars.env
|
||||||
|
|
||||||
cd bin
|
cd bin
|
||||||
mkdir "rpcs3.app/Contents/lib/"
|
mkdir "rpcs3.app/Contents/lib/" || true
|
||||||
|
mkdir -p "rpcs3.app/Contents/Resources/vulkan/icd.d" || true
|
||||||
|
cp "$(realpath /usr/local/lib/libMoltenVK.dylib)" "rpcs3.app/Contents/Frameworks/libMoltenVK.dylib"
|
||||||
|
cp "$(realpath /usr/local/etc/vulkan/icd.d/MoltenVK_icd.json)" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
|
||||||
|
sed -i '' "s/lib\//Frameworks\//g" "rpcs3.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json"
|
||||||
|
|
||||||
cp "/usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib"
|
cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/c++/libc++abi.1.0.dylib)" "rpcs3.app/Contents/Frameworks/libc++abi.1.dylib"
|
||||||
cp "/usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib"
|
cp "$(realpath /usr/local/opt/llvm@$LLVM_COMPILER_VER/lib/unwind/libunwind.1.dylib)" "rpcs3.app/Contents/Frameworks/libunwind.1.dylib"
|
||||||
|
cp "$(realpath /usr/local/opt/gcc/lib/gcc/current/libgcc_s.1.1.dylib)" "rpcs3.app/Contents/Frameworks/libgcc_s.1.1.dylib"
|
||||||
cp "$(realpath /usr/local/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib"
|
cp "$(realpath /usr/local/lib/libsharpyuv.0.dylib)" "rpcs3.app/Contents/lib/libsharpyuv.0.dylib"
|
||||||
cp "$(realpath /usr/local/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib"
|
cp "$(realpath /usr/local/lib/libintl.8.dylib)" "rpcs3.app/Contents/lib/libintl.8.dylib"
|
||||||
|
|
||||||
|
|||||||
2
3rdparty/flatbuffers
vendored
2
3rdparty/flatbuffers
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 595bf0007ab1929570c7671f091313c8fc20644e
|
Subproject commit 187240970746d00bbd26b0f5873ed54d2477f9f3
|
||||||
2
3rdparty/fusion/fusion
vendored
2
3rdparty/fusion/fusion
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 759ac5d698baefca53f1975a0bb1d2dcbdb9f836
|
Subproject commit 008e03eac0ac1d5f85e16f5fcaefdda3fee75cb8
|
||||||
@ -83,7 +83,8 @@ std::string fmt::win_error_to_string(unsigned long error, void* module_handle)
|
|||||||
if (FormatMessageW((module_handle ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
|
if (FormatMessageW((module_handle ? FORMAT_MESSAGE_FROM_HMODULE : FORMAT_MESSAGE_FROM_SYSTEM) | FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||||
module_handle, error, 0, reinterpret_cast<LPWSTR>(&message_buffer), 0, nullptr))
|
module_handle, error, 0, reinterpret_cast<LPWSTR>(&message_buffer), 0, nullptr))
|
||||||
{
|
{
|
||||||
message = fmt::format("%s (0x%x)", fmt::trim(wchar_to_utf8(message_buffer), " \t\n\r\f\v"), error);
|
const std::string utf8 = wchar_to_utf8(message_buffer);
|
||||||
|
message = fmt::format("%s (0x%x)", fmt::trim_sv(utf8, " \t\n\r\f\v"), error);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -823,6 +824,50 @@ std::vector<std::string> fmt::split(std::string_view source, std::initializer_li
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::vector<std::string_view> fmt::split_sv(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty)
|
||||||
|
{
|
||||||
|
std::vector<std::string_view> result;
|
||||||
|
|
||||||
|
for (usz index = 0; index < source.size();)
|
||||||
|
{
|
||||||
|
usz pos = -1;
|
||||||
|
usz sep_size = 0;
|
||||||
|
|
||||||
|
for (auto& separator : separators)
|
||||||
|
{
|
||||||
|
if (usz pos0 = source.find(separator, index); pos0 < pos)
|
||||||
|
{
|
||||||
|
pos = pos0;
|
||||||
|
sep_size = separator.size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!sep_size)
|
||||||
|
{
|
||||||
|
result.emplace_back(&source[index], source.size() - index);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string_view piece = {&source[index], pos - index};
|
||||||
|
|
||||||
|
index = pos + sep_size;
|
||||||
|
|
||||||
|
if (piece.empty() && is_skip_empty)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result.emplace_back(std::move(piece));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.empty() && !is_skip_empty)
|
||||||
|
{
|
||||||
|
result.emplace_back();
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
std::string fmt::trim(const std::string& source, std::string_view values)
|
std::string fmt::trim(const std::string& source, std::string_view values)
|
||||||
{
|
{
|
||||||
const usz begin = source.find_first_not_of(values);
|
const usz begin = source.find_first_not_of(values);
|
||||||
@ -838,6 +883,21 @@ std::string fmt::trim(const std::string& source, std::string_view values)
|
|||||||
return source.substr(begin, end + 1 - begin);
|
return source.substr(begin, end + 1 - begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view fmt::trim_sv(std::string_view source, std::string_view values)
|
||||||
|
{
|
||||||
|
const usz begin = source.find_first_not_of(values);
|
||||||
|
|
||||||
|
if (begin == source.npos)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
const usz end = source.find_last_not_of(values);
|
||||||
|
|
||||||
|
if (end == source.npos)
|
||||||
|
return source.substr(begin);
|
||||||
|
|
||||||
|
return source.substr(begin, end + 1 - begin);
|
||||||
|
}
|
||||||
|
|
||||||
std::string fmt::trim_front(const std::string& source, std::string_view values)
|
std::string fmt::trim_front(const std::string& source, std::string_view values)
|
||||||
{
|
{
|
||||||
const usz begin = source.find_first_not_of(values);
|
const usz begin = source.find_first_not_of(values);
|
||||||
@ -848,12 +908,32 @@ std::string fmt::trim_front(const std::string& source, std::string_view values)
|
|||||||
return source.substr(begin);
|
return source.substr(begin);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view fmt::trim_front_sv(std::string_view source, std::string_view values)
|
||||||
|
{
|
||||||
|
const usz begin = source.find_first_not_of(values);
|
||||||
|
|
||||||
|
if (begin == source.npos)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
return source.substr(begin);
|
||||||
|
}
|
||||||
|
|
||||||
void fmt::trim_back(std::string& source, std::string_view values)
|
void fmt::trim_back(std::string& source, std::string_view values)
|
||||||
{
|
{
|
||||||
const usz index = source.find_last_not_of(values);
|
const usz index = source.find_last_not_of(values);
|
||||||
source.resize(index + 1);
|
source.resize(index + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string_view fmt::trim_back_sv(std::string_view source, std::string_view values)
|
||||||
|
{
|
||||||
|
const usz index = source.find_last_not_of(values);
|
||||||
|
if (index == std::string_view::npos)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
source.remove_suffix(source.size() - (index + 1));
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
std::string fmt::to_upper(std::string_view string)
|
std::string fmt::to_upper(std::string_view string)
|
||||||
{
|
{
|
||||||
std::string result;
|
std::string result;
|
||||||
|
|||||||
@ -139,57 +139,90 @@ namespace fmt
|
|||||||
// Splits the string into a vector of strings using the separators. The vector may contain empty strings unless is_skip_empty is true.
|
// Splits the string into a vector of strings using the separators. The vector may contain empty strings unless is_skip_empty is true.
|
||||||
std::vector<std::string> split(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty = true);
|
std::vector<std::string> split(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty = true);
|
||||||
|
|
||||||
|
// Splits the string_view into a vector of string_views using the separators. The vector may contain empty string_views unless is_skip_empty is true.
|
||||||
|
std::vector<std::string_view> split_sv(std::string_view source, std::initializer_list<std::string_view> separators, bool is_skip_empty = true);
|
||||||
|
|
||||||
// Removes all preceding and trailing characters specified by 'values' from 'source'.
|
// Removes all preceding and trailing characters specified by 'values' from 'source'.
|
||||||
std::string trim(const std::string& source, std::string_view values = " \t");
|
std::string trim(const std::string& source, std::string_view values = " \t");
|
||||||
|
|
||||||
|
// Removes all preceding and trailing characters specified by 'values' from 'source' and returns the result.
|
||||||
|
std::string_view trim_sv(std::string_view source, std::string_view values = " \t");
|
||||||
|
|
||||||
// Removes all preceding characters specified by 'values' from 'source'.
|
// Removes all preceding characters specified by 'values' from 'source'.
|
||||||
std::string trim_front(const std::string& source, std::string_view values = " \t");
|
std::string trim_front(const std::string& source, std::string_view values = " \t");
|
||||||
|
|
||||||
|
// Removes all preceding characters specified by 'values' from 'source' and returns the result.
|
||||||
|
std::string_view trim_front_sv(std::string_view source, std::string_view values = " \t");
|
||||||
|
|
||||||
// Removes all trailing characters specified by 'values' from 'source'.
|
// Removes all trailing characters specified by 'values' from 'source'.
|
||||||
void trim_back(std::string& source, std::string_view values = " \t");
|
void trim_back(std::string& source, std::string_view values = " \t");
|
||||||
|
|
||||||
|
// Removes all trailing characters specified by 'values' from 'source' and returns the result.
|
||||||
|
std::string_view trim_back_sv(std::string_view source, std::string_view values = " \t");
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::string merge(const T& source, const std::string& separator)
|
std::string merge(const T& source, std::string_view separator)
|
||||||
{
|
{
|
||||||
if (source.empty())
|
if (source.empty())
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usz total = (source.size() - 1) * separator.size();
|
||||||
|
for (const auto& s : source)
|
||||||
|
{
|
||||||
|
total += s.size();
|
||||||
|
}
|
||||||
|
|
||||||
std::string result;
|
std::string result;
|
||||||
|
result.reserve(total);
|
||||||
|
|
||||||
auto it = source.begin();
|
auto it = source.begin();
|
||||||
auto end = source.end();
|
auto end = source.end();
|
||||||
|
|
||||||
for (--end; it != end; ++it)
|
for (--end; it != end; ++it)
|
||||||
{
|
{
|
||||||
result += std::string{*it} + separator;
|
result.append(*it);
|
||||||
|
|
||||||
|
if (!separator.empty())
|
||||||
|
result.append(separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result + std::string{source.back()};
|
return result.append(source.back());
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::string merge(std::initializer_list<T> sources, const std::string& separator)
|
std::string merge(std::initializer_list<T> sources, std::string_view separator)
|
||||||
{
|
{
|
||||||
if (!sources.size())
|
if (!sources.size())
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
usz total = (sources.size() - 1) * separator.size();
|
||||||
|
for (const auto& s : sources)
|
||||||
|
{
|
||||||
|
if (s.empty()) continue;
|
||||||
|
total += s.size() + (s.size() - 1) * separator.size();
|
||||||
|
}
|
||||||
|
|
||||||
std::string result;
|
std::string result;
|
||||||
|
result.reserve(total);
|
||||||
|
|
||||||
bool first = true;
|
bool first = true;
|
||||||
|
|
||||||
for (const auto& v : sources)
|
for (const auto& v : sources)
|
||||||
{
|
{
|
||||||
if (first)
|
if (first)
|
||||||
{
|
{
|
||||||
result = fmt::merge(v, separator);
|
first = false;
|
||||||
first = false;
|
|
||||||
}
|
}
|
||||||
else
|
else if (!separator.empty())
|
||||||
{
|
{
|
||||||
result += separator + fmt::merge(v, separator);
|
result.append(separator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.append(fmt::merge(v, separator));
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@ -23,7 +23,6 @@ Intersection (&) and symmetric difference (^) is also available.
|
|||||||
|
|
||||||
#include "util/types.hpp"
|
#include "util/types.hpp"
|
||||||
#include "util/atomic.hpp"
|
#include "util/atomic.hpp"
|
||||||
#include "Utilities/StrFmt.h"
|
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
concept BitSetEnum = std::is_enum_v<T> && requires(T x)
|
concept BitSetEnum = std::is_enum_v<T> && requires(T x)
|
||||||
@ -384,6 +383,9 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
struct fmt_unveil;
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
struct fmt_unveil<bs_t<T>>
|
struct fmt_unveil<bs_t<T>>
|
||||||
{
|
{
|
||||||
|
|||||||
@ -27,9 +27,9 @@ void fmt_class_string<cheat_type>::format(std::string& out, u64 arg)
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool cheat_info::from_str(const std::string& cheat_line)
|
bool cheat_info::from_str(std::string_view cheat_line)
|
||||||
{
|
{
|
||||||
auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false);
|
const auto cheat_vec = fmt::split(cheat_line, {"@@@"}, false);
|
||||||
|
|
||||||
s64 val64 = 0;
|
s64 val64 = 0;
|
||||||
if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1))
|
if (cheat_vec.size() != 5 || !try_to_int64(&val64, cheat_vec[2], 0, cheat_type_max - 1))
|
||||||
|
|||||||
@ -28,6 +28,6 @@ struct cheat_info
|
|||||||
u32 offset{};
|
u32 offset{};
|
||||||
std::string red_script{};
|
std::string red_script{};
|
||||||
|
|
||||||
bool from_str(const std::string& cheat_line);
|
bool from_str(std::string_view cheat_line);
|
||||||
std::string to_str() const;
|
std::string to_str() const;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -193,6 +193,7 @@ if(BUILD_RPCS3_TESTS)
|
|||||||
tests/test_simple_array.cpp
|
tests/test_simple_array.cpp
|
||||||
tests/test_address_range.cpp
|
tests/test_address_range.cpp
|
||||||
tests/test_rsx_cfg.cpp
|
tests/test_rsx_cfg.cpp
|
||||||
|
tests/test_rsx_fp_asm.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(rpcs3_test
|
target_link_libraries(rpcs3_test
|
||||||
|
|||||||
@ -598,15 +598,15 @@ bool package_reader::read_param_sfo()
|
|||||||
|
|
||||||
const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u;
|
const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u;
|
||||||
|
|
||||||
std::string name(entry.name_size + BUF_PADDING, '\0');
|
std::string name_buf(entry.name_size + BUF_PADDING, '\0');
|
||||||
|
|
||||||
if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size)
|
if (usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size)
|
||||||
{
|
{
|
||||||
pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset);
|
pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt::trim_back(name, "\0"sv);
|
std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv);
|
||||||
|
|
||||||
// We're looking for the PARAM.SFO file, if there is any
|
// We're looking for the PARAM.SFO file, if there is any
|
||||||
if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != "PARAM.SFO")
|
if (usz ndelim = name.find_first_not_of('/'); ndelim == umax || name.substr(ndelim) != "PARAM.SFO")
|
||||||
@ -854,18 +854,18 @@ bool package_reader::fill_data(std::map<std::string, install_entry*>& all_instal
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string name(entry.name_size + BUF_PADDING, '\0');
|
std::string name_buf(entry.name_size + BUF_PADDING, '\0');
|
||||||
|
|
||||||
const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u;
|
const bool is_psp = (entry.type & PKG_FILE_ENTRY_PSP) != 0u;
|
||||||
|
|
||||||
if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name.data()); read_size < entry.name_size)
|
if (const usz read_size = decrypt(entry.name_offset, entry.name_size, is_psp ? PKG_AES_KEY2 : m_dec_key.data(), name_buf.data()); read_size < entry.name_size)
|
||||||
{
|
{
|
||||||
num_failures++;
|
num_failures++;
|
||||||
pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset);
|
pkg_log.error("PKG name could not be read (size=0x%x, offset=0x%x)", entry.name_size, entry.name_offset);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt::trim_back(name, "\0"sv);
|
std::string_view name = fmt::trim_back_sv(name_buf, "\0"sv);
|
||||||
|
|
||||||
std::string path = m_install_path + vfs::escape(name);
|
std::string path = m_install_path + vfs::escape(name);
|
||||||
|
|
||||||
|
|||||||
@ -347,6 +347,7 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
bool is_valid() const { return m_is_valid; }
|
bool is_valid() const { return m_is_valid; }
|
||||||
|
const PKGHeader& get_header() const { return m_header; }
|
||||||
package_install_result check_target_app_version() const;
|
package_install_result check_target_app_version() const;
|
||||||
static package_install_result extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths);
|
static package_install_result extract_data(std::deque<package_reader>& readers, std::deque<std::string>& bootable_paths);
|
||||||
const psf::registry& get_psf() const { return m_psf; }
|
const psf::registry& get_psf() const { return m_psf; }
|
||||||
|
|||||||
@ -12,6 +12,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include "Utilities/StrFmt.h"
|
||||||
#include "Utilities/StrUtil.h"
|
#include "Utilities/StrUtil.h"
|
||||||
#include "Utilities/File.h"
|
#include "Utilities/File.h"
|
||||||
|
|
||||||
|
|||||||
@ -411,6 +411,7 @@ target_sources(rpcs3_emu PRIVATE
|
|||||||
Io/pad_config_types.cpp
|
Io/pad_config_types.cpp
|
||||||
Io/pad_types.cpp
|
Io/pad_types.cpp
|
||||||
Io/PadHandler.cpp
|
Io/PadHandler.cpp
|
||||||
|
Io/ps_move_data.cpp
|
||||||
Io/rb3drums_config.cpp
|
Io/rb3drums_config.cpp
|
||||||
Io/RB3MidiDrums.cpp
|
Io/RB3MidiDrums.cpp
|
||||||
Io/RB3MidiGuitar.cpp
|
Io/RB3MidiGuitar.cpp
|
||||||
@ -516,12 +517,15 @@ target_sources(rpcs3_emu PRIVATE
|
|||||||
RSX/Overlays/overlay_video.cpp
|
RSX/Overlays/overlay_video.cpp
|
||||||
RSX/Overlays/Shaders/shader_loading_dialog.cpp
|
RSX/Overlays/Shaders/shader_loading_dialog.cpp
|
||||||
RSX/Overlays/Shaders/shader_loading_dialog_native.cpp
|
RSX/Overlays/Shaders/shader_loading_dialog_native.cpp
|
||||||
|
RSX/Program/Assembler/FPASM.cpp
|
||||||
|
RSX/Program/Assembler/FPOpcodes.cpp
|
||||||
RSX/Program/Assembler/FPToCFG.cpp
|
RSX/Program/Assembler/FPToCFG.cpp
|
||||||
|
RSX/Program/Assembler/Passes/FP/RegisterAnnotationPass.cpp
|
||||||
|
RSX/Program/Assembler/Passes/FP/RegisterDependencyPass.cpp
|
||||||
RSX/Program/CgBinaryProgram.cpp
|
RSX/Program/CgBinaryProgram.cpp
|
||||||
RSX/Program/CgBinaryFragmentProgram.cpp
|
RSX/Program/CgBinaryFragmentProgram.cpp
|
||||||
RSX/Program/CgBinaryVertexProgram.cpp
|
RSX/Program/CgBinaryVertexProgram.cpp
|
||||||
RSX/Program/FragmentProgramDecompiler.cpp
|
RSX/Program/FragmentProgramDecompiler.cpp
|
||||||
RSX/Program/FragmentProgramRegister.cpp
|
|
||||||
RSX/Program/GLSLCommon.cpp
|
RSX/Program/GLSLCommon.cpp
|
||||||
RSX/Program/ProgramStateCache.cpp
|
RSX/Program/ProgramStateCache.cpp
|
||||||
RSX/Program/program_util.cpp
|
RSX/Program/program_util.cpp
|
||||||
@ -623,6 +627,11 @@ if(TARGET 3rdparty_vulkan)
|
|||||||
RSX/VK/VKTextureCache.cpp
|
RSX/VK/VKTextureCache.cpp
|
||||||
RSX/VK/VulkanAPI.cpp
|
RSX/VK/VulkanAPI.cpp
|
||||||
)
|
)
|
||||||
|
if(APPLE)
|
||||||
|
target_sources(rpcs3_emu PRIVATE
|
||||||
|
RSX/VK/vkutils/metal_layer.mm
|
||||||
|
)
|
||||||
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
find_package(Threads REQUIRED)
|
find_package(Threads REQUIRED)
|
||||||
|
|||||||
@ -276,6 +276,8 @@ public:
|
|||||||
|
|
||||||
u64 start_timestamp_us = 0;
|
u64 start_timestamp_us = 0;
|
||||||
|
|
||||||
|
std::array<ps_move_data, CELL_GEM_MAX_NUM> fake_move_data {}; // No need to be in savestate
|
||||||
|
|
||||||
atomic_t<u32> m_wake_up = 0;
|
atomic_t<u32> m_wake_up = 0;
|
||||||
atomic_t<u32> m_done = 0;
|
atomic_t<u32> m_done = 0;
|
||||||
|
|
||||||
@ -624,9 +626,9 @@ public:
|
|||||||
cellGem.notice("Could not load mouse gem config. Using defaults.");
|
cellGem.notice("Could not load mouse gem config. Using defaults.");
|
||||||
}
|
}
|
||||||
|
|
||||||
cellGem.notice("Real gem config=\n", g_cfg_gem_real.to_string());
|
cellGem.notice("Real gem config=%s", g_cfg_gem_real.to_string());
|
||||||
cellGem.notice("Fake gem config=\n", g_cfg_gem_fake.to_string());
|
cellGem.notice("Fake gem config=%s", g_cfg_gem_fake.to_string());
|
||||||
cellGem.notice("Mouse gem config=\n", g_cfg_gem_mouse.to_string());
|
cellGem.notice("Mouse gem config=%s", g_cfg_gem_mouse.to_string());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -696,22 +698,165 @@ namespace gem
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline u8 Y(u8 r, u8 g, u8 b) { return static_cast<u8>(0.299f * r + 0.587f * g + 0.114f * b); }
|
YUV(const u8 rgb[3])
|
||||||
static inline u8 U(u8 r, u8 g, u8 b) { return static_cast<u8>(-0.14713f * r - 0.28886f * g + 0.436f * b); }
|
{
|
||||||
static inline u8 V(u8 r, u8 g, u8 b) { return static_cast<u8>(0.615f * r - 0.51499f * g - 0.10001f * b); }
|
const u8 r = rgb[0];
|
||||||
|
const u8 g = rgb[1];
|
||||||
|
const u8 b = rgb[2];
|
||||||
|
y = Y(r, g, b);
|
||||||
|
u = U(r, g, b);
|
||||||
|
v = V(r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline u8 Y(u8 r, u8 g, u8 b) { return static_cast<u8>(std::clamp(0.299f * r + 0.587f * g + 0.114f * b, 0.0f, 255.0f)); }
|
||||||
|
static inline u8 U(u8 r, u8 g, u8 b) { return static_cast<u8>(std::clamp(-0.169f * r - 0.331f * g + 0.499f * b + 128, 0.0f, 255.0f)); }
|
||||||
|
static inline u8 V(u8 r, u8 g, u8 b) { return static_cast<u8>(std::clamp(0.499f * r - 0.460f * g - 0.040f * b + 128, 0.0f, 255.0f)); }
|
||||||
};
|
};
|
||||||
|
|
||||||
bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format,
|
template <bool use_gain>
|
||||||
const std::vector<u8>& video_data_in, u32 width, u32 height,
|
static inline void debayer_raw8_impl(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b)
|
||||||
u8* video_data_out, u32 video_data_out_size, std::string_view caller)
|
|
||||||
{
|
{
|
||||||
if (output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out)
|
constexpr u32 in_pitch = 640;
|
||||||
|
constexpr u32 out_pitch = 640 * 4;
|
||||||
|
|
||||||
|
// Hamilton–Adams demosaicing
|
||||||
|
for (s32 y = 0; y < 480; y++)
|
||||||
|
{
|
||||||
|
const bool is_even_y = (y % 2) == 0;
|
||||||
|
const u8* srcc = src + y * in_pitch;
|
||||||
|
const u8* srcu = src + std::max(0, y - 1) * in_pitch;
|
||||||
|
const u8* srcd = src + std::min(480 - 1, y + 1) * in_pitch;
|
||||||
|
|
||||||
|
u8* dst0 = dst + y * out_pitch;
|
||||||
|
|
||||||
|
// Split loops (roughly twice the performance by removing one condition)
|
||||||
|
if (is_even_y)
|
||||||
|
{
|
||||||
|
for (s32 x = 0; x < 640; x++, dst0 += 4)
|
||||||
|
{
|
||||||
|
const bool is_even_x = (x % 2) == 0;
|
||||||
|
const int xl = std::max(0, x - 1);
|
||||||
|
const int xr = std::min(640 - 1, x + 1);
|
||||||
|
|
||||||
|
u8 r, b, g;
|
||||||
|
|
||||||
|
if (is_even_x)
|
||||||
|
{
|
||||||
|
// Blue pixel
|
||||||
|
const u8 up = srcu[x];
|
||||||
|
const u8 down = srcd[x];
|
||||||
|
const u8 left = srcc[xl];
|
||||||
|
const u8 right = srcc[xr];
|
||||||
|
const int dh = std::abs(int(left) - int(right));
|
||||||
|
const int dv = std::abs(int(up) - int(down));
|
||||||
|
|
||||||
|
r = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4;
|
||||||
|
if (dh < dv)
|
||||||
|
g = (left + right) / 2;
|
||||||
|
else if (dv < dh)
|
||||||
|
g = (up + down) / 2;
|
||||||
|
else
|
||||||
|
g = (up + down + left + right) / 4;
|
||||||
|
b = srcc[x];
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Green (on blue row)
|
||||||
|
r = (srcu[x] + srcd[x]) / 2;
|
||||||
|
g = srcc[x];
|
||||||
|
b = (srcc[xl] + srcc[xr]) / 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (use_gain)
|
||||||
|
{
|
||||||
|
dst0[0] = static_cast<u8>(std::clamp(r * gain_r, 0.0f, 255.0f));
|
||||||
|
dst0[1] = static_cast<u8>(std::clamp(b * gain_b, 0.0f, 255.0f));
|
||||||
|
dst0[2] = static_cast<u8>(std::clamp(g * gain_g, 0.0f, 255.0f));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dst0[0] = r;
|
||||||
|
dst0[1] = g;
|
||||||
|
dst0[2] = b;
|
||||||
|
}
|
||||||
|
dst0[3] = alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (s32 x = 0; x < 640; x++, dst0 += 4)
|
||||||
|
{
|
||||||
|
const bool is_even_x = (x % 2) == 0;
|
||||||
|
const int xl = std::max(0, x - 1);
|
||||||
|
const int xr = std::min(640 - 1, x + 1);
|
||||||
|
|
||||||
|
u8 r, b, g;
|
||||||
|
|
||||||
|
if (is_even_x)
|
||||||
|
{
|
||||||
|
// Green (on red row)
|
||||||
|
r = (srcc[xl] + srcc[xr]) / 2;
|
||||||
|
g = srcc[x];
|
||||||
|
b = (srcu[x] + srcd[x]) / 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Red pixel
|
||||||
|
const u8 up = srcu[x];
|
||||||
|
const u8 down = srcd[x];
|
||||||
|
const u8 left = srcc[xl];
|
||||||
|
const u8 right = srcc[xr];
|
||||||
|
const int dh = std::abs(int(left) - int(right));
|
||||||
|
const int dv = std::abs(int(up) - int(down));
|
||||||
|
|
||||||
|
r = srcc[x];
|
||||||
|
if (dh < dv)
|
||||||
|
g = (left + right) / 2;
|
||||||
|
else if (dv < dh)
|
||||||
|
g = (up + down) / 2;
|
||||||
|
else
|
||||||
|
g = (up + down + left + right) / 4;
|
||||||
|
b = (srcu[xl] + srcu[xr] + srcd[xl] + srcd[xr]) / 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
if constexpr (use_gain)
|
||||||
|
{
|
||||||
|
dst0[0] = static_cast<u8>(std::clamp(r * gain_r, 0.0f, 255.0f));
|
||||||
|
dst0[1] = static_cast<u8>(std::clamp(b * gain_b, 0.0f, 255.0f));
|
||||||
|
dst0[2] = static_cast<u8>(std::clamp(g * gain_g, 0.0f, 255.0f));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
dst0[0] = r;
|
||||||
|
dst0[1] = g;
|
||||||
|
dst0[2] = b;
|
||||||
|
}
|
||||||
|
dst0[3] = alpha;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void debayer_raw8(const u8* src, u8* dst, u8 alpha, f32 gain_r, f32 gain_g, f32 gain_b)
|
||||||
|
{
|
||||||
|
if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f)
|
||||||
|
debayer_raw8_impl<true>(src, dst, alpha, gain_r, gain_g, gain_b);
|
||||||
|
else
|
||||||
|
debayer_raw8_impl<false>(src, dst, alpha, gain_r, gain_g, gain_b);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc,
|
||||||
|
const std::vector<u8>& video_data_in, u32 width, u32 height,
|
||||||
|
u8* video_data_out, u32 video_data_out_size, u8* buffer_memory,
|
||||||
|
std::string_view caller)
|
||||||
|
{
|
||||||
|
if (vc.output_format != CELL_GEM_NO_VIDEO_OUTPUT && !video_data_out)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const u32 required_in_size = get_buffer_size_by_format(static_cast<s32>(input_format), width, height);
|
const u32 required_in_size = get_buffer_size_by_format(static_cast<s32>(input_format), width, height);
|
||||||
const s32 required_out_size = cellGemGetVideoConvertSize(output_format);
|
const s32 required_out_size = cellGemGetVideoConvertSize(vc.output_format);
|
||||||
|
|
||||||
if (video_data_in.size() != required_in_size)
|
if (video_data_in.size() != required_in_size)
|
||||||
{
|
{
|
||||||
@ -721,7 +866,7 @@ namespace gem
|
|||||||
|
|
||||||
if (required_out_size < 0 || video_data_out_size != static_cast<u32>(required_out_size))
|
if (required_out_size < 0 || video_data_out_size != static_cast<u32>(required_out_size))
|
||||||
{
|
{
|
||||||
cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, output_format, caller);
|
cellGem.error("convert: out_size unknown: required=%d, actual=%d, format %d (called from %s)", required_out_size, video_data_out_size, vc.output_format, caller);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -730,7 +875,121 @@ namespace gem
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (output_format)
|
thread_local std::vector<u8> corrected_buffer;
|
||||||
|
thread_local std::vector<u8> combined_buffer;
|
||||||
|
thread_local std::vector<u8> conversion_buffer;
|
||||||
|
|
||||||
|
const u8* src_data = video_data_in.data();
|
||||||
|
const u8 alpha = vc.alpha;
|
||||||
|
const f32 gain_r = vc.gain * vc.blue_gain;
|
||||||
|
const f32 gain_g = vc.gain * vc.green_gain;
|
||||||
|
const f32 gain_b = vc.gain * vc.red_gain;
|
||||||
|
|
||||||
|
// Only RAW8 should be relevant for cellGem unless I'm mistaken
|
||||||
|
if (input_format == CELL_CAMERA_RAW8)
|
||||||
|
{
|
||||||
|
// TODO: CELL_GEM_AUTO_WHITE_BALANCE
|
||||||
|
// TODO: CELL_GEM_GAMMA_BOOST
|
||||||
|
|
||||||
|
// Correct outliers
|
||||||
|
if (vc.conversion_flags & CELL_GEM_FILTER_OUTLIER_PIXELS)
|
||||||
|
{
|
||||||
|
corrected_buffer.resize(width * height);
|
||||||
|
|
||||||
|
for (u32 y = 0; y < height; y++)
|
||||||
|
{
|
||||||
|
const u8* src = src_data + y * 640;
|
||||||
|
u8* dst = &corrected_buffer[y * 640];
|
||||||
|
|
||||||
|
for (u32 x = 0; x < width; x++, src++)
|
||||||
|
{
|
||||||
|
// Let's just say these 2 are outliers
|
||||||
|
if (const u8 val = *src; val > 0 && val < 255)
|
||||||
|
{
|
||||||
|
*dst++ = val;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just take the 4 neighbours for now
|
||||||
|
s32 sum = 0;
|
||||||
|
if (y >= 2) sum += *(src - (2 * 640));
|
||||||
|
if (x >= 2) sum += *(src - 2);
|
||||||
|
if (x < 638) sum += *(src + 2);
|
||||||
|
if (y < 478) sum += *(src + (2 * 640));
|
||||||
|
|
||||||
|
*dst++ = sum / 4; // Ignore count. It will only be less than 4 on the edges
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src_data = corrected_buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine with previous frame
|
||||||
|
if (buffer_memory && (vc.conversion_flags & CELL_GEM_COMBINE_PREVIOUS_INPUT_FRAME))
|
||||||
|
{
|
||||||
|
combined_buffer.resize(width * height);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < combined_buffer.size(); i++)
|
||||||
|
{
|
||||||
|
const u8 val = src_data[i];
|
||||||
|
u8& old = buffer_memory[i];
|
||||||
|
combined_buffer[i] = (old + val) / 2;
|
||||||
|
old = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
src_data = combined_buffer.data();
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (vc.output_format)
|
||||||
|
{
|
||||||
|
case CELL_GEM_YUV_640x480:
|
||||||
|
case CELL_GEM_YUV422_640x480:
|
||||||
|
case CELL_GEM_YUV411_640x480:
|
||||||
|
{
|
||||||
|
// Let's debayer the image first for YUV formats
|
||||||
|
conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480));
|
||||||
|
|
||||||
|
debayer_raw8(src_data, conversion_buffer.data(), alpha, gain_r, gain_g, gain_b);
|
||||||
|
|
||||||
|
src_data = conversion_buffer.data();
|
||||||
|
input_format = CELL_CAMERA_RGBA;
|
||||||
|
width = 640;
|
||||||
|
height = 480;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case CELL_GEM_BAYER_RESTORED:
|
||||||
|
case CELL_GEM_BAYER_RESTORED_RGGB:
|
||||||
|
case CELL_GEM_BAYER_RESTORED_RASTERIZED:
|
||||||
|
{
|
||||||
|
// Let's apply gain
|
||||||
|
if (gain_r != 1.0f || gain_g != 1.0f || gain_b != 1.0f)
|
||||||
|
{
|
||||||
|
conversion_buffer.resize(cellGemGetVideoConvertSize(CELL_GEM_RGBA_640x480));
|
||||||
|
|
||||||
|
const f32 bggr_gains[2][2] = {{gain_b, gain_g}, {gain_g, gain_r}};
|
||||||
|
const u8* src = src_data;
|
||||||
|
u8* dst = conversion_buffer.data();
|
||||||
|
|
||||||
|
for (u32 y = 0; y < 480; y++)
|
||||||
|
{
|
||||||
|
const f32* gains = bggr_gains[y % 2];
|
||||||
|
|
||||||
|
for (u32 x = 0; x < 640; x++)
|
||||||
|
{
|
||||||
|
*dst++ = static_cast<u8>(std::clamp(*src++ * gains[x % 2], 0.0f, 255.0f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
src_data = conversion_buffer.data();
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (vc.output_format)
|
||||||
{
|
{
|
||||||
case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required
|
case CELL_GEM_RGBA_640x480: // RGBA output; 640*480*4-byte output buffer required
|
||||||
{
|
{
|
||||||
@ -738,51 +997,18 @@ namespace gem
|
|||||||
{
|
{
|
||||||
case CELL_CAMERA_RAW8:
|
case CELL_CAMERA_RAW8:
|
||||||
{
|
{
|
||||||
const u32 in_pitch = width;
|
debayer_raw8(src_data, video_data_out, alpha, gain_r, gain_g, gain_b);
|
||||||
const u32 out_pitch = width * 4;
|
|
||||||
|
|
||||||
for (u32 y = 0; y < height - 1; y += 2)
|
|
||||||
{
|
|
||||||
const u8* src0 = &video_data_in[y * in_pitch];
|
|
||||||
const u8* src1 = src0 + in_pitch;
|
|
||||||
|
|
||||||
u8* dst0 = video_data_out + y * out_pitch;
|
|
||||||
u8* dst1 = dst0 + out_pitch;
|
|
||||||
|
|
||||||
for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst0 += 8, dst1 += 8)
|
|
||||||
{
|
|
||||||
const u8 b = src0[0];
|
|
||||||
const u8 g0 = src0[1];
|
|
||||||
const u8 g1 = src1[0];
|
|
||||||
const u8 r = src1[1];
|
|
||||||
|
|
||||||
const u8 top[4] = { r, g0, b, 255 };
|
|
||||||
const u8 bottom[4] = { r, g1, b, 255 };
|
|
||||||
|
|
||||||
// Top-Left
|
|
||||||
std::memcpy(dst0, top, 4);
|
|
||||||
|
|
||||||
// Top-Right Pixel
|
|
||||||
std::memcpy(dst0 + 4, top, 4);
|
|
||||||
|
|
||||||
// Bottom-Left Pixel
|
|
||||||
std::memcpy(dst1, bottom, 4);
|
|
||||||
|
|
||||||
// Bottom-Right Pixel
|
|
||||||
std::memcpy(dst1 + 4, bottom, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CELL_CAMERA_RGBA:
|
case CELL_CAMERA_RGBA:
|
||||||
{
|
{
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller);
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -792,17 +1018,18 @@ namespace gem
|
|||||||
{
|
{
|
||||||
if (input_format == CELL_CAMERA_RAW8)
|
if (input_format == CELL_CAMERA_RAW8)
|
||||||
{
|
{
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller);
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
|
case CELL_GEM_YUV_640x480: // YUV output; 640*480+640*480+640*480-byte output buffer required (contiguous)
|
||||||
{
|
{
|
||||||
|
// YUV 4:4:4 planar. 1 value each per pixel
|
||||||
const u32 yuv_pitch = width;
|
const u32 yuv_pitch = width;
|
||||||
|
|
||||||
u8* dst_y = video_data_out;
|
u8* dst_y = video_data_out;
|
||||||
@ -813,61 +1040,21 @@ namespace gem
|
|||||||
{
|
{
|
||||||
case CELL_CAMERA_RAW8:
|
case CELL_CAMERA_RAW8:
|
||||||
{
|
{
|
||||||
const u32 in_pitch = width;
|
fmt::throw_exception("Unreachable: should already be debayered");
|
||||||
|
|
||||||
for (u32 y = 0; y < height - 1; y += 2)
|
|
||||||
{
|
|
||||||
const u8* src0 = &video_data_in[y * in_pitch];
|
|
||||||
const u8* src1 = src0 + in_pitch;
|
|
||||||
|
|
||||||
u8* dst_y0 = dst_y + y * yuv_pitch;
|
|
||||||
u8* dst_y1 = dst_y0 + yuv_pitch;
|
|
||||||
|
|
||||||
u8* dst_u0 = dst_u + y * yuv_pitch;
|
|
||||||
u8* dst_u1 = dst_u0 + yuv_pitch;
|
|
||||||
|
|
||||||
u8* dst_v0 = dst_v + y * yuv_pitch;
|
|
||||||
u8* dst_v1 = dst_v0 + yuv_pitch;
|
|
||||||
|
|
||||||
for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2, dst_u0 += 2, dst_u1 += 2, dst_v0 += 2, dst_v1 += 2)
|
|
||||||
{
|
|
||||||
const u8 b = src0[0];
|
|
||||||
const u8 g0 = src0[1];
|
|
||||||
const u8 g1 = src1[0];
|
|
||||||
const u8 r = src1[1];
|
|
||||||
|
|
||||||
// Convert RGBA to YUV
|
|
||||||
const YUV yuv_top = YUV(r, g0, b);
|
|
||||||
const YUV yuv_bottom = YUV(r, g1, b);
|
|
||||||
|
|
||||||
dst_y0[0] = dst_y0[1] = yuv_top.y;
|
|
||||||
dst_y1[0] = dst_y1[1] = yuv_bottom.y;
|
|
||||||
|
|
||||||
dst_u0[0] = dst_u0[1] = yuv_top.u;
|
|
||||||
dst_u1[0] = dst_u1[1] = yuv_bottom.u;
|
|
||||||
|
|
||||||
dst_v0[0] = dst_v0[1] = yuv_top.v;
|
|
||||||
dst_v1[0] = dst_v1[1] = yuv_bottom.v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CELL_CAMERA_RGBA:
|
case CELL_CAMERA_RGBA:
|
||||||
{
|
{
|
||||||
const u32 in_pitch = width / 4;
|
const u32 in_pitch = width * 4;
|
||||||
|
|
||||||
for (u32 y = 0; y < height; y++)
|
for (u32 y = 0; y < height; y++)
|
||||||
{
|
{
|
||||||
const u8* src = &video_data_in[y * in_pitch];
|
const u8* src = src_data + y * in_pitch;
|
||||||
|
|
||||||
for (u32 x = 0; x < width; x++, src += 4)
|
for (u32 x = 0; x < width; x++, src += 4)
|
||||||
{
|
{
|
||||||
const u8 r = src[0];
|
|
||||||
const u8 g = src[1];
|
|
||||||
const u8 b = src[2];
|
|
||||||
|
|
||||||
// Convert RGBA to YUV
|
// Convert RGBA to YUV
|
||||||
const YUV yuv = YUV(r, g, b);
|
const YUV yuv = YUV(src);
|
||||||
|
|
||||||
*dst_y++ = yuv.y;
|
*dst_y++ = yuv.y;
|
||||||
*dst_u++ = yuv.u;
|
*dst_u++ = yuv.u;
|
||||||
@ -878,8 +1065,8 @@ namespace gem
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller);
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -887,6 +1074,7 @@ namespace gem
|
|||||||
}
|
}
|
||||||
case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
|
case CELL_GEM_YUV422_640x480: // YUV output; 640*480+320*480+320*480-byte output buffer required (contiguous)
|
||||||
{
|
{
|
||||||
|
// YUV 4:2:2 planar. 1 Y value per pixel, 1 U/V value per 2 horizontal pixels
|
||||||
const u32 y_pitch = width;
|
const u32 y_pitch = width;
|
||||||
const u32 uv_pitch = width / 2;
|
const u32 uv_pitch = width / 2;
|
||||||
|
|
||||||
@ -898,43 +1086,7 @@ namespace gem
|
|||||||
{
|
{
|
||||||
case CELL_CAMERA_RAW8:
|
case CELL_CAMERA_RAW8:
|
||||||
{
|
{
|
||||||
const u32 in_pitch = width;
|
fmt::throw_exception("Unreachable: should already be debayered");
|
||||||
|
|
||||||
for (u32 y = 0; y < height - 1; y += 2)
|
|
||||||
{
|
|
||||||
const u8* src0 = &video_data_in[y * in_pitch];
|
|
||||||
const u8* src1 = src0 + in_pitch;
|
|
||||||
|
|
||||||
u8* dst_y0 = dst_y + y * y_pitch;
|
|
||||||
u8* dst_y1 = dst_y0 + y_pitch;
|
|
||||||
|
|
||||||
u8* dst_u0 = dst_u + y * uv_pitch;
|
|
||||||
u8* dst_u1 = dst_u0 + uv_pitch;
|
|
||||||
|
|
||||||
u8* dst_v0 = dst_v + y * uv_pitch;
|
|
||||||
u8* dst_v1 = dst_v0 + uv_pitch;
|
|
||||||
|
|
||||||
for (u32 x = 0; x < width - 1; x += 2, src0 += 2, src1 += 2, dst_y0 += 2, dst_y1 += 2)
|
|
||||||
{
|
|
||||||
const u8 b = src0[0];
|
|
||||||
const u8 g0 = src0[1];
|
|
||||||
const u8 g1 = src1[0];
|
|
||||||
const u8 r = src1[1];
|
|
||||||
|
|
||||||
// Convert RGBA to YUV
|
|
||||||
const YUV yuv_top = YUV(r, g0, b);
|
|
||||||
const YUV yuv_bottom = YUV(r, g1, b);
|
|
||||||
|
|
||||||
dst_y0[0] = dst_y0[1] = yuv_top.y;
|
|
||||||
dst_y1[0] = dst_y1[1] = yuv_bottom.y;
|
|
||||||
|
|
||||||
*dst_u0++ = yuv_top.u;
|
|
||||||
*dst_u1++ = yuv_bottom.u;
|
|
||||||
|
|
||||||
*dst_v0++ = yuv_top.v;
|
|
||||||
*dst_v1++ = yuv_bottom.v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CELL_CAMERA_RGBA:
|
case CELL_CAMERA_RGBA:
|
||||||
@ -943,33 +1095,28 @@ namespace gem
|
|||||||
|
|
||||||
for (u32 y = 0; y < height; y++)
|
for (u32 y = 0; y < height; y++)
|
||||||
{
|
{
|
||||||
const u8* src = &video_data_in[y * in_pitch];
|
const u8* src = src_data + y * in_pitch;
|
||||||
|
|
||||||
for (u32 x = 0; x < width - 1; x += 2, src += 8, dst_y += 2)
|
for (u32 x = 0; x < width - 1; x += 2, src += 8, dst_y += 2)
|
||||||
{
|
{
|
||||||
const u8 r_0 = src[0];
|
|
||||||
const u8 g_0 = src[1];
|
|
||||||
const u8 b_0 = src[2];
|
|
||||||
const u8 r_1 = src[4];
|
|
||||||
const u8 g_1 = src[5];
|
|
||||||
const u8 b_1 = src[6];
|
|
||||||
|
|
||||||
// Convert RGBA to YUV
|
// Convert RGBA to YUV
|
||||||
const YUV yuv_0 = YUV(r_0, g_0, b_0);
|
const YUV yuv_0 = YUV(src);
|
||||||
const u8 y_1 = YUV::Y(r_1, g_1, b_1);
|
const YUV yuv_1 = YUV(src + 4);
|
||||||
|
|
||||||
dst_y[0] = yuv_0.y;
|
dst_y[0] = yuv_0.y;
|
||||||
dst_y[1] = y_1;
|
dst_y[1] = yuv_1.y;
|
||||||
*dst_u++ = yuv_0.u;
|
|
||||||
*dst_v++ = yuv_0.v;
|
// Average U/V from 2 horizontal pixels
|
||||||
|
*dst_u++ = (yuv_0.u + yuv_1.u) / 2;
|
||||||
|
*dst_v++ = (yuv_0.v + yuv_1.v) / 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller);
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -977,109 +1124,54 @@ namespace gem
|
|||||||
}
|
}
|
||||||
case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
|
case CELL_GEM_YUV411_640x480: // YUV411 output; 640*480+320*240+320*240-byte output buffer required (contiguous)
|
||||||
{
|
{
|
||||||
const u32 y_pitch = width;
|
// YUV 4:1:1 planar. 1 Y value per pixel, 1 U/V value per 2x2 pixel block
|
||||||
const u32 uv_pitch = width / 4;
|
|
||||||
|
|
||||||
u8* dst_y = video_data_out;
|
u8* dst_y = video_data_out;
|
||||||
u8* dst_u = dst_y + y_pitch * height;
|
u8* dst_u = dst_y + 640 * 480;
|
||||||
u8* dst_v = dst_u + uv_pitch * height;
|
u8* dst_v = dst_u + 320 * 240;
|
||||||
|
|
||||||
switch (input_format)
|
switch (input_format)
|
||||||
{
|
{
|
||||||
case CELL_CAMERA_RAW8:
|
case CELL_CAMERA_RAW8:
|
||||||
{
|
{
|
||||||
const u32 in_pitch = width;
|
fmt::throw_exception("Unreachable: should already be debayered");
|
||||||
|
|
||||||
for (u32 y = 0; y < height - 1; y += 2)
|
|
||||||
{
|
|
||||||
const u8* src0 = &video_data_in[y * in_pitch];
|
|
||||||
const u8* src1 = src0 + in_pitch;
|
|
||||||
|
|
||||||
u8* dst_y0 = dst_y + y * y_pitch;
|
|
||||||
u8* dst_y1 = dst_y0 + y_pitch;
|
|
||||||
|
|
||||||
u8* dst_u0 = dst_u + y * uv_pitch;
|
|
||||||
u8* dst_u1 = dst_u0 + uv_pitch;
|
|
||||||
|
|
||||||
u8* dst_v0 = dst_v + y * uv_pitch;
|
|
||||||
u8* dst_v1 = dst_v0 + uv_pitch;
|
|
||||||
|
|
||||||
for (u32 x = 0; x < width - 3; x += 4, src0 += 4, src1 += 4, dst_y0 += 4, dst_y1 += 4)
|
|
||||||
{
|
|
||||||
const u8 b_left = src0[0];
|
|
||||||
const u8 g0_left = src0[1];
|
|
||||||
const u8 b_right = src0[2];
|
|
||||||
const u8 g0_right = src0[3];
|
|
||||||
|
|
||||||
const u8 g1_left = src1[0];
|
|
||||||
const u8 r_left = src1[1];
|
|
||||||
const u8 g1_right = src1[2];
|
|
||||||
const u8 r_right = src1[3];
|
|
||||||
|
|
||||||
// Convert RGBA to YUV
|
|
||||||
const YUV yuv_top_left = YUV(r_left, g0_left, b_left); // Re-used for top-right
|
|
||||||
const u8 y_top_right = YUV::Y(r_right, g0_right, b_right);
|
|
||||||
const YUV yuv_bottom_left = YUV(r_left, g1_left, b_left); // Re-used for bottom-right
|
|
||||||
const u8 y_bottom_right = YUV::Y(r_right, g1_right, b_right);
|
|
||||||
|
|
||||||
dst_y0[0] = dst_y0[1] = yuv_top_left.y;
|
|
||||||
dst_y0[2] = dst_y0[3] = y_top_right;
|
|
||||||
|
|
||||||
dst_y1[0] = dst_y1[1] = yuv_bottom_left.y;
|
|
||||||
dst_y1[2] = dst_y1[3] = y_bottom_right;
|
|
||||||
|
|
||||||
*dst_u0++ = yuv_top_left.u;
|
|
||||||
*dst_u1++ = yuv_bottom_left.u;
|
|
||||||
|
|
||||||
*dst_v0++ = yuv_top_left.v;
|
|
||||||
*dst_v1++ = yuv_bottom_left.v;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CELL_CAMERA_RGBA:
|
case CELL_CAMERA_RGBA:
|
||||||
{
|
{
|
||||||
const u32 in_pitch = width * 4;
|
const u32 in_pitch = width * 4;
|
||||||
|
|
||||||
for (u32 y = 0; y < height; y++)
|
// 2 rows at a time to get a 2x2 pixel block
|
||||||
|
for (u32 y = 0; y < height - 1; y += 2)
|
||||||
{
|
{
|
||||||
const u8* src = &video_data_in[y * in_pitch];
|
const u8* src = src_data + y * in_pitch;
|
||||||
|
const u8* src2 = src + in_pitch;
|
||||||
|
u8* dst_y1 = dst_y + y * 640;
|
||||||
|
u8* dst_y2 = dst_y1 + 640;
|
||||||
|
|
||||||
for (u32 x = 0; x < width - 3; x += 4, src += 16, dst_y += 4)
|
for (u32 x = 0; x < width - 1; x += 2, src += 8, src2 += 8, dst_y1 += 2, dst_y2 += 2)
|
||||||
{
|
{
|
||||||
const u8 r_0 = src[0];
|
|
||||||
const u8 g_0 = src[1];
|
|
||||||
const u8 b_0 = src[2];
|
|
||||||
const u8 r_1 = src[4];
|
|
||||||
const u8 g_1 = src[5];
|
|
||||||
const u8 b_1 = src[6];
|
|
||||||
const u8 r_2 = src[8];
|
|
||||||
const u8 g_2 = src[9];
|
|
||||||
const u8 b_2 = src[10];
|
|
||||||
const u8 r_3 = src[12];
|
|
||||||
const u8 g_3 = src[13];
|
|
||||||
const u8 b_3 = src[14];
|
|
||||||
|
|
||||||
// Convert RGBA to YUV
|
// Convert RGBA to YUV
|
||||||
const YUV yuv_0 = YUV(r_0, g_0, b_0);
|
const YUV yuv_0 = YUV(src);
|
||||||
const u8 y_1 = YUV::Y(r_1, g_1, b_1);
|
const YUV yuv_1 = YUV(src + 4);
|
||||||
const u8 y_2 = YUV::Y(r_2, g_2, b_2);
|
const YUV yuv_2 = YUV(src2);
|
||||||
const u8 y_3 = YUV::Y(r_3, g_3, b_3);
|
const YUV yuv_3 = YUV(src2 + 4);
|
||||||
|
|
||||||
dst_y[0] = yuv_0.y;
|
dst_y1[0] = yuv_0.y;
|
||||||
dst_y[1] = y_1;
|
dst_y1[1] = yuv_1.y;
|
||||||
dst_y[2] = y_2;
|
dst_y2[0] = yuv_2.y;
|
||||||
dst_y[3] = y_3;
|
dst_y2[1] = yuv_3.y;
|
||||||
*dst_u++ = yuv_0.u;
|
|
||||||
*dst_v++ = yuv_0.v;
|
// Average U/V from 2x2 pixel block
|
||||||
|
*dst_u++ = (yuv_0.u + yuv_1.u + yuv_2.u + yuv_3.u) / 4;
|
||||||
|
*dst_v++ = (yuv_0.v + yuv_1.v + yuv_2.v + yuv_3.v) / 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller);
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1096,7 +1188,7 @@ namespace gem
|
|||||||
|
|
||||||
for (u32 y = 0; y < height - 1; y += 2)
|
for (u32 y = 0; y < height - 1; y += 2)
|
||||||
{
|
{
|
||||||
const u8* src0 = &video_data_in[y * in_pitch];
|
const u8* src0 = src_data + y * in_pitch;
|
||||||
const u8* src1 = src0 + in_pitch;
|
const u8* src1 = src0 + in_pitch;
|
||||||
|
|
||||||
u8* dst0 = video_data_out + (y / 2) * out_pitch;
|
u8* dst0 = video_data_out + (y / 2) * out_pitch;
|
||||||
@ -1109,8 +1201,8 @@ namespace gem
|
|||||||
const u8 g1 = src1[0];
|
const u8 g1 = src1[0];
|
||||||
const u8 r = src1[1];
|
const u8 r = src1[1];
|
||||||
|
|
||||||
const u8 top[4] = { r, g0, b, 255 };
|
const u8 top[4] = { r, g0, b, alpha };
|
||||||
const u8 bottom[4] = { r, g1, b, 255 };
|
const u8 bottom[4] = { r, g1, b, alpha };
|
||||||
|
|
||||||
// Top-Left
|
// Top-Left
|
||||||
std::memcpy(dst0, top, 4);
|
std::memcpy(dst0, top, 4);
|
||||||
@ -1128,7 +1220,7 @@ namespace gem
|
|||||||
|
|
||||||
for (u32 y = 0; y < height / 2; y++)
|
for (u32 y = 0; y < height / 2; y++)
|
||||||
{
|
{
|
||||||
const u8* src = &video_data_in[y * 2 * in_pitch];
|
const u8* src = src_data + y * 2 * in_pitch;
|
||||||
u8* dst = video_data_out + y * out_pitch;
|
u8* dst = video_data_out + y * out_pitch;
|
||||||
|
|
||||||
for (u32 x = 0; x < width / 2; x++, src += 4 * 2, dst += 4)
|
for (u32 x = 0; x < width / 2; x++, src += 4 * 2, dst += 4)
|
||||||
@ -1140,19 +1232,97 @@ namespace gem
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller);
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
|
case CELL_GEM_BAYER_RESTORED_RGGB: // Restored Bayer output, 2x2 pixels rearranged into 320x240 RG1G2B
|
||||||
|
{
|
||||||
|
if (input_format == CELL_CAMERA_RAW8)
|
||||||
|
{
|
||||||
|
const u32 dst_w = std::min(320u, width / 2);
|
||||||
|
const u32 dst_h = std::min(240u, height / 2);
|
||||||
|
const u32 in_pitch = width;
|
||||||
|
constexpr u32 out_pitch = 320 * 4;
|
||||||
|
|
||||||
|
for (u32 y = 0; y < dst_h; y++)
|
||||||
|
{
|
||||||
|
const u8* src0 = src_data + y * 2 * in_pitch;
|
||||||
|
const u8* src1 = src0 + in_pitch;
|
||||||
|
|
||||||
|
u8* dst = video_data_out + y * out_pitch;
|
||||||
|
|
||||||
|
for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2, dst += 4)
|
||||||
|
{
|
||||||
|
const u8 b = src0[0];
|
||||||
|
const u8 g0 = src0[1];
|
||||||
|
const u8 g1 = src1[0];
|
||||||
|
const u8 r = src1[1];
|
||||||
|
|
||||||
|
dst[0] = r;
|
||||||
|
dst[1] = g0;
|
||||||
|
dst[2] = g1;
|
||||||
|
dst[3] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
|
case CELL_GEM_BAYER_RESTORED_RASTERIZED: // Restored Bayer output, R,G1,G2,B rearranged into 4 contiguous 320x240 1-channel rasters
|
||||||
{
|
{
|
||||||
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, output_format, caller);
|
if (input_format == CELL_CAMERA_RAW8)
|
||||||
std::memcpy(video_data_out, video_data_in.data(), std::min<usz>(required_in_size, required_out_size));
|
{
|
||||||
return false;
|
const u32 dst_w = std::min(320u, width / 2);
|
||||||
|
const u32 dst_h = std::min(240u, height / 2);
|
||||||
|
const u32 in_pitch = width;
|
||||||
|
constexpr u32 out_plane = 320 * 240;
|
||||||
|
constexpr u32 out_pitch = 320;
|
||||||
|
|
||||||
|
u8* dst_plane_r = video_data_out;
|
||||||
|
u8* dst_plane_g1 = video_data_out + out_plane;
|
||||||
|
u8* dst_plane_g2 = video_data_out + out_plane * 2;
|
||||||
|
u8* dst_plane_b = video_data_out + out_plane * 3;
|
||||||
|
|
||||||
|
for (u32 y = 0; y < dst_h; y++)
|
||||||
|
{
|
||||||
|
const u8* src0 = src_data + y * 2 * in_pitch;
|
||||||
|
const u8* src1 = src0 + in_pitch;
|
||||||
|
|
||||||
|
u8* dst_r = dst_plane_r + y * out_pitch;
|
||||||
|
u8* dst_g1 = dst_plane_g1 + y * out_pitch;
|
||||||
|
u8* dst_g2 = dst_plane_g2 + y * out_pitch;
|
||||||
|
u8* dst_b = dst_plane_b + y * out_pitch;
|
||||||
|
|
||||||
|
for (u32 x = 0; x < dst_w; x++, src0 += 2, src1 += 2)
|
||||||
|
{
|
||||||
|
const u8 b = src0[0];
|
||||||
|
const u8 g0 = src0[1];
|
||||||
|
const u8 g1 = src1[0];
|
||||||
|
const u8 r = src1[1];
|
||||||
|
|
||||||
|
dst_r[x] = r;
|
||||||
|
dst_g1[x] = g0;
|
||||||
|
dst_g2[x] = g1;
|
||||||
|
dst_b[x] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
cellGem.error("Unimplemented: Converting %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
|
std::memcpy(video_data_out, src_data, std::min<usz>(required_in_size, required_out_size));
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output
|
case CELL_GEM_NO_VIDEO_OUTPUT: // Disable video output
|
||||||
{
|
{
|
||||||
@ -1161,7 +1331,7 @@ namespace gem
|
|||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
{
|
{
|
||||||
cellGem.error("Trying to convert %s to %s (called from %s)", input_format, output_format, caller);
|
cellGem.error("Trying to convert %s to %s (called from %s)", input_format, vc.output_format, caller);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1340,13 +1510,15 @@ void gem_config_data::operator()()
|
|||||||
|
|
||||||
const auto& shared_data = g_fxo->get<gem_camera_shared>();
|
const auto& shared_data = g_fxo->get<gem_camera_shared>();
|
||||||
|
|
||||||
if (gem::convert_image_format(shared_data.format, vc.output_format, video_data_in, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size, "cellGem"))
|
if (gem::convert_image_format(shared_data.format, vc, video_data_in, shared_data.width, shared_data.height,
|
||||||
|
vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size,
|
||||||
|
vc.buffer_memory ? vc.buffer_memory.get_ptr() : nullptr, "cellGem"))
|
||||||
{
|
{
|
||||||
cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get());
|
cellGem.trace("Converted video frame of format %s to %s", shared_data.format.load(), vc.output_format.get());
|
||||||
|
|
||||||
if (g_cfg.io.paint_move_spheres)
|
if (g_cfg.io.paint_move_spheres)
|
||||||
{
|
{
|
||||||
paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc_attribute.video_data_out ? vc_attribute.video_data_out.get_ptr() : nullptr, video_data_out_size);
|
paint_spheres(vc.output_format, shared_data.width, shared_data.height, vc.video_data_out ? vc.video_data_out.get_ptr() : nullptr, video_data_out_size);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1617,7 +1789,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
|
|||||||
|
|
||||||
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
||||||
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
||||||
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius;
|
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
|
||||||
|
|
||||||
// Image coordinates in pixels
|
// Image coordinates in pixels
|
||||||
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
|
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
|
||||||
@ -1657,7 +1829,7 @@ static inline void pos_to_gem_image_state(u32 gem_num, gem_config::gem_controlle
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, const ps_move_data& move_data)
|
static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& controller, vm::ptr<CellGemState>& gem_state, s32 x_pos, s32 y_pos, s32 x_max, s32 y_max, ps_move_data& move_data)
|
||||||
{
|
{
|
||||||
const auto& shared_data = g_fxo->get<gem_camera_shared>();
|
const auto& shared_data = g_fxo->get<gem_camera_shared>();
|
||||||
|
|
||||||
@ -1670,7 +1842,7 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
|||||||
|
|
||||||
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
const f32 scaling_width = x_max / static_cast<f32>(shared_data.width);
|
||||||
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
const f32 scaling_height = y_max / static_cast<f32>(shared_data.height);
|
||||||
const f32 mmPerPixel = CELL_GEM_SPHERE_RADIUS_MM / controller.radius;
|
const f32 mmPerPixel = controller.radius <= 0.0f ? 0.0f : (CELL_GEM_SPHERE_RADIUS_MM / controller.radius);
|
||||||
|
|
||||||
// Image coordinates in pixels
|
// Image coordinates in pixels
|
||||||
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
|
const f32 image_x = static_cast<f32>(x_pos) / scaling_width;
|
||||||
@ -1703,10 +1875,10 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
|||||||
// Calculate orientation
|
// Calculate orientation
|
||||||
if (g_cfg.io.move == move_handler::real || (g_cfg.io.move == move_handler::fake && move_data.orientation_enabled))
|
if (g_cfg.io.move == move_handler::real || (g_cfg.io.move == move_handler::fake && move_data.orientation_enabled))
|
||||||
{
|
{
|
||||||
gem_state->quat[0] = move_data.quaternion[0]; // x
|
gem_state->quat[0] = move_data.quaternion.x();
|
||||||
gem_state->quat[1] = move_data.quaternion[1]; // y
|
gem_state->quat[1] = move_data.quaternion.y();
|
||||||
gem_state->quat[2] = move_data.quaternion[2]; // z
|
gem_state->quat[2] = move_data.quaternion.z();
|
||||||
gem_state->quat[3] = move_data.quaternion[3]; // w
|
gem_state->quat[3] = move_data.quaternion.w();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -1733,6 +1905,17 @@ static inline void pos_to_gem_state(u32 gem_num, gem_config::gem_controller& con
|
|||||||
gem_state->quat[3] = q_w;
|
gem_state->quat[3] = q_w;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if constexpr (!ps_move_data::use_imu_for_velocity)
|
||||||
|
{
|
||||||
|
move_data.update_velocity(shared_data.frame_timestamp_us, gem_state->pos);
|
||||||
|
|
||||||
|
for (u32 i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
gem_state->vel[i] = move_data.vel_world[i];
|
||||||
|
gem_state->accel[i] = move_data.accel_world[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update visibility for fake handlers
|
// Update visibility for fake handlers
|
||||||
if (g_cfg.io.move != move_handler::real)
|
if (g_cfg.io.move != move_handler::real)
|
||||||
{
|
{
|
||||||
@ -1906,9 +2089,17 @@ static void ps_move_pos_to_gem_state(u32 gem_num, gem_config::gem_controller& co
|
|||||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||||
{
|
{
|
||||||
gem_state->temperature = pad->move_data.temperature;
|
gem_state->temperature = pad->move_data.temperature;
|
||||||
gem_state->accel[0] = pad->move_data.accelerometer_x * 1000; // linear velocity in mm/s²
|
|
||||||
gem_state->accel[1] = pad->move_data.accelerometer_y * 1000; // linear velocity in mm/s²
|
for (u32 i = 0; i < 3; i++)
|
||||||
gem_state->accel[2] = pad->move_data.accelerometer_z * 1000; // linear velocity in mm/s²
|
{
|
||||||
|
if constexpr (ps_move_data::use_imu_for_velocity)
|
||||||
|
{
|
||||||
|
gem_state->vel[i] = pad->move_data.vel_world[i];
|
||||||
|
gem_state->accel[i] = pad->move_data.accel_world[i];
|
||||||
|
}
|
||||||
|
gem_state->angvel[i] = pad->move_data.angvel_world[i];
|
||||||
|
gem_state->angaccel[i] = pad->move_data.angaccel_world[i];
|
||||||
|
}
|
||||||
|
|
||||||
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data);
|
pos_to_gem_state(gem_num, controller, gem_state, info.x_pos, info.y_pos, info.x_max, info.y_max, pad->move_data);
|
||||||
}
|
}
|
||||||
@ -2147,7 +2338,8 @@ static void mouse_pos_to_gem_state(u32 mouse_no, gem_config::gem_controller& con
|
|||||||
|
|
||||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||||
{
|
{
|
||||||
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, {});
|
ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, mouse_no);
|
||||||
|
pos_to_gem_state(mouse_no, controller, gem_state, mouse.x_pos, mouse.y_pos, mouse.x_max, mouse.y_max, move_data);
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||||
{
|
{
|
||||||
@ -2215,7 +2407,8 @@ static void gun_pos_to_gem_state(u32 gem_no, gem_config::gem_controller& control
|
|||||||
|
|
||||||
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
if constexpr (std::is_same_v<T, vm::ptr<CellGemState>>)
|
||||||
{
|
{
|
||||||
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, {});
|
ps_move_data& move_data = ::at32(g_fxo->get<gem_config>().fake_move_data, gem_no);
|
||||||
|
pos_to_gem_state(gem_no, controller, gem_state, x_pos, y_pos, x_max, y_max, move_data);
|
||||||
}
|
}
|
||||||
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
else if constexpr (std::is_same_v<T, vm::ptr<CellGemImageState>>)
|
||||||
{
|
{
|
||||||
@ -2769,7 +2962,7 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
|
|||||||
|
|
||||||
inertial_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us);
|
inertial_state->timestamp = (get_guest_system_time() - gem.start_timestamp_us);
|
||||||
inertial_state->counter = gem.inertial_counter++;
|
inertial_state->counter = gem.inertial_counter++;
|
||||||
inertial_state->accelerometer[0] = 10; // Current gravity in m/s²
|
inertial_state->accelerometer[2] = 1.0f; // Current gravity in G units (9.81 == 1 unit)
|
||||||
|
|
||||||
switch (g_cfg.io.move)
|
switch (g_cfg.io.move)
|
||||||
{
|
{
|
||||||
@ -2786,12 +2979,12 @@ error_code cellGemGetInertialState(u32 gem_num, u32 state_flag, u64 timestamp, v
|
|||||||
if (pad && pad->is_connected() && !pad->is_copilot())
|
if (pad && pad->is_connected() && !pad->is_copilot())
|
||||||
{
|
{
|
||||||
inertial_state->temperature = pad->move_data.temperature;
|
inertial_state->temperature = pad->move_data.temperature;
|
||||||
inertial_state->accelerometer[0] = pad->move_data.accelerometer_x;
|
|
||||||
inertial_state->accelerometer[1] = pad->move_data.accelerometer_y;
|
for (u32 i = 0; i < 3; i++)
|
||||||
inertial_state->accelerometer[2] = pad->move_data.accelerometer_z;
|
{
|
||||||
inertial_state->gyro[0] = pad->move_data.gyro_x;
|
inertial_state->accelerometer[i] = pad->move_data.accelerometer[i];
|
||||||
inertial_state->gyro[1] = pad->move_data.gyro_y;
|
inertial_state->gyro[i] = pad->move_data.gyro[i];
|
||||||
inertial_state->gyro[2] = pad->move_data.gyro_z;
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -246,15 +246,15 @@ struct CellGemInfo
|
|||||||
// z increases towards user (away from the camera)
|
// z increases towards user (away from the camera)
|
||||||
struct CellGemState
|
struct CellGemState
|
||||||
{
|
{
|
||||||
be_t<f32> pos[4]; // center of sphere (mm)
|
be_t<f32> pos[4]; // center of sphere in world coordinates (mm)
|
||||||
be_t<f32> vel[4]; // velocity of sphere (mm/s)
|
be_t<f32> vel[4]; // velocity of sphere in world coordinates (mm/s)
|
||||||
be_t<f32> accel[4]; // acceleration of sphere (mm/s²)
|
be_t<f32> accel[4]; // acceleration of sphere in world coordinates (mm/s²)
|
||||||
be_t<f32> quat[4]; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
be_t<f32> quat[4]; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
||||||
be_t<f32> angvel[4]; // angular velocity of controller (radians/s)
|
be_t<f32> angvel[4]; // angular velocity of controller in world coordinates (radians/s)
|
||||||
be_t<f32> angaccel[4]; // angular acceleration of controller (radians/s²)
|
be_t<f32> angaccel[4]; // angular acceleration of controller in world coordinates (radians/s²)
|
||||||
be_t<f32> handle_pos[4]; // center of controller handle (mm)
|
be_t<f32> handle_pos[4]; // center of controller handle in world coordinates (mm)
|
||||||
be_t<f32> handle_vel[4]; // velocity of controller handle (mm/s)
|
be_t<f32> handle_vel[4]; // velocity of controller handle in world coordinates (mm/s)
|
||||||
be_t<f32> handle_accel[4]; // acceleration of controller handle (mm/s²)
|
be_t<f32> handle_accel[4]; // acceleration of controller handle in world coordinates (mm/s²)
|
||||||
CellGemPadData pad;
|
CellGemPadData pad;
|
||||||
CellGemExtPortData ext;
|
CellGemExtPortData ext;
|
||||||
be_t<u64> timestamp; // system_time_t (microseconds)
|
be_t<u64> timestamp; // system_time_t (microseconds)
|
||||||
|
|||||||
@ -7320,7 +7320,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||||||
}
|
}
|
||||||
|
|
||||||
// spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all);
|
// spu_log.success("PUTLLC0 Pattern Detected! (put_pc=0x%x, %s) (putllc0=%d, putllc16+0=%d, all=%d)", pattern.put_pc, func_hash, ++stats.nowrite, ++stats.single, +stats.all);
|
||||||
// add_pattern(false, inst_attr::putllc0, pattern.put_pc - lsa, value.data);
|
// add_pattern(inst_attr::putllc0, pattern.put_pc - lsa, value.data);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -7411,7 +7411,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||||||
|
|
||||||
if (allow_pattern)
|
if (allow_pattern)
|
||||||
{
|
{
|
||||||
add_pattern(false, inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data);
|
add_pattern(inst_attr::putllc16, pattern.put_pc - result.entry_point, value.data);
|
||||||
}
|
}
|
||||||
|
|
||||||
spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)"
|
spu_log.success("PUTLLC16 Pattern Detected! (mem_count=%d, put_pc=0x%x, pc_rel=%d, offset=0x%x, const=%u, two_regs=%d, reg=%u, runtime=%d, 0x%x-%s, pattern-hash=%s) (putllc0=%d, putllc16+0=%d, all=%d)"
|
||||||
@ -7433,7 +7433,7 @@ spu_program spu_recompiler_base::analyse(const be_t<u32>* ls, u32 entry_point, s
|
|||||||
|
|
||||||
if (inst_attr attr = m_inst_attrs[(read_pc - entry_point) / 4]; attr == inst_attr::none)
|
if (inst_attr attr = m_inst_attrs[(read_pc - entry_point) / 4]; attr == inst_attr::none)
|
||||||
{
|
{
|
||||||
add_pattern(false, inst_attr::rchcnt_loop, read_pc - result.entry_point, 0);
|
add_pattern(inst_attr::rchcnt_loop, read_pc - result.entry_point, 0);
|
||||||
|
|
||||||
spu_log.error("Channel Loop Pattern Detected! Report to developers! (read_pc=0x%x, branch_pc=0x%x, branch_target=0x%x, 0x%x-%s)", read_pc, pattern.branch_pc, pattern.branch_target, entry_point, func_hash);
|
spu_log.error("Channel Loop Pattern Detected! Report to developers! (read_pc=0x%x, branch_pc=0x%x, branch_target=0x%x, 0x%x-%s)", read_pc, pattern.branch_pc, pattern.branch_target, entry_point, func_hash);
|
||||||
}
|
}
|
||||||
@ -8519,7 +8519,7 @@ std::array<reg_state_t, s_reg_max>& block_reg_info::evaluate_start_state(const s
|
|||||||
return walkby_state;
|
return walkby_state;
|
||||||
}
|
}
|
||||||
|
|
||||||
void spu_recompiler_base::add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info)
|
void spu_recompiler_base::add_pattern(inst_attr attr, u32 start, u64 info)
|
||||||
{
|
{
|
||||||
m_patterns[start] = pattern_info{info};
|
m_patterns[start] = pattern_info{info};
|
||||||
m_inst_attrs[start / 4] = attr;
|
m_inst_attrs[start / 4] = attr;
|
||||||
|
|||||||
@ -402,7 +402,7 @@ protected:
|
|||||||
|
|
||||||
std::unordered_map<u32, pattern_info> m_patterns;
|
std::unordered_map<u32, pattern_info> m_patterns;
|
||||||
|
|
||||||
void add_pattern(bool fill_all, inst_attr attr, u32 start, u64 info);
|
void add_pattern(inst_attr attr, u32 start, u64 info);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// For private use
|
// For private use
|
||||||
|
|||||||
@ -321,7 +321,7 @@ static void LIBUSB_CALL log_cb(libusb_context* /*ctx*/, enum libusb_log_level le
|
|||||||
if (!str)
|
if (!str)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
const std::string msg = fmt::trim(str, " \t\n");
|
const std::string_view msg = fmt::trim_sv(str, " \t\n");
|
||||||
|
|
||||||
switch (level)
|
switch (level)
|
||||||
{
|
{
|
||||||
@ -555,7 +555,8 @@ usb_handler_thread::usb_handler_thread()
|
|||||||
usb_devices.push_back(std::make_shared<usb_device_vfs>(usb_info, get_new_location()));
|
usb_devices.push_back(std::make_shared<usb_device_vfs>(usb_info, get_new_location()));
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string> devices_list = fmt::split(g_cfg.io.midi_devices.to_string(), { "@@@" });
|
const std::string midi_devices = g_cfg.io.midi_devices.to_string();
|
||||||
|
const std::vector<std::string_view> devices_list = fmt::split_sv(midi_devices, { "@@@" });
|
||||||
for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++)
|
for (usz index = 0; index < std::min(max_midi_devices, devices_list.size()); index++)
|
||||||
{
|
{
|
||||||
const midi_device device = midi_device::from_string(::at32(devices_list, index));
|
const midi_device device = midi_device::from_string(::at32(devices_list, index));
|
||||||
|
|||||||
@ -874,12 +874,12 @@ void PadHandlerBase::set_raw_orientation(ps_move_data& move_data, f32 accel_x, f
|
|||||||
// The default position is flat on the ground, pointing forward.
|
// The default position is flat on the ground, pointing forward.
|
||||||
// The accelerometers constantly measure G forces.
|
// The accelerometers constantly measure G forces.
|
||||||
// The gyros measure changes in orientation and will reset when the device isn't moved anymore.
|
// The gyros measure changes in orientation and will reset when the device isn't moved anymore.
|
||||||
move_data.accelerometer_x = -accel_x; // move_data: Increases if the device is rolled to the left
|
move_data.accelerometer.x() = -accel_x; // move_data: Increases if the device is rolled to the left
|
||||||
move_data.accelerometer_y = accel_z; // move_data: Increases if the device is pitched upwards
|
move_data.accelerometer.y() = accel_z; // move_data: Increases if the device is pitched upwards
|
||||||
move_data.accelerometer_z = accel_y; // move_data: Increases if the device is moved upwards
|
move_data.accelerometer.z() = accel_y; // move_data: Increases if the device is moved upwards
|
||||||
move_data.gyro_x = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards
|
move_data.gyro.x() = degree_to_rad(-gyro_x); // move_data: Increases if the device is pitched upwards
|
||||||
move_data.gyro_y = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right
|
move_data.gyro.y() = degree_to_rad(gyro_z); // move_data: Increases if the device is rolled to the right
|
||||||
move_data.gyro_z = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left
|
move_data.gyro.z() = degree_to_rad(-gyro_y); // move_data: Increases if the device is yawed to the left
|
||||||
}
|
}
|
||||||
|
|
||||||
void PadHandlerBase::set_raw_orientation(Pad& pad)
|
void PadHandlerBase::set_raw_orientation(Pad& pad)
|
||||||
@ -950,7 +950,7 @@ void PadDevice::update_orientation(ps_move_data& move_data)
|
|||||||
|
|
||||||
// Get elapsed time since last update
|
// Get elapsed time since last update
|
||||||
const u64 now_us = get_system_time();
|
const u64 now_us = get_system_time();
|
||||||
const float elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
|
const f32 elapsed_sec = (last_ahrs_update_time_us == 0) ? 0.0f : ((now_us - last_ahrs_update_time_us) / 1'000'000.0f);
|
||||||
last_ahrs_update_time_us = now_us;
|
last_ahrs_update_time_us = now_us;
|
||||||
|
|
||||||
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
|
// The ps move handler's axis may differ from the Fusion axis, so we have to map them correctly.
|
||||||
@ -959,17 +959,17 @@ void PadDevice::update_orientation(ps_move_data& move_data)
|
|||||||
|
|
||||||
const FusionVector accelerometer{
|
const FusionVector accelerometer{
|
||||||
.axis {
|
.axis {
|
||||||
.x = -move_data.accelerometer_x,
|
.x = -move_data.accelerometer.x(),
|
||||||
.y = +move_data.accelerometer_y,
|
.y = +move_data.accelerometer.y(),
|
||||||
.z = +move_data.accelerometer_z
|
.z = +move_data.accelerometer.z()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const FusionVector gyroscope{
|
const FusionVector gyroscope{
|
||||||
.axis {
|
.axis {
|
||||||
.x = +PadHandlerBase::rad_to_degree(move_data.gyro_x),
|
.x = +PadHandlerBase::rad_to_degree(move_data.gyro.x()),
|
||||||
.y = +PadHandlerBase::rad_to_degree(move_data.gyro_z),
|
.y = +PadHandlerBase::rad_to_degree(move_data.gyro.z()),
|
||||||
.z = -PadHandlerBase::rad_to_degree(move_data.gyro_y)
|
.z = -PadHandlerBase::rad_to_degree(move_data.gyro.y())
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -979,9 +979,9 @@ void PadDevice::update_orientation(ps_move_data& move_data)
|
|||||||
{
|
{
|
||||||
magnetometer = FusionVector{
|
magnetometer = FusionVector{
|
||||||
.axis {
|
.axis {
|
||||||
.x = move_data.magnetometer_x,
|
.x = move_data.magnetometer.x(),
|
||||||
.y = move_data.magnetometer_y,
|
.y = move_data.magnetometer.y(),
|
||||||
.z = move_data.magnetometer_z
|
.z = move_data.magnetometer.z()
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -995,4 +995,5 @@ void PadDevice::update_orientation(ps_move_data& move_data)
|
|||||||
move_data.quaternion[1] = quaternion.array[2];
|
move_data.quaternion[1] = quaternion.array[2];
|
||||||
move_data.quaternion[2] = quaternion.array[3];
|
move_data.quaternion[2] = quaternion.array[3];
|
||||||
move_data.quaternion[3] = quaternion.array[0];
|
move_data.quaternion[3] = quaternion.array[0];
|
||||||
|
move_data.update_orientation(elapsed_sec);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -205,7 +205,7 @@ protected:
|
|||||||
std::set<T> key_codes;
|
std::set<T> key_codes;
|
||||||
|
|
||||||
const std::string& def = cfg_string.def;
|
const std::string& def = cfg_string.def;
|
||||||
const std::vector<std::string> names = cfg_pad::get_buttons(cfg_string);
|
const std::vector<std::string> names = cfg_pad::get_buttons(cfg_string.to_string());
|
||||||
T def_code = umax;
|
T def_code = umax;
|
||||||
|
|
||||||
for (const std::string& nam : names)
|
for (const std::string& nam : names)
|
||||||
|
|||||||
@ -177,7 +177,7 @@ Note str_to_note(const std::string_view name)
|
|||||||
|
|
||||||
std::optional<std::pair<Id, Note>> parse_midi_override(const std::string_view config)
|
std::optional<std::pair<Id, Note>> parse_midi_override(const std::string_view config)
|
||||||
{
|
{
|
||||||
auto split = fmt::split(config, {"="});
|
const auto split = fmt::split_sv(config, {"="});
|
||||||
if (split.size() != 2)
|
if (split.size() != 2)
|
||||||
{
|
{
|
||||||
return {};
|
return {};
|
||||||
@ -236,8 +236,9 @@ std::unordered_map<Id, Note> create_id_to_note_mapping()
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Apply configured overrides.
|
// Apply configured overrides.
|
||||||
const std::vector<std::string> segments = fmt::split(g_cfg_rb3drums.midi_overrides.to_string(), {","});
|
const std::string midi_overrides = g_cfg_rb3drums.midi_overrides.to_string();
|
||||||
for (const std::string& segment : segments)
|
const std::vector<std::string_view> segments = fmt::split_sv(midi_overrides, {","});
|
||||||
|
for (const std::string_view& segment : segments)
|
||||||
{
|
{
|
||||||
if (const auto midi_override = parse_midi_override(segment))
|
if (const auto midi_override = parse_midi_override(segment))
|
||||||
{
|
{
|
||||||
@ -259,7 +260,7 @@ std::vector<u8> parse_combo(const std::string_view name, const std::string_view
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
std::vector<u8> notes;
|
std::vector<u8> notes;
|
||||||
const auto& note_names = fmt::split(csv, {","});
|
const auto note_names = fmt::split_sv(csv, {","});
|
||||||
for (const auto& note_name : note_names)
|
for (const auto& note_name : note_names)
|
||||||
{
|
{
|
||||||
const auto note = str_to_note(note_name);
|
const auto note = str_to_note(note_name);
|
||||||
|
|||||||
@ -36,7 +36,7 @@ void cfg_camera::save() const
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg_camera::camera_setting cfg_camera::get_camera_setting(const std::string& camera, bool& success)
|
cfg_camera::camera_setting cfg_camera::get_camera_setting(std::string_view camera, bool& success)
|
||||||
{
|
{
|
||||||
camera_setting setting;
|
camera_setting setting;
|
||||||
const std::string value = cameras.get_value(camera);
|
const std::string value = cameras.get_value(camera);
|
||||||
@ -64,7 +64,7 @@ std::string cfg_camera::camera_setting::to_string() const
|
|||||||
return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format);
|
return fmt::format("%d,%d,%f,%f,%d", width, height, min_fps, max_fps, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
void cfg_camera::camera_setting::from_string(const std::string& text)
|
void cfg_camera::camera_setting::from_string(std::string_view text)
|
||||||
{
|
{
|
||||||
if (text.empty())
|
if (text.empty())
|
||||||
{
|
{
|
||||||
|
|||||||
@ -19,9 +19,9 @@ struct cfg_camera final : cfg::node
|
|||||||
static constexpr u32 member_count = 5;
|
static constexpr u32 member_count = 5;
|
||||||
|
|
||||||
std::string to_string() const;
|
std::string to_string() const;
|
||||||
void from_string(const std::string& text);
|
void from_string(std::string_view text);
|
||||||
};
|
};
|
||||||
camera_setting get_camera_setting(const std::string& camera, bool& success);
|
camera_setting get_camera_setting(std::string_view camera, bool& success);
|
||||||
void set_camera_setting(const std::string& camera, const camera_setting& setting);
|
void set_camera_setting(const std::string& camera, const camera_setting& setting);
|
||||||
|
|
||||||
const std::string path;
|
const std::string path;
|
||||||
|
|||||||
@ -27,11 +27,11 @@ void fmt_class_string<midi_device>::format(std::string& out, u64 arg)
|
|||||||
fmt::append(out, "%sßßß%s", obj.type, obj.name);
|
fmt::append(out, "%sßßß%s", obj.type, obj.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
midi_device midi_device::from_string(const std::string& str)
|
midi_device midi_device::from_string(std::string_view str)
|
||||||
{
|
{
|
||||||
midi_device res{};
|
midi_device res{};
|
||||||
|
|
||||||
if (const std::vector<std::string> parts = fmt::split(str, {"ßßß"}); !parts.empty())
|
if (const std::vector<std::string_view> parts = fmt::split_sv(str, {"ßßß"}); !parts.empty())
|
||||||
{
|
{
|
||||||
u64 result;
|
u64 result;
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
|
||||||
static constexpr usz max_midi_devices = 3;
|
static constexpr usz max_midi_devices = 3;
|
||||||
|
|
||||||
@ -17,5 +18,5 @@ struct midi_device
|
|||||||
midi_device_type type{};
|
midi_device_type type{};
|
||||||
std::string name;
|
std::string name;
|
||||||
|
|
||||||
static midi_device from_string(const std::string& str);
|
static midi_device from_string(std::string_view str);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
extern std::string g_input_config_override;
|
extern std::string g_input_config_override;
|
||||||
|
|
||||||
std::vector<std::string> cfg_pad::get_buttons(const std::string& str)
|
std::vector<std::string> cfg_pad::get_buttons(std::string_view str)
|
||||||
{
|
{
|
||||||
std::vector<std::string> vec = fmt::split(str, {","});
|
std::vector<std::string> vec = fmt::split(str, {","});
|
||||||
|
|
||||||
|
|||||||
@ -25,7 +25,7 @@ struct cfg_pad final : cfg::node
|
|||||||
cfg_pad() {};
|
cfg_pad() {};
|
||||||
cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {}
|
cfg_pad(node* owner, const std::string& name) : cfg::node(owner, name) {}
|
||||||
|
|
||||||
static std::vector<std::string> get_buttons(const std::string& str);
|
static std::vector<std::string> get_buttons(std::string_view str);
|
||||||
static std::string get_buttons(std::vector<std::string> vec);
|
static std::string get_buttons(std::vector<std::string> vec);
|
||||||
|
|
||||||
u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const;
|
u8 get_motor_speed(VibrateMotor& motor, f32 multiplier) const;
|
||||||
|
|||||||
@ -159,20 +159,6 @@ u32 get_axis_keycode(u32 offset, u16 value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ps_move_data::reset_sensors()
|
|
||||||
{
|
|
||||||
quaternion = default_quaternion;
|
|
||||||
accelerometer_x = 0.0f;
|
|
||||||
accelerometer_y = 0.0f;
|
|
||||||
accelerometer_z = 0.0f;
|
|
||||||
gyro_x = 0.0f;
|
|
||||||
gyro_y = 0.0f;
|
|
||||||
gyro_z = 0.0f;
|
|
||||||
magnetometer_x = 0.0f;
|
|
||||||
magnetometer_y = 0.0f;
|
|
||||||
magnetometer_z = 0.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id)
|
bool Pad::get_pressure_intensity_button_active(bool is_toggle_mode, u32 player_id)
|
||||||
{
|
{
|
||||||
if (m_pressure_intensity_button_index < 0)
|
if (m_pressure_intensity_button_index < 0)
|
||||||
|
|||||||
@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
#include "util/types.hpp"
|
#include "util/types.hpp"
|
||||||
#include "util/endian.hpp"
|
#include "util/endian.hpp"
|
||||||
#include "Emu/Io/pad_config_types.h"
|
#include "pad_config_types.h"
|
||||||
|
#include "ps_move_data.h"
|
||||||
|
|
||||||
#include <map>
|
#include <map>
|
||||||
#include <set>
|
#include <set>
|
||||||
@ -469,38 +470,6 @@ struct VibrateMotor
|
|||||||
{}
|
{}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct ps_move_data
|
|
||||||
{
|
|
||||||
u32 external_device_id = 0;
|
|
||||||
std::array<u8, 38> external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE
|
|
||||||
std::array<u8, 40> external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE
|
|
||||||
std::array<u8, 5> external_device_data{};
|
|
||||||
bool external_device_connected = false;
|
|
||||||
bool external_device_read_requested = false;
|
|
||||||
bool external_device_write_requested = false;
|
|
||||||
|
|
||||||
bool calibration_requested = false;
|
|
||||||
bool calibration_succeeded = false;
|
|
||||||
|
|
||||||
bool magnetometer_enabled = false;
|
|
||||||
bool orientation_enabled = false;
|
|
||||||
|
|
||||||
static constexpr std::array<f32, 4> default_quaternion { 1.0f, 0.0f, 0.0f, 0.0f };
|
|
||||||
std::array<f32, 4> quaternion = default_quaternion; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
|
||||||
f32 accelerometer_x = 0.0f; // linear velocity in m/s²
|
|
||||||
f32 accelerometer_y = 0.0f; // linear velocity in m/s²
|
|
||||||
f32 accelerometer_z = 0.0f; // linear velocity in m/s²
|
|
||||||
f32 gyro_x = 0.0f; // angular velocity in rad/s
|
|
||||||
f32 gyro_y = 0.0f; // angular velocity in rad/s
|
|
||||||
f32 gyro_z = 0.0f; // angular velocity in rad/s
|
|
||||||
f32 magnetometer_x = 0.0f;
|
|
||||||
f32 magnetometer_y = 0.0f;
|
|
||||||
f32 magnetometer_z = 0.0f;
|
|
||||||
s16 temperature = 0;
|
|
||||||
|
|
||||||
void reset_sensors();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Pad
|
struct Pad
|
||||||
{
|
{
|
||||||
const pad_handler m_pad_handler;
|
const pad_handler m_pad_handler;
|
||||||
|
|||||||
137
rpcs3/Emu/Io/ps_move_data.cpp
Normal file
137
rpcs3/Emu/Io/ps_move_data.cpp
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "ps_move_data.h"
|
||||||
|
|
||||||
|
const ps_move_data::vect<4> ps_move_data::default_quaternion = ps_move_data::vect<4>({ 0.0f, 0.0f, 0.0f, 1.0f });
|
||||||
|
|
||||||
|
ps_move_data::ps_move_data()
|
||||||
|
: quaternion(default_quaternion)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
void ps_move_data::reset_sensors()
|
||||||
|
{
|
||||||
|
quaternion = default_quaternion;
|
||||||
|
accelerometer = {};
|
||||||
|
gyro = {};
|
||||||
|
prev_gyro = {};
|
||||||
|
angular_acceleration = {};
|
||||||
|
magnetometer = {};
|
||||||
|
//prev_pos_world = {}; // probably no reset needed ?
|
||||||
|
vel_world = {};
|
||||||
|
prev_vel_world = {};
|
||||||
|
accel_world = {};
|
||||||
|
angvel_world = {};
|
||||||
|
angaccel_world = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void ps_move_data::update_orientation(f32 delta_time)
|
||||||
|
{
|
||||||
|
if (!delta_time)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Rotate vector v by quaternion q
|
||||||
|
const auto rotate_vector = [](const vect<4>& q, const vect<3>& v)
|
||||||
|
{
|
||||||
|
const vect<4> qv({0.0f, v.x(), v.y(), v.z()});
|
||||||
|
const vect<4> q_inv({q.w(), -q.x(), -q.y(), -q.z()});
|
||||||
|
|
||||||
|
// t = q * v
|
||||||
|
vect<4> t;
|
||||||
|
t.w() = -q.x() * qv.x() - q.y() * qv.y() - q.z() * qv.z();
|
||||||
|
t.x() = q.w() * qv.x() + q.y() * qv.z() - q.z() * qv.y();
|
||||||
|
t.y() = q.w() * qv.y() - q.x() * qv.z() + q.z() * qv.x();
|
||||||
|
t.z() = q.w() * qv.z() + q.x() * qv.y() - q.y() * qv.x();
|
||||||
|
|
||||||
|
// r = t * q_inv
|
||||||
|
vect<4> r;
|
||||||
|
r.w() = -t.x() * q_inv.x() - t.y() * q_inv.y() - t.z() * q_inv.z();
|
||||||
|
r.x() = t.w() * q_inv.x() + t.y() * q_inv.z() - t.z() * q_inv.y();
|
||||||
|
r.y() = t.w() * q_inv.y() - t.x() * q_inv.z() + t.z() * q_inv.x();
|
||||||
|
r.z() = t.w() * q_inv.z() + t.x() * q_inv.y() - t.y() * q_inv.x();
|
||||||
|
|
||||||
|
return vect<3>({r.x(), r.y(), r.z()});
|
||||||
|
};
|
||||||
|
|
||||||
|
if constexpr (use_imu_for_velocity)
|
||||||
|
{
|
||||||
|
// Gravity in world frame
|
||||||
|
constexpr f32 gravity = 9.81f;
|
||||||
|
constexpr vect<3> g({0.0f, 0.0f, -gravity});
|
||||||
|
|
||||||
|
// Rotate gravity into sensor frame
|
||||||
|
const vect<3> g_sensor = rotate_vector(quaternion, g);
|
||||||
|
|
||||||
|
// Remove gravity
|
||||||
|
vect<3> linear_local;
|
||||||
|
for (u32 i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
linear_local[i] = (accelerometer[i] * gravity) - g_sensor[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linear acceleration (rotate to world coordinates)
|
||||||
|
accel_world = rotate_vector(quaternion, linear_local);
|
||||||
|
|
||||||
|
// convert to mm/s²
|
||||||
|
for (u32 i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
accel_world[i] *= 1000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Linear velocity (integrate acceleration)
|
||||||
|
for (u32 i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
vel_world[i] = prev_vel_world[i] + accel_world[i] * delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_vel_world = vel_world;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compute raw angular acceleration
|
||||||
|
for (u32 i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
const f32 alpha = (gyro[i] - prev_gyro[i]) / delta_time;
|
||||||
|
|
||||||
|
// Filtering
|
||||||
|
constexpr f32 weight = 0.8f;
|
||||||
|
constexpr f32 weight_inv = 1.0f - weight;
|
||||||
|
angular_acceleration[i] = weight * angular_acceleration[i] + weight_inv * alpha;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Angular velocity (rotate to world coordinates)
|
||||||
|
angvel_world = rotate_vector(quaternion, gyro);
|
||||||
|
|
||||||
|
// Angular acceleration (rotate to world coordinates)
|
||||||
|
angaccel_world = rotate_vector(quaternion, angular_acceleration);
|
||||||
|
|
||||||
|
prev_gyro = gyro;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ps_move_data::update_velocity(u64 timestamp, be_t<f32> pos_world[4])
|
||||||
|
{
|
||||||
|
if constexpr (use_imu_for_velocity)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (last_velocity_update_time_us == timestamp)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Get elapsed time since last update
|
||||||
|
const f32 delta_time = (last_velocity_update_time_us == 0) ? 0.0f : ((timestamp - last_velocity_update_time_us) / 1'000'000.0f);
|
||||||
|
last_velocity_update_time_us = timestamp;
|
||||||
|
|
||||||
|
if (!delta_time)
|
||||||
|
return;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < 3; i++)
|
||||||
|
{
|
||||||
|
// Linear velocity
|
||||||
|
constexpr f32 weight = 0.8f;
|
||||||
|
constexpr f32 weight_inv = 1.0f - weight;
|
||||||
|
vel_world[i] = weight * ((pos_world[i] - prev_pos_world[i]) / delta_time) + weight_inv * prev_vel_world[i];
|
||||||
|
prev_pos_world[i] = pos_world[i];
|
||||||
|
|
||||||
|
// Linear acceleration
|
||||||
|
accel_world[i] = (vel_world[i] - prev_vel_world[i]) / delta_time;
|
||||||
|
}
|
||||||
|
|
||||||
|
prev_vel_world = vel_world;
|
||||||
|
}
|
||||||
75
rpcs3/Emu/Io/ps_move_data.h
Normal file
75
rpcs3/Emu/Io/ps_move_data.h
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
struct ps_move_data
|
||||||
|
{
|
||||||
|
template <int Size, typename T = f32>
|
||||||
|
struct vect
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
constexpr vect() = default;
|
||||||
|
constexpr vect(const std::array<T, Size>& vec) : data(vec) {};
|
||||||
|
|
||||||
|
template <typename I>
|
||||||
|
T& operator[](I i) { return data[i]; }
|
||||||
|
|
||||||
|
template <typename I>
|
||||||
|
const T& operator[](I i) const { return data[i]; }
|
||||||
|
|
||||||
|
T x() const requires (Size >= 1) { return data[0]; }
|
||||||
|
T y() const requires (Size >= 2) { return data[1]; }
|
||||||
|
T z() const requires (Size >= 3) { return data[2]; }
|
||||||
|
T w() const requires (Size >= 4) { return data[3]; }
|
||||||
|
|
||||||
|
T& x() requires (Size >= 1) { return data[0]; }
|
||||||
|
T& y() requires (Size >= 2) { return data[1]; }
|
||||||
|
T& z() requires (Size >= 3) { return data[2]; }
|
||||||
|
T& w() requires (Size >= 4) { return data[3]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::array<T, Size> data {};
|
||||||
|
};
|
||||||
|
|
||||||
|
ps_move_data();
|
||||||
|
|
||||||
|
u32 external_device_id = 0;
|
||||||
|
std::array<u8, 38> external_device_read{}; // CELL_GEM_EXTERNAL_PORT_DEVICE_INFO_SIZE
|
||||||
|
std::array<u8, 40> external_device_write{}; // CELL_GEM_EXTERNAL_PORT_OUTPUT_SIZE
|
||||||
|
std::array<u8, 5> external_device_data{};
|
||||||
|
bool external_device_connected = false;
|
||||||
|
bool external_device_read_requested = false;
|
||||||
|
bool external_device_write_requested = false;
|
||||||
|
|
||||||
|
bool calibration_requested = false;
|
||||||
|
bool calibration_succeeded = false;
|
||||||
|
|
||||||
|
bool magnetometer_enabled = false;
|
||||||
|
bool orientation_enabled = false;
|
||||||
|
|
||||||
|
// Disable IMU tracking of velocity and acceleration (massive drift)
|
||||||
|
static constexpr bool use_imu_for_velocity = false;
|
||||||
|
u64 last_velocity_update_time_us = 0;
|
||||||
|
|
||||||
|
static const vect<4> default_quaternion;
|
||||||
|
vect<4> quaternion {}; // quaternion orientation (x,y,z,w) of controller relative to default (facing the camera with buttons up)
|
||||||
|
|
||||||
|
// Raw values (local)
|
||||||
|
vect<3> accelerometer {}; // linear acceleration in G units (9.81 = 1 unit)
|
||||||
|
vect<3> gyro {}; // angular velocity in rad/s
|
||||||
|
vect<3> prev_gyro {}; // previous angular velocity in rad/s
|
||||||
|
vect<3> angular_acceleration {}; // angular acceleration in rad/s²
|
||||||
|
vect<3> magnetometer {};
|
||||||
|
|
||||||
|
// In world coordinates
|
||||||
|
vect<3> prev_pos_world {};
|
||||||
|
vect<3> vel_world {}; // velocity of sphere in world coordinates (mm/s)
|
||||||
|
vect<3> prev_vel_world {}; // previous velocity of sphere in world coordinates (mm/s)
|
||||||
|
vect<3> accel_world {}; // acceleration of sphere in world coordinates (mm/s²)
|
||||||
|
vect<3> angvel_world {}; // angular velocity of controller in world coordinates (radians/s)
|
||||||
|
vect<3> angaccel_world {}; // angular acceleration of controller in world coordinates (radians/s²)
|
||||||
|
|
||||||
|
s16 temperature = 0;
|
||||||
|
|
||||||
|
void reset_sensors();
|
||||||
|
void update_orientation(f32 delta_time);
|
||||||
|
void update_velocity(u64 timestamp, be_t<f32> pos_world[4]);
|
||||||
|
};
|
||||||
@ -206,7 +206,7 @@ table SetRoomDataInternalRequest {
|
|||||||
flagAttr:uint32;
|
flagAttr:uint32;
|
||||||
roomBinAttrInternal:[BinAttr];
|
roomBinAttrInternal:[BinAttr];
|
||||||
passwordConfig:[RoomGroupPasswordConfig];
|
passwordConfig:[RoomGroupPasswordConfig];
|
||||||
passwordSlotMask:uint64;
|
passwordSlotMask:[uint64];
|
||||||
ownerPrivilegeRank:[uint16];
|
ownerPrivilegeRank:[uint16];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -8,9 +8,9 @@
|
|||||||
|
|
||||||
// Ensure the included flatbuffers.h is the same version as when this file was
|
// Ensure the included flatbuffers.h is the same version as when this file was
|
||||||
// generated, otherwise it may not be compatible.
|
// generated, otherwise it may not be compatible.
|
||||||
static_assert(FLATBUFFERS_VERSION_MAJOR == 24 &&
|
static_assert(FLATBUFFERS_VERSION_MAJOR == 25 &&
|
||||||
FLATBUFFERS_VERSION_MINOR == 3 &&
|
FLATBUFFERS_VERSION_MINOR == 9 &&
|
||||||
FLATBUFFERS_VERSION_REVISION == 25,
|
FLATBUFFERS_VERSION_REVISION == 23,
|
||||||
"Non-compatible flatbuffers version included");
|
"Non-compatible flatbuffers version included");
|
||||||
|
|
||||||
struct SignalingAddr;
|
struct SignalingAddr;
|
||||||
@ -2762,8 +2762,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer
|
|||||||
const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig() const {
|
const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig() const {
|
||||||
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *>(VT_PASSWORDCONFIG);
|
return GetPointer<const ::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *>(VT_PASSWORDCONFIG);
|
||||||
}
|
}
|
||||||
uint64_t passwordSlotMask() const {
|
const ::flatbuffers::Vector<uint64_t> *passwordSlotMask() const {
|
||||||
return GetField<uint64_t>(VT_PASSWORDSLOTMASK, 0);
|
return GetPointer<const ::flatbuffers::Vector<uint64_t> *>(VT_PASSWORDSLOTMASK);
|
||||||
}
|
}
|
||||||
const ::flatbuffers::Vector<uint16_t> *ownerPrivilegeRank() const {
|
const ::flatbuffers::Vector<uint16_t> *ownerPrivilegeRank() const {
|
||||||
return GetPointer<const ::flatbuffers::Vector<uint16_t> *>(VT_OWNERPRIVILEGERANK);
|
return GetPointer<const ::flatbuffers::Vector<uint16_t> *>(VT_OWNERPRIVILEGERANK);
|
||||||
@ -2779,7 +2779,8 @@ struct SetRoomDataInternalRequest FLATBUFFERS_FINAL_CLASS : private ::flatbuffer
|
|||||||
VerifyOffset(verifier, VT_PASSWORDCONFIG) &&
|
VerifyOffset(verifier, VT_PASSWORDCONFIG) &&
|
||||||
verifier.VerifyVector(passwordConfig()) &&
|
verifier.VerifyVector(passwordConfig()) &&
|
||||||
verifier.VerifyVectorOfTables(passwordConfig()) &&
|
verifier.VerifyVectorOfTables(passwordConfig()) &&
|
||||||
VerifyField<uint64_t>(verifier, VT_PASSWORDSLOTMASK, 8) &&
|
VerifyOffset(verifier, VT_PASSWORDSLOTMASK) &&
|
||||||
|
verifier.VerifyVector(passwordSlotMask()) &&
|
||||||
VerifyOffset(verifier, VT_OWNERPRIVILEGERANK) &&
|
VerifyOffset(verifier, VT_OWNERPRIVILEGERANK) &&
|
||||||
verifier.VerifyVector(ownerPrivilegeRank()) &&
|
verifier.VerifyVector(ownerPrivilegeRank()) &&
|
||||||
verifier.EndTable();
|
verifier.EndTable();
|
||||||
@ -2805,8 +2806,8 @@ struct SetRoomDataInternalRequestBuilder {
|
|||||||
void add_passwordConfig(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig) {
|
void add_passwordConfig(::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig) {
|
||||||
fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDCONFIG, passwordConfig);
|
fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDCONFIG, passwordConfig);
|
||||||
}
|
}
|
||||||
void add_passwordSlotMask(uint64_t passwordSlotMask) {
|
void add_passwordSlotMask(::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> passwordSlotMask) {
|
||||||
fbb_.AddElement<uint64_t>(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask, 0);
|
fbb_.AddOffset(SetRoomDataInternalRequest::VT_PASSWORDSLOTMASK, passwordSlotMask);
|
||||||
}
|
}
|
||||||
void add_ownerPrivilegeRank(::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank) {
|
void add_ownerPrivilegeRank(::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank) {
|
||||||
fbb_.AddOffset(SetRoomDataInternalRequest::VT_OWNERPRIVILEGERANK, ownerPrivilegeRank);
|
fbb_.AddOffset(SetRoomDataInternalRequest::VT_OWNERPRIVILEGERANK, ownerPrivilegeRank);
|
||||||
@ -2829,12 +2830,12 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
|
|||||||
uint32_t flagAttr = 0,
|
uint32_t flagAttr = 0,
|
||||||
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<BinAttr>>> roomBinAttrInternal = 0,
|
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<BinAttr>>> roomBinAttrInternal = 0,
|
||||||
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig = 0,
|
::flatbuffers::Offset<::flatbuffers::Vector<::flatbuffers::Offset<RoomGroupPasswordConfig>>> passwordConfig = 0,
|
||||||
uint64_t passwordSlotMask = 0,
|
::flatbuffers::Offset<::flatbuffers::Vector<uint64_t>> passwordSlotMask = 0,
|
||||||
::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank = 0) {
|
::flatbuffers::Offset<::flatbuffers::Vector<uint16_t>> ownerPrivilegeRank = 0) {
|
||||||
SetRoomDataInternalRequestBuilder builder_(_fbb);
|
SetRoomDataInternalRequestBuilder builder_(_fbb);
|
||||||
builder_.add_passwordSlotMask(passwordSlotMask);
|
|
||||||
builder_.add_roomId(roomId);
|
builder_.add_roomId(roomId);
|
||||||
builder_.add_ownerPrivilegeRank(ownerPrivilegeRank);
|
builder_.add_ownerPrivilegeRank(ownerPrivilegeRank);
|
||||||
|
builder_.add_passwordSlotMask(passwordSlotMask);
|
||||||
builder_.add_passwordConfig(passwordConfig);
|
builder_.add_passwordConfig(passwordConfig);
|
||||||
builder_.add_roomBinAttrInternal(roomBinAttrInternal);
|
builder_.add_roomBinAttrInternal(roomBinAttrInternal);
|
||||||
builder_.add_flagAttr(flagAttr);
|
builder_.add_flagAttr(flagAttr);
|
||||||
@ -2849,10 +2850,11 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
|
|||||||
uint32_t flagAttr = 0,
|
uint32_t flagAttr = 0,
|
||||||
const std::vector<::flatbuffers::Offset<BinAttr>> *roomBinAttrInternal = nullptr,
|
const std::vector<::flatbuffers::Offset<BinAttr>> *roomBinAttrInternal = nullptr,
|
||||||
const std::vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig = nullptr,
|
const std::vector<::flatbuffers::Offset<RoomGroupPasswordConfig>> *passwordConfig = nullptr,
|
||||||
uint64_t passwordSlotMask = 0,
|
const std::vector<uint64_t> *passwordSlotMask = nullptr,
|
||||||
const std::vector<uint16_t> *ownerPrivilegeRank = nullptr) {
|
const std::vector<uint16_t> *ownerPrivilegeRank = nullptr) {
|
||||||
auto roomBinAttrInternal__ = roomBinAttrInternal ? _fbb.CreateVector<::flatbuffers::Offset<BinAttr>>(*roomBinAttrInternal) : 0;
|
auto roomBinAttrInternal__ = roomBinAttrInternal ? _fbb.CreateVector<::flatbuffers::Offset<BinAttr>>(*roomBinAttrInternal) : 0;
|
||||||
auto passwordConfig__ = passwordConfig ? _fbb.CreateVector<::flatbuffers::Offset<RoomGroupPasswordConfig>>(*passwordConfig) : 0;
|
auto passwordConfig__ = passwordConfig ? _fbb.CreateVector<::flatbuffers::Offset<RoomGroupPasswordConfig>>(*passwordConfig) : 0;
|
||||||
|
auto passwordSlotMask__ = passwordSlotMask ? _fbb.CreateVector<uint64_t>(*passwordSlotMask) : 0;
|
||||||
auto ownerPrivilegeRank__ = ownerPrivilegeRank ? _fbb.CreateVector<uint16_t>(*ownerPrivilegeRank) : 0;
|
auto ownerPrivilegeRank__ = ownerPrivilegeRank ? _fbb.CreateVector<uint16_t>(*ownerPrivilegeRank) : 0;
|
||||||
return CreateSetRoomDataInternalRequest(
|
return CreateSetRoomDataInternalRequest(
|
||||||
_fbb,
|
_fbb,
|
||||||
@ -2861,7 +2863,7 @@ inline ::flatbuffers::Offset<SetRoomDataInternalRequest> CreateSetRoomDataIntern
|
|||||||
flagAttr,
|
flagAttr,
|
||||||
roomBinAttrInternal__,
|
roomBinAttrInternal__,
|
||||||
passwordConfig__,
|
passwordConfig__,
|
||||||
passwordSlotMask,
|
passwordSlotMask__,
|
||||||
ownerPrivilegeRank__);
|
ownerPrivilegeRank__);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -28,7 +28,8 @@ namespace np
|
|||||||
dnshook::dnshook()
|
dnshook::dnshook()
|
||||||
{
|
{
|
||||||
// Init switch map for dns
|
// Init switch map for dns
|
||||||
auto swaps = fmt::split(g_cfg.net.swap_list.to_string(), {"&&"});
|
const std::string swap_list = g_cfg.net.swap_list.to_string();
|
||||||
|
const auto swaps = fmt::split_sv(swap_list, {"&&"});
|
||||||
for (usz i = 0; i < swaps.size(); i++)
|
for (usz i = 0; i < swaps.size(); i++)
|
||||||
{
|
{
|
||||||
auto host_and_ip = fmt::split(swaps[i], {"="});
|
auto host_and_ip = fmt::split(swaps[i], {"="});
|
||||||
|
|||||||
@ -255,7 +255,7 @@ namespace rpcn
|
|||||||
rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size()));
|
rpcn_log.notice("online: %s, pr_com_id: %s, pr_title: %s, pr_status: %s, pr_comment: %s, pr_data: %s", online ? "true" : "false", pr_com_id.data, pr_title, pr_status, pr_comment, fmt::buf_to_hexstring(pr_data.data(), pr_data.size()));
|
||||||
}
|
}
|
||||||
|
|
||||||
constexpr u32 RPCN_PROTOCOL_VERSION = 26;
|
constexpr u32 RPCN_PROTOCOL_VERSION = 27;
|
||||||
constexpr usz RPCN_HEADER_SIZE = 15;
|
constexpr usz RPCN_HEADER_SIZE = 15;
|
||||||
|
|
||||||
const char* error_to_explanation(rpcn::ErrorType error)
|
const char* error_to_explanation(rpcn::ErrorType error)
|
||||||
@ -2019,9 +2019,13 @@ namespace rpcn
|
|||||||
}
|
}
|
||||||
final_grouppasswordconfig_vec = builder.CreateVector(davec);
|
final_grouppasswordconfig_vec = builder.CreateVector(davec);
|
||||||
}
|
}
|
||||||
u64 final_passwordSlotMask = 0;
|
|
||||||
|
flatbuffers::Offset<flatbuffers::Vector<u64>> final_passwordSlotMask;
|
||||||
if (req->passwordSlotMask)
|
if (req->passwordSlotMask)
|
||||||
final_passwordSlotMask = *req->passwordSlotMask;
|
{
|
||||||
|
const u64 value = *req->passwordSlotMask;
|
||||||
|
final_passwordSlotMask = builder.CreateVector(&value, 1);
|
||||||
|
}
|
||||||
|
|
||||||
flatbuffers::Offset<flatbuffers::Vector<u16>> final_ownerprivilege_vec;
|
flatbuffers::Offset<flatbuffers::Vector<u16>> final_ownerprivilege_vec;
|
||||||
if (req->ownerPrivilegeRankNum && req->ownerPrivilegeRank)
|
if (req->ownerPrivilegeRankNum && req->ownerPrivilegeRank)
|
||||||
|
|||||||
@ -79,7 +79,8 @@ std::string cfg_rpcn::get_host() const
|
|||||||
std::vector<std::pair<std::string, std::string>> cfg_rpcn::get_hosts()
|
std::vector<std::pair<std::string, std::string>> cfg_rpcn::get_hosts()
|
||||||
{
|
{
|
||||||
std::vector<std::pair<std::string, std::string>> vec_hosts;
|
std::vector<std::pair<std::string, std::string>> vec_hosts;
|
||||||
auto hosts_list = fmt::split(hosts.to_string(), {"|||"});
|
const std::string host_str = hosts.to_string();
|
||||||
|
const auto hosts_list = fmt::split_sv(host_str, {"|||"});
|
||||||
|
|
||||||
for (const auto& cur_host : hosts_list)
|
for (const auto& cur_host : hosts_list)
|
||||||
{
|
{
|
||||||
@ -190,9 +191,8 @@ bool cfg_rpcn::add_host(std::string_view new_description, std::string_view new_h
|
|||||||
|
|
||||||
bool cfg_rpcn::del_host(std::string_view del_description, std::string_view del_host)
|
bool cfg_rpcn::del_host(std::string_view del_description, std::string_view del_host)
|
||||||
{
|
{
|
||||||
// Do not delete default servers
|
// Do not delete default server
|
||||||
if ((del_description == "Official RPCN Server" && del_host == "np.rpcs3.net") ||
|
if (del_description == "Official RPCN Server" && del_host == "np.rpcs3.net")
|
||||||
(del_description == "RPCN Test Server" && del_host == "test-np.rpcs3.net"))
|
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,7 @@ struct cfg_rpcn : cfg::node
|
|||||||
cfg::string npid{this, "NPID", ""};
|
cfg::string npid{this, "NPID", ""};
|
||||||
cfg::string password{this, "Password", ""};
|
cfg::string password{this, "Password", ""};
|
||||||
cfg::string token{this, "Token", ""};
|
cfg::string token{this, "Token", ""};
|
||||||
cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net|||RPCN Test Server|test-np.rpcs3.net"};
|
cfg::string hosts{this, "Hosts", "Official RPCN Server|np.rpcs3.net"};
|
||||||
cfg::_bool ipv6_support{this, "IPv6 support", true};
|
cfg::_bool ipv6_support{this, "IPv6 support", true};
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
|
|||||||
@ -337,7 +337,7 @@ namespace rsx
|
|||||||
AUDIT(_loc < _size);
|
AUDIT(_loc < _size);
|
||||||
|
|
||||||
const auto remaining = (_size - _loc);
|
const auto remaining = (_size - _loc);
|
||||||
memmove(pos + 1, pos, remaining * sizeof(Ty));
|
std::memmove(pos + 1, pos, remaining * sizeof(Ty));
|
||||||
|
|
||||||
*pos = val;
|
*pos = val;
|
||||||
_size++;
|
_size++;
|
||||||
@ -365,7 +365,7 @@ namespace rsx
|
|||||||
AUDIT(_loc < _size);
|
AUDIT(_loc < _size);
|
||||||
|
|
||||||
const u32 remaining = (_size - _loc);
|
const u32 remaining = (_size - _loc);
|
||||||
memmove(pos + 1, pos, remaining * sizeof(Ty));
|
std::memmove(pos + 1, pos, remaining * sizeof(Ty));
|
||||||
|
|
||||||
*pos = val;
|
*pos = val;
|
||||||
_size++;
|
_size++;
|
||||||
@ -373,6 +373,31 @@ namespace rsx
|
|||||||
return pos;
|
return pos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
iterator insert(iterator where, span_like<Ty> auto const& values)
|
||||||
|
{
|
||||||
|
ensure(where >= _data);
|
||||||
|
const auto _loc = offset(where);
|
||||||
|
const auto in_size = static_cast<u32>(values.size());
|
||||||
|
const auto in_size_bytes = in_size * sizeof(Ty);
|
||||||
|
|
||||||
|
reserve(_size + in_size);
|
||||||
|
|
||||||
|
if (_loc >= _size)
|
||||||
|
{
|
||||||
|
where = _data + _size;
|
||||||
|
std::memcpy(where, values.data(), in_size_bytes);
|
||||||
|
_size += in_size;
|
||||||
|
return where;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 remaining_bytes = (_size - _loc) * sizeof(Ty);
|
||||||
|
where = _data + _loc;
|
||||||
|
std::memmove(where + in_size, where, remaining_bytes);
|
||||||
|
std::memmove(where, values.data(), in_size_bytes);
|
||||||
|
_size += in_size;
|
||||||
|
return where;
|
||||||
|
}
|
||||||
|
|
||||||
void operator += (const rsx::simple_array<Ty>& that)
|
void operator += (const rsx::simple_array<Ty>& that)
|
||||||
{
|
{
|
||||||
const auto old_size = _size;
|
const auto old_size = _size;
|
||||||
|
|||||||
@ -120,9 +120,15 @@ namespace rsx
|
|||||||
{
|
{
|
||||||
result.font_names.emplace_back("Arial.ttf");
|
result.font_names.emplace_back("Arial.ttf");
|
||||||
result.font_names.emplace_back("arial.ttf");
|
result.font_names.emplace_back("arial.ttf");
|
||||||
#ifndef _WIN32
|
#ifdef __APPLE__
|
||||||
result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu
|
result.font_names.emplace_back("DejaVuSans.ttf");
|
||||||
result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch
|
result.font_names.emplace_back("NotoSans-Regular.ttf");
|
||||||
|
result.font_names.emplace_back("Roboto-Regular.ttf");
|
||||||
|
result.font_names.emplace_back("OpenSans-Regular.ttf");
|
||||||
|
result.font_names.emplace_back("FreeSans.ttf");
|
||||||
|
#elif !defined(_WIN32)
|
||||||
|
result.font_names.emplace_back("/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf"); // ubuntu
|
||||||
|
result.font_names.emplace_back("/usr/share/fonts/TTF/DejaVuSans.ttf"); // arch
|
||||||
#endif
|
#endif
|
||||||
// Attempt to load a font from dev_flash as a last resort
|
// Attempt to load a font from dev_flash as a last resort
|
||||||
result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF");
|
result.font_names.emplace_back("SCE-PS3-VR-R-LATIN.TTF");
|
||||||
|
|||||||
@ -34,6 +34,11 @@ namespace rsx::assembler
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct CFGPass
|
||||||
|
{
|
||||||
|
virtual void run(FlowGraph& graph) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog);
|
FlowGraph deconstruct_fragment_program(const RSXFragmentProgram& prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
455
rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp
Normal file
455
rpcs3/Emu/RSX/Program/Assembler/FPASM.cpp
Normal file
@ -0,0 +1,455 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "FPASM.h"
|
||||||
|
#include "Emu/RSX/Program/RSXFragmentProgram.h"
|
||||||
|
|
||||||
|
#include <stack>
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#define sscanf_s sscanf
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace rsx::assembler
|
||||||
|
{
|
||||||
|
struct FP_opcode_encoding_t
|
||||||
|
{
|
||||||
|
FP_opcode op;
|
||||||
|
bool exec_if_lt;
|
||||||
|
bool exec_if_eq;
|
||||||
|
bool exec_if_gt;
|
||||||
|
bool set_cond;
|
||||||
|
};
|
||||||
|
|
||||||
|
static std::unordered_map<std::string_view, FP_opcode_encoding_t> s_opcode_lookup
|
||||||
|
{
|
||||||
|
// Arithmetic
|
||||||
|
{ "NOP", { .op = RSX_FP_OPCODE_NOP, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "MOV", { .op = RSX_FP_OPCODE_MOV, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "MUL", { .op = RSX_FP_OPCODE_MUL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "ADD", { .op = RSX_FP_OPCODE_ADD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "MAD", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "FMA", { .op = RSX_FP_OPCODE_MAD, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "DP3", { .op = RSX_FP_OPCODE_DP3, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "DP4", { .op = RSX_FP_OPCODE_DP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
|
||||||
|
// Constant load
|
||||||
|
{ "SFL", {.op = RSX_FP_OPCODE_SFL, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "STR", {.op = RSX_FP_OPCODE_STR, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
|
||||||
|
// Pack-unpack operations are great for testing dependencies
|
||||||
|
{ "PKH", { .op = RSX_FP_OPCODE_PK2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "UPH", { .op = RSX_FP_OPCODE_UP2, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "PK16U", { .op = RSX_FP_OPCODE_PK16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "UP16U", { .op = RSX_FP_OPCODE_UP16, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "PK8U", { .op = RSX_FP_OPCODE_PKB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "UP8U", { .op = RSX_FP_OPCODE_UPB, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "PK8G", { .op = RSX_FP_OPCODE_PKG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "UP8G", { .op = RSX_FP_OPCODE_UPG, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "PK8S", { .op = RSX_FP_OPCODE_PK4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "UP8S", { .op = RSX_FP_OPCODE_UP4, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
|
||||||
|
// Basic conditionals
|
||||||
|
{ "IF.LT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = false, .exec_if_gt = false, .set_cond = false } },
|
||||||
|
{ "IF.LE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = true, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } },
|
||||||
|
{ "IF.EQ", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = false, .set_cond = false } },
|
||||||
|
{ "IF.GE", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = true, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
{ "IF.GT", { .op = RSX_FP_OPCODE_IFE, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = true, .set_cond = false } },
|
||||||
|
|
||||||
|
{ "SLT", { .op = RSX_FP_OPCODE_SLT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } },
|
||||||
|
{ "SEQ", { .op = RSX_FP_OPCODE_SEQ, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } },
|
||||||
|
{ "SGT", { .op = RSX_FP_OPCODE_SGT, .exec_if_lt = false, .exec_if_eq = false, .exec_if_gt = false, .set_cond = true } },
|
||||||
|
|
||||||
|
// TODO: Add more
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
Instruction* FPIR::load(const RegisterRef& ref, int operand, Instruction* prev)
|
||||||
|
{
|
||||||
|
Instruction* target = prev;
|
||||||
|
if (!target)
|
||||||
|
{
|
||||||
|
m_instructions.push_back({});
|
||||||
|
target = &m_instructions.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
SRC_Common src{ .HEX = target->bytecode[operand + 1] };
|
||||||
|
src.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
|
||||||
|
src.fp16 = ref.reg.f16 ? 1 : 0;
|
||||||
|
src.tmp_reg_index = static_cast<u32>(ref.reg.id);
|
||||||
|
|
||||||
|
src.swizzle_x = 0;
|
||||||
|
src.swizzle_y = 1;
|
||||||
|
src.swizzle_z = 2;
|
||||||
|
src.swizzle_w = 3;
|
||||||
|
|
||||||
|
target->bytecode[operand + 1] = src.HEX;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction* FPIR::load(const std::array<f32, 4>& constants, int operand, Instruction* prev)
|
||||||
|
{
|
||||||
|
Instruction* target = prev;
|
||||||
|
if (!target)
|
||||||
|
{
|
||||||
|
m_instructions.push_back({});
|
||||||
|
target = &m_instructions.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unsupported for now
|
||||||
|
ensure(target->length == 4, "FPIR cannot encode more than one constant load per instruction");
|
||||||
|
|
||||||
|
SRC_Common src{ .HEX = target->bytecode[operand + 1] };
|
||||||
|
src.reg_type = RSX_FP_REGISTER_TYPE_CONSTANT;
|
||||||
|
target->bytecode[operand + 1] = src.HEX;
|
||||||
|
|
||||||
|
src.swizzle_x = 0;
|
||||||
|
src.swizzle_y = 1;
|
||||||
|
src.swizzle_z = 2;
|
||||||
|
src.swizzle_w = 3;
|
||||||
|
|
||||||
|
// Embed literal constant
|
||||||
|
std::memcpy(&target->bytecode[4], constants.data(), 4 * sizeof(u32));
|
||||||
|
target->length = 8;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction* FPIR::store(const RegisterRef& ref, Instruction* prev)
|
||||||
|
{
|
||||||
|
Instruction* target = prev;
|
||||||
|
if (!target)
|
||||||
|
{
|
||||||
|
m_instructions.push_back({});
|
||||||
|
target = &m_instructions.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
OPDEST dst{ .HEX = target->bytecode[0] };
|
||||||
|
dst.dest_reg = static_cast<u32>(ref.reg.id);
|
||||||
|
dst.fp16 = ref.reg.f16 ? 1 : 0;
|
||||||
|
dst.write_mask = ref.mask;
|
||||||
|
dst.prec = ref.reg.f16 ? RSX_FP_PRECISION_HALF : RSX_FP_PRECISION_REAL;
|
||||||
|
|
||||||
|
target->bytecode[0] = dst.HEX;
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPIR::mov(const RegisterRef& dst, f32 constant)
|
||||||
|
{
|
||||||
|
Instruction* inst = store(dst);
|
||||||
|
inst = load(std::array<f32, 4>{ constant, constant, constant, constant }, 0);
|
||||||
|
inst->opcode = RSX_FP_OPCODE_MOV;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPIR::mov(const RegisterRef& dst, const RegisterRef& src)
|
||||||
|
{
|
||||||
|
Instruction* inst = store(dst);
|
||||||
|
inst = load(src, 0);
|
||||||
|
inst->opcode = RSX_FP_OPCODE_MOV;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPIR::add(const RegisterRef& dst, const std::array<f32, 4>& constants)
|
||||||
|
{
|
||||||
|
Instruction* inst = store(dst);
|
||||||
|
inst = load(constants, 0);
|
||||||
|
inst->opcode = RSX_FP_OPCODE_ADD;
|
||||||
|
}
|
||||||
|
|
||||||
|
void FPIR::add(const RegisterRef& dst, const RegisterRef& src)
|
||||||
|
{
|
||||||
|
Instruction* inst = store(dst);
|
||||||
|
inst = load(src, 0);
|
||||||
|
inst->opcode = RSX_FP_OPCODE_ADD;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::vector<Instruction>& FPIR::instructions() const
|
||||||
|
{
|
||||||
|
return m_instructions;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u32> FPIR::compile() const
|
||||||
|
{
|
||||||
|
std::vector<u32> result;
|
||||||
|
result.reserve(m_instructions.size() * 4);
|
||||||
|
|
||||||
|
for (const auto& inst : m_instructions)
|
||||||
|
{
|
||||||
|
const auto src = reinterpret_cast<const be_t<u16>*>(inst.bytecode);
|
||||||
|
for (u32 j = 0; j < inst.length; ++j)
|
||||||
|
{
|
||||||
|
const u16 low = src[j * 2];
|
||||||
|
const u16 hi = src[j * 2 + 1];
|
||||||
|
const u32 word = static_cast<u16>(low) | (static_cast<u32>(hi) << 16u);
|
||||||
|
result.push_back(word);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
FPIR FPIR::from_source(std::string_view asm_)
|
||||||
|
{
|
||||||
|
std::vector<std::string> instructions = fmt::split(asm_, { "\n", ";" });
|
||||||
|
if (instructions.empty())
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto transform_inst = [](std::string_view s)
|
||||||
|
{
|
||||||
|
std::string result;
|
||||||
|
result.reserve(s.size());
|
||||||
|
|
||||||
|
bool literal = false;
|
||||||
|
for (const auto& c : s)
|
||||||
|
{
|
||||||
|
if (c == ' ')
|
||||||
|
{
|
||||||
|
if (!literal && !result.empty() && result.back() != ',')
|
||||||
|
{
|
||||||
|
result += ','; // Replace token separator space with comma
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::isspace(c))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!literal && c == '{')
|
||||||
|
{
|
||||||
|
literal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (literal && c == '}')
|
||||||
|
{
|
||||||
|
literal = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (c == ',')
|
||||||
|
{
|
||||||
|
result += (literal ? '|' : ',');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
result += c;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto decode_instruction = [&](std::string_view inst, std::string& op, std::string& dst, std::vector<std::string>& sources)
|
||||||
|
{
|
||||||
|
const auto i = transform_inst(inst);
|
||||||
|
if (i.empty())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto tokens = fmt::split(i, { "," });
|
||||||
|
ensure(!tokens.empty(), "Invalid input");
|
||||||
|
|
||||||
|
op = tokens.front();
|
||||||
|
|
||||||
|
if (tokens.size() > 1)
|
||||||
|
{
|
||||||
|
dst = tokens[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (size_t n = 2; n < tokens.size(); ++n)
|
||||||
|
{
|
||||||
|
sources.push_back(tokens[n]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto get_ref = [](std::string_view reg)
|
||||||
|
{
|
||||||
|
ensure(reg.length() > 1, "Invalid register specifier");
|
||||||
|
|
||||||
|
const auto parts = fmt::split(reg, { "." });
|
||||||
|
ensure(parts.size() > 0 && parts.size() <= 2);
|
||||||
|
|
||||||
|
const auto index = std::stoi(parts[0].substr(1));
|
||||||
|
RegisterRef ref
|
||||||
|
{
|
||||||
|
.reg { .id = index, .f16 = false },
|
||||||
|
.mask = 0x0F
|
||||||
|
};
|
||||||
|
|
||||||
|
if (parts.size() > 1 && parts[1].length() > 0)
|
||||||
|
{
|
||||||
|
// FIXME: No swizzles for now, just lane masking
|
||||||
|
ref.mask = 0;
|
||||||
|
if (parts[1].find("x") != std::string::npos) ref.mask |= (1u << 0);
|
||||||
|
if (parts[1].find("y") != std::string::npos) ref.mask |= (1u << 1);
|
||||||
|
if (parts[1].find("z") != std::string::npos) ref.mask |= (1u << 2);
|
||||||
|
if (parts[1].find("w") != std::string::npos) ref.mask |= (1u << 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reg[0] == 'H' || reg[0] == 'h')
|
||||||
|
{
|
||||||
|
ref.reg.f16 = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto get_constants = [](std::string_view reg) -> std::array<f32, 4>
|
||||||
|
{
|
||||||
|
float x, y, z, w;
|
||||||
|
if (sscanf_s(reg.data(), "#{%f|%f|%f|%f}", &x, &y, &z, &w) == 4)
|
||||||
|
{
|
||||||
|
return { x, y, z, w };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sscanf_s(reg.data(), "#{%f}", &x) == 1)
|
||||||
|
{
|
||||||
|
return { x, x, x, x };
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt::throw_exception("Invalid constant literal");
|
||||||
|
};
|
||||||
|
|
||||||
|
auto encode_branch_else = [](Instruction* inst, u32 end)
|
||||||
|
{
|
||||||
|
SRC1 src1{ .HEX = inst->bytecode[2] };
|
||||||
|
src1.else_offset = static_cast<u32>(end);
|
||||||
|
inst->bytecode[2] = src1.HEX;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto encode_branch_end = [](Instruction *inst, u32 end)
|
||||||
|
{
|
||||||
|
SRC2 src2 { .HEX = inst->bytecode[3] };
|
||||||
|
src2.end_offset = static_cast<u32>(end);
|
||||||
|
inst->bytecode[3] = src2.HEX;
|
||||||
|
|
||||||
|
SRC1 src1{ .HEX = inst->bytecode[2] };
|
||||||
|
if (!src1.else_offset)
|
||||||
|
{
|
||||||
|
src1.else_offset = static_cast<u32>(end);
|
||||||
|
inst->bytecode[2] = src1.HEX;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
auto encode_opcode = [](std::string_view op, Instruction* inst)
|
||||||
|
{
|
||||||
|
OPDEST d0 { .HEX = inst->bytecode[0] };
|
||||||
|
SRC0 s0 { .HEX = inst->bytecode[1] };
|
||||||
|
SRC1 s1 { .HEX = inst->bytecode[2] };
|
||||||
|
|
||||||
|
const auto found = s_opcode_lookup.find(op);
|
||||||
|
if (found == s_opcode_lookup.end())
|
||||||
|
{
|
||||||
|
fmt::throw_exception("Unhandled instruction '%s'", op);
|
||||||
|
}
|
||||||
|
const auto& encoding = found->second;
|
||||||
|
|
||||||
|
inst->opcode = encoding.op;
|
||||||
|
d0.opcode = encoding.op & 0x3F;
|
||||||
|
s1.opcode_hi = (encoding.op > 0x3F)? 1 : 0;
|
||||||
|
s0.exec_if_eq = encoding.exec_if_eq ? 1 : 0;
|
||||||
|
s0.exec_if_gr = encoding.exec_if_gt ? 1 : 0;
|
||||||
|
s0.exec_if_lt = encoding.exec_if_lt ? 1 : 0;
|
||||||
|
d0.set_cond = encoding.set_cond ? 1 : 0;
|
||||||
|
inst->bytecode[0] = d0.HEX;
|
||||||
|
inst->bytecode[1] = s0.HEX;
|
||||||
|
inst->bytecode[2] = s1.HEX;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string op, dst;
|
||||||
|
std::vector<std::string> sources;
|
||||||
|
|
||||||
|
std::stack<size_t> if_ops;
|
||||||
|
std::stack<size_t> loop_ops;
|
||||||
|
u32 pc = 0;
|
||||||
|
|
||||||
|
FPIR ir{};
|
||||||
|
|
||||||
|
for (const auto& instruction : instructions)
|
||||||
|
{
|
||||||
|
op.clear();
|
||||||
|
dst.clear();
|
||||||
|
sources.clear();
|
||||||
|
decode_instruction(instruction, op, dst, sources);
|
||||||
|
|
||||||
|
if (op.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op.starts_with("IF."))
|
||||||
|
{
|
||||||
|
if_ops.push(ir.m_instructions.size());
|
||||||
|
}
|
||||||
|
else if (op == "LOOP")
|
||||||
|
{
|
||||||
|
loop_ops.push(ir.m_instructions.size());
|
||||||
|
}
|
||||||
|
else if (op == "ELSE")
|
||||||
|
{
|
||||||
|
ensure(!if_ops.empty());
|
||||||
|
encode_branch_else(&ir.m_instructions[if_ops.top()], pc);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (op == "ENDIF")
|
||||||
|
{
|
||||||
|
ensure(!if_ops.empty());
|
||||||
|
encode_branch_end(&ir.m_instructions[if_ops.top()], pc);
|
||||||
|
if_ops.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
else if (op == "ENDLOOP")
|
||||||
|
{
|
||||||
|
ensure(!loop_ops.empty());
|
||||||
|
encode_branch_end(&ir.m_instructions[loop_ops.top()], pc);
|
||||||
|
loop_ops.pop();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ir.m_instructions.push_back({});
|
||||||
|
Instruction* target = &ir.m_instructions.back();
|
||||||
|
pc += 4;
|
||||||
|
|
||||||
|
encode_opcode(op, target);
|
||||||
|
ensure(sources.size() == FP::get_operand_count(static_cast<FP_opcode>(target->opcode)), "Invalid operand count for opcode");
|
||||||
|
|
||||||
|
if (dst.empty())
|
||||||
|
{
|
||||||
|
OPDEST dst{ .HEX = target->bytecode[0] };
|
||||||
|
dst.no_dest = 1;
|
||||||
|
target->bytecode[0] = dst.HEX;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ir.store(get_ref(dst), target);
|
||||||
|
}
|
||||||
|
|
||||||
|
int operand = 0;
|
||||||
|
bool has_literal = false;
|
||||||
|
for (const auto& source : sources)
|
||||||
|
{
|
||||||
|
if (source.front() == '#')
|
||||||
|
{
|
||||||
|
const auto literal = get_constants(source);
|
||||||
|
ir.load(literal, operand++, target);
|
||||||
|
has_literal = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ir.load(get_ref(source), operand++, target);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_literal)
|
||||||
|
{
|
||||||
|
pc += 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ir.m_instructions.empty())
|
||||||
|
{
|
||||||
|
OPDEST d0{ .HEX = ir.m_instructions.back().bytecode[0] };
|
||||||
|
d0.end = 1;
|
||||||
|
|
||||||
|
ir.m_instructions.back().bytecode[0] = d0.HEX;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ir;
|
||||||
|
}
|
||||||
|
}
|
||||||
29
rpcs3/Emu/RSX/Program/Assembler/FPASM.h
Normal file
29
rpcs3/Emu/RSX/Program/Assembler/FPASM.h
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IR.h"
|
||||||
|
|
||||||
|
namespace rsx::assembler
|
||||||
|
{
|
||||||
|
class FPIR
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void mov(const RegisterRef& dst, f32 constant);
|
||||||
|
void mov(const RegisterRef& dst, const RegisterRef& src);
|
||||||
|
|
||||||
|
void add(const RegisterRef& dst, const std::array<f32, 4>& constants);
|
||||||
|
void add(const RegisterRef& dst, const RegisterRef& src);
|
||||||
|
|
||||||
|
const std::vector<Instruction>& instructions() const;
|
||||||
|
std::vector<u32> compile() const;
|
||||||
|
|
||||||
|
static FPIR from_source(std::string_view asm_);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Instruction* load(const RegisterRef& reg, int operand, Instruction* target = nullptr);
|
||||||
|
Instruction* load(const std::array<f32, 4>& constants, int operand, Instruction* target = nullptr);
|
||||||
|
Instruction* store(const RegisterRef& reg, Instruction* target = nullptr);
|
||||||
|
|
||||||
|
std::vector<Instruction> m_instructions;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
428
rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp
Normal file
428
rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.cpp
Normal file
@ -0,0 +1,428 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
#include "FPOpcodes.h"
|
||||||
|
|
||||||
|
#include "Emu/RSX/Common/simple_array.hpp"
|
||||||
|
#include "Emu/RSX/Program/RSXFragmentProgram.h"
|
||||||
|
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace rsx::assembler::FP
|
||||||
|
{
|
||||||
|
u8 get_operand_count(FP_opcode opcode)
|
||||||
|
{
|
||||||
|
switch (opcode)
|
||||||
|
{
|
||||||
|
case RSX_FP_OPCODE_NOP:
|
||||||
|
return 0;
|
||||||
|
case RSX_FP_OPCODE_MOV:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_MUL:
|
||||||
|
case RSX_FP_OPCODE_ADD:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_MAD:
|
||||||
|
return 3;
|
||||||
|
case RSX_FP_OPCODE_DP3:
|
||||||
|
case RSX_FP_OPCODE_DP4:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_DST:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_MIN:
|
||||||
|
case RSX_FP_OPCODE_MAX:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_SLT:
|
||||||
|
case RSX_FP_OPCODE_SGE:
|
||||||
|
case RSX_FP_OPCODE_SLE:
|
||||||
|
case RSX_FP_OPCODE_SGT:
|
||||||
|
case RSX_FP_OPCODE_SNE:
|
||||||
|
case RSX_FP_OPCODE_SEQ:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_FRC:
|
||||||
|
case RSX_FP_OPCODE_FLR:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_KIL:
|
||||||
|
return 0;
|
||||||
|
case RSX_FP_OPCODE_PK4:
|
||||||
|
case RSX_FP_OPCODE_UP4:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_DDX:
|
||||||
|
case RSX_FP_OPCODE_DDY:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_TEX:
|
||||||
|
case RSX_FP_OPCODE_TXD:
|
||||||
|
case RSX_FP_OPCODE_TXP:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_RCP:
|
||||||
|
case RSX_FP_OPCODE_RSQ:
|
||||||
|
case RSX_FP_OPCODE_EX2:
|
||||||
|
case RSX_FP_OPCODE_LG2:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_LIT:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_LRP:
|
||||||
|
return 3;
|
||||||
|
case RSX_FP_OPCODE_STR:
|
||||||
|
case RSX_FP_OPCODE_SFL:
|
||||||
|
return 0;
|
||||||
|
case RSX_FP_OPCODE_COS:
|
||||||
|
case RSX_FP_OPCODE_SIN:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_PK2:
|
||||||
|
case RSX_FP_OPCODE_UP2:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_PKB:
|
||||||
|
case RSX_FP_OPCODE_UPB:
|
||||||
|
case RSX_FP_OPCODE_PK16:
|
||||||
|
case RSX_FP_OPCODE_UP16:
|
||||||
|
case RSX_FP_OPCODE_PKG:
|
||||||
|
case RSX_FP_OPCODE_UPG:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_DP2A:
|
||||||
|
return 3;
|
||||||
|
case RSX_FP_OPCODE_TXL:
|
||||||
|
case RSX_FP_OPCODE_TXB:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_DP2:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_NRM:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_DIV:
|
||||||
|
case RSX_FP_OPCODE_DIVSQ:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_LIF:
|
||||||
|
return 1;
|
||||||
|
case RSX_FP_OPCODE_FENCT:
|
||||||
|
case RSX_FP_OPCODE_FENCB:
|
||||||
|
case RSX_FP_OPCODE_BRK:
|
||||||
|
case RSX_FP_OPCODE_CAL:
|
||||||
|
case RSX_FP_OPCODE_IFE:
|
||||||
|
case RSX_FP_OPCODE_LOOP:
|
||||||
|
case RSX_FP_OPCODE_REP:
|
||||||
|
case RSX_FP_OPCODE_RET:
|
||||||
|
// Flow control. Special registers are provided for these outside the common file
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
// The rest are unimplemented and not encountered in real software.
|
||||||
|
// TODO: Probe these on real PS3 and figure out what they actually do.
|
||||||
|
case RSX_FP_OPCODE_POW:
|
||||||
|
fmt::throw_exception("Unimplemented POW instruction."); // Unused
|
||||||
|
case RSX_FP_OPCODE_BEM:
|
||||||
|
case RSX_FP_OPCODE_TEXBEM:
|
||||||
|
case RSX_FP_OPCODE_TXPBEM:
|
||||||
|
case RSX_FP_OPCODE_BEMLUM:
|
||||||
|
fmt::throw_exception("Unimplemented BEM class instruction"); // Unused
|
||||||
|
case RSX_FP_OPCODE_REFL:
|
||||||
|
return 2;
|
||||||
|
case RSX_FP_OPCODE_TIMESWTEX:
|
||||||
|
fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a lane mask for the given operand.
|
||||||
|
// The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel.
|
||||||
|
u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand)
|
||||||
|
{
|
||||||
|
constexpr u32 x = 0b0001;
|
||||||
|
constexpr u32 y = 0b0010;
|
||||||
|
constexpr u32 z = 0b0100;
|
||||||
|
constexpr u32 w = 0b1000;
|
||||||
|
constexpr u32 xy = 0b0011;
|
||||||
|
constexpr u32 xyz = 0b0111;
|
||||||
|
constexpr u32 xyzw = 0b1111;
|
||||||
|
|
||||||
|
const auto decode = [&](const rsx::simple_array<u32>& masks) -> u32
|
||||||
|
{
|
||||||
|
return operand < masks.size()
|
||||||
|
? masks[operand]
|
||||||
|
: 0u;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto opcode = static_cast<FP_opcode>(instruction->opcode);
|
||||||
|
if (operand >= get_operand_count(opcode))
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
OPDEST d0 { .HEX = instruction->bytecode[0] };
|
||||||
|
const u32 dst_write_mask = d0.no_dest ? 0 : d0.write_mask;
|
||||||
|
|
||||||
|
switch (opcode)
|
||||||
|
{
|
||||||
|
case RSX_FP_OPCODE_NOP:
|
||||||
|
return 0;
|
||||||
|
case RSX_FP_OPCODE_MOV:
|
||||||
|
case RSX_FP_OPCODE_MUL:
|
||||||
|
case RSX_FP_OPCODE_ADD:
|
||||||
|
case RSX_FP_OPCODE_MAD:
|
||||||
|
return xyzw & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_DP3:
|
||||||
|
return xyz;
|
||||||
|
case RSX_FP_OPCODE_DP4:
|
||||||
|
return xyzw;
|
||||||
|
case RSX_FP_OPCODE_DST:
|
||||||
|
return decode({ y | z, y | w });
|
||||||
|
case RSX_FP_OPCODE_MIN:
|
||||||
|
case RSX_FP_OPCODE_MAX:
|
||||||
|
return xyzw & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_SLT:
|
||||||
|
case RSX_FP_OPCODE_SGE:
|
||||||
|
case RSX_FP_OPCODE_SLE:
|
||||||
|
case RSX_FP_OPCODE_SGT:
|
||||||
|
case RSX_FP_OPCODE_SNE:
|
||||||
|
case RSX_FP_OPCODE_SEQ:
|
||||||
|
return xyzw & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_FRC:
|
||||||
|
case RSX_FP_OPCODE_FLR:
|
||||||
|
return xyzw & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_KIL:
|
||||||
|
return 0;
|
||||||
|
case RSX_FP_OPCODE_PK4:
|
||||||
|
return xyzw;
|
||||||
|
case RSX_FP_OPCODE_UP4:
|
||||||
|
return x;
|
||||||
|
case RSX_FP_OPCODE_DDX:
|
||||||
|
case RSX_FP_OPCODE_DDY:
|
||||||
|
return xyzw & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_TEX:
|
||||||
|
case RSX_FP_OPCODE_TXD:
|
||||||
|
switch (prog.get_texture_dimension(d0.tex_num))
|
||||||
|
{
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_1d:
|
||||||
|
return x;
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_2d:
|
||||||
|
return xy;
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_3d:
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_cubemap:
|
||||||
|
return xyz;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case RSX_FP_OPCODE_TXP:
|
||||||
|
switch (prog.get_texture_dimension(d0.tex_num))
|
||||||
|
{
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_1d:
|
||||||
|
return xy;
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_2d:
|
||||||
|
return xyz;
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_3d:
|
||||||
|
case rsx::texture_dimension_extended::texture_dimension_cubemap:
|
||||||
|
return xyzw;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case RSX_FP_OPCODE_RCP:
|
||||||
|
case RSX_FP_OPCODE_RSQ:
|
||||||
|
case RSX_FP_OPCODE_EX2:
|
||||||
|
case RSX_FP_OPCODE_LG2:
|
||||||
|
return x;
|
||||||
|
case RSX_FP_OPCODE_LIT:
|
||||||
|
return xyzw;
|
||||||
|
case RSX_FP_OPCODE_LRP:
|
||||||
|
return xyzw & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_STR:
|
||||||
|
case RSX_FP_OPCODE_SFL:
|
||||||
|
return xyzw & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_COS:
|
||||||
|
case RSX_FP_OPCODE_SIN:
|
||||||
|
return x;
|
||||||
|
case RSX_FP_OPCODE_PK2:
|
||||||
|
return xy;
|
||||||
|
case RSX_FP_OPCODE_UP2:
|
||||||
|
return x;
|
||||||
|
case RSX_FP_OPCODE_PKB:
|
||||||
|
return xyzw;
|
||||||
|
case RSX_FP_OPCODE_UPB:
|
||||||
|
return x;
|
||||||
|
case RSX_FP_OPCODE_PK16:
|
||||||
|
return xy;
|
||||||
|
case RSX_FP_OPCODE_UP16:
|
||||||
|
return x;
|
||||||
|
case RSX_FP_OPCODE_PKG:
|
||||||
|
return xyzw;
|
||||||
|
case RSX_FP_OPCODE_UPG:
|
||||||
|
return x;
|
||||||
|
case RSX_FP_OPCODE_DP2A:
|
||||||
|
return decode({ xy, xy, x });
|
||||||
|
case RSX_FP_OPCODE_TXL:
|
||||||
|
case RSX_FP_OPCODE_TXB:
|
||||||
|
return decode({ xy, x });
|
||||||
|
case RSX_FP_OPCODE_REFL:
|
||||||
|
return xyzw;
|
||||||
|
case RSX_FP_OPCODE_DP2:
|
||||||
|
return xy;
|
||||||
|
case RSX_FP_OPCODE_NRM:
|
||||||
|
return xyz;
|
||||||
|
case RSX_FP_OPCODE_DIV:
|
||||||
|
case RSX_FP_OPCODE_DIVSQ:
|
||||||
|
return decode({ xyzw, x }) & dst_write_mask;
|
||||||
|
case RSX_FP_OPCODE_LIF:
|
||||||
|
return decode({ y | w });
|
||||||
|
case RSX_FP_OPCODE_FENCT:
|
||||||
|
case RSX_FP_OPCODE_FENCB:
|
||||||
|
case RSX_FP_OPCODE_BRK:
|
||||||
|
case RSX_FP_OPCODE_CAL:
|
||||||
|
case RSX_FP_OPCODE_IFE:
|
||||||
|
case RSX_FP_OPCODE_LOOP:
|
||||||
|
case RSX_FP_OPCODE_REP:
|
||||||
|
case RSX_FP_OPCODE_RET:
|
||||||
|
// Flow control. Special registers are provided for these outside the common file
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
case RSX_FP_OPCODE_POW:
|
||||||
|
fmt::throw_exception("Unimplemented POW instruction."); // Unused ??
|
||||||
|
case RSX_FP_OPCODE_BEM:
|
||||||
|
case RSX_FP_OPCODE_TEXBEM:
|
||||||
|
case RSX_FP_OPCODE_TXPBEM:
|
||||||
|
case RSX_FP_OPCODE_BEMLUM:
|
||||||
|
fmt::throw_exception("Unimplemented BEM class instruction"); // Unused
|
||||||
|
case RSX_FP_OPCODE_TIMESWTEX:
|
||||||
|
fmt::throw_exception("Unimplemented TIMESWTEX instruction"); // Unused
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolved vector lane mask with swizzles applied.
|
||||||
|
u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand)
|
||||||
|
{
|
||||||
|
// Brute-force this. There's only 16 permutations.
|
||||||
|
constexpr u32 x = 0b0001;
|
||||||
|
constexpr u32 y = 0b0010;
|
||||||
|
constexpr u32 z = 0b0100;
|
||||||
|
constexpr u32 w = 0b1000;
|
||||||
|
|
||||||
|
const u32 lane_mask = get_src_vector_lane_mask(prog, instruction, operand);
|
||||||
|
if (!lane_mask)
|
||||||
|
{
|
||||||
|
return lane_mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now we resolve matching lanes.
|
||||||
|
// This sequence can be drastically sped up using lookup tables but that will come later.
|
||||||
|
std::unordered_set<u32> inputs;
|
||||||
|
SRC_Common src { .HEX = instruction->bytecode[operand + 1] };
|
||||||
|
|
||||||
|
if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lane_mask & x) inputs.insert(src.swizzle_x);
|
||||||
|
if (lane_mask & y) inputs.insert(src.swizzle_y);
|
||||||
|
if (lane_mask & z) inputs.insert(src.swizzle_z);
|
||||||
|
if (lane_mask & w) inputs.insert(src.swizzle_w);
|
||||||
|
|
||||||
|
u32 result = 0;
|
||||||
|
if (inputs.contains(0)) result |= x;
|
||||||
|
if (inputs.contains(1)) result |= y;
|
||||||
|
if (inputs.contains(2)) result |= z;
|
||||||
|
if (inputs.contains(3)) result |= w;
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool is_delay_slot(const Instruction* instruction)
|
||||||
|
{
|
||||||
|
OPDEST dst { .HEX = instruction->bytecode[0] };
|
||||||
|
SRC0 src0 { .HEX = instruction->bytecode[1] };
|
||||||
|
SRC1 src1{ .HEX = instruction->bytecode[2] };
|
||||||
|
|
||||||
|
if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV
|
||||||
|
dst.no_dest || // Must have a sink
|
||||||
|
src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg
|
||||||
|
dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self
|
||||||
|
dst.fp16 || // Always full lane. We need to collect more data on this but it won't matter
|
||||||
|
dst.saturate || // Precision modifier
|
||||||
|
(dst.prec != RSX_FP_PRECISION_REAL &&
|
||||||
|
dst.prec != RSX_FP_PRECISION_UNKNOWN)) // Cannot have precision modifiers
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have precision modifiers on the source
|
||||||
|
if (src0.abs || src0.neg || src1.scale)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst.mask_x && src0.swizzle_x != 0) return false;
|
||||||
|
if (dst.mask_y && src0.swizzle_y != 1) return false;
|
||||||
|
if (dst.mask_z && src0.swizzle_z != 2) return false;
|
||||||
|
if (dst.mask_w && src0.swizzle_w != 3) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand)
|
||||||
|
{
|
||||||
|
SRC_Common src{ .HEX = instruction->bytecode[operand + 1] };
|
||||||
|
if (src.reg_type != RSX_FP_REGISTER_TYPE_TEMP)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 read_lanes = get_src_vector_lane_mask_shuffled(prog, instruction, operand);
|
||||||
|
if (!read_lanes)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef ref{ .mask = read_lanes };
|
||||||
|
Register& reg = ref.reg;
|
||||||
|
|
||||||
|
reg.f16 = !!src.fp16;
|
||||||
|
reg.id = src.tmp_reg_index;
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef get_dst_register(const Instruction* instruction)
|
||||||
|
{
|
||||||
|
OPDEST dst { .HEX = instruction->bytecode[0] };
|
||||||
|
if (dst.no_dest)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef ref{ .mask = dst.write_mask };
|
||||||
|
ref.reg.f16 = dst.fp16;
|
||||||
|
ref.reg.id = dst.dest_reg;
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert vector mask to file range
|
||||||
|
rsx::simple_array<u32> get_register_file_range(const RegisterRef& reg)
|
||||||
|
{
|
||||||
|
if (!reg.mask)
|
||||||
|
{
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
constexpr u32 register_file_max_len = 48 * 8; // H0 - H47, R0 - R23
|
||||||
|
|
||||||
|
const u32 lane_width = reg.reg.f16 ? 2 : 4;
|
||||||
|
const u32 file_offset = reg.reg.id * lane_width * 4;
|
||||||
|
|
||||||
|
ensure(file_offset < register_file_max_len, "Invalid register index");
|
||||||
|
|
||||||
|
rsx::simple_array<u32> result{};
|
||||||
|
auto insert_lane = [&](u32 word_offset)
|
||||||
|
{
|
||||||
|
for (u32 i = 0; i < lane_width; ++i)
|
||||||
|
{
|
||||||
|
result.push_back(file_offset + (word_offset * lane_width) + i);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (reg.x) insert_lane(0);
|
||||||
|
if (reg.y) insert_lane(1);
|
||||||
|
if (reg.z) insert_lane(2);
|
||||||
|
if (reg.w) insert_lane(3);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
111
rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h
Normal file
111
rpcs3/Emu/RSX/Program/Assembler/FPOpcodes.h
Normal file
@ -0,0 +1,111 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "IR.h"
|
||||||
|
#include "Emu/RSX/Common/simple_array.hpp"
|
||||||
|
|
||||||
|
struct RSXFragmentProgram;
|
||||||
|
|
||||||
|
namespace rsx::assembler
|
||||||
|
{
|
||||||
|
enum FP_opcode
|
||||||
|
{
|
||||||
|
RSX_FP_OPCODE_NOP = 0x00, // No-Operation
|
||||||
|
RSX_FP_OPCODE_MOV = 0x01, // Move
|
||||||
|
RSX_FP_OPCODE_MUL = 0x02, // Multiply
|
||||||
|
RSX_FP_OPCODE_ADD = 0x03, // Add
|
||||||
|
RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add
|
||||||
|
RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product
|
||||||
|
RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product
|
||||||
|
RSX_FP_OPCODE_DST = 0x07, // Distance
|
||||||
|
RSX_FP_OPCODE_MIN = 0x08, // Minimum
|
||||||
|
RSX_FP_OPCODE_MAX = 0x09, // Maximum
|
||||||
|
RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan
|
||||||
|
RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual
|
||||||
|
RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual
|
||||||
|
RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan
|
||||||
|
RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual
|
||||||
|
RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal
|
||||||
|
RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract)
|
||||||
|
RSX_FP_OPCODE_FLR = 0x11, // Floor
|
||||||
|
RSX_FP_OPCODE_KIL = 0x12, // Kill fragment
|
||||||
|
RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values
|
||||||
|
RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values
|
||||||
|
RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x)
|
||||||
|
RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y)
|
||||||
|
RSX_FP_OPCODE_TEX = 0x17, // Texture lookup
|
||||||
|
RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup)
|
||||||
|
RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives)
|
||||||
|
RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal
|
||||||
|
RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root
|
||||||
|
RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2
|
||||||
|
RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2
|
||||||
|
RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients
|
||||||
|
RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation
|
||||||
|
RSX_FP_OPCODE_STR = 0x20, // Set-If-True
|
||||||
|
RSX_FP_OPCODE_SFL = 0x21, // Set-If-False
|
||||||
|
RSX_FP_OPCODE_COS = 0x22, // Cosine
|
||||||
|
RSX_FP_OPCODE_SIN = 0x23, // Sine
|
||||||
|
RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats
|
||||||
|
RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats
|
||||||
|
RSX_FP_OPCODE_POW = 0x26, // Power
|
||||||
|
RSX_FP_OPCODE_PKB = 0x27, // Pack bytes
|
||||||
|
RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes
|
||||||
|
RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits
|
||||||
|
RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16
|
||||||
|
RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform)
|
||||||
|
RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation
|
||||||
|
RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma
|
||||||
|
RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition
|
||||||
|
RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD
|
||||||
|
RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias
|
||||||
|
RSX_FP_OPCODE_TEXBEM = 0x33,
|
||||||
|
RSX_FP_OPCODE_TXPBEM = 0x34,
|
||||||
|
RSX_FP_OPCODE_BEMLUM = 0x35,
|
||||||
|
RSX_FP_OPCODE_REFL = 0x36, // Reflection vector
|
||||||
|
RSX_FP_OPCODE_TIMESWTEX = 0x37,
|
||||||
|
RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product
|
||||||
|
RSX_FP_OPCODE_NRM = 0x39, // Normalize
|
||||||
|
RSX_FP_OPCODE_DIV = 0x3A, // Division
|
||||||
|
RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root
|
||||||
|
RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT
|
||||||
|
RSX_FP_OPCODE_FENCT = 0x3D, // Fence T?
|
||||||
|
RSX_FP_OPCODE_FENCB = 0x3E, // Fence B?
|
||||||
|
RSX_FP_OPCODE_BRK = 0x40, // Break
|
||||||
|
RSX_FP_OPCODE_CAL = 0x41, // Subroutine call
|
||||||
|
RSX_FP_OPCODE_IFE = 0x42, // If
|
||||||
|
RSX_FP_OPCODE_LOOP = 0x43, // Loop
|
||||||
|
RSX_FP_OPCODE_REP = 0x44, // Repeat
|
||||||
|
RSX_FP_OPCODE_RET = 0x45, // Return
|
||||||
|
|
||||||
|
|
||||||
|
// Custom opcodes for dependency injection
|
||||||
|
RSX_FP_OPCODE_OR16_LO = 0x46, // Performs a 16-bit OR, taking one register channel as input and overwriting low 16 bits of the output
|
||||||
|
RSX_FP_OPCODE_OR16_HI = 0x47, // Same as the lo variant but now overwrites the high 16-bit block
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace FP
|
||||||
|
{
|
||||||
|
// Returns number of operands consumed by an instruction
|
||||||
|
u8 get_operand_count(FP_opcode opcode);
|
||||||
|
|
||||||
|
// Returns a lane mask for the given operand.
|
||||||
|
// The lane mask is the fixed function hardware lane so swizzles need to be applied on top to resolve the real data channel.
|
||||||
|
u32 get_src_vector_lane_mask(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand);
|
||||||
|
|
||||||
|
// Resolved vector lane mask with swizzles applied.
|
||||||
|
u32 get_src_vector_lane_mask_shuffled(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand);
|
||||||
|
|
||||||
|
// Returns true on delay slot instructions.
|
||||||
|
bool is_delay_slot(const Instruction* instruction);
|
||||||
|
|
||||||
|
// Generate register references
|
||||||
|
RegisterRef get_src_register(const RSXFragmentProgram& prog, const Instruction* instruction, u32 operand);
|
||||||
|
RegisterRef get_dst_register(const Instruction* instruction);
|
||||||
|
|
||||||
|
// Convert vector mask to file ranges
|
||||||
|
rsx::simple_array<u32> get_register_file_range(const RegisterRef& reg);
|
||||||
|
|
||||||
|
// Compile a register file annotated blob to register references
|
||||||
|
std::vector<RegisterRef> compile_register_file(const std::array<char, 48 * 8>& file);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,4 @@
|
|||||||
#include "stdafx.h"
|
#include "stdafx.h"
|
||||||
|
|
||||||
#include "CFG.h"
|
#include "CFG.h"
|
||||||
|
|
||||||
#include "Emu/RSX/Common/simple_array.hpp"
|
#include "Emu/RSX/Common/simple_array.hpp"
|
||||||
@ -75,8 +74,19 @@ namespace rsx::assembler
|
|||||||
{
|
{
|
||||||
if (auto found = find_block_for_pc(id))
|
if (auto found = find_block_for_pc(id))
|
||||||
{
|
{
|
||||||
parent->insert_succ(found, edge_type);
|
auto succ = found;
|
||||||
found->insert_pred(parent, edge_type);
|
if (found->is_of_type(EdgeType::ELSE) &&
|
||||||
|
(edge_type == EdgeType::ENDIF || edge_type == EdgeType::ENDLOOP))
|
||||||
|
{
|
||||||
|
// If we landed on an "ELSE" node, link to its "ENDIF" counterpart
|
||||||
|
auto if_parent = found->pred.front().from;
|
||||||
|
auto endif_edge = std::find_if(if_parent->succ.begin(), if_parent->succ.end(), FN(x.type == EdgeType::ENDIF));
|
||||||
|
ensure(endif_edge != if_parent->succ.end(), "CFG: Invalid ELSE node");
|
||||||
|
succ = endif_edge->to;
|
||||||
|
}
|
||||||
|
|
||||||
|
parent->insert_succ(succ, edge_type);
|
||||||
|
succ->insert_pred(parent, edge_type);
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,6 +111,43 @@ namespace rsx::assembler
|
|||||||
|
|
||||||
if (found)
|
if (found)
|
||||||
{
|
{
|
||||||
|
auto front_edge = std::find_if(bb->pred.begin(), bb->pred.end(), FN(x.type != EdgeType::ENDIF && x.type != EdgeType::ENDLOOP));
|
||||||
|
if (front_edge != bb->pred.end())
|
||||||
|
{
|
||||||
|
auto parent = ensure(front_edge->from);
|
||||||
|
switch (front_edge->type)
|
||||||
|
{
|
||||||
|
case EdgeType::IF:
|
||||||
|
case EdgeType::ELSE:
|
||||||
|
{
|
||||||
|
// Find the merge node from the parent.
|
||||||
|
auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDIF));
|
||||||
|
ensure(succ != parent->succ.end(), "CFG: Broken IF linkage. Please report to developers.");
|
||||||
|
bb->insert_succ(succ->to, EdgeType::ENDIF);
|
||||||
|
succ->to->insert_pred(bb, EdgeType::ENDIF);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case EdgeType::LOOP:
|
||||||
|
{
|
||||||
|
// Find the merge node from the parent
|
||||||
|
auto succ = std::find_if(parent->succ.begin(), parent->succ.end(), FN(x.type == EdgeType::ENDLOOP));
|
||||||
|
ensure(succ != parent->succ.end(), "CFG: Broken LOOP linkage. Please report to developers.");
|
||||||
|
bb->insert_succ(succ->to, EdgeType::ENDLOOP);
|
||||||
|
succ->to->insert_pred(bb, EdgeType::ENDLOOP);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// Missing an edge type?
|
||||||
|
rsx_log.error("CFG: Unexpected block exit. Report to developers.");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (bb->pred.empty())
|
||||||
|
{
|
||||||
|
// Impossible situation.
|
||||||
|
rsx_log.error("CFG: Child block has no parent but has successor! Report to developers.");
|
||||||
|
}
|
||||||
|
|
||||||
bb = *found;
|
bb = *found;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -113,7 +160,7 @@ namespace rsx::assembler
|
|||||||
src2.HEX = decoded._u32[3];
|
src2.HEX = decoded._u32[3];
|
||||||
|
|
||||||
end = !!dst.end;
|
end = !!dst.end;
|
||||||
const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6);
|
const u32 opcode = dst.opcode | (src1.opcode_hi << 6);
|
||||||
|
|
||||||
if (opcode == RSX_FP_OPCODE_NOP)
|
if (opcode == RSX_FP_OPCODE_NOP)
|
||||||
{
|
{
|
||||||
@ -126,6 +173,7 @@ namespace rsx::assembler
|
|||||||
std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16);
|
std::memcpy(ir_inst.bytecode, &decoded._u32[0], 16);
|
||||||
ir_inst.length = 4;
|
ir_inst.length = 4;
|
||||||
ir_inst.addr = pc * 16;
|
ir_inst.addr = pc * 16;
|
||||||
|
ir_inst.opcode = opcode;
|
||||||
|
|
||||||
switch (opcode)
|
switch (opcode)
|
||||||
{
|
{
|
||||||
@ -146,22 +194,45 @@ namespace rsx::assembler
|
|||||||
case RSX_FP_OPCODE_IFE:
|
case RSX_FP_OPCODE_IFE:
|
||||||
{
|
{
|
||||||
// Inserts if and else and end blocks
|
// Inserts if and else and end blocks
|
||||||
auto parent = bb;
|
const u32 end_addr = src2.end_offset >> 2u;
|
||||||
bb = safe_insert_block(parent, pc + 1, EdgeType::IF);
|
const u32 else_addr = src1.else_offset >> 2u;
|
||||||
if (src2.end_offset != src1.else_offset)
|
if (end_addr == pc + 1u)
|
||||||
{
|
{
|
||||||
else_blocks.push_back(safe_insert_block(parent, src1.else_offset >> 2, EdgeType::ELSE));
|
// NOP. Empty IF block
|
||||||
|
bb->instructions.pop_back();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDIF));
|
|
||||||
|
if (else_addr > end_addr)
|
||||||
|
{
|
||||||
|
// Our systems support this, but it is not verified on real hardware.
|
||||||
|
rsx_log.error("CFG: Non-contiguous branch detected. Report to developers.");
|
||||||
|
}
|
||||||
|
|
||||||
|
auto parent = bb;
|
||||||
|
bb = safe_insert_block(parent, pc + 1u, EdgeType::IF);
|
||||||
|
if (end_addr != else_addr)
|
||||||
|
{
|
||||||
|
else_blocks.push_back(safe_insert_block(parent, else_addr, EdgeType::ELSE));
|
||||||
|
}
|
||||||
|
end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDIF));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case RSX_FP_OPCODE_LOOP:
|
case RSX_FP_OPCODE_LOOP:
|
||||||
case RSX_FP_OPCODE_REP:
|
case RSX_FP_OPCODE_REP:
|
||||||
{
|
{
|
||||||
// Inserts for and end blocks
|
// Inserts for and end blocks
|
||||||
|
const u32 end_addr = src2.end_offset >> 2u;
|
||||||
|
if (end_addr == pc + 1u)
|
||||||
|
{
|
||||||
|
// NOP. Empty LOOP block
|
||||||
|
bb->instructions.pop_back();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
auto parent = bb;
|
auto parent = bb;
|
||||||
bb = safe_insert_block(parent, pc + 1, EdgeType::LOOP);
|
bb = safe_insert_block(parent, pc + 1u, EdgeType::LOOP);
|
||||||
end_blocks.push_back(safe_insert_block(parent, src2.end_offset >> 2, EdgeType::ENDLOOP));
|
end_blocks.push_back(safe_insert_block(parent, end_addr, EdgeType::ENDLOOP));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@ -174,6 +245,7 @@ namespace rsx::assembler
|
|||||||
ir_inst.length += 4;
|
ir_inst.length += 4;
|
||||||
pc++;
|
pc++;
|
||||||
}
|
}
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
pc++;
|
pc++;
|
||||||
|
|||||||
@ -10,6 +10,16 @@ namespace rsx::assembler
|
|||||||
{
|
{
|
||||||
int id = 0;
|
int id = 0;
|
||||||
bool f16 = false;
|
bool f16 = false;
|
||||||
|
|
||||||
|
bool operator == (const Register& other) const
|
||||||
|
{
|
||||||
|
return id == other.id && f16 == other.f16;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string to_string() const
|
||||||
|
{
|
||||||
|
return std::string(f16 ? "H" : "R") + std::to_string(id);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct RegisterRef
|
struct RegisterRef
|
||||||
@ -19,7 +29,7 @@ namespace rsx::assembler
|
|||||||
// Vector information
|
// Vector information
|
||||||
union
|
union
|
||||||
{
|
{
|
||||||
u32 mask;
|
u32 mask = 0;
|
||||||
|
|
||||||
struct
|
struct
|
||||||
{
|
{
|
||||||
@ -29,6 +39,16 @@ namespace rsx::assembler
|
|||||||
bool w : 1;
|
bool w : 1;
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
operator bool() const
|
||||||
|
{
|
||||||
|
return !!mask;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator == (const RegisterRef& other) const
|
||||||
|
{
|
||||||
|
return reg == other.reg && mask == other.mask;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Instruction
|
struct Instruction
|
||||||
@ -71,6 +91,7 @@ namespace rsx::assembler
|
|||||||
struct BasicBlock
|
struct BasicBlock
|
||||||
{
|
{
|
||||||
u32 id = 0;
|
u32 id = 0;
|
||||||
|
|
||||||
std::vector<Instruction> instructions; // Program instructions for the RSX processor
|
std::vector<Instruction> instructions; // Program instructions for the RSX processor
|
||||||
std::vector<FlowEdge> succ; // Forward edges. Sorted closest first.
|
std::vector<FlowEdge> succ; // Forward edges. Sorted closest first.
|
||||||
std::vector<FlowEdge> pred; // Back edges. Sorted closest first.
|
std::vector<FlowEdge> pred; // Back edges. Sorted closest first.
|
||||||
@ -78,6 +99,9 @@ namespace rsx::assembler
|
|||||||
std::vector<Instruction> prologue; // Prologue, created by passes
|
std::vector<Instruction> prologue; // Prologue, created by passes
|
||||||
std::vector<Instruction> epilogue; // Epilogue, created by passes
|
std::vector<Instruction> epilogue; // Epilogue, created by passes
|
||||||
|
|
||||||
|
std::vector<RegisterRef> input_list; // Register inputs.
|
||||||
|
std::vector<RegisterRef> clobber_list; // Clobbered outputs
|
||||||
|
|
||||||
FlowEdge* insert_succ(BasicBlock* b, EdgeType type = EdgeType::NONE)
|
FlowEdge* insert_succ(BasicBlock* b, EdgeType type = EdgeType::NONE)
|
||||||
{
|
{
|
||||||
FlowEdge e{ .type = type, .from = this, .to = b };
|
FlowEdge e{ .type = type, .from = this, .to = b };
|
||||||
@ -91,5 +115,25 @@ namespace rsx::assembler
|
|||||||
pred.push_back(e);
|
pred.push_back(e);
|
||||||
return &pred.back();
|
return &pred.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool is_of_type(EdgeType type) const
|
||||||
|
{
|
||||||
|
return pred.size() == 1 &&
|
||||||
|
pred.front().type == type;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_sibling_of_type(EdgeType type) const
|
||||||
|
{
|
||||||
|
if (pred.size() != 1)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto source_node = pred.front().from;
|
||||||
|
return std::find_if(
|
||||||
|
source_node->succ.begin(),
|
||||||
|
source_node->succ.end(),
|
||||||
|
FN(x.type == type)) != source_node->succ.end();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,230 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
|
||||||
|
#include "RegisterAnnotationPass.h"
|
||||||
|
#include "Emu/RSX/Program/Assembler/FPOpcodes.h"
|
||||||
|
#include "Emu/RSX/Program/RSXFragmentProgram.h"
|
||||||
|
|
||||||
|
#include <span>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace rsx::assembler::FP
|
||||||
|
{
|
||||||
|
static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers
|
||||||
|
static constexpr char content_unknown = 0;
|
||||||
|
static constexpr char content_float32 = 'R';
|
||||||
|
static constexpr char content_float16 = 'H';
|
||||||
|
static constexpr char content_dual = 'D';
|
||||||
|
|
||||||
|
bool is_delay_slot(const Instruction& instruction)
|
||||||
|
{
|
||||||
|
const OPDEST dst{ .HEX = instruction.bytecode[0] };
|
||||||
|
const SRC0 src0{ .HEX = instruction.bytecode[1] };
|
||||||
|
const SRC1 src1{ .HEX = instruction.bytecode[2] };
|
||||||
|
|
||||||
|
if (dst.opcode != RSX_FP_OPCODE_MOV || // These slots are always populated with MOV
|
||||||
|
dst.no_dest || // Must have a sink
|
||||||
|
src0.reg_type != RSX_FP_REGISTER_TYPE_TEMP || // Must read from reg
|
||||||
|
dst.dest_reg != src0.tmp_reg_index || // Must be a write-to-self
|
||||||
|
dst.fp16 != src0.fp16 || // Must really be the same register
|
||||||
|
src0.abs || src0.neg ||
|
||||||
|
dst.saturate) // Precision modifier
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (dst.prec)
|
||||||
|
{
|
||||||
|
case RSX_FP_PRECISION_REAL:
|
||||||
|
case RSX_FP_PRECISION_UNKNOWN:
|
||||||
|
break;
|
||||||
|
case RSX_FP_PRECISION_HALF:
|
||||||
|
if (!src0.fp16) return false;
|
||||||
|
break;
|
||||||
|
case RSX_FP_PRECISION_FIXED12:
|
||||||
|
case RSX_FP_PRECISION_FIXED9:
|
||||||
|
case RSX_FP_PRECISION_SATURATE:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if we have precision modifiers on the source
|
||||||
|
if (src0.abs || src0.neg || src1.scale)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (dst.mask_x && src0.swizzle_x != 0) return false;
|
||||||
|
if (dst.mask_y && src0.swizzle_y != 1) return false;
|
||||||
|
if (dst.mask_z && src0.swizzle_z != 2) return false;
|
||||||
|
if (dst.mask_w && src0.swizzle_w != 3) return false;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<RegisterRef> compile_register_file(const std::array<char, 48 * 8>& file)
|
||||||
|
{
|
||||||
|
std::vector<RegisterRef> results;
|
||||||
|
|
||||||
|
// F16 register processing
|
||||||
|
for (int reg16 = 0; reg16 < 48; ++reg16)
|
||||||
|
{
|
||||||
|
const u32 offset = reg16 * 8;
|
||||||
|
auto word = *reinterpret_cast<const u64*>(&file[offset]);
|
||||||
|
|
||||||
|
if (!word) [[ likely ]]
|
||||||
|
{
|
||||||
|
// Trivial rejection, very commonly hit.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef ref{ .reg {.id = reg16, .f16 = true } };
|
||||||
|
ref.x = (file[offset] == content_dual || file[offset] == content_float16);
|
||||||
|
ref.y = (file[offset + 2] == content_dual || file[offset + 2] == content_float16);
|
||||||
|
ref.z = (file[offset + 4] == content_dual || file[offset + 4] == content_float16);
|
||||||
|
ref.w = (file[offset + 6] == content_dual || file[offset + 6] == content_float16);
|
||||||
|
|
||||||
|
if (ref)
|
||||||
|
{
|
||||||
|
results.push_back(std::move(ref));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper to check a span for 32-bit access
|
||||||
|
auto match_any_32 = [](const std::span<const char> lanes)
|
||||||
|
{
|
||||||
|
return std::any_of(lanes.begin(), lanes.end(), FN(x == content_dual || x == content_float32));
|
||||||
|
};
|
||||||
|
|
||||||
|
// F32 register processing
|
||||||
|
for (int reg32 = 0; reg32 < 24; ++reg32)
|
||||||
|
{
|
||||||
|
const u32 offset = reg32 * 16;
|
||||||
|
auto word0 = *reinterpret_cast<const u64*>(&file[offset]);
|
||||||
|
auto word1 = *reinterpret_cast<const u64*>(&file[offset + 8]);
|
||||||
|
|
||||||
|
if (!word0 && !word1) [[ likely ]]
|
||||||
|
{
|
||||||
|
// Trivial rejection, very commonly hit.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef ref{ .reg {.id = reg32, .f16 = false } };
|
||||||
|
if (word0)
|
||||||
|
{
|
||||||
|
ref.x = match_any_32({ &file[offset], 4 });
|
||||||
|
ref.y = match_any_32({ &file[offset + 4], 4 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (word1)
|
||||||
|
{
|
||||||
|
ref.z = match_any_32({ &file[offset + 8], 4 });
|
||||||
|
ref.w = match_any_32({ &file[offset + 12], 4 });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref)
|
||||||
|
{
|
||||||
|
results.push_back(std::move(ref));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Decay instructions into register references
|
||||||
|
void annotate_instructions(BasicBlock* block, const RSXFragmentProgram& prog, bool skip_delay_slots)
|
||||||
|
{
|
||||||
|
for (auto& instruction : block->instructions)
|
||||||
|
{
|
||||||
|
if (skip_delay_slots && is_delay_slot(instruction))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 operand_count = get_operand_count(static_cast<FP_opcode>(instruction.opcode));
|
||||||
|
for (u32 i = 0; i < operand_count; i++)
|
||||||
|
{
|
||||||
|
RegisterRef reg = get_src_register(prog, &instruction, i);
|
||||||
|
if (!reg.mask)
|
||||||
|
{
|
||||||
|
// Likely a literal constant
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
instruction.srcs.push_back(std::move(reg));
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef dst = get_dst_register(&instruction);
|
||||||
|
if (dst)
|
||||||
|
{
|
||||||
|
instruction.dsts.push_back(std::move(dst));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Annotate each block with input and output lanes (read and clobber list)
|
||||||
|
void annotate_block_io(BasicBlock* block)
|
||||||
|
{
|
||||||
|
alignas(16) std::array<char, register_file_length> output_register_file;
|
||||||
|
alignas(16) std::array<char, register_file_length> input_register_file; // We'll eventually replace with a bitfield mask, but for ease of debugging, we use char for now
|
||||||
|
|
||||||
|
std::memset(output_register_file.data(), content_unknown, register_file_length);
|
||||||
|
std::memset(input_register_file.data(), content_unknown, register_file_length);
|
||||||
|
|
||||||
|
for (const auto& instruction : block->instructions)
|
||||||
|
{
|
||||||
|
for (const auto& src : instruction.srcs)
|
||||||
|
{
|
||||||
|
const auto read_bytes = get_register_file_range(src);
|
||||||
|
const char expected_type = src.reg.f16 ? content_float16 : content_float32;
|
||||||
|
for (const auto& index : read_bytes)
|
||||||
|
{
|
||||||
|
if (output_register_file[index] != content_unknown)
|
||||||
|
{
|
||||||
|
// Something already wrote to this lane
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_register_file[index] == expected_type)
|
||||||
|
{
|
||||||
|
// We already know about this input
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input_register_file[index] == 0)
|
||||||
|
{
|
||||||
|
// Not known, tag as input
|
||||||
|
input_register_file[index] = expected_type;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collision on the lane
|
||||||
|
input_register_file[index] = content_dual;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!instruction.dsts.empty())
|
||||||
|
{
|
||||||
|
const auto& dst = instruction.dsts.front();
|
||||||
|
const auto write_bytes = get_register_file_range(dst);
|
||||||
|
const char expected_type = dst.reg.f16 ? content_float16 : content_float32;
|
||||||
|
|
||||||
|
for (const auto& index : write_bytes)
|
||||||
|
{
|
||||||
|
output_register_file[index] = expected_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile the input and output refs into register references
|
||||||
|
block->clobber_list = compile_register_file(output_register_file);
|
||||||
|
block->input_list = compile_register_file(input_register_file);
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterAnnotationPass::run(FlowGraph& graph)
|
||||||
|
{
|
||||||
|
for (auto& block : graph.blocks)
|
||||||
|
{
|
||||||
|
annotate_instructions(&block, m_prog, m_config.skip_delay_slots);
|
||||||
|
annotate_block_io(&block);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,34 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../CFG.h"
|
||||||
|
|
||||||
|
struct RSXFragmentProgram;
|
||||||
|
|
||||||
|
namespace rsx::assembler::FP
|
||||||
|
{
|
||||||
|
struct RegisterAnnotationPassOptions
|
||||||
|
{
|
||||||
|
bool skip_delay_slots = false; // When enabled, detect delay slots and ignore annotating them.
|
||||||
|
};
|
||||||
|
|
||||||
|
// The annotation pass annotates each basic block with 2 pieces of information:
|
||||||
|
// 1. The "input" register list for a block.
|
||||||
|
// 2. The "output" register list for a block (clobber list).
|
||||||
|
// The information can be used by other passes to set up prologue/epilogue on each block.
|
||||||
|
// The pass also populates register reference members of each instruction, such as the input and output lanes.
|
||||||
|
class RegisterAnnotationPass : public CFGPass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RegisterAnnotationPass(
|
||||||
|
const RSXFragmentProgram& prog,
|
||||||
|
const RegisterAnnotationPassOptions& options = {})
|
||||||
|
: m_prog(prog), m_config(options)
|
||||||
|
{}
|
||||||
|
|
||||||
|
void run(FlowGraph& graph) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
const RSXFragmentProgram& m_prog;
|
||||||
|
RegisterAnnotationPassOptions m_config;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -0,0 +1,490 @@
|
|||||||
|
#include "stdafx.h"
|
||||||
|
|
||||||
|
#include "RegisterDependencyPass.h"
|
||||||
|
#include "Emu/RSX/Program/Assembler/FPOpcodes.h"
|
||||||
|
#include "Emu/RSX/Program/RSXFragmentProgram.h"
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
namespace rsx::assembler::FP
|
||||||
|
{
|
||||||
|
static constexpr u32 register_file_length = 48 * 8; // 24 F32 or 48 F16 registers
|
||||||
|
static constexpr char content_unknown = 0;
|
||||||
|
static constexpr char content_float32 = 'R';
|
||||||
|
static constexpr char content_float16 = 'H';
|
||||||
|
static constexpr char content_dual = 'D';
|
||||||
|
|
||||||
|
using register_file_t = std::array<char, register_file_length>;
|
||||||
|
|
||||||
|
struct DependencyPassContext
|
||||||
|
{
|
||||||
|
std::unordered_map<BasicBlock*, register_file_t> exec_register_map;
|
||||||
|
std::unordered_map<BasicBlock*, register_file_t> sync_register_map;
|
||||||
|
};
|
||||||
|
|
||||||
|
enum Register32BarrierFlags
|
||||||
|
{
|
||||||
|
NONE = 0,
|
||||||
|
OR_WORD0 = 1,
|
||||||
|
OR_WORD1 = 2,
|
||||||
|
DEFAULT = OR_WORD0 | OR_WORD1
|
||||||
|
};
|
||||||
|
|
||||||
|
struct RegisterBarrier32
|
||||||
|
{
|
||||||
|
RegisterRef ref;
|
||||||
|
u32 flags[4];
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<RegisterRef> decode_lanes16(const std::unordered_set<u32>& lanes)
|
||||||
|
{
|
||||||
|
std::vector<RegisterRef> result;
|
||||||
|
|
||||||
|
for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 8)
|
||||||
|
{
|
||||||
|
// Each register has 4 16-bit lanes
|
||||||
|
u32 mask = 0;
|
||||||
|
if (lanes.contains(file_offset + 0)) mask |= (1u << 0);
|
||||||
|
if (lanes.contains(file_offset + 2)) mask |= (1u << 1);
|
||||||
|
if (lanes.contains(file_offset + 4)) mask |= (1u << 2);
|
||||||
|
if (lanes.contains(file_offset + 6)) mask |= (1u << 3);
|
||||||
|
|
||||||
|
if (mask == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
RegisterRef ref{ .reg{.id = static_cast<int>(index), .f16 = true } };
|
||||||
|
ref.mask = mask;
|
||||||
|
result.push_back(std::move(ref));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<RegisterBarrier32> decode_lanes32(const std::unordered_set<u32>& lanes)
|
||||||
|
{
|
||||||
|
std::vector<RegisterBarrier32> result;
|
||||||
|
|
||||||
|
for (u32 index = 0, file_offset = 0; index < 48; ++index, file_offset += 16)
|
||||||
|
{
|
||||||
|
// Each register has 8 16-bit lanes
|
||||||
|
RegisterBarrier32 barrier{};
|
||||||
|
auto& ref = barrier.ref;
|
||||||
|
|
||||||
|
for (u32 lane = 0; lane < 16; lane += 2)
|
||||||
|
{
|
||||||
|
if (!lanes.contains(file_offset + lane))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const u32 ch = (lane / 4);
|
||||||
|
const u32 flags = (lane & 3)
|
||||||
|
? Register32BarrierFlags::OR_WORD1
|
||||||
|
: Register32BarrierFlags::OR_WORD0;
|
||||||
|
|
||||||
|
ref.mask |= (1u << ch);
|
||||||
|
barrier.flags[ch] |= flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ref.mask == 0)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref.reg = {.id = static_cast<int>(index), .f16 = false };
|
||||||
|
result.push_back(std::move(barrier));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Instruction> build_barrier32(const RegisterBarrier32& barrier)
|
||||||
|
{
|
||||||
|
// Upto 4 instructions are needed per 32-bit register
|
||||||
|
// R0.x = packHalf2x16(H0.xy)
|
||||||
|
// R0.y = packHalf2x16(H0.zw);
|
||||||
|
// R0.z = packHalf2x16(H1.xy);
|
||||||
|
// R0.w = packHalf2x16(H1.zw);
|
||||||
|
|
||||||
|
std::vector<Instruction> result;
|
||||||
|
|
||||||
|
for (u32 mask = barrier.ref.mask, ch = 0; mask > 0; mask >>= 1, ++ch)
|
||||||
|
{
|
||||||
|
if (!(mask & 1))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& reg = barrier.ref.reg;
|
||||||
|
const auto reg_id = reg.id;
|
||||||
|
|
||||||
|
Instruction instruction{};
|
||||||
|
OPDEST dst{};
|
||||||
|
dst.prec = RSX_FP_PRECISION_REAL;
|
||||||
|
dst.fp16 = 0;
|
||||||
|
dst.dest_reg = reg_id;
|
||||||
|
dst.write_mask = (1u << ch);
|
||||||
|
|
||||||
|
const u32 src_reg_id = (ch / 2) + (reg_id * 2);
|
||||||
|
const bool is_word0 = !(ch & 1); // Only even
|
||||||
|
|
||||||
|
SRC0 src0{};
|
||||||
|
if (is_word0)
|
||||||
|
{
|
||||||
|
src0.swizzle_x = 0;
|
||||||
|
src0.swizzle_y = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
src0.swizzle_x = 2;
|
||||||
|
src0.swizzle_y = 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
src0.swizzle_z = 2;
|
||||||
|
src0.swizzle_w = 3;
|
||||||
|
src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
|
||||||
|
src0.tmp_reg_index = src_reg_id;
|
||||||
|
src0.fp16 = 1;
|
||||||
|
|
||||||
|
// Prepare source 1 to match the output in case we need to encode an OR
|
||||||
|
SRC1 src1{};
|
||||||
|
src1.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
|
||||||
|
src1.tmp_reg_index = reg_id;
|
||||||
|
src1.swizzle_x = ch;
|
||||||
|
src1.swizzle_y = ch;
|
||||||
|
src1.swizzle_z = ch;
|
||||||
|
src1.swizzle_w = ch;
|
||||||
|
|
||||||
|
u32 opcode = 0;
|
||||||
|
switch (barrier.flags[ch])
|
||||||
|
{
|
||||||
|
case Register32BarrierFlags::DEFAULT:
|
||||||
|
opcode = RSX_FP_OPCODE_PK2;
|
||||||
|
break;
|
||||||
|
case Register32BarrierFlags::OR_WORD0:
|
||||||
|
opcode = RSX_FP_OPCODE_OR16_LO;
|
||||||
|
// Swap inputs
|
||||||
|
std::swap(src0.HEX, src1.HEX);
|
||||||
|
break;
|
||||||
|
case Register32BarrierFlags::OR_WORD1:
|
||||||
|
opcode = RSX_FP_OPCODE_OR16_HI;
|
||||||
|
src0.swizzle_x = src0.swizzle_y;
|
||||||
|
std::swap(src0.HEX, src1.HEX);
|
||||||
|
break;
|
||||||
|
case Register32BarrierFlags::NONE:
|
||||||
|
default:
|
||||||
|
fmt::throw_exception("Unexpected lane barrier with no mask.");
|
||||||
|
}
|
||||||
|
|
||||||
|
dst.opcode = opcode & 0x3F;
|
||||||
|
src1.opcode_hi = (opcode > 0x3F) ? 1 : 0;
|
||||||
|
src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1;
|
||||||
|
|
||||||
|
instruction.opcode = opcode;
|
||||||
|
instruction.bytecode[0] = dst.HEX;
|
||||||
|
instruction.bytecode[1] = src0.HEX;
|
||||||
|
instruction.bytecode[2] = src1.HEX;
|
||||||
|
|
||||||
|
Register src_reg{ .id = static_cast<int>(src_reg_id), .f16 = true };
|
||||||
|
instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF });
|
||||||
|
instruction.dsts.push_back({ .reg{ .id = reg_id, .f16 = false }, .mask = (1u << ch) });
|
||||||
|
result.push_back(std::move(instruction));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Instruction> build_barrier16(const RegisterRef& reg)
|
||||||
|
{
|
||||||
|
// H0.xy = unpackHalf2x16(R0.x)
|
||||||
|
// H0.zw = unpackHalf2x16(R0.y)
|
||||||
|
// H1.xy = unpackHalf2x16(R0.z)
|
||||||
|
// H1.zw = unpackHalf2x16(R0.w)
|
||||||
|
|
||||||
|
std::vector<Instruction> result;
|
||||||
|
|
||||||
|
for (u32 mask = reg.mask, ch = 0; mask > 0; mask >>= 1, ++ch)
|
||||||
|
{
|
||||||
|
if (!(mask & 1))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Instruction instruction{};
|
||||||
|
OPDEST dst{};
|
||||||
|
dst.opcode = RSX_FP_OPCODE_UP2;
|
||||||
|
dst.prec = RSX_FP_PRECISION_HALF;
|
||||||
|
dst.fp16 = 1;
|
||||||
|
dst.dest_reg = reg.reg.id;
|
||||||
|
dst.write_mask = 1u << ch;
|
||||||
|
|
||||||
|
const u32 src_reg_id = reg.reg.id / 2;
|
||||||
|
const bool is_odd_reg = !!(reg.reg.id & 1);
|
||||||
|
const bool is_odd_ch = !!(ch & 1);
|
||||||
|
const bool is_word0 = ch < 2;
|
||||||
|
|
||||||
|
// If we're an even channel, we should also write the next channel (y/w)
|
||||||
|
if (!is_odd_ch && (mask & 2))
|
||||||
|
{
|
||||||
|
mask >>= 1;
|
||||||
|
++ch;
|
||||||
|
dst.write_mask |= (1u << ch);
|
||||||
|
}
|
||||||
|
|
||||||
|
SRC0 src0{};
|
||||||
|
src0.exec_if_eq = src0.exec_if_gr = src0.exec_if_lt = 1;
|
||||||
|
|
||||||
|
if (is_word0)
|
||||||
|
{
|
||||||
|
src0.swizzle_x = is_odd_reg ? 2 : 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
src0.swizzle_x = is_odd_reg ? 3 : 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
src0.swizzle_y = 1;
|
||||||
|
src0.swizzle_z = 2;
|
||||||
|
src0.swizzle_w = 3;
|
||||||
|
src0.reg_type = RSX_FP_REGISTER_TYPE_TEMP;
|
||||||
|
src0.tmp_reg_index = src_reg_id;
|
||||||
|
|
||||||
|
instruction.opcode = dst.opcode;
|
||||||
|
instruction.bytecode[0] = dst.HEX;
|
||||||
|
instruction.bytecode[1] = src0.HEX;
|
||||||
|
|
||||||
|
Register src_reg{ .id = static_cast<int>(src_reg_id), .f16 = true };
|
||||||
|
instruction.srcs.push_back({ .reg = src_reg, .mask = 0xF });
|
||||||
|
instruction.dsts.push_back({ .reg{.id = reg.reg.id, .f16 = false }, .mask = dst.write_mask });
|
||||||
|
result.push_back(std::move(instruction));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Instruction> resolve_dependencies(const std::unordered_set<u32>& lanes, bool f16)
|
||||||
|
{
|
||||||
|
std::vector<Instruction> result;
|
||||||
|
|
||||||
|
if (f16)
|
||||||
|
{
|
||||||
|
const auto regs = decode_lanes16(lanes);
|
||||||
|
for (const auto& ref : regs)
|
||||||
|
{
|
||||||
|
auto instructions = build_barrier16(ref);
|
||||||
|
result.insert(result.end(), instructions.begin(), instructions.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto barriers = decode_lanes32(lanes);
|
||||||
|
for (const auto& barrier : barriers)
|
||||||
|
{
|
||||||
|
auto instructions = build_barrier32(barrier);
|
||||||
|
result.insert(result.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_dependency_barriers(DependencyPassContext& ctx, BasicBlock* block)
|
||||||
|
{
|
||||||
|
register_file_t& register_file = ctx.exec_register_map[block];
|
||||||
|
std::memset(register_file.data(), content_unknown, register_file_length);
|
||||||
|
|
||||||
|
std::unordered_set<u32> barrier16;
|
||||||
|
std::unordered_set<u32> barrier32;
|
||||||
|
|
||||||
|
// This subpass does not care about the prologue and epilogue and assumes each block is unique.
|
||||||
|
for (auto it = block->instructions.begin(); it != block->instructions.end(); ++it)
|
||||||
|
{
|
||||||
|
auto& inst = *it;
|
||||||
|
|
||||||
|
barrier16.clear();
|
||||||
|
barrier32.clear();
|
||||||
|
|
||||||
|
for (const auto& src : inst.srcs)
|
||||||
|
{
|
||||||
|
const auto read_bytes = get_register_file_range(src);
|
||||||
|
const char expected_type = src.reg.f16 ? content_float16 : content_float32;
|
||||||
|
for (const auto& index : read_bytes)
|
||||||
|
{
|
||||||
|
if (register_file[index] == content_unknown)
|
||||||
|
{
|
||||||
|
// Skip input
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (register_file[index] == expected_type || register_file[index] == content_dual)
|
||||||
|
{
|
||||||
|
// Match - nothing to do
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collision on the lane
|
||||||
|
register_file[index] = content_dual;
|
||||||
|
(src.reg.f16 ? barrier16 : barrier32).insert(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& dst : inst.dsts)
|
||||||
|
{
|
||||||
|
const auto write_bytes = get_register_file_range(dst);
|
||||||
|
const char expected_type = dst.reg.f16 ? content_float16 : content_float32;
|
||||||
|
|
||||||
|
for (const auto& index : write_bytes)
|
||||||
|
{
|
||||||
|
register_file[index] = expected_type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We need to inject some barrier instructions
|
||||||
|
if (!barrier16.empty())
|
||||||
|
{
|
||||||
|
auto barrier16_in = decode_lanes16(barrier16);
|
||||||
|
std::vector<Instruction> instructions;
|
||||||
|
instructions.reserve(barrier16_in.size());
|
||||||
|
|
||||||
|
for (const auto& reg : barrier16_in)
|
||||||
|
{
|
||||||
|
auto barrier = build_barrier16(reg);
|
||||||
|
instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
|
||||||
|
std::advance(it, instructions.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!barrier32.empty())
|
||||||
|
{
|
||||||
|
auto barrier32_in = decode_lanes32(barrier32);
|
||||||
|
std::vector<Instruction> instructions;
|
||||||
|
instructions.reserve(barrier32_in.size());
|
||||||
|
|
||||||
|
for (const auto& reg : barrier32_in)
|
||||||
|
{
|
||||||
|
auto barrier = build_barrier32(reg);
|
||||||
|
instructions.insert(instructions.end(), std::make_move_iterator(barrier.begin()), std::make_move_iterator(barrier.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
it = block->instructions.insert(it, std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
|
||||||
|
std::advance(it, instructions.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_block_register_dependency(DependencyPassContext& ctx, BasicBlock* block, const std::unordered_set<u32>& lanes, bool f16)
|
||||||
|
{
|
||||||
|
std::unordered_set<u32> clobbered_lanes;
|
||||||
|
std::unordered_set<u32> lanes_to_search;
|
||||||
|
|
||||||
|
for (auto& back_edge : block->pred)
|
||||||
|
{
|
||||||
|
auto target = back_edge.from;
|
||||||
|
|
||||||
|
// Quick check - if we've reached an IF-ELSE anchor, don't traverse upwards.
|
||||||
|
// The IF and ELSE edges are already a complete set and will bre processed before this node.
|
||||||
|
if (back_edge.type == EdgeType::ENDIF &&
|
||||||
|
&back_edge == &block->pred.back() &&
|
||||||
|
target->succ.size() == 3 &&
|
||||||
|
target->succ[1].type == EdgeType::ELSE &&
|
||||||
|
target->succ[2].type == EdgeType::ENDIF &&
|
||||||
|
target->succ[2].to == block)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Did this target even clobber our register?
|
||||||
|
ensure(ctx.exec_register_map.find(target) != ctx.exec_register_map.end(), "Block has not been pre-processed");
|
||||||
|
|
||||||
|
if (ctx.sync_register_map.find(target) == ctx.sync_register_map.end())
|
||||||
|
{
|
||||||
|
auto& blob = ctx.sync_register_map[target];
|
||||||
|
std::memset(blob.data(), content_unknown, register_file_length);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& sync_register_file = ctx.sync_register_map[target];
|
||||||
|
const auto& exec_register_file = ctx.exec_register_map[target];
|
||||||
|
const auto clobber_type = f16 ? content_float32 : content_float16;
|
||||||
|
|
||||||
|
lanes_to_search.clear();
|
||||||
|
clobbered_lanes.clear();
|
||||||
|
|
||||||
|
for (auto& lane : lanes)
|
||||||
|
{
|
||||||
|
if (exec_register_file[lane] == clobber_type &&
|
||||||
|
sync_register_file[lane] == content_unknown)
|
||||||
|
{
|
||||||
|
clobbered_lanes.insert(lane);
|
||||||
|
sync_register_file[lane] = content_dual;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (exec_register_file[lane] == content_unknown)
|
||||||
|
{
|
||||||
|
lanes_to_search.insert(lane);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clobbered_lanes.empty())
|
||||||
|
{
|
||||||
|
auto instructions = resolve_dependencies(clobbered_lanes, f16);
|
||||||
|
target->epilogue.insert(target->epilogue.end(), std::make_move_iterator(instructions.begin()), std::make_move_iterator(instructions.end()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lanes_to_search.empty())
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We have some missing lanes. Search upwards
|
||||||
|
if (!target->pred.empty())
|
||||||
|
{
|
||||||
|
// We only need to search the last predecessor which is the true "root" of the branch
|
||||||
|
insert_block_register_dependency(ctx, target, lanes_to_search, f16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void insert_block_dependencies(DependencyPassContext& ctx, BasicBlock* block)
|
||||||
|
{
|
||||||
|
auto range_from_ref = [](const RegisterRef& ref)
|
||||||
|
{
|
||||||
|
const auto range = get_register_file_range(ref);
|
||||||
|
|
||||||
|
std::unordered_set<u32> result;
|
||||||
|
for (const auto& value : range)
|
||||||
|
{
|
||||||
|
result.insert(value);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (auto& ref : block->input_list)
|
||||||
|
{
|
||||||
|
const auto range = range_from_ref(ref);
|
||||||
|
insert_block_register_dependency(ctx, block, range, ref.reg.f16);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegisterDependencyPass::run(FlowGraph& graph)
|
||||||
|
{
|
||||||
|
DependencyPassContext ctx{};
|
||||||
|
|
||||||
|
// First, run intra-block dependency
|
||||||
|
for (auto& block : graph.blocks)
|
||||||
|
{
|
||||||
|
insert_dependency_barriers(ctx, &block);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Then, create prologue/epilogue instructions
|
||||||
|
// Traverse the list in reverse order to bubble up dependencies correctly.
|
||||||
|
for (auto it = graph.blocks.rbegin(); it != graph.blocks.rend(); ++it)
|
||||||
|
{
|
||||||
|
insert_block_dependencies(ctx, &(*it));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,15 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "../../CFG.h"
|
||||||
|
|
||||||
|
namespace rsx::assembler::FP
|
||||||
|
{
|
||||||
|
// The register dependency pass identifies data hazards for each basic block and injects barrier instructions.
|
||||||
|
// Real PS3 does not have explicit barriers, but does instead often use delay slots or fence instructions to stall until a specific hardware unit clears the fence to advance.
|
||||||
|
// For decompiled shaders, we have the problem that aliasing is not real and is instead simulated. We do not have access to unions on the GPU without really nasty tricks.
|
||||||
|
class RegisterDependencyPass : public CFGPass
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
void run(FlowGraph& graph) override;
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -273,7 +273,7 @@ void CgBinaryDisasm::TaskFP()
|
|||||||
src2.HEX = GetData(data[3]);
|
src2.HEX = GetData(data[3]);
|
||||||
|
|
||||||
m_step = 4 * sizeof(u32);
|
m_step = 4 * sizeof(u32);
|
||||||
m_opcode = dst.opcode | (src1.opcode_is_branch << 6);
|
m_opcode = dst.opcode | (src1.opcode_hi << 6);
|
||||||
|
|
||||||
auto SCT = [&]()
|
auto SCT = [&]()
|
||||||
{
|
{
|
||||||
|
|||||||
@ -3,12 +3,19 @@
|
|||||||
#include "FragmentProgramDecompiler.h"
|
#include "FragmentProgramDecompiler.h"
|
||||||
#include "ProgramStateCache.h"
|
#include "ProgramStateCache.h"
|
||||||
|
|
||||||
|
#include "Assembler/Passes/FP/RegisterAnnotationPass.h"
|
||||||
|
#include "Assembler/Passes/FP/RegisterDependencyPass.h"
|
||||||
|
|
||||||
|
#include "Emu/system_config.h"
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace rsx
|
namespace rsx
|
||||||
{
|
{
|
||||||
namespace fragment_program
|
namespace fragment_program
|
||||||
{
|
{
|
||||||
|
using namespace rsx::assembler;
|
||||||
|
|
||||||
static const std::string reg_table[] =
|
static const std::string reg_table[] =
|
||||||
{
|
{
|
||||||
"wpos",
|
"wpos",
|
||||||
@ -17,10 +24,33 @@ namespace rsx
|
|||||||
"tc0", "tc1", "tc2", "tc3", "tc4", "tc5", "tc6", "tc7", "tc8", "tc9",
|
"tc0", "tc1", "tc2", "tc3", "tc4", "tc5", "tc6", "tc7", "tc8", "tc9",
|
||||||
"ssa"
|
"ssa"
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static const std::vector<RegisterRef> s_fp32_output_set =
|
||||||
|
{
|
||||||
|
{.reg {.id = 0, .f16 = false }, .mask = 0xf },
|
||||||
|
{.reg {.id = 2, .f16 = false }, .mask = 0xf },
|
||||||
|
{.reg {.id = 3, .f16 = false }, .mask = 0xf },
|
||||||
|
{.reg {.id = 4, .f16 = false }, .mask = 0xf },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const std::vector<RegisterRef> s_fp16_output_set =
|
||||||
|
{
|
||||||
|
{.reg {.id = 0, .f16 = true }, .mask = 0xf },
|
||||||
|
{.reg {.id = 4, .f16 = true }, .mask = 0xf },
|
||||||
|
{.reg {.id = 6, .f16 = true }, .mask = 0xf },
|
||||||
|
{.reg {.id = 8, .f16 = true }, .mask = 0xf },
|
||||||
|
};
|
||||||
|
|
||||||
|
static const RegisterRef s_z_export_reg =
|
||||||
|
{
|
||||||
|
.reg {.id = 1, .f16 = false },
|
||||||
|
.mask = (1u << 2)
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
using namespace rsx::fragment_program;
|
using namespace rsx::fragment_program;
|
||||||
|
using namespace rsx::assembler;
|
||||||
|
|
||||||
// SIMD vector lanes
|
// SIMD vector lanes
|
||||||
enum VectorLane : u8
|
enum VectorLane : u8
|
||||||
@ -31,6 +61,26 @@ enum VectorLane : u8
|
|||||||
W = 3,
|
W = 3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
std::vector<RegisterRef> get_fragment_program_output_set(u32 ctrl, u32 mrt_count)
|
||||||
|
{
|
||||||
|
std::vector<RegisterRef> result;
|
||||||
|
if (mrt_count > 0)
|
||||||
|
{
|
||||||
|
result = (ctrl & CELL_GCM_SHADER_CONTROL_32_BITS_EXPORTS)
|
||||||
|
? s_fp32_output_set
|
||||||
|
: s_fp16_output_set;
|
||||||
|
|
||||||
|
result.resize(mrt_count);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctrl & CELL_GCM_SHADER_CONTROL_DEPTH_EXPORT)
|
||||||
|
{
|
||||||
|
result.push_back(s_z_export_reg);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size)
|
FragmentProgramDecompiler::FragmentProgramDecompiler(const RSXFragmentProgram &prog, u32& size)
|
||||||
: m_size(size)
|
: m_size(size)
|
||||||
, m_prog(prog)
|
, m_prog(prog)
|
||||||
@ -151,8 +201,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags)
|
|||||||
}
|
}
|
||||||
|
|
||||||
const u32 reg_index = dst.fp16 ? (dst.dest_reg >> 1) : dst.dest_reg;
|
const u32 reg_index = dst.fp16 ? (dst.dest_reg >> 1) : dst.dest_reg;
|
||||||
ensure(reg_index < temp_registers.size());
|
|
||||||
|
|
||||||
if (dst.opcode == RSX_FP_OPCODE_MOV &&
|
if (dst.opcode == RSX_FP_OPCODE_MOV &&
|
||||||
src0.reg_type == RSX_FP_REGISTER_TYPE_TEMP &&
|
src0.reg_type == RSX_FP_REGISTER_TYPE_TEMP &&
|
||||||
src0.tmp_reg_index == reg_index)
|
src0.tmp_reg_index == reg_index)
|
||||||
@ -165,8 +213,6 @@ void FragmentProgramDecompiler::SetDst(std::string code, u32 flags)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
temp_registers[reg_index].tag(dst.dest_reg, !!dst.fp16, dst.mask_x, dst.mask_y, dst.mask_z, dst.mask_w);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void FragmentProgramDecompiler::AddFlowOp(const std::string& code)
|
void FragmentProgramDecompiler::AddFlowOp(const std::string& code)
|
||||||
@ -522,26 +568,7 @@ template<typename T> std::string FragmentProgramDecompiler::GetSRC(T src)
|
|||||||
switch (src.reg_type)
|
switch (src.reg_type)
|
||||||
{
|
{
|
||||||
case RSX_FP_REGISTER_TYPE_TEMP:
|
case RSX_FP_REGISTER_TYPE_TEMP:
|
||||||
|
if (src.fp16 && precision_modifier == RSX_FP_PRECISION_HALF)
|
||||||
if (!src.fp16)
|
|
||||||
{
|
|
||||||
if (dst.opcode == RSX_FP_OPCODE_UP16 ||
|
|
||||||
dst.opcode == RSX_FP_OPCODE_UP2 ||
|
|
||||||
dst.opcode == RSX_FP_OPCODE_UP4 ||
|
|
||||||
dst.opcode == RSX_FP_OPCODE_UPB ||
|
|
||||||
dst.opcode == RSX_FP_OPCODE_UPG)
|
|
||||||
{
|
|
||||||
auto ® = temp_registers[src.tmp_reg_index];
|
|
||||||
if (reg.requires_gather(src.swizzle_x))
|
|
||||||
{
|
|
||||||
properties.has_gather_op = true;
|
|
||||||
AddReg(src.tmp_reg_index, src.fp16);
|
|
||||||
ret = getFloatTypeName(4) + reg.gather_r();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (precision_modifier == RSX_FP_PRECISION_HALF)
|
|
||||||
{
|
{
|
||||||
// clamp16() is not a cheap operation when emulated; avoid at all costs
|
// clamp16() is not a cheap operation when emulated; avoid at all costs
|
||||||
precision_modifier = RSX_FP_PRECISION_REAL;
|
precision_modifier = RSX_FP_PRECISION_REAL;
|
||||||
@ -762,7 +789,6 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||||||
const std::string float4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4);
|
const std::string float4_type = (fp16_out && device_props.has_native_half_support)? getHalfTypeName(4) : getFloatTypeName(4);
|
||||||
const std::string init_value = float4_type + "(0.)";
|
const std::string init_value = float4_type + "(0.)";
|
||||||
std::array<std::string, 4> output_register_names;
|
std::array<std::string, 4> output_register_names;
|
||||||
std::array<u32, 4> ouput_register_indices = { 0, 2, 3, 4 };
|
|
||||||
|
|
||||||
// Holder for any "cleanup" before exiting main
|
// Holder for any "cleanup" before exiting main
|
||||||
std::stringstream main_epilogue;
|
std::stringstream main_epilogue;
|
||||||
@ -772,17 +798,6 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||||||
{
|
{
|
||||||
// Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!!
|
// Hw tests show that the depth export register is default-initialized to 0 and not wpos.z!!
|
||||||
m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value);
|
m_parr.AddParam(PF_PARAM_NONE, getFloatTypeName(4), "r1", init_value);
|
||||||
|
|
||||||
auto& r1 = temp_registers[1];
|
|
||||||
if (r1.requires_gather(VectorLane::Z))
|
|
||||||
{
|
|
||||||
// r1.zw was not written to
|
|
||||||
properties.has_gather_op = true;
|
|
||||||
main_epilogue << " r1.z = " << float4_type << r1.gather_r() << ".z;\n";
|
|
||||||
|
|
||||||
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
|
|
||||||
rsx_log.warning("ROP reads from shader depth without writing to it. Final value will be gathered.");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z)
|
// Add the color output registers. They are statically written to and have guaranteed initialization (except r1.z which == wpos.z)
|
||||||
@ -810,33 +825,6 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto block_index = ouput_register_indices[n];
|
|
||||||
auto& r = temp_registers[block_index];
|
|
||||||
|
|
||||||
if (fp16_out)
|
|
||||||
{
|
|
||||||
// Check if we need a split/extract op
|
|
||||||
if (r.requires_split(0))
|
|
||||||
{
|
|
||||||
main_epilogue << " " << reg_name << " = " << float4_type << r.split_h0() << ";\n";
|
|
||||||
|
|
||||||
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
|
|
||||||
rsx_log.warning("ROP reads from %s without writing to it. Final value will be extracted from the 32-bit register.", reg_name);
|
|
||||||
}
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!r.requires_gather128())
|
|
||||||
{
|
|
||||||
// Nothing to do
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We need to gather the data from existing registers
|
|
||||||
main_epilogue << " " << reg_name << " = " << float4_type << r.gather_r() << ";\n";
|
|
||||||
properties.has_gather_op = true;
|
|
||||||
|
|
||||||
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
|
// Emit debug warning. Useful to diagnose regressions, but should be removed in future.
|
||||||
rsx_log.warning("ROP reads from %s without writing to it. Final value will be gathered.", reg_name);
|
rsx_log.warning("ROP reads from %s without writing to it. Final value will be gathered.", reg_name);
|
||||||
}
|
}
|
||||||
@ -1024,28 +1012,6 @@ std::string FragmentProgramDecompiler::BuildCode()
|
|||||||
OS << Format(divsq_func);
|
OS << Format(divsq_func);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Declare register gather/merge if needed
|
|
||||||
if (properties.has_gather_op)
|
|
||||||
{
|
|
||||||
std::string float2 = getFloatTypeName(2);
|
|
||||||
|
|
||||||
OS << float4 << " gather(" << float4 << " _h0, " << float4 << " _h1)\n";
|
|
||||||
OS << "{\n";
|
|
||||||
OS << " float x = uintBitsToFloat(packHalf2x16(_h0.xy));\n";
|
|
||||||
OS << " float y = uintBitsToFloat(packHalf2x16(_h0.zw));\n";
|
|
||||||
OS << " float z = uintBitsToFloat(packHalf2x16(_h1.xy));\n";
|
|
||||||
OS << " float w = uintBitsToFloat(packHalf2x16(_h1.zw));\n";
|
|
||||||
OS << " return " << float4 << "(x, y, z, w);\n";
|
|
||||||
OS << "}\n\n";
|
|
||||||
|
|
||||||
OS << float2 << " gather(" << float4 << " _h)\n";
|
|
||||||
OS << "{\n";
|
|
||||||
OS << " float x = uintBitsToFloat(packHalf2x16(_h.xy));\n";
|
|
||||||
OS << " float y = uintBitsToFloat(packHalf2x16(_h.zw));\n";
|
|
||||||
OS << " return " << float2 << "(x, y);\n";
|
|
||||||
OS << "}\n\n";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (properties.has_dynamic_register_load)
|
if (properties.has_dynamic_register_load)
|
||||||
{
|
{
|
||||||
OS <<
|
OS <<
|
||||||
@ -1149,6 +1115,14 @@ bool FragmentProgramDecompiler::handle_sct_scb(u32 opcode)
|
|||||||
return true;
|
return true;
|
||||||
case RSX_FP_OPCODE_PKB: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packUnorm4x8($0)))"); return true;
|
case RSX_FP_OPCODE_PKB: SetDst(getFloatTypeName(4) + "(uintBitsToFloat(packUnorm4x8($0)))"); return true;
|
||||||
case RSX_FP_OPCODE_SIN: SetDst("sin($0.xxxx)"); return true;
|
case RSX_FP_OPCODE_SIN: SetDst("sin($0.xxxx)"); return true;
|
||||||
|
|
||||||
|
// Custom ISA extensions for 16-bit OR
|
||||||
|
case RSX_FP_OPCODE_OR16_HI:
|
||||||
|
SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0x0000ffff) | (packHalf2x16($1.xx) & 0xffff0000)))");
|
||||||
|
return true;
|
||||||
|
case RSX_FP_OPCODE_OR16_LO:
|
||||||
|
SetDst("$float4(uintBitsToFloat((floatBitsToUint($0.x) & 0xffff0000) | (packHalf2x16($1.xx) & 0x0000ffff)))");
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -1295,7 +1269,37 @@ bool FragmentProgramDecompiler::handle_tex_srb(u32 opcode)
|
|||||||
|
|
||||||
std::string FragmentProgramDecompiler::Decompile()
|
std::string FragmentProgramDecompiler::Decompile()
|
||||||
{
|
{
|
||||||
const auto graph = rsx::assembler::deconstruct_fragment_program(m_prog);
|
auto graph = deconstruct_fragment_program(m_prog);
|
||||||
|
|
||||||
|
if (!graph.blocks.empty())
|
||||||
|
{
|
||||||
|
// The RSX CFG is missing the output block. We inject a fake tail block that ingests the ROP outputs.
|
||||||
|
BasicBlock* rop_block = nullptr;
|
||||||
|
BasicBlock* tail_block = &graph.blocks.back();
|
||||||
|
if (tail_block->instructions.empty())
|
||||||
|
{
|
||||||
|
// Merge block. Use this directly
|
||||||
|
rop_block = tail_block;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
graph.blocks.push_back({});
|
||||||
|
rop_block = &graph.blocks.back();
|
||||||
|
|
||||||
|
tail_block->insert_succ(rop_block);
|
||||||
|
rop_block->insert_pred(tail_block);
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto rop_inputs = get_fragment_program_output_set(m_prog.ctrl, m_prog.mrt_buffers_count);
|
||||||
|
rop_block->input_list.insert(rop_block->input_list.end(), rop_inputs.begin(), rop_inputs.end());
|
||||||
|
|
||||||
|
FP::RegisterAnnotationPass annotation_pass{ m_prog, { .skip_delay_slots = true } };
|
||||||
|
FP::RegisterDependencyPass dependency_pass{};
|
||||||
|
|
||||||
|
annotation_pass.run(graph);
|
||||||
|
dependency_pass.run(graph);
|
||||||
|
}
|
||||||
|
|
||||||
m_size = 0;
|
m_size = 0;
|
||||||
m_location = 0;
|
m_location = 0;
|
||||||
m_loop_count = 0;
|
m_loop_count = 0;
|
||||||
@ -1303,57 +1307,105 @@ std::string FragmentProgramDecompiler::Decompile()
|
|||||||
m_is_valid_ucode = true;
|
m_is_valid_ucode = true;
|
||||||
m_constant_offsets.clear();
|
m_constant_offsets.clear();
|
||||||
|
|
||||||
enum
|
// For GLSL scope wind/unwind. We store the min scope depth and loop count for each block and "unwind" to it.
|
||||||
|
// This should recover information lost when multiple nodes converge on a single merge node or even skip a merge node as is the case with "ELSE" nodes.
|
||||||
|
std::unordered_map<const BasicBlock*, std::pair<int, u32>> block_data;
|
||||||
|
|
||||||
|
auto push_block_info = [&](const BasicBlock* block)
|
||||||
{
|
{
|
||||||
FORCE_NONE,
|
u32 loop = m_loop_count;
|
||||||
FORCE_SCT,
|
int level = m_code_level;
|
||||||
FORCE_SCB,
|
|
||||||
|
auto found = block_data.find(block);
|
||||||
|
if (found != block_data.end())
|
||||||
|
{
|
||||||
|
level = std::min(level, found->second.first);
|
||||||
|
loop = std::min(loop, found->second.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
block_data[block] = { level, loop };
|
||||||
};
|
};
|
||||||
|
|
||||||
int forced_unit = FORCE_NONE;
|
auto emit_block = [&](const std::vector<Instruction>& instructions)
|
||||||
|
{
|
||||||
|
for (auto& inst : instructions)
|
||||||
|
{
|
||||||
|
m_instruction = &inst;
|
||||||
|
dst.HEX = inst.bytecode[0];
|
||||||
|
src0.HEX = inst.bytecode[1];
|
||||||
|
src1.HEX = inst.bytecode[2];
|
||||||
|
src2.HEX = inst.bytecode[3];
|
||||||
|
|
||||||
|
ensure(handle_tex_srb(inst.opcode) || handle_sct_scb(inst.opcode), "Unsupported operation");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for (const auto &block : graph.blocks)
|
for (const auto &block : graph.blocks)
|
||||||
{
|
{
|
||||||
// TODO: Handle block prologue if any
|
auto found = block_data.find(&block);
|
||||||
|
if (found != block_data.end())
|
||||||
|
{
|
||||||
|
const auto [level, loop] = found->second;
|
||||||
|
for (int i = m_code_level; i > level; i--)
|
||||||
|
{
|
||||||
|
m_code_level--;
|
||||||
|
AddCode("}");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_loop_count = loop;
|
||||||
|
}
|
||||||
|
|
||||||
if (!block.pred.empty())
|
if (!block.pred.empty())
|
||||||
{
|
{
|
||||||
// CFG guarantees predecessors are sorted, closest one first
|
// Predecessors are always sorted closest last.
|
||||||
for (const auto& pred : block.pred)
|
// This gives some adjacency info and tells us how the previous block connects to this one.
|
||||||
|
const auto& pred = block.pred.back();
|
||||||
|
switch (pred.type)
|
||||||
{
|
{
|
||||||
switch (pred.type)
|
case EdgeType::LOOP:
|
||||||
{
|
m_loop_count++;
|
||||||
case rsx::assembler::EdgeType::ENDLOOP:
|
[[ fallthrough ]];
|
||||||
m_loop_count--;
|
case EdgeType::IF:
|
||||||
[[ fallthrough ]];
|
AddCode("{");
|
||||||
case rsx::assembler::EdgeType::ENDIF:
|
m_code_level++;
|
||||||
m_code_level--;
|
break;
|
||||||
AddCode("}");
|
case EdgeType::ELSE:
|
||||||
break;
|
AddCode("else");
|
||||||
case rsx::assembler::EdgeType::LOOP:
|
AddCode("{");
|
||||||
m_loop_count++;
|
m_code_level++;
|
||||||
[[ fallthrough ]];
|
break;
|
||||||
case rsx::assembler::EdgeType::IF:
|
case EdgeType::ENDIF:
|
||||||
// Instruction will be inserted by the SIP decoder
|
case EdgeType::ENDLOOP:
|
||||||
AddCode("{");
|
// Pure merge block?
|
||||||
m_code_level++;
|
break;
|
||||||
break;
|
case EdgeType::NONE:
|
||||||
case rsx::assembler::EdgeType::ELSE:
|
ensure(block.instructions.empty());
|
||||||
// This one needs more testing
|
break;
|
||||||
m_code_level--;
|
default:
|
||||||
AddCode("}");
|
fmt::throw_exception("Unhandled edge type %d", static_cast<int>(pred.type));
|
||||||
AddCode("else");
|
break;
|
||||||
AddCode("{");
|
|
||||||
m_code_level++;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
// Start a new block anyway
|
|
||||||
fmt::throw_exception("Unexpected block found");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!block.prologue.empty())
|
||||||
|
{
|
||||||
|
AddCode("// Prologue");
|
||||||
|
emit_block(block.prologue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bool early_epilogue =
|
||||||
|
!block.epilogue.empty() &&
|
||||||
|
!block.succ.empty() &&
|
||||||
|
(block.succ.front().type == EdgeType::IF || block.succ.front().type == EdgeType::LOOP);
|
||||||
|
|
||||||
for (const auto& inst : block.instructions)
|
for (const auto& inst : block.instructions)
|
||||||
{
|
{
|
||||||
|
if (early_epilogue && &inst == &block.instructions.back())
|
||||||
|
{
|
||||||
|
AddCode("// Epilogue");
|
||||||
|
emit_block(block.epilogue);
|
||||||
|
}
|
||||||
|
|
||||||
m_instruction = &inst;
|
m_instruction = &inst;
|
||||||
|
|
||||||
dst.HEX = inst.bytecode[0];
|
dst.HEX = inst.bytecode[0];
|
||||||
@ -1363,11 +1415,9 @@ std::string FragmentProgramDecompiler::Decompile()
|
|||||||
|
|
||||||
opflags = 0;
|
opflags = 0;
|
||||||
|
|
||||||
const u32 opcode = dst.opcode | (src1.opcode_is_branch << 6);
|
|
||||||
|
|
||||||
auto SIP = [&]()
|
auto SIP = [&]()
|
||||||
{
|
{
|
||||||
switch (opcode)
|
switch (m_instruction->opcode)
|
||||||
{
|
{
|
||||||
case RSX_FP_OPCODE_BRK:
|
case RSX_FP_OPCODE_BRK:
|
||||||
if (m_loop_count) AddFlowOp("break");
|
if (m_loop_count) AddFlowOp("break");
|
||||||
@ -1377,12 +1427,10 @@ std::string FragmentProgramDecompiler::Decompile()
|
|||||||
rsx_log.error("Unimplemented SIP instruction: CAL");
|
rsx_log.error("Unimplemented SIP instruction: CAL");
|
||||||
break;
|
break;
|
||||||
case RSX_FP_OPCODE_FENCT:
|
case RSX_FP_OPCODE_FENCT:
|
||||||
AddCode("//FENCT");
|
AddCode("// FENCT");
|
||||||
forced_unit = FORCE_SCT;
|
|
||||||
break;
|
break;
|
||||||
case RSX_FP_OPCODE_FENCB:
|
case RSX_FP_OPCODE_FENCB:
|
||||||
AddCode("//FENCB");
|
AddCode("// FENCB");
|
||||||
forced_unit = FORCE_SCB;
|
|
||||||
break;
|
break;
|
||||||
case RSX_FP_OPCODE_IFE:
|
case RSX_FP_OPCODE_IFE:
|
||||||
AddCode("if($cond)");
|
AddCode("if($cond)");
|
||||||
@ -1406,7 +1454,7 @@ std::string FragmentProgramDecompiler::Decompile()
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
switch (opcode)
|
switch (m_instruction->opcode)
|
||||||
{
|
{
|
||||||
case RSX_FP_OPCODE_NOP:
|
case RSX_FP_OPCODE_NOP:
|
||||||
break;
|
break;
|
||||||
@ -1415,19 +1463,10 @@ std::string FragmentProgramDecompiler::Decompile()
|
|||||||
AddFlowOp("_kill()");
|
AddFlowOp("_kill()");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
int prev_force_unit = forced_unit;
|
|
||||||
|
|
||||||
// Some instructions do not respect forced unit
|
|
||||||
// Tested with Tales of Vesperia
|
|
||||||
if (SIP()) break;
|
if (SIP()) break;
|
||||||
if (handle_tex_srb(opcode)) break;
|
if (handle_tex_srb(m_instruction->opcode)) break;
|
||||||
|
if (handle_sct_scb(m_instruction->opcode)) break;
|
||||||
// FENCT/FENCB do not actually reject instructions if they dont match the forced unit
|
rsx_log.error("Unknown/illegal instruction: 0x%x", m_instruction->opcode);
|
||||||
// Looks like they are optimization hints and not hard-coded forced paths
|
|
||||||
if (handle_sct_scb(opcode)) break;
|
|
||||||
forced_unit = FORCE_NONE;
|
|
||||||
|
|
||||||
rsx_log.error("Unknown/illegal instruction: 0x%x (forced unit %d)", opcode, prev_force_unit);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1435,16 +1474,28 @@ std::string FragmentProgramDecompiler::Decompile()
|
|||||||
if (dst.end) break;
|
if (dst.end) break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Handle block epilogue if needed
|
if (!early_epilogue && !block.epilogue.empty())
|
||||||
|
{
|
||||||
|
AddCode("// Epilogue");
|
||||||
|
emit_block(block.epilogue);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& succ : block.succ)
|
||||||
|
{
|
||||||
|
switch (succ.type)
|
||||||
|
{
|
||||||
|
case EdgeType::ENDIF:
|
||||||
|
case EdgeType::ENDLOOP:
|
||||||
|
case EdgeType::ELSE:
|
||||||
|
push_block_info(succ.to);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
while (m_code_level > 1)
|
ensure(m_code_level == 1);
|
||||||
{
|
|
||||||
rsx_log.error("Hanging block found at end of shader. Malformed shader?");
|
|
||||||
|
|
||||||
m_code_level--;
|
|
||||||
AddCode("}");
|
|
||||||
}
|
|
||||||
|
|
||||||
// flush m_code_level
|
// flush m_code_level
|
||||||
m_code_level = 1;
|
m_code_level = 1;
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include "ShaderParam.h"
|
#include "ShaderParam.h"
|
||||||
#include "FragmentProgramRegister.h"
|
|
||||||
#include "RSXFragmentProgram.h"
|
#include "RSXFragmentProgram.h"
|
||||||
|
|
||||||
#include "Assembler/CFG.h"
|
#include "Assembler/CFG.h"
|
||||||
@ -53,8 +52,6 @@ class FragmentProgramDecompiler
|
|||||||
int m_code_level;
|
int m_code_level;
|
||||||
std::unordered_map<u32, u32> m_constant_offsets;
|
std::unordered_map<u32, u32> m_constant_offsets;
|
||||||
|
|
||||||
std::array<rsx::MixedPrecisionRegister, 64> temp_registers;
|
|
||||||
|
|
||||||
std::string GetMask() const;
|
std::string GetMask() const;
|
||||||
|
|
||||||
void SetDst(std::string code, u32 flags = 0);
|
void SetDst(std::string code, u32 flags = 0);
|
||||||
@ -175,7 +172,6 @@ public:
|
|||||||
|
|
||||||
// Decoded properties (out)
|
// Decoded properties (out)
|
||||||
bool has_lit_op = false;
|
bool has_lit_op = false;
|
||||||
bool has_gather_op = false;
|
|
||||||
bool has_no_output = false;
|
bool has_no_output = false;
|
||||||
bool has_discard_op = false;
|
bool has_discard_op = false;
|
||||||
bool has_tex_op = false;
|
bool has_tex_op = false;
|
||||||
|
|||||||
@ -1,196 +0,0 @@
|
|||||||
#include "stdafx.h"
|
|
||||||
#include "FragmentProgramRegister.h"
|
|
||||||
|
|
||||||
namespace rsx
|
|
||||||
{
|
|
||||||
MixedPrecisionRegister::MixedPrecisionRegister()
|
|
||||||
{
|
|
||||||
std::fill(content_mask.begin(), content_mask.end(), data_type_bits::undefined);
|
|
||||||
}
|
|
||||||
|
|
||||||
void MixedPrecisionRegister::tag_h0(bool x, bool y, bool z, bool w)
|
|
||||||
{
|
|
||||||
if (x) content_mask[0] = data_type_bits::f16;
|
|
||||||
if (y) content_mask[1] = data_type_bits::f16;
|
|
||||||
if (z) content_mask[2] = data_type_bits::f16;
|
|
||||||
if (w) content_mask[3] = data_type_bits::f16;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MixedPrecisionRegister::tag_h1(bool x, bool y, bool z, bool w)
|
|
||||||
{
|
|
||||||
if (x) content_mask[4] = data_type_bits::f16;
|
|
||||||
if (y) content_mask[5] = data_type_bits::f16;
|
|
||||||
if (z) content_mask[6] = data_type_bits::f16;
|
|
||||||
if (w) content_mask[7] = data_type_bits::f16;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MixedPrecisionRegister::tag_r(bool x, bool y, bool z, bool w)
|
|
||||||
{
|
|
||||||
if (x) content_mask[0] = content_mask[1] = data_type_bits::f32;
|
|
||||||
if (y) content_mask[2] = content_mask[3] = data_type_bits::f32;
|
|
||||||
if (z) content_mask[4] = content_mask[5] = data_type_bits::f32;
|
|
||||||
if (w) content_mask[6] = content_mask[7] = data_type_bits::f32;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MixedPrecisionRegister::tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w)
|
|
||||||
{
|
|
||||||
if (file_index == umax)
|
|
||||||
{
|
|
||||||
// First-time use. Initialize...
|
|
||||||
const u32 real_index = is_fp16 ? (index >> 1) : index;
|
|
||||||
file_index = real_index;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (is_fp16)
|
|
||||||
{
|
|
||||||
ensure((index / 2) == file_index);
|
|
||||||
|
|
||||||
if (index & 1)
|
|
||||||
{
|
|
||||||
tag_h1(x, y, z, w);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tag_h0(x, y, z, w);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
tag_r(x, y, z, w);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string MixedPrecisionRegister::gather_r() const
|
|
||||||
{
|
|
||||||
const auto half_index = file_index << 1;
|
|
||||||
const std::string reg = "r" + std::to_string(file_index);
|
|
||||||
const std::string gather_half_regs[] = {
|
|
||||||
"gather(h" + std::to_string(half_index) + ")",
|
|
||||||
"gather(h" + std::to_string(half_index + 1) + ")"
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string outputs[4];
|
|
||||||
for (int ch = 0; ch < 4; ++ch)
|
|
||||||
{
|
|
||||||
// FIXME: This approach ignores mixed register bits. Not ideal!!!!
|
|
||||||
const auto channel0 = content_mask[ch * 2];
|
|
||||||
const auto is_fp16_ch = channel0 == content_mask[ch * 2 + 1] && channel0 == data_type_bits::f16;
|
|
||||||
outputs[ch] = is_fp16_ch ? gather_half_regs[ch / 2] : reg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grouping. Only replace relevant bits...
|
|
||||||
if (outputs[0] == outputs[1]) outputs[0] = "";
|
|
||||||
if (outputs[2] == outputs[3]) outputs[2] = "";
|
|
||||||
|
|
||||||
// Assemble
|
|
||||||
bool group = false;
|
|
||||||
std::string result = "";
|
|
||||||
constexpr std::string_view swz_mask = "xyzw";
|
|
||||||
|
|
||||||
for (int ch = 0; ch < 4; ++ch)
|
|
||||||
{
|
|
||||||
if (outputs[ch].empty())
|
|
||||||
{
|
|
||||||
group = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.empty())
|
|
||||||
{
|
|
||||||
result += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (group)
|
|
||||||
{
|
|
||||||
ensure(ch > 0);
|
|
||||||
group = false;
|
|
||||||
|
|
||||||
if (outputs[ch] == reg)
|
|
||||||
{
|
|
||||||
result += reg + "." + swz_mask[ch - 1] + swz_mask[ch];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
result += outputs[ch];
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int subch = outputs[ch] == reg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
|
|
||||||
result += outputs[ch] + "." + swz_mask[subch];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Optimize dual-gather (128-bit gather) to use special function
|
|
||||||
const std::string double_gather = gather_half_regs[0] + ", " + gather_half_regs[1];
|
|
||||||
if (result == double_gather)
|
|
||||||
{
|
|
||||||
result = "gather(h" + std::to_string(half_index) + ", h" + std::to_string(half_index + 1) + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
return "(" + result + ")";
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string MixedPrecisionRegister::fetch_halfreg(u32 word_index) const
|
|
||||||
{
|
|
||||||
// Reads half-word 0 (H16x4) from a full real (R32x4) register
|
|
||||||
constexpr std::string_view swz_mask = "xyzw";
|
|
||||||
const std::string reg = "r" + std::to_string(file_index);
|
|
||||||
const std::string hreg = "h" + std::to_string(file_index * 2 + word_index);
|
|
||||||
|
|
||||||
const std::string word0_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2] + ")";
|
|
||||||
const std::string word1_bits = "floatBitsToUint(" + reg + "." + swz_mask[word_index * 2 + 1] + ")";
|
|
||||||
const std::string words[] = {
|
|
||||||
"unpackHalf2x16(" + word0_bits + ")",
|
|
||||||
"unpackHalf2x16(" + word1_bits + ")"
|
|
||||||
};
|
|
||||||
|
|
||||||
// Assemble
|
|
||||||
std::string outputs[4];
|
|
||||||
|
|
||||||
ensure(word_index <= 1);
|
|
||||||
const int word_offset = word_index * 4;
|
|
||||||
for (int ch = 0; ch < 4; ++ch)
|
|
||||||
{
|
|
||||||
outputs[ch] = content_mask[ch + word_offset] == data_type_bits::f32
|
|
||||||
? words[ch / 2]
|
|
||||||
: hreg;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Grouping. Only replace relevant bits...
|
|
||||||
if (outputs[0] == outputs[1]) outputs[0] = "";
|
|
||||||
if (outputs[2] == outputs[3]) outputs[2] = "";
|
|
||||||
|
|
||||||
// Assemble
|
|
||||||
bool group = false;
|
|
||||||
std::string result = "";
|
|
||||||
|
|
||||||
for (int ch = 0; ch < 4; ++ch)
|
|
||||||
{
|
|
||||||
if (outputs[ch].empty())
|
|
||||||
{
|
|
||||||
group = true;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!result.empty())
|
|
||||||
{
|
|
||||||
result += ", ";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (group)
|
|
||||||
{
|
|
||||||
ensure(ch > 0);
|
|
||||||
group = false;
|
|
||||||
result += outputs[ch];
|
|
||||||
|
|
||||||
if (outputs[ch] == hreg)
|
|
||||||
{
|
|
||||||
result += std::string(".") + swz_mask[ch - 1] + swz_mask[ch];
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int subch = outputs[ch] == hreg ? ch : (ch % 2); // Avoid .xyxy.z and other such ugly swizzles
|
|
||||||
result += outputs[ch] + "." + swz_mask[subch];
|
|
||||||
}
|
|
||||||
|
|
||||||
return "(" + result + ")";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
|
|
||||||
#include <util/types.hpp>
|
|
||||||
|
|
||||||
namespace rsx
|
|
||||||
{
|
|
||||||
class MixedPrecisionRegister
|
|
||||||
{
|
|
||||||
enum data_type_bits
|
|
||||||
{
|
|
||||||
undefined = 0,
|
|
||||||
f16 = 1,
|
|
||||||
f32 = 2
|
|
||||||
};
|
|
||||||
|
|
||||||
std::array<data_type_bits, 8> content_mask; // Content details for each half-word
|
|
||||||
u32 file_index = umax;
|
|
||||||
|
|
||||||
void tag_h0(bool x, bool y, bool z, bool w);
|
|
||||||
|
|
||||||
void tag_h1(bool x, bool y, bool z, bool w);
|
|
||||||
|
|
||||||
void tag_r(bool x, bool y, bool z, bool w);
|
|
||||||
|
|
||||||
std::string fetch_halfreg(u32 word_index) const;
|
|
||||||
|
|
||||||
public:
|
|
||||||
MixedPrecisionRegister();
|
|
||||||
|
|
||||||
void tag(u32 index, bool is_fp16, bool x, bool y, bool z, bool w);
|
|
||||||
|
|
||||||
std::string gather_r() const;
|
|
||||||
|
|
||||||
std::string split_h0() const
|
|
||||||
{
|
|
||||||
return fetch_halfreg(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string split_h1() const
|
|
||||||
{
|
|
||||||
return fetch_halfreg(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Getters
|
|
||||||
|
|
||||||
// Return true if all values are unwritten to (undefined)
|
|
||||||
bool floating() const
|
|
||||||
{
|
|
||||||
return file_index == umax;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if the first half register is all undefined
|
|
||||||
bool floating_h0() const
|
|
||||||
{
|
|
||||||
return content_mask[0] == content_mask[1] &&
|
|
||||||
content_mask[1] == content_mask[2] &&
|
|
||||||
content_mask[2] == content_mask[3] &&
|
|
||||||
content_mask[3] == data_type_bits::undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if the second half register is all undefined
|
|
||||||
bool floating_h1() const
|
|
||||||
{
|
|
||||||
return content_mask[4] == content_mask[5] &&
|
|
||||||
content_mask[5] == content_mask[6] &&
|
|
||||||
content_mask[6] == content_mask[7] &&
|
|
||||||
content_mask[7] == data_type_bits::undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if any of the half-words are 16-bit
|
|
||||||
bool requires_gather(u8 channel) const
|
|
||||||
{
|
|
||||||
// Data fetched from the single precision register requires merging of the two half registers
|
|
||||||
const auto channel_offset = channel * 2;
|
|
||||||
ensure(channel_offset <= 6);
|
|
||||||
|
|
||||||
return (content_mask[channel_offset] == data_type_bits::f16 || content_mask[channel_offset + 1] == data_type_bits::f16);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if the entire 128-bit register is filled with 2xfp16x4 data words
|
|
||||||
bool requires_gather128() const
|
|
||||||
{
|
|
||||||
// Full 128-bit check
|
|
||||||
for (const auto& ch : content_mask)
|
|
||||||
{
|
|
||||||
if (ch == data_type_bits::f16)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return true if the half-register is polluted with fp32 data
|
|
||||||
bool requires_split(u32 word_index) const
|
|
||||||
{
|
|
||||||
const u32 content_offset = word_index * 4;
|
|
||||||
for (u32 i = 0; i < 4; ++i)
|
|
||||||
{
|
|
||||||
if (content_mask[content_offset + i] == data_type_bits::f32)
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "program_util.h"
|
#include "program_util.h"
|
||||||
|
#include "Assembler/FPOpcodes.h"
|
||||||
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
@ -23,76 +24,7 @@ enum register_precision
|
|||||||
RSX_FP_PRECISION_UNKNOWN = 5 // Unknown what this actually does; seems to do nothing on hwtests but then why would their compiler emit it?
|
RSX_FP_PRECISION_UNKNOWN = 5 // Unknown what this actually does; seems to do nothing on hwtests but then why would their compiler emit it?
|
||||||
};
|
};
|
||||||
|
|
||||||
enum fp_opcode
|
using enum rsx::assembler::FP_opcode;
|
||||||
{
|
|
||||||
RSX_FP_OPCODE_NOP = 0x00, // No-Operation
|
|
||||||
RSX_FP_OPCODE_MOV = 0x01, // Move
|
|
||||||
RSX_FP_OPCODE_MUL = 0x02, // Multiply
|
|
||||||
RSX_FP_OPCODE_ADD = 0x03, // Add
|
|
||||||
RSX_FP_OPCODE_MAD = 0x04, // Multiply-Add
|
|
||||||
RSX_FP_OPCODE_DP3 = 0x05, // 3-component Dot Product
|
|
||||||
RSX_FP_OPCODE_DP4 = 0x06, // 4-component Dot Product
|
|
||||||
RSX_FP_OPCODE_DST = 0x07, // Distance
|
|
||||||
RSX_FP_OPCODE_MIN = 0x08, // Minimum
|
|
||||||
RSX_FP_OPCODE_MAX = 0x09, // Maximum
|
|
||||||
RSX_FP_OPCODE_SLT = 0x0A, // Set-If-LessThan
|
|
||||||
RSX_FP_OPCODE_SGE = 0x0B, // Set-If-GreaterEqual
|
|
||||||
RSX_FP_OPCODE_SLE = 0x0C, // Set-If-LessEqual
|
|
||||||
RSX_FP_OPCODE_SGT = 0x0D, // Set-If-GreaterThan
|
|
||||||
RSX_FP_OPCODE_SNE = 0x0E, // Set-If-NotEqual
|
|
||||||
RSX_FP_OPCODE_SEQ = 0x0F, // Set-If-Equal
|
|
||||||
RSX_FP_OPCODE_FRC = 0x10, // Fraction (fract)
|
|
||||||
RSX_FP_OPCODE_FLR = 0x11, // Floor
|
|
||||||
RSX_FP_OPCODE_KIL = 0x12, // Kill fragment
|
|
||||||
RSX_FP_OPCODE_PK4 = 0x13, // Pack four signed 8-bit values
|
|
||||||
RSX_FP_OPCODE_UP4 = 0x14, // Unpack four signed 8-bit values
|
|
||||||
RSX_FP_OPCODE_DDX = 0x15, // Partial-derivative in x (Screen space derivative w.r.t. x)
|
|
||||||
RSX_FP_OPCODE_DDY = 0x16, // Partial-derivative in y (Screen space derivative w.r.t. y)
|
|
||||||
RSX_FP_OPCODE_TEX = 0x17, // Texture lookup
|
|
||||||
RSX_FP_OPCODE_TXP = 0x18, // Texture sample with projection (Projective texture lookup)
|
|
||||||
RSX_FP_OPCODE_TXD = 0x19, // Texture sample with partial differentiation (Texture lookup with derivatives)
|
|
||||||
RSX_FP_OPCODE_RCP = 0x1A, // Reciprocal
|
|
||||||
RSX_FP_OPCODE_RSQ = 0x1B, // Reciprocal Square Root
|
|
||||||
RSX_FP_OPCODE_EX2 = 0x1C, // Exponentiation base 2
|
|
||||||
RSX_FP_OPCODE_LG2 = 0x1D, // Log base 2
|
|
||||||
RSX_FP_OPCODE_LIT = 0x1E, // Lighting coefficients
|
|
||||||
RSX_FP_OPCODE_LRP = 0x1F, // Linear Interpolation
|
|
||||||
RSX_FP_OPCODE_STR = 0x20, // Set-If-True
|
|
||||||
RSX_FP_OPCODE_SFL = 0x21, // Set-If-False
|
|
||||||
RSX_FP_OPCODE_COS = 0x22, // Cosine
|
|
||||||
RSX_FP_OPCODE_SIN = 0x23, // Sine
|
|
||||||
RSX_FP_OPCODE_PK2 = 0x24, // Pack two 16-bit floats
|
|
||||||
RSX_FP_OPCODE_UP2 = 0x25, // Unpack two 16-bit floats
|
|
||||||
RSX_FP_OPCODE_POW = 0x26, // Power
|
|
||||||
RSX_FP_OPCODE_PKB = 0x27, // Pack bytes
|
|
||||||
RSX_FP_OPCODE_UPB = 0x28, // Unpack bytes
|
|
||||||
RSX_FP_OPCODE_PK16 = 0x29, // Pack 16 bits
|
|
||||||
RSX_FP_OPCODE_UP16 = 0x2A, // Unpack 16
|
|
||||||
RSX_FP_OPCODE_BEM = 0x2B, // Bump-environment map (a.k.a. 2D coordinate transform)
|
|
||||||
RSX_FP_OPCODE_PKG = 0x2C, // Pack with sRGB transformation
|
|
||||||
RSX_FP_OPCODE_UPG = 0x2D, // Unpack gamma
|
|
||||||
RSX_FP_OPCODE_DP2A = 0x2E, // 2-component dot product with scalar addition
|
|
||||||
RSX_FP_OPCODE_TXL = 0x2F, // Texture sample with explicit LOD
|
|
||||||
RSX_FP_OPCODE_TXB = 0x31, // Texture sample with bias
|
|
||||||
RSX_FP_OPCODE_TEXBEM = 0x33,
|
|
||||||
RSX_FP_OPCODE_TXPBEM = 0x34,
|
|
||||||
RSX_FP_OPCODE_BEMLUM = 0x35,
|
|
||||||
RSX_FP_OPCODE_REFL = 0x36, // Reflection vector
|
|
||||||
RSX_FP_OPCODE_TIMESWTEX = 0x37,
|
|
||||||
RSX_FP_OPCODE_DP2 = 0x38, // 2-component dot product
|
|
||||||
RSX_FP_OPCODE_NRM = 0x39, // Normalize
|
|
||||||
RSX_FP_OPCODE_DIV = 0x3A, // Division
|
|
||||||
RSX_FP_OPCODE_DIVSQ = 0x3B, // Divide by Square Root
|
|
||||||
RSX_FP_OPCODE_LIF = 0x3C, // Final part of LIT
|
|
||||||
RSX_FP_OPCODE_FENCT = 0x3D, // Fence T?
|
|
||||||
RSX_FP_OPCODE_FENCB = 0x3E, // Fence B?
|
|
||||||
RSX_FP_OPCODE_BRK = 0x40, // Break
|
|
||||||
RSX_FP_OPCODE_CAL = 0x41, // Subroutine call
|
|
||||||
RSX_FP_OPCODE_IFE = 0x42, // If
|
|
||||||
RSX_FP_OPCODE_LOOP = 0x43, // Loop
|
|
||||||
RSX_FP_OPCODE_REP = 0x44, // Repeat
|
|
||||||
RSX_FP_OPCODE_RET = 0x45 // Return
|
|
||||||
};
|
|
||||||
|
|
||||||
union OPDEST
|
union OPDEST
|
||||||
{
|
{
|
||||||
@ -116,6 +48,12 @@ union OPDEST
|
|||||||
u32 no_dest : 1;
|
u32 no_dest : 1;
|
||||||
u32 saturate : 1; // _sat
|
u32 saturate : 1; // _sat
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u32 : 9;
|
||||||
|
u32 write_mask : 4;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
union SRC0
|
union SRC0
|
||||||
@ -164,7 +102,7 @@ union SRC1
|
|||||||
u32 src1_prec_mod : 3; // Precision modifier for src1 (CoD:MW series)
|
u32 src1_prec_mod : 3; // Precision modifier for src1 (CoD:MW series)
|
||||||
u32 src2_prec_mod : 3; // Precision modifier for src2 (unproven, should affect MAD instruction)
|
u32 src2_prec_mod : 3; // Precision modifier for src2 (unproven, should affect MAD instruction)
|
||||||
u32 scale : 3;
|
u32 scale : 3;
|
||||||
u32 opcode_is_branch : 1;
|
u32 opcode_hi : 1; // Opcode high bit
|
||||||
};
|
};
|
||||||
|
|
||||||
struct
|
struct
|
||||||
@ -207,6 +145,23 @@ union SRC2
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
union SRC_Common
|
||||||
|
{
|
||||||
|
u32 HEX;
|
||||||
|
|
||||||
|
struct
|
||||||
|
{
|
||||||
|
u32 reg_type : 2;
|
||||||
|
u32 tmp_reg_index : 6;
|
||||||
|
u32 fp16 : 1;
|
||||||
|
u32 swizzle_x : 2;
|
||||||
|
u32 swizzle_y : 2;
|
||||||
|
u32 swizzle_z : 2;
|
||||||
|
u32 swizzle_w : 2;
|
||||||
|
u32 neg : 1;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
constexpr const char* rsx_fp_input_attr_regs[] =
|
constexpr const char* rsx_fp_input_attr_regs[] =
|
||||||
{
|
{
|
||||||
"WPOS", "COL0", "COL1", "FOGC", "TEX0",
|
"WPOS", "COL0", "COL1", "FOGC", "TEX0",
|
||||||
|
|||||||
@ -244,10 +244,10 @@ public:
|
|||||||
std::vector<std::string> swizzles;
|
std::vector<std::string> swizzles;
|
||||||
|
|
||||||
ShaderVariable() = default;
|
ShaderVariable() = default;
|
||||||
ShaderVariable(const std::string& var)
|
ShaderVariable(std::string_view var)
|
||||||
{
|
{
|
||||||
// Separate 'double destination' variables 'X=Y=SRC'
|
// Separate 'double destination' variables 'X=Y=SRC'
|
||||||
std::string simple_var;
|
std::string_view simple_var;
|
||||||
const auto eq_pos = var.find('=');
|
const auto eq_pos = var.find('=');
|
||||||
|
|
||||||
if (eq_pos != umax)
|
if (eq_pos != umax)
|
||||||
@ -267,11 +267,11 @@ public:
|
|||||||
simple_var = simple_var.substr(brace_pos);
|
simple_var = simple_var.substr(brace_pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto var_blocks = fmt::split(simple_var, { "." });
|
const auto var_blocks = fmt::split_sv(simple_var, { "." });
|
||||||
|
|
||||||
ensure((!var_blocks.empty()));
|
ensure((!var_blocks.empty()));
|
||||||
|
|
||||||
name = prefix + var_blocks[0];
|
name = prefix + std::string(var_blocks[0]);
|
||||||
|
|
||||||
if (var_blocks.size() == 1)
|
if (var_blocks.size() == 1)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#define VK_USE_PLATFORM_WIN32_KHR
|
#define VK_USE_PLATFORM_WIN32_KHR
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
#define VK_USE_PLATFORM_MACOS_MVK
|
#define VK_USE_PLATFORM_METAL_EXT
|
||||||
#elif defined(ANDROID)
|
#elif defined(ANDROID)
|
||||||
#define VK_USE_PLATFORM_ANDROID_KHR
|
#define VK_USE_PLATFORM_ANDROID_KHR
|
||||||
#else
|
#else
|
||||||
@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
// Undefine header configuration variables
|
// Undefine header configuration variables
|
||||||
#undef VK_USE_PLATFORM_WIN32_KHR
|
#undef VK_USE_PLATFORM_WIN32_KHR
|
||||||
#undef VK_USE_PLATFORM_MACOS_MVK
|
#undef VK_USE_PLATFORM_METAL_EXT
|
||||||
#undef VK_USE_PLATFORM_ANDROID_KHR
|
#undef VK_USE_PLATFORM_ANDROID_KHR
|
||||||
#undef VK_USE_PLATFORM_XLIB_KHR
|
#undef VK_USE_PLATFORM_XLIB_KHR
|
||||||
#undef VK_USE_PLATFORM_WAYLAND_KHR
|
#undef VK_USE_PLATFORM_WAYLAND_KHR
|
||||||
|
|||||||
@ -50,6 +50,7 @@ namespace vk
|
|||||||
: m_device(dev)
|
: m_device(dev)
|
||||||
{
|
{
|
||||||
const bool nullable = !!(flags & VK_BUFFER_CREATE_ALLOW_NULL_RPCS3);
|
const bool nullable = !!(flags & VK_BUFFER_CREATE_ALLOW_NULL_RPCS3);
|
||||||
|
const bool no_vmem_recovery = !!(flags & VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3);
|
||||||
|
|
||||||
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
|
||||||
info.flags = flags & ~VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3;
|
info.flags = flags & ~VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3;
|
||||||
@ -69,18 +70,27 @@ namespace vk
|
|||||||
fmt::throw_exception("No compatible memory type was found!");
|
fmt::throw_exception("No compatible memory type was found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
memory = std::make_unique<memory_block>(m_device, memory_reqs.size, memory_reqs.alignment, allocation_type_info, allocation_pool, nullable);
|
memory_allocation_request request
|
||||||
|
{
|
||||||
|
.size = memory_reqs.size,
|
||||||
|
.alignment = memory_reqs.alignment,
|
||||||
|
.memory_type = &allocation_type_info,
|
||||||
|
.pool = allocation_pool,
|
||||||
|
.throw_on_fail = !nullable,
|
||||||
|
.recover_vmem_on_fail = !no_vmem_recovery
|
||||||
|
};
|
||||||
|
memory = std::make_unique<memory_block>(m_device, request);
|
||||||
|
|
||||||
if (auto device_memory = memory->get_vk_device_memory();
|
if (auto device_memory = memory->get_vk_device_memory();
|
||||||
device_memory != VK_NULL_HANDLE)
|
device_memory != VK_NULL_HANDLE)
|
||||||
{
|
{
|
||||||
vkBindBufferMemory(dev, value, device_memory, memory->get_vk_device_memory_offset());
|
vkBindBufferMemory(dev, value, device_memory, memory->get_vk_device_memory_offset());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
ensure(nullable);
|
||||||
ensure(nullable);
|
vkDestroyBuffer(m_device, value, nullptr);
|
||||||
vkDestroyBuffer(m_device, value, nullptr);
|
value = VK_NULL_HANDLE;
|
||||||
value = VK_NULL_HANDLE;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer::buffer(const vk::render_device& dev, VkBufferUsageFlags usage, void* host_pointer, u64 size)
|
buffer::buffer(const vk::render_device& dev, VkBufferUsageFlags usage, void* host_pointer, u64 size)
|
||||||
|
|||||||
@ -9,9 +9,10 @@ namespace vk
|
|||||||
{
|
{
|
||||||
enum : u32
|
enum : u32
|
||||||
{
|
{
|
||||||
VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x80000000,
|
VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 = 0x10000000, // If we cannot allocate memory for the buffer, just return an empty but valid object with a null handle.
|
||||||
|
VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3 = 0x20000000, // If we cannot allocate memory for the buffer, do not run recovery routine to recover VRAM. Crash or return empty handle immediately instead.
|
||||||
|
|
||||||
VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3)
|
VK_BUFFER_CREATE_SPECIAL_FLAGS_RPCS3 = (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3)
|
||||||
};
|
};
|
||||||
|
|
||||||
struct buffer_view : public unique_resource
|
struct buffer_view : public unique_resource
|
||||||
|
|||||||
@ -53,7 +53,7 @@ namespace vk
|
|||||||
VkFlags create_flags = 0;
|
VkFlags create_flags = 0;
|
||||||
if (m_prefer_writethrough)
|
if (m_prefer_writethrough)
|
||||||
{
|
{
|
||||||
create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3;
|
create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3);
|
||||||
}
|
}
|
||||||
|
|
||||||
heap = std::make_unique<buffer>(*g_render_device, size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM);
|
heap = std::make_unique<buffer>(*g_render_device, size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM);
|
||||||
@ -146,7 +146,7 @@ namespace vk
|
|||||||
VkFlags create_flags = 0;
|
VkFlags create_flags = 0;
|
||||||
if (m_prefer_writethrough)
|
if (m_prefer_writethrough)
|
||||||
{
|
{
|
||||||
create_flags |= VK_BUFFER_CREATE_ALLOW_NULL_RPCS3;
|
create_flags |= (VK_BUFFER_CREATE_ALLOW_NULL_RPCS3 | VK_BUFFER_CREATE_IGNORE_VMEM_PRESSURE_RPCS3);
|
||||||
}
|
}
|
||||||
|
|
||||||
heap = std::make_unique<buffer>(*g_render_device, aligned_new_size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM);
|
heap = std::make_unique<buffer>(*g_render_device, aligned_new_size, memory_index, memory_flags, usage, create_flags, VMM_ALLOCATION_POOL_SYSTEM);
|
||||||
|
|||||||
@ -3,6 +3,9 @@
|
|||||||
#include "util/logs.hpp"
|
#include "util/logs.hpp"
|
||||||
#include "Emu/system_config.h"
|
#include "Emu/system_config.h"
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <vulkan/vulkan_beta.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace vk
|
namespace vk
|
||||||
{
|
{
|
||||||
@ -555,6 +558,10 @@ namespace vk
|
|||||||
{
|
{
|
||||||
requested_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME);
|
requested_extensions.push_back(VK_EXT_DEVICE_FAULT_EXTENSION_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
requested_extensions.push_back(VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME);
|
||||||
|
#endif
|
||||||
|
|
||||||
enabled_features.robustBufferAccess = VK_TRUE;
|
enabled_features.robustBufferAccess = VK_TRUE;
|
||||||
enabled_features.fullDrawIndexUint32 = VK_TRUE;
|
enabled_features.fullDrawIndexUint32 = VK_TRUE;
|
||||||
|
|||||||
@ -128,7 +128,16 @@ namespace vk
|
|||||||
fmt::throw_exception("No compatible memory type was found!");
|
fmt::throw_exception("No compatible memory type was found!");
|
||||||
}
|
}
|
||||||
|
|
||||||
memory = std::make_shared<vk::memory_block>(m_device, memory_req.size, memory_req.alignment, allocation_type_info, allocation_pool, nullable);
|
memory_allocation_request alloc_request
|
||||||
|
{
|
||||||
|
.size = memory_req.size,
|
||||||
|
.alignment = memory_req.alignment,
|
||||||
|
.memory_type = &allocation_type_info,
|
||||||
|
.pool = allocation_pool,
|
||||||
|
.throw_on_fail = !nullable
|
||||||
|
};
|
||||||
|
memory = std::make_shared<vk::memory_block>(m_device, alloc_request);
|
||||||
|
|
||||||
if (auto device_mem = memory->get_vk_device_memory();
|
if (auto device_mem = memory->get_vk_device_memory();
|
||||||
device_mem != VK_NULL_HANDLE) [[likely]]
|
device_mem != VK_NULL_HANDLE) [[likely]]
|
||||||
{
|
{
|
||||||
|
|||||||
@ -124,10 +124,11 @@ namespace vk
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||||
|
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||||
if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME))
|
if (support.is_supported(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME))
|
||||||
{
|
{
|
||||||
extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
|
extensions.push_back(VK_EXT_LAYER_SETTINGS_EXTENSION_NAME);
|
||||||
layers.push_back(kMVKMoltenVKDriverLayerName);
|
|
||||||
|
|
||||||
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true });
|
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_RESUME_LOST_DEVICE", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_true });
|
||||||
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math });
|
mvk_settings.push_back(VkLayerSettingEXT{ kMVKMoltenVKDriverLayerName, "MVK_CONFIG_FAST_MATH_ENABLED", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_fast_math });
|
||||||
@ -154,7 +155,7 @@ namespace vk
|
|||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
extensions.push_back(VK_KHR_WIN32_SURFACE_EXTENSION_NAME);
|
||||||
#elif defined(__APPLE__)
|
#elif defined(__APPLE__)
|
||||||
extensions.push_back(VK_MVK_MACOS_SURFACE_EXTENSION_NAME);
|
extensions.push_back(VK_EXT_METAL_SURFACE_EXTENSION_NAME);
|
||||||
#else
|
#else
|
||||||
bool found_surface_ext = false;
|
bool found_surface_ext = false;
|
||||||
#ifdef HAVE_X11
|
#ifdef HAVE_X11
|
||||||
@ -187,15 +188,32 @@ namespace vk
|
|||||||
if (g_cfg.video.debug_output)
|
if (g_cfg.video.debug_output)
|
||||||
layers.push_back("VK_LAYER_KHRONOS_validation");
|
layers.push_back("VK_LAYER_KHRONOS_validation");
|
||||||
}
|
}
|
||||||
|
#ifdef __APPLE__
|
||||||
|
// MoltenVK's ICD will not be detected without these extensions enabled.
|
||||||
|
else
|
||||||
|
{
|
||||||
|
extensions_loaded = true;
|
||||||
|
extensions.push_back(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
|
||||||
|
extensions.push_back(VK_KHR_GET_PHYSICAL_DEVICE_PROPERTIES_2_EXTENSION_NAME);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
VkInstanceCreateInfo instance_info = {};
|
VkInstanceCreateInfo instance_info = {};
|
||||||
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
||||||
instance_info.pApplicationInfo = &app;
|
instance_info.pApplicationInfo = &app;
|
||||||
instance_info.enabledLayerCount = static_cast<u32>(layers.size());
|
instance_info.enabledLayerCount = static_cast<u32>(layers.size());
|
||||||
instance_info.ppEnabledLayerNames = layers.data();
|
instance_info.ppEnabledLayerNames = layers.data();
|
||||||
|
#ifdef __APPLE__
|
||||||
|
instance_info.enabledExtensionCount = static_cast<u32>(extensions.size());
|
||||||
|
instance_info.ppEnabledExtensionNames = extensions.data();
|
||||||
|
#else
|
||||||
instance_info.enabledExtensionCount = fast ? 0 : static_cast<u32>(extensions.size());
|
instance_info.enabledExtensionCount = fast ? 0 : static_cast<u32>(extensions.size());
|
||||||
instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data();
|
instance_info.ppEnabledExtensionNames = fast ? nullptr : extensions.data();
|
||||||
|
#endif
|
||||||
instance_info.pNext = next_info;
|
instance_info.pNext = next_info;
|
||||||
|
#ifdef __APPLE__
|
||||||
|
instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
||||||
|
#endif
|
||||||
|
|
||||||
if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS)
|
if (VkResult result = vkCreateInstance(&instance_info, nullptr, &m_instance); result != VK_SUCCESS)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -224,7 +224,7 @@ namespace vk
|
|||||||
vmaDestroyAllocator(m_allocator);
|
vmaDestroyAllocator(m_allocator);
|
||||||
}
|
}
|
||||||
|
|
||||||
mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail)
|
mem_allocator_vk::mem_handle_t mem_allocator_vma::alloc(const memory_allocation_request& request)
|
||||||
{
|
{
|
||||||
VmaAllocation vma_alloc;
|
VmaAllocation vma_alloc;
|
||||||
VkMemoryRequirements mem_req = {};
|
VkMemoryRequirements mem_req = {};
|
||||||
@ -233,11 +233,11 @@ namespace vk
|
|||||||
|
|
||||||
auto do_vma_alloc = [&]() -> std::tuple<VkResult, u32>
|
auto do_vma_alloc = [&]() -> std::tuple<VkResult, u32>
|
||||||
{
|
{
|
||||||
for (const auto& memory_type_index : memory_type)
|
for (const auto& memory_type_index : *request.memory_type)
|
||||||
{
|
{
|
||||||
mem_req.memoryTypeBits = 1u << memory_type_index;
|
mem_req.memoryTypeBits = 1u << memory_type_index;
|
||||||
mem_req.size = ::align2(block_sz, alignment);
|
mem_req.size = ::align2(request.size, request.alignment);
|
||||||
mem_req.alignment = alignment;
|
mem_req.alignment = request.alignment;
|
||||||
create_info.memoryTypeBits = 1u << memory_type_index;
|
create_info.memoryTypeBits = 1u << memory_type_index;
|
||||||
create_info.flags = m_allocation_flags;
|
create_info.flags = m_allocation_flags;
|
||||||
|
|
||||||
@ -256,26 +256,29 @@ namespace vk
|
|||||||
const auto [status, type] = do_vma_alloc();
|
const auto [status, type] = do_vma_alloc();
|
||||||
if (status == VK_SUCCESS)
|
if (status == VK_SUCCESS)
|
||||||
{
|
{
|
||||||
vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool);
|
vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool);
|
||||||
return vma_alloc;
|
return vma_alloc;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe;
|
if (request.recover_vmem_on_fail)
|
||||||
if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
|
|
||||||
vmm_handle_memory_pressure(severity))
|
|
||||||
{
|
{
|
||||||
// Out of memory. Try again.
|
const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe;
|
||||||
const auto [status, type] = do_vma_alloc();
|
if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
|
||||||
if (status == VK_SUCCESS)
|
vmm_handle_memory_pressure(severity))
|
||||||
{
|
{
|
||||||
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
|
// Out of memory. Try again.
|
||||||
vmm_notify_memory_allocated(vma_alloc, type, block_sz, pool);
|
const auto [status, type] = do_vma_alloc();
|
||||||
return vma_alloc;
|
if (status == VK_SUCCESS)
|
||||||
|
{
|
||||||
|
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
|
||||||
|
vmm_notify_memory_allocated(vma_alloc, type, request.size, request.pool);
|
||||||
|
return vma_alloc;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!throw_on_fail)
|
if (!request.throw_on_fail)
|
||||||
{
|
{
|
||||||
return VK_NULL_HANDLE;
|
return VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
@ -361,18 +364,18 @@ namespace vk
|
|||||||
m_allocation_flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
|
m_allocation_flags = VMA_ALLOCATION_CREATE_STRATEGY_MIN_TIME_BIT;
|
||||||
}
|
}
|
||||||
|
|
||||||
mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail)
|
mem_allocator_vk::mem_handle_t mem_allocator_vk::alloc(const memory_allocation_request& request)
|
||||||
{
|
{
|
||||||
VkResult error_code = VK_ERROR_UNKNOWN;
|
VkResult error_code = VK_ERROR_UNKNOWN;
|
||||||
VkDeviceMemory memory;
|
VkDeviceMemory memory;
|
||||||
|
|
||||||
VkMemoryAllocateInfo info = {};
|
VkMemoryAllocateInfo info = {};
|
||||||
info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
|
||||||
info.allocationSize = block_sz;
|
info.allocationSize = request.size;
|
||||||
|
|
||||||
auto do_vk_alloc = [&]() -> std::tuple<VkResult, u32>
|
auto do_vk_alloc = [&]() -> std::tuple<VkResult, u32>
|
||||||
{
|
{
|
||||||
for (const auto& memory_type_index : memory_type)
|
for (const auto& memory_type_index : *request.memory_type)
|
||||||
{
|
{
|
||||||
info.memoryTypeIndex = memory_type_index;
|
info.memoryTypeIndex = memory_type_index;
|
||||||
error_code = vkAllocateMemory(m_device, &info, nullptr, &memory);
|
error_code = vkAllocateMemory(m_device, &info, nullptr, &memory);
|
||||||
@ -389,26 +392,29 @@ namespace vk
|
|||||||
const auto [status, type] = do_vk_alloc();
|
const auto [status, type] = do_vk_alloc();
|
||||||
if (status == VK_SUCCESS)
|
if (status == VK_SUCCESS)
|
||||||
{
|
{
|
||||||
vmm_notify_memory_allocated(memory, type, block_sz, pool);
|
vmm_notify_memory_allocated(memory, type, request.size, request.pool);
|
||||||
return memory;
|
return memory;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto severity = (throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe;
|
if (request.recover_vmem_on_fail)
|
||||||
if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
|
|
||||||
vmm_handle_memory_pressure(severity))
|
|
||||||
{
|
{
|
||||||
// Out of memory. Try again.
|
const auto severity = (request.throw_on_fail) ? rsx::problem_severity::fatal : rsx::problem_severity::severe;
|
||||||
const auto [status, type] = do_vk_alloc();
|
if (error_code == VK_ERROR_OUT_OF_DEVICE_MEMORY &&
|
||||||
if (status == VK_SUCCESS)
|
vmm_handle_memory_pressure(severity))
|
||||||
{
|
{
|
||||||
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
|
// Out of memory. Try again.
|
||||||
vmm_notify_memory_allocated(memory, type, block_sz, pool);
|
const auto [status, type] = do_vk_alloc();
|
||||||
return memory;
|
if (status == VK_SUCCESS)
|
||||||
|
{
|
||||||
|
rsx_log.warning("Renderer ran out of video memory but successfully recovered.");
|
||||||
|
vmm_notify_memory_allocated(memory, type, request.size, request.pool);
|
||||||
|
return memory;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!throw_on_fail)
|
if (!request.throw_on_fail)
|
||||||
{
|
{
|
||||||
return VK_NULL_HANDLE;
|
return VK_NULL_HANDLE;
|
||||||
}
|
}
|
||||||
@ -455,11 +461,11 @@ namespace vk
|
|||||||
return g_render_device->get_allocator();
|
return g_render_device->get_allocator();
|
||||||
}
|
}
|
||||||
|
|
||||||
memory_block::memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable)
|
memory_block::memory_block(VkDevice dev, const memory_allocation_request& alloc_request)
|
||||||
: m_device(dev), m_size(block_sz)
|
: m_device(dev), m_size(alloc_request.size)
|
||||||
{
|
{
|
||||||
m_mem_allocator = get_current_mem_allocator();
|
m_mem_allocator = get_current_mem_allocator();
|
||||||
m_mem_handle = m_mem_allocator->alloc(block_sz, alignment, memory_type, pool, !nullable);
|
m_mem_handle = m_mem_allocator->alloc(alloc_request);
|
||||||
}
|
}
|
||||||
|
|
||||||
memory_block::~memory_block()
|
memory_block::~memory_block()
|
||||||
|
|||||||
@ -66,6 +66,16 @@ namespace vk
|
|||||||
u64 size;
|
u64 size;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct memory_allocation_request
|
||||||
|
{
|
||||||
|
u64 size = 0;
|
||||||
|
u64 alignment = 1;
|
||||||
|
const memory_type_info* memory_type = nullptr;
|
||||||
|
vmm_allocation_pool pool = VMM_ALLOCATION_POOL_UNDEFINED;
|
||||||
|
bool throw_on_fail = true;
|
||||||
|
bool recover_vmem_on_fail = true;
|
||||||
|
};
|
||||||
|
|
||||||
class mem_allocator_base
|
class mem_allocator_base
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -76,7 +86,7 @@ namespace vk
|
|||||||
|
|
||||||
virtual void destroy() = 0;
|
virtual void destroy() = 0;
|
||||||
|
|
||||||
virtual mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) = 0;
|
virtual mem_handle_t alloc(const memory_allocation_request& request) = 0;
|
||||||
virtual void free(mem_handle_t mem_handle) = 0;
|
virtual void free(mem_handle_t mem_handle) = 0;
|
||||||
virtual void* map(mem_handle_t mem_handle, u64 offset, u64 size) = 0;
|
virtual void* map(mem_handle_t mem_handle, u64 offset, u64 size) = 0;
|
||||||
virtual void unmap(mem_handle_t mem_handle) = 0;
|
virtual void unmap(mem_handle_t mem_handle) = 0;
|
||||||
@ -104,7 +114,7 @@ namespace vk
|
|||||||
|
|
||||||
void destroy() override;
|
void destroy() override;
|
||||||
|
|
||||||
mem_handle_t alloc(u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override;
|
mem_handle_t alloc(const memory_allocation_request& request) override;
|
||||||
|
|
||||||
void free(mem_handle_t mem_handle) override;
|
void free(mem_handle_t mem_handle) override;
|
||||||
void* map(mem_handle_t mem_handle, u64 offset, u64 /*size*/) override;
|
void* map(mem_handle_t mem_handle, u64 offset, u64 /*size*/) override;
|
||||||
@ -134,7 +144,7 @@ namespace vk
|
|||||||
|
|
||||||
void destroy() override {}
|
void destroy() override {}
|
||||||
|
|
||||||
mem_handle_t alloc(u64 block_sz, u64 /*alignment*/, const memory_type_info& memory_type, vmm_allocation_pool pool, bool throw_on_fail) override;
|
mem_handle_t alloc(const memory_allocation_request& request) override;
|
||||||
|
|
||||||
void free(mem_handle_t mem_handle) override;
|
void free(mem_handle_t mem_handle) override;
|
||||||
void* map(mem_handle_t mem_handle, u64 offset, u64 size) override;
|
void* map(mem_handle_t mem_handle, u64 offset, u64 size) override;
|
||||||
@ -147,7 +157,7 @@ namespace vk
|
|||||||
|
|
||||||
struct memory_block
|
struct memory_block
|
||||||
{
|
{
|
||||||
memory_block(VkDevice dev, u64 block_sz, u64 alignment, const memory_type_info& memory_type, vmm_allocation_pool pool, bool nullable = false);
|
memory_block(VkDevice dev, const memory_allocation_request& alloc_request);
|
||||||
virtual ~memory_block();
|
virtual ~memory_block();
|
||||||
|
|
||||||
virtual VkDeviceMemory get_vk_device_memory();
|
virtual VkDeviceMemory get_vk_device_memory();
|
||||||
|
|||||||
2
rpcs3/Emu/RSX/VK/vkutils/metal_layer.h
Normal file
2
rpcs3/Emu/RSX/VK/vkutils/metal_layer.h
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
#pragma once
|
||||||
|
void* GetCAMetalLayerFromMetalView(void* view);
|
||||||
10
rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm
Normal file
10
rpcs3/Emu/RSX/VK/vkutils/metal_layer.mm
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
#pragma GCC diagnostic push
|
||||||
|
#pragma GCC diagnostic ignored "-Wold-style-cast"
|
||||||
|
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
|
||||||
|
#pragma GCC diagnostic ignored "-Wmissing-declarations"
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import <AppKit/AppKit.h>
|
||||||
|
#import <QuartzCore/QuartzCore.h>
|
||||||
|
|
||||||
|
void* GetCAMetalLayerFromMetalView(void* view) { return ((NSView*)view).layer; }
|
||||||
|
#pragma GCC diagnostic pop
|
||||||
@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "swapchain_core.h"
|
#include "swapchain_core.h"
|
||||||
|
#include "metal_layer.h"
|
||||||
|
|
||||||
namespace vk
|
namespace vk
|
||||||
{
|
{
|
||||||
@ -12,11 +13,11 @@ namespace vk
|
|||||||
VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/)
|
VkSurfaceKHR make_WSI_surface(VkInstance vk_instance, display_handle_t window_handle, WSI_config* /*config*/)
|
||||||
{
|
{
|
||||||
VkSurfaceKHR result = VK_NULL_HANDLE;
|
VkSurfaceKHR result = VK_NULL_HANDLE;
|
||||||
VkMacOSSurfaceCreateInfoMVK createInfo = {};
|
VkMetalSurfaceCreateInfoEXT createInfo = {};
|
||||||
createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
|
createInfo.sType = VK_STRUCTURE_TYPE_METAL_SURFACE_CREATE_INFO_EXT;
|
||||||
createInfo.pView = window_handle;
|
createInfo.pLayer = GetCAMetalLayerFromMetalView(window_handle);
|
||||||
|
|
||||||
CHECK_RESULT(vkCreateMacOSSurfaceMVK(vk_instance, &createInfo, NULL, &result));
|
CHECK_RESULT(vkCreateMetalSurfaceEXT(vk_instance, &createInfo, NULL, &result));
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -1866,8 +1866,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||||||
// PS1 Classic located in dev_hdd0/game
|
// PS1 Classic located in dev_hdd0/game
|
||||||
sys_log.notice("PS1 Game: %s, %s", m_title_id, m_title);
|
sys_log.notice("PS1 Game: %s, %s", m_title_id, m_title);
|
||||||
|
|
||||||
const std::string tail = m_path.substr(hdd0_game.size());
|
const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size());
|
||||||
const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim));
|
const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)));
|
||||||
const std::string game_path = "/dev_hdd0/game/" + dirname;
|
const std::string game_path = "/dev_hdd0/game/" + dirname;
|
||||||
|
|
||||||
argv.resize(9);
|
argv.resize(9);
|
||||||
@ -1894,8 +1894,8 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||||||
// PSP Remaster located in dev_hdd0/game
|
// PSP Remaster located in dev_hdd0/game
|
||||||
sys_log.notice("PSP Remaster Game: %s, %s", m_title_id, m_title);
|
sys_log.notice("PSP Remaster Game: %s, %s", m_title_id, m_title);
|
||||||
|
|
||||||
const std::string tail = m_path.substr(hdd0_game.size());
|
const std::string_view tail = std::string_view(m_path).substr(hdd0_game.size());
|
||||||
const std::string dirname = fmt::trim_front(tail, fs::delim).substr(0, tail.find_first_of(fs::delim));
|
const std::string dirname = std::string(fmt::trim_front_sv(tail, fs::delim).substr(0, tail.find_first_of(fs::delim)));
|
||||||
const std::string game_path = "/dev_hdd0/game/" + dirname;
|
const std::string game_path = "/dev_hdd0/game/" + dirname;
|
||||||
|
|
||||||
argv.resize(2);
|
argv.resize(2);
|
||||||
@ -1913,7 +1913,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||||||
// Add HG games not in HDD0 to games.yml
|
// Add HG games not in HDD0 to games.yml
|
||||||
[[maybe_unused]] const games_config::result res = m_games_config.add_external_hdd_game(m_title_id, game_dir);
|
[[maybe_unused]] const games_config::result res = m_games_config.add_external_hdd_game(m_title_id, game_dir);
|
||||||
|
|
||||||
const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim);
|
const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim));
|
||||||
vfs::mount("/dev_hdd0/game/" + dir, game_dir + '/');
|
vfs::mount("/dev_hdd0/game/" + dir, game_dir + '/');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2265,10 +2265,10 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||||||
auto unescape = [](std::string_view path)
|
auto unescape = [](std::string_view path)
|
||||||
{
|
{
|
||||||
// Unescape from host FS
|
// Unescape from host FS
|
||||||
std::vector<std::string> escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}});
|
const std::vector<std::string_view> escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}});
|
||||||
std::vector<std::string> result;
|
std::vector<std::string> result;
|
||||||
for (auto& sv : escaped)
|
for (const auto& sv : escaped)
|
||||||
result.emplace_back(vfs::unescape(sv));
|
result.push_back(vfs::unescape(sv));
|
||||||
|
|
||||||
return fmt::merge(result, "/");
|
return fmt::merge(result, "/");
|
||||||
};
|
};
|
||||||
@ -2315,7 +2315,7 @@ game_boot_result Emulator::Load(const std::string& title_id, bool is_disc_patch,
|
|||||||
game_dir = game_dir.substr(0, game_dir.size() - 4);
|
game_dir = game_dir.substr(0, game_dir.size() - 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string dir = fmt::trim(game_dir.substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim);
|
const std::string dir = std::string(fmt::trim_sv(std::string_view(game_dir).substr(fs::get_parent_dir_view(game_dir).size() + 1), fs::delim));
|
||||||
|
|
||||||
m_dir = "/dev_hdd0/game/" + dir + '/';
|
m_dir = "/dev_hdd0/game/" + dir + '/';
|
||||||
argv[0] = m_dir + unescape(resolved_path.substr(GetCallbacks().resolve_path(game_dir).size()));
|
argv[0] = m_dir + unescape(resolved_path.substr(GetCallbacks().resolve_path(game_dir).size()));
|
||||||
|
|||||||
@ -137,7 +137,7 @@ bool vfs::unmount(std::string_view vpath)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::vector<std::string> entry_list = fmt::split(vpath, {"/"});
|
const std::vector<std::string_view> entry_list = fmt::split_sv(vpath, {"/"});
|
||||||
|
|
||||||
if (entry_list.empty())
|
if (entry_list.empty())
|
||||||
{
|
{
|
||||||
@ -166,7 +166,7 @@ bool vfs::unmount(std::string_view vpath)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Get the current name based on the depth
|
// Get the current name based on the depth
|
||||||
const std::string& name = ::at32(entry_list, depth);
|
const std::string_view name = ::at32(entry_list, depth);
|
||||||
|
|
||||||
// Go through all children of this node
|
// Go through all children of this node
|
||||||
for (auto it = dir.dirs.begin(); it != dir.dirs.end();)
|
for (auto it = dir.dirs.begin(); it != dir.dirs.end();)
|
||||||
@ -456,10 +456,10 @@ std::string vfs::retrieve(std::string_view path, const vfs_directory* node, std:
|
|||||||
auto unescape_path = [](std::string_view path)
|
auto unescape_path = [](std::string_view path)
|
||||||
{
|
{
|
||||||
// Unescape from host FS
|
// Unescape from host FS
|
||||||
std::vector<std::string> escaped = fmt::split(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}});
|
const std::vector<std::string_view> escaped = fmt::split_sv(path, {std::string_view{&fs::delim[0], 1}, std::string_view{&fs::delim[1], 1}});
|
||||||
std::vector<std::string> result;
|
std::vector<std::string> result;
|
||||||
for (auto& sv : escaped)
|
for (const auto& sv : escaped)
|
||||||
result.emplace_back(vfs::unescape(sv));
|
result.push_back(vfs::unescape(sv));
|
||||||
|
|
||||||
return fmt::merge(result, "/");
|
return fmt::merge(result, "/");
|
||||||
};
|
};
|
||||||
|
|||||||
@ -184,6 +184,11 @@ namespace rpcs3::utils
|
|||||||
return g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, get_emu_dir());
|
return g_cfg_vfs.get(g_cfg_vfs.dev_bdvd, get_emu_dir());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string get_hdd0_game_dir()
|
||||||
|
{
|
||||||
|
return get_hdd0_dir() + "game/";
|
||||||
|
}
|
||||||
|
|
||||||
u64 get_cache_disk_usage()
|
u64 get_cache_disk_usage()
|
||||||
{
|
{
|
||||||
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_cache_dir(), 1); data_size != umax)
|
if (const u64 data_size = fs::get_dir_size(rpcs3::utils::get_cache_dir(), 1); data_size != umax)
|
||||||
|
|||||||
@ -34,6 +34,8 @@ namespace rpcs3::utils
|
|||||||
std::string get_flash3_dir();
|
std::string get_flash3_dir();
|
||||||
std::string get_bdvd_dir();
|
std::string get_bdvd_dir();
|
||||||
|
|
||||||
|
std::string get_hdd0_game_dir();
|
||||||
|
|
||||||
// Cache directories and disk usage
|
// Cache directories and disk usage
|
||||||
u64 get_cache_disk_usage();
|
u64 get_cache_disk_usage();
|
||||||
std::string get_cache_dir();
|
std::string get_cache_dir();
|
||||||
|
|||||||
@ -1346,7 +1346,7 @@ bool evdev_joystick_handler::bindPadToDevice(std::shared_ptr<Pad> pad)
|
|||||||
|
|
||||||
const auto find_buttons = [&](const cfg::string& name) -> std::set<u32>
|
const auto find_buttons = [&](const cfg::string& name) -> std::set<u32>
|
||||||
{
|
{
|
||||||
const std::vector<std::string> names = cfg_pad::get_buttons(name);
|
const std::vector<std::string> names = cfg_pad::get_buttons(name.to_string());
|
||||||
|
|
||||||
// In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes.
|
// In evdev we store indices to an EvdevButton vector in our pad objects instead of the usual key codes.
|
||||||
std::set<u32> indices;
|
std::set<u32> indices;
|
||||||
|
|||||||
@ -835,7 +835,7 @@ std::string keyboard_pad_handler::GetKeyName(const u32& keyCode)
|
|||||||
std::set<u32> keyboard_pad_handler::GetKeyCodes(const cfg::string& cfg_string)
|
std::set<u32> keyboard_pad_handler::GetKeyCodes(const cfg::string& cfg_string)
|
||||||
{
|
{
|
||||||
std::set<u32> key_codes;
|
std::set<u32> key_codes;
|
||||||
for (const std::string& key_name : cfg_pad::get_buttons(cfg_string))
|
for (const std::string& key_name : cfg_pad::get_buttons(cfg_string.to_string()))
|
||||||
{
|
{
|
||||||
if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton)
|
if (u32 code = GetKeyCode(QString::fromStdString(key_name)); code != Qt::NoButton)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -148,7 +148,7 @@ std::vector<pad_list_entry> mm_joystick_handler::list_devices()
|
|||||||
template <typename T>
|
template <typename T>
|
||||||
std::set<T> mm_joystick_handler::find_keys(const cfg::string& cfg_string) const
|
std::set<T> mm_joystick_handler::find_keys(const cfg::string& cfg_string) const
|
||||||
{
|
{
|
||||||
return find_keys<T>(cfg_pad::get_buttons(cfg_string));
|
return find_keys<T>(cfg_pad::get_buttons(cfg_string.to_string()));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
|
|||||||
@ -176,6 +176,9 @@ void ps_move_handler::init_config(cfg_pad* cfg)
|
|||||||
cfg->ltriggerthreshold.def = 0; // between 0 and 255
|
cfg->ltriggerthreshold.def = 0; // between 0 and 255
|
||||||
cfg->rtriggerthreshold.def = 0; // between 0 and 255
|
cfg->rtriggerthreshold.def = 0; // between 0 and 255
|
||||||
|
|
||||||
|
// We have to enable orientation by default
|
||||||
|
cfg->orientation_enabled.def = true;
|
||||||
|
|
||||||
// apply defaults
|
// apply defaults
|
||||||
cfg->from_default();
|
cfg->from_default();
|
||||||
}
|
}
|
||||||
@ -608,7 +611,17 @@ std::unordered_map<u64, u16> ps_move_handler::get_button_values(const std::share
|
|||||||
const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3;
|
const u16 extra_buttons = input.sequence_number << 8 | input.buttons_3;
|
||||||
key_buf[ps_move_key_codes::ps] = (extra_buttons & button_flags::ps) ? 255 : 0;
|
key_buf[ps_move_key_codes::ps] = (extra_buttons & button_flags::ps) ? 255 : 0;
|
||||||
key_buf[ps_move_key_codes::move] = (extra_buttons & button_flags::move) ? 255 : 0;
|
key_buf[ps_move_key_codes::move] = (extra_buttons & button_flags::move) ? 255 : 0;
|
||||||
key_buf[ps_move_key_codes::t] = (extra_buttons & button_flags::t) ? input.trigger_2 : 0;
|
|
||||||
|
u16 trigger = 0;
|
||||||
|
if (extra_buttons & button_flags::t)
|
||||||
|
{
|
||||||
|
switch (dev->model)
|
||||||
|
{
|
||||||
|
case ps_move_model::ZCM1: trigger = (input.trigger_1 + input.trigger_2) / 2; break;
|
||||||
|
case ps_move_model::ZCM2: trigger = input.trigger_1; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
key_buf[ps_move_key_codes::t] = trigger;
|
||||||
|
|
||||||
dev->battery_level = input.battery_level;
|
dev->battery_level = input.battery_level;
|
||||||
|
|
||||||
@ -675,12 +688,20 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding)
|
|||||||
|
|
||||||
if (dev->model == ps_move_model::ZCM1)
|
if (dev->model == ps_move_model::ZCM1)
|
||||||
{
|
{
|
||||||
accel_x -= static_cast<f32>(zero_shift);
|
const auto decode_16bit = [](s16 val)
|
||||||
accel_y -= static_cast<f32>(zero_shift);
|
{
|
||||||
accel_z -= static_cast<f32>(zero_shift);
|
const u8* data = reinterpret_cast<const u8*>(&val);
|
||||||
gyro_x -= static_cast<f32>(zero_shift);
|
const u8 low = data[0] & 0xFF;
|
||||||
gyro_y -= static_cast<f32>(zero_shift);
|
const u8 high = data[1] & 0xFF;
|
||||||
gyro_z -= static_cast<f32>(zero_shift);
|
const s32 res = (low | (high << 8)) - zero_shift;
|
||||||
|
return static_cast<f32>(res);
|
||||||
|
};
|
||||||
|
accel_x = decode_16bit(input.accel_x_1);
|
||||||
|
accel_y = decode_16bit(input.accel_y_1);
|
||||||
|
accel_z = decode_16bit(input.accel_z_1);
|
||||||
|
gyro_x = decode_16bit(input.gyro_x_1);
|
||||||
|
gyro_y = decode_16bit(input.gyro_y_1);
|
||||||
|
gyro_z = decode_16bit(input.gyro_z_1);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!device->config || !device->config->orientation_enabled)
|
if (!device->config || !device->config->orientation_enabled)
|
||||||
@ -711,21 +732,21 @@ void ps_move_handler::get_extended_info(const pad_ensemble& binding)
|
|||||||
gyro_z /= MOVE_ONE_G;
|
gyro_z /= MOVE_ONE_G;
|
||||||
}
|
}
|
||||||
|
|
||||||
pad->move_data.accelerometer_x = accel_x;
|
pad->move_data.accelerometer.x() = accel_x;
|
||||||
pad->move_data.accelerometer_y = accel_y;
|
pad->move_data.accelerometer.y() = accel_y;
|
||||||
pad->move_data.accelerometer_z = accel_z;
|
pad->move_data.accelerometer.z() = accel_z;
|
||||||
pad->move_data.gyro_x = gyro_x;
|
pad->move_data.gyro.x() = gyro_x;
|
||||||
pad->move_data.gyro_y = gyro_y;
|
pad->move_data.gyro.y() = gyro_y;
|
||||||
pad->move_data.gyro_z = gyro_z;
|
pad->move_data.gyro.z() = gyro_z;
|
||||||
|
|
||||||
if (dev->model == ps_move_model::ZCM1)
|
if (dev->model == ps_move_model::ZCM1)
|
||||||
{
|
{
|
||||||
const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1;
|
const ps_move_input_report_ZCM1& input_zcm1 = dev->input_report_ZCM1;
|
||||||
|
|
||||||
#define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x))
|
#define TWELVE_BIT_SIGNED(x) (((x) & 0x800) ? (-(((~(x)) & 0xFFF) + 1)) : (x))
|
||||||
pad->move_data.magnetometer_x = static_cast<f32>(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2));
|
pad->move_data.magnetometer.x() = static_cast<f32>(TWELVE_BIT_SIGNED(((input.magnetometer_x & 0x0F) << 8) | input_zcm1.magnetometer_x2));
|
||||||
pad->move_data.magnetometer_y = static_cast<f32>(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4));
|
pad->move_data.magnetometer.y() = static_cast<f32>(TWELVE_BIT_SIGNED((input_zcm1.magnetometer_y << 4) | (input_zcm1.magnetometer_yz & 0xF0) >> 4));
|
||||||
pad->move_data.magnetometer_z = static_cast<f32>(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z));
|
pad->move_data.magnetometer.z() = static_cast<f32>(TWELVE_BIT_SIGNED(((input_zcm1.magnetometer_yz & 0x0F) << 8) | input_zcm1.magnetometer_z));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -13,14 +13,21 @@ LOG_CHANNEL(ps_move);
|
|||||||
|
|
||||||
namespace gem
|
namespace gem
|
||||||
{
|
{
|
||||||
extern bool convert_image_format(CellCameraFormat input_format, CellGemVideoConvertFormatEnum output_format,
|
extern bool convert_image_format(CellCameraFormat input_format, const CellGemVideoConvertAttribute& vc,
|
||||||
const std::vector<u8>& video_data_in, u32 width, u32 height,
|
const std::vector<u8>& video_data_in, u32 width, u32 height,
|
||||||
u8* video_data_out, u32 video_data_out_size, std::string_view caller);
|
u8* video_data_out, u32 video_data_out_size, u8* buffer_memory,
|
||||||
|
std::string_view caller);
|
||||||
}
|
}
|
||||||
|
|
||||||
template <bool DiagnosticsEnabled>
|
template <bool DiagnosticsEnabled>
|
||||||
ps_move_tracker<DiagnosticsEnabled>::ps_move_tracker()
|
ps_move_tracker<DiagnosticsEnabled>::ps_move_tracker()
|
||||||
{
|
{
|
||||||
|
m_vc_attr.alpha = 255;
|
||||||
|
m_vc_attr.gain = 1.0f;
|
||||||
|
m_vc_attr.red_gain = 1.0f;
|
||||||
|
m_vc_attr.green_gain = 1.0f;
|
||||||
|
m_vc_attr.blue_gain = 1.0f;
|
||||||
|
|
||||||
init_workers();
|
init_workers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +245,9 @@ void ps_move_tracker<DiagnosticsEnabled>::convert_image(s32 output_format)
|
|||||||
m_image_binary[index].resize(size);
|
m_image_binary[index].resize(size);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gem::convert_image_format(CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format}, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), "gemTracker"))
|
m_vc_attr.output_format = CellGemVideoConvertFormatEnum{output_format};
|
||||||
|
|
||||||
|
if (gem::convert_image_format(CellCameraFormat{m_format}, m_vc_attr, m_image_data, width, height, m_image_rgba.data(), ::size32(m_image_rgba), nullptr, "gemTracker"))
|
||||||
{
|
{
|
||||||
ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format});
|
ps_move.trace("Converted video frame of format %s to %s", CellCameraFormat{m_format}, CellGemVideoConvertFormatEnum{output_format});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -77,6 +77,8 @@ private:
|
|||||||
|
|
||||||
void draw_sphere_size_range(f32 result_radius);
|
void draw_sphere_size_range(f32 result_radius);
|
||||||
|
|
||||||
|
CellGemVideoConvertAttribute m_vc_attr {};
|
||||||
|
|
||||||
u32 m_width = 0;
|
u32 m_width = 0;
|
||||||
u32 m_height = 0;
|
u32 m_height = 0;
|
||||||
s32 m_format = 0;
|
s32 m_format = 0;
|
||||||
|
|||||||
@ -96,7 +96,7 @@ namespace disc
|
|||||||
|
|
||||||
for (usz i = 0; i < lines.size(); i++)
|
for (usz i = 0; i < lines.size(); i++)
|
||||||
{
|
{
|
||||||
const std::string& line = lines[i];
|
const std::string_view line = lines[i];
|
||||||
const usz pos = line.find('=');
|
const usz pos = line.find('=');
|
||||||
|
|
||||||
if (pos == umax)
|
if (pos == umax)
|
||||||
@ -104,12 +104,12 @@ namespace disc
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string key = fmt::trim(line.substr(0, pos));
|
const std::string_view key = fmt::trim_sv(line.substr(0, pos));
|
||||||
std::string value;
|
std::string_view value;
|
||||||
|
|
||||||
if (pos != (line.size() - 1))
|
if (pos != (line.size() - 1))
|
||||||
{
|
{
|
||||||
value = fmt::trim(line.substr(pos + 1));
|
value = fmt::trim_sv(line.substr(pos + 1));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (value.empty() && i != (lines.size() - 1) && line.size() != 1)
|
if (value.empty() && i != (lines.size() - 1) && line.size() != 1)
|
||||||
|
|||||||
@ -91,6 +91,7 @@
|
|||||||
<ClCompile Include="Emu\Io\midi_config_types.cpp" />
|
<ClCompile Include="Emu\Io\midi_config_types.cpp" />
|
||||||
<ClCompile Include="Emu\Io\MouseHandler.cpp" />
|
<ClCompile Include="Emu\Io\MouseHandler.cpp" />
|
||||||
<ClCompile Include="Emu\Io\pad_types.cpp" />
|
<ClCompile Include="Emu\Io\pad_types.cpp" />
|
||||||
|
<ClCompile Include="Emu\Io\ps_move_data.cpp" />
|
||||||
<ClCompile Include="Emu\Io\rb3drums_config.cpp" />
|
<ClCompile Include="Emu\Io\rb3drums_config.cpp" />
|
||||||
<ClCompile Include="Emu\Io\RB3MidiDrums.cpp" />
|
<ClCompile Include="Emu\Io\RB3MidiDrums.cpp" />
|
||||||
<ClCompile Include="Emu\Io\RB3MidiGuitar.cpp" />
|
<ClCompile Include="Emu\Io\RB3MidiGuitar.cpp" />
|
||||||
@ -156,8 +157,11 @@
|
|||||||
<ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog.cpp" />
|
<ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog.cpp" />
|
||||||
<ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog_native.cpp" />
|
<ClCompile Include="Emu\RSX\Overlays\Shaders\shader_loading_dialog_native.cpp" />
|
||||||
<ClCompile Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.cpp" />
|
<ClCompile Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.cpp" />
|
||||||
|
<ClCompile Include="Emu\RSX\Program\Assembler\FPASM.cpp" />
|
||||||
|
<ClCompile Include="Emu\RSX\Program\Assembler\FPOpcodes.cpp" />
|
||||||
<ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp" />
|
<ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp" />
|
||||||
<ClCompile Include="Emu\RSX\Program\FragmentProgramRegister.cpp" />
|
<ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.cpp" />
|
||||||
|
<ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.cpp" />
|
||||||
<ClCompile Include="Emu\RSX\Program\ProgramStateCache.cpp" />
|
<ClCompile Include="Emu\RSX\Program\ProgramStateCache.cpp" />
|
||||||
<ClCompile Include="Emu\RSX\Program\program_util.cpp" />
|
<ClCompile Include="Emu\RSX\Program\program_util.cpp" />
|
||||||
<ClCompile Include="Emu\RSX\Program\SPIRVCommon.cpp" />
|
<ClCompile Include="Emu\RSX\Program\SPIRVCommon.cpp" />
|
||||||
@ -601,6 +605,7 @@
|
|||||||
<ClInclude Include="Emu\Io\pad_config.h" />
|
<ClInclude Include="Emu\Io\pad_config.h" />
|
||||||
<ClInclude Include="Emu\Io\pad_config_types.h" />
|
<ClInclude Include="Emu\Io\pad_config_types.h" />
|
||||||
<ClInclude Include="Emu\Io\pad_types.h" />
|
<ClInclude Include="Emu\Io\pad_types.h" />
|
||||||
|
<ClInclude Include="Emu\Io\ps_move_data.h" />
|
||||||
<ClInclude Include="Emu\Io\rb3drums_config.h" />
|
<ClInclude Include="Emu\Io\rb3drums_config.h" />
|
||||||
<ClInclude Include="Emu\Io\RB3MidiDrums.h" />
|
<ClInclude Include="Emu\Io\RB3MidiDrums.h" />
|
||||||
<ClInclude Include="Emu\Io\RB3MidiGuitar.h" />
|
<ClInclude Include="Emu\Io\RB3MidiGuitar.h" />
|
||||||
@ -701,8 +706,11 @@
|
|||||||
<ClInclude Include="Emu\RSX\Overlays\overlay_video.h" />
|
<ClInclude Include="Emu\RSX\Overlays\overlay_video.h" />
|
||||||
<ClInclude Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.h" />
|
<ClInclude Include="Emu\RSX\Overlays\Trophies\overlay_trophy_list_dialog.h" />
|
||||||
<ClInclude Include="Emu\RSX\Program\Assembler\CFG.h" />
|
<ClInclude Include="Emu\RSX\Program\Assembler\CFG.h" />
|
||||||
|
<ClInclude Include="Emu\RSX\Program\Assembler\FPASM.h" />
|
||||||
|
<ClInclude Include="Emu\RSX\Program\Assembler\FPOpcodes.h" />
|
||||||
<ClInclude Include="Emu\RSX\Program\Assembler\IR.h" />
|
<ClInclude Include="Emu\RSX\Program\Assembler\IR.h" />
|
||||||
<ClInclude Include="Emu\RSX\Program\FragmentProgramRegister.h" />
|
<ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.h" />
|
||||||
|
<ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.h" />
|
||||||
<ClInclude Include="Emu\RSX\Program\GLSLTypes.h" />
|
<ClInclude Include="Emu\RSX\Program\GLSLTypes.h" />
|
||||||
<ClInclude Include="Emu\RSX\Program\ProgramStateCache.h" />
|
<ClInclude Include="Emu\RSX\Program\ProgramStateCache.h" />
|
||||||
<ClInclude Include="Emu\RSX\Program\program_util.h" />
|
<ClInclude Include="Emu\RSX\Program\program_util.h" />
|
||||||
|
|||||||
@ -136,6 +136,12 @@
|
|||||||
<Filter Include="Emu\GPU\RSX\Program\Assembler">
|
<Filter Include="Emu\GPU\RSX\Program\Assembler">
|
||||||
<UniqueIdentifier>{d99df916-8a99-428b-869a-9f14ac0ab411}</UniqueIdentifier>
|
<UniqueIdentifier>{d99df916-8a99-428b-869a-9f14ac0ab411}</UniqueIdentifier>
|
||||||
</Filter>
|
</Filter>
|
||||||
|
<Filter Include="Emu\GPU\RSX\Program\Assembler\Passes">
|
||||||
|
<UniqueIdentifier>{d13db076-47e4-45b9-bb8a-6b711ea40622}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
|
<Filter Include="Emu\GPU\RSX\Program\Assembler\Passes\FP">
|
||||||
|
<UniqueIdentifier>{7fb59544-9761-4b4a-bb04-07deb43cf3c2}</UniqueIdentifier>
|
||||||
|
</Filter>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="Crypto\aes.cpp">
|
<ClCompile Include="Crypto\aes.cpp">
|
||||||
@ -1354,9 +1360,6 @@
|
|||||||
<ClCompile Include="Emu\Cell\ErrorCodes.cpp">
|
<ClCompile Include="Emu\Cell\ErrorCodes.cpp">
|
||||||
<Filter>Emu\Cell</Filter>
|
<Filter>Emu\Cell</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
<ClCompile Include="Emu\RSX\Program\FragmentProgramRegister.cpp">
|
|
||||||
<Filter>Emu\GPU\RSX\Program</Filter>
|
|
||||||
</ClCompile>
|
|
||||||
<ClCompile Include="util\emu_utils.cpp">
|
<ClCompile Include="util\emu_utils.cpp">
|
||||||
<Filter>Utilities</Filter>
|
<Filter>Utilities</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@ -1378,6 +1381,21 @@
|
|||||||
<ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp">
|
<ClCompile Include="Emu\RSX\Program\Assembler\FPToCFG.cpp">
|
||||||
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.cpp">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.cpp">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Emu\RSX\Program\Assembler\FPOpcodes.cpp">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Emu\RSX\Program\Assembler\FPASM.cpp">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
||||||
|
</ClCompile>
|
||||||
|
<ClCompile Include="Emu\Io\ps_move_data.cpp">
|
||||||
|
<Filter>Emu\Io</Filter>
|
||||||
|
</ClCompile>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="Crypto\aes.h">
|
<ClInclude Include="Crypto\aes.h">
|
||||||
@ -2746,9 +2764,6 @@
|
|||||||
<ClInclude Include="Emu\Audio\audio_utils.h">
|
<ClInclude Include="Emu\Audio\audio_utils.h">
|
||||||
<Filter>Emu\Audio</Filter>
|
<Filter>Emu\Audio</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="Emu\RSX\Program\FragmentProgramRegister.h">
|
|
||||||
<Filter>Emu\GPU\RSX\Program</Filter>
|
|
||||||
</ClInclude>
|
|
||||||
<ClInclude Include="util\video_source.h">
|
<ClInclude Include="util\video_source.h">
|
||||||
<Filter>Utilities</Filter>
|
<Filter>Utilities</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
@ -2776,6 +2791,21 @@
|
|||||||
<ClInclude Include="Emu\RSX\Program\Assembler\IR.h">
|
<ClInclude Include="Emu\RSX\Program\Assembler\IR.h">
|
||||||
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
||||||
</ClInclude>
|
</ClInclude>
|
||||||
|
<ClInclude Include="Emu\RSX\Program\Assembler\FPOpcodes.h">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterAnnotationPass.h">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Emu\RSX\Program\Assembler\Passes\FP\RegisterDependencyPass.h">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler\Passes\FP</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Emu\RSX\Program\Assembler\FPASM.h">
|
||||||
|
<Filter>Emu\GPU\RSX\Program\Assembler</Filter>
|
||||||
|
</ClInclude>
|
||||||
|
<ClInclude Include="Emu\Io\ps_move_data.h">
|
||||||
|
<Filter>Emu\Io</Filter>
|
||||||
|
</ClInclude>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">
|
<None Include="Emu\RSX\Program\GLSLSnippets\GPUDeswizzle.glsl">
|
||||||
|
|||||||
@ -17,11 +17,11 @@ namespace rpcs3
|
|||||||
|
|
||||||
std::pair<std::string, std::string> get_commit_and_hash()
|
std::pair<std::string, std::string> get_commit_and_hash()
|
||||||
{
|
{
|
||||||
const auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"});
|
auto commit_and_hash = fmt::split(RPCS3_GIT_VERSION, {"-"});
|
||||||
if (commit_and_hash.size() != 2)
|
if (commit_and_hash.size() != 2)
|
||||||
return std::make_pair("0", "00000000");
|
return std::make_pair("0", "00000000");
|
||||||
|
|
||||||
return std::make_pair(commit_and_hash[0], commit_and_hash[1]);
|
return std::make_pair(std::move(commit_and_hash[0]), std::move(commit_and_hash[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Make this accessible from cmake and keep in sync with MACOSX_BUNDLE_BUNDLE_VERSION.
|
// TODO: Make this accessible from cmake and keep in sync with MACOSX_BUNDLE_BUNDLE_VERSION.
|
||||||
|
|||||||
@ -70,8 +70,8 @@ camera_settings_dialog::camera_settings_dialog(QWidget* parent)
|
|||||||
camera_log.notice("Found camera: '%s'", camera_info.description());
|
camera_log.notice("Found camera: '%s'", camera_info.description());
|
||||||
}
|
}
|
||||||
|
|
||||||
connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_camera_change);
|
connect(ui->combo_camera, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_camera_change);
|
||||||
connect(ui->combo_settings, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &camera_settings_dialog::handle_settings_change);
|
connect(ui->combo_settings, &QComboBox::currentIndexChanged, this, &camera_settings_dialog::handle_settings_change);
|
||||||
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
|
connect(ui->buttonBox, &QDialogButtonBox::clicked, [this](QAbstractButton* button)
|
||||||
{
|
{
|
||||||
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
|
if (button == ui->buttonBox->button(QDialogButtonBox::Save))
|
||||||
|
|||||||
@ -129,11 +129,11 @@ void cheat_engine::save() const
|
|||||||
cheat_file.write(out.c_str(), out.size());
|
cheat_file.write(out.c_str(), out.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
void cheat_engine::import_cheats_from_str(const std::string& str_cheats)
|
void cheat_engine::import_cheats_from_str(std::string_view str_cheats)
|
||||||
{
|
{
|
||||||
auto cheats_vec = fmt::split(str_cheats, {"^^^"});
|
const auto cheats_vec = fmt::split_sv(str_cheats, {"^^^"});
|
||||||
|
|
||||||
for (auto& cheat_line : cheats_vec)
|
for (const auto& cheat_line : cheats_vec)
|
||||||
{
|
{
|
||||||
cheat_info new_cheat;
|
cheat_info new_cheat;
|
||||||
if (new_cheat.from_str(cheat_line))
|
if (new_cheat.from_str(cheat_line))
|
||||||
|
|||||||
@ -25,7 +25,7 @@ public:
|
|||||||
cheat_info* get(const std::string& game, const u32 offset);
|
cheat_info* get(const std::string& game, const u32 offset);
|
||||||
bool erase(const std::string& game, const u32 offset);
|
bool erase(const std::string& game, const u32 offset);
|
||||||
|
|
||||||
void import_cheats_from_str(const std::string& str_cheats);
|
void import_cheats_from_str(std::string_view str_cheats);
|
||||||
std::string export_cheats_to_str() const;
|
std::string export_cheats_to_str() const;
|
||||||
void save() const;
|
void save() const;
|
||||||
|
|
||||||
|
|||||||
@ -126,7 +126,7 @@ debugger_frame::debugger_frame(std::shared_ptr<gui_settings> gui_settings, QWidg
|
|||||||
m_regs->setLineWrapMode(QPlainTextEdit::NoWrap);
|
m_regs->setLineWrapMode(QPlainTextEdit::NoWrap);
|
||||||
m_regs->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
|
m_regs->setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
|
||||||
m_regs->setContextMenuPolicy(Qt::CustomContextMenu);
|
m_regs->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
|
|
||||||
m_debugger_list->setFont(m_mono);
|
m_debugger_list->setFont(m_mono);
|
||||||
m_misc_state->setFont(m_mono);
|
m_misc_state->setFont(m_mono);
|
||||||
m_regs->setFont(m_mono);
|
m_regs->setFont(m_mono);
|
||||||
@ -180,7 +180,7 @@ debugger_frame::debugger_frame(std::shared_ptr<gui_settings> gui_settings, QWidg
|
|||||||
m_choice_units->clearFocus();
|
m_choice_units->clearFocus();
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(m_choice_units, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); });
|
connect(m_choice_units, &QComboBox::currentIndexChanged, this, [&](){ m_is_spu_disasm_mode = false; OnSelectUnit(); });
|
||||||
connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer);
|
connect(this, &QDockWidget::visibilityChanged, this, &debugger_frame::EnableUpdateTimer);
|
||||||
|
|
||||||
connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest);
|
connect(m_debugger_list, &debugger_list::BreakpointRequested, m_breakpoint_list, &breakpoint_list::HandleBreakpointRequest);
|
||||||
@ -1082,7 +1082,7 @@ void debugger_frame::UpdateUnitList()
|
|||||||
|
|
||||||
if (reselected_index != umax)
|
if (reselected_index != umax)
|
||||||
{
|
{
|
||||||
// Include no-thread at index 0
|
// Include no-thread at index 0
|
||||||
m_choice_units->setCurrentIndex(::narrow<s32>(reselected_index + 1));
|
m_choice_units->setCurrentIndex(::narrow<s32>(reselected_index + 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -433,7 +433,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent)
|
|||||||
|
|
||||||
setLayout(vbox_panel);
|
setLayout(vbox_panel);
|
||||||
|
|
||||||
connect(combo_figlist, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index)
|
connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index)
|
||||||
{
|
{
|
||||||
const u16 fig_info = combo_figlist->itemData(index).toUInt();
|
const u16 fig_info = combo_figlist->itemData(index).toUInt();
|
||||||
if (fig_info != 0xFFFF)
|
if (fig_info != 0xFFFF)
|
||||||
@ -486,7 +486,7 @@ minifig_creator_dialog::minifig_creator_dialog(QWidget* parent)
|
|||||||
|
|
||||||
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
|
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
|
||||||
|
|
||||||
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated), [=](const QString& text)
|
connect(co_compl, qOverload<const QString&>(&QCompleter::activated), [=](const QString& text)
|
||||||
{
|
{
|
||||||
combo_figlist->setCurrentIndex(combo_figlist->findText(text));
|
combo_figlist->setCurrentIndex(combo_figlist->findText(text));
|
||||||
});
|
});
|
||||||
|
|||||||
@ -375,7 +375,7 @@ void emu_settings::EnhanceComboBox(QComboBox* combobox, emu_settings_type type,
|
|||||||
|
|
||||||
combobox->setCurrentIndex(index);
|
combobox->setCurrentIndex(index);
|
||||||
|
|
||||||
connect(combobox, QOverload<int>::of(&QComboBox::currentIndexChanged), combobox, [this, is_ranged, combobox, type](int index)
|
connect(combobox, &QComboBox::currentIndexChanged, combobox, [this, is_ranged, combobox, type](int index)
|
||||||
{
|
{
|
||||||
if (index < 0) return;
|
if (index < 0) return;
|
||||||
|
|
||||||
|
|||||||
@ -302,7 +302,7 @@ void emulated_pad_settings_dialog::add_tabs(QTabWidget* tabs)
|
|||||||
|
|
||||||
combo->setCurrentIndex(combo->findData(static_cast<int>(saved_btn_id)));
|
combo->setCurrentIndex(combo->findData(static_cast<int>(saved_btn_id)));
|
||||||
|
|
||||||
connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this, player, id, combo](int index)
|
connect(combo, &QComboBox::currentIndexChanged, this, [this, player, id, combo](int index)
|
||||||
{
|
{
|
||||||
if (index < 0 || !combo)
|
if (index < 0 || !combo)
|
||||||
return;
|
return;
|
||||||
|
|||||||
@ -272,6 +272,10 @@ compat::package_info game_compatibility::GetPkgInfo(const QString& pkg_path, gam
|
|||||||
info.category = QString::fromStdString(std::string(psf::get_string(psf, "CATEGORY")));
|
info.category = QString::fromStdString(std::string(psf::get_string(psf, "CATEGORY")));
|
||||||
info.version = QString::fromStdString(std::string(psf::get_string(psf, "APP_VER")));
|
info.version = QString::fromStdString(std::string(psf::get_string(psf, "APP_VER")));
|
||||||
|
|
||||||
|
// Technically, there is no specific package's header info providing its installation size on disk.
|
||||||
|
// We use "data_size" header as an approximation (a bit larger) for this purpose
|
||||||
|
info.data_size = reader.get_header().data_size.value();
|
||||||
|
|
||||||
if (!info.category.isEmpty())
|
if (!info.category.isEmpty())
|
||||||
{
|
{
|
||||||
const Localized localized;
|
const Localized localized;
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "util/types.hpp"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
@ -108,6 +110,7 @@ namespace compat
|
|||||||
QString version; // May be empty
|
QString version; // May be empty
|
||||||
QString category; // HG, DG, GD etc.
|
QString category; // HG, DG, GD etc.
|
||||||
QString local_cat; // Localized category
|
QString local_cat; // Localized category
|
||||||
|
u64 data_size = 0; // Installation size
|
||||||
|
|
||||||
package_type type = package_type::other; // The type of package (Update, DLC or other)
|
package_type type = package_type::other; // The type of package (Update, DLC or other)
|
||||||
};
|
};
|
||||||
|
|||||||
@ -185,13 +185,13 @@ game_list_frame::game_list_frame(std::shared_ptr<gui_settings> gui_settings, std
|
|||||||
|
|
||||||
connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
|
connect(m_game_list, &QTableWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
|
||||||
connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot);
|
connect(m_game_list, &QTableWidget::itemSelectionChanged, this, &game_list_frame::ItemSelectionChangedSlot);
|
||||||
connect(m_game_list, &QTableWidget::itemDoubleClicked, this, QOverload<QTableWidgetItem*>::of(&game_list_frame::doubleClickedSlot));
|
connect(m_game_list, &QTableWidget::itemDoubleClicked, this, qOverload<QTableWidgetItem*>(&game_list_frame::doubleClickedSlot));
|
||||||
|
|
||||||
connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked);
|
connect(m_game_list->horizontalHeader(), &QHeaderView::sectionClicked, this, &game_list_frame::OnColClicked);
|
||||||
|
|
||||||
connect(m_game_grid, &QWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
|
connect(m_game_grid, &QWidget::customContextMenuRequested, this, &game_list_frame::ShowContextMenu);
|
||||||
connect(m_game_grid, &game_list_grid::ItemSelectionChanged, this, &game_list_frame::NotifyGameSelection);
|
connect(m_game_grid, &game_list_grid::ItemSelectionChanged, this, &game_list_frame::NotifyGameSelection);
|
||||||
connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, QOverload<const game_info&>::of(&game_list_frame::doubleClickedSlot));
|
connect(m_game_grid, &game_list_grid::ItemDoubleClicked, this, qOverload<const game_info&>(&game_list_frame::doubleClickedSlot));
|
||||||
|
|
||||||
connect(m_game_compat, &game_compatibility::DownloadStarted, this, [this]()
|
connect(m_game_compat, &game_compatibility::DownloadStarted, this, [this]()
|
||||||
{
|
{
|
||||||
@ -1383,7 +1383,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos)
|
|||||||
|
|
||||||
const std::string hdd1_cache_base_dir = rpcs3::utils::get_hdd1_dir() + "caches/";
|
const std::string hdd1_cache_base_dir = rpcs3::utils::get_hdd1_dir() + "caches/";
|
||||||
const bool has_hdd1_cache_dir = !GetDirListBySerial(hdd1_cache_base_dir, current_game.serial).empty();
|
const bool has_hdd1_cache_dir = !GetDirListBySerial(hdd1_cache_base_dir, current_game.serial).empty();
|
||||||
|
|
||||||
if (has_hdd1_cache_dir)
|
if (has_hdd1_cache_dir)
|
||||||
{
|
{
|
||||||
QAction* remove_hdd1_cache = remove_menu->addAction(tr("&Remove HDD1 Cache"));
|
QAction* remove_hdd1_cache = remove_menu->addAction(tr("&Remove HDD1 Cache"));
|
||||||
@ -1856,7 +1856,7 @@ void game_list_frame::ShowContextMenu(const QPoint& pos)
|
|||||||
{
|
{
|
||||||
text += tr("\nCurrent free disk space: %0\n").arg(gui::utils::format_byte_size(stat.avail_free));
|
text += tr("\nCurrent free disk space: %0\n").arg(gui::utils::format_byte_size(stat.avail_free));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (has_data_dir)
|
if (has_data_dir)
|
||||||
{
|
{
|
||||||
text += tr("\nPermanently remove %0 and selected (optional) contents from drive?\n").arg(is_disc_game ? tr("Game Data") : gameinfo->localized_category);
|
text += tr("\nPermanently remove %0 and selected (optional) contents from drive?\n").arg(is_disc_game ? tr("Game Data") : gameinfo->localized_category);
|
||||||
|
|||||||
@ -448,7 +448,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot)
|
|||||||
for (const auto& [figure, entry] : list_figures)
|
for (const auto& [figure, entry] : list_figures)
|
||||||
{
|
{
|
||||||
const auto& [num, figure_name] = entry;
|
const auto& [num, figure_name] = entry;
|
||||||
|
|
||||||
// Apply series filter (0 = all, 1-3 = specific series)
|
// Apply series filter (0 = all, 1-3 = specific series)
|
||||||
if (series_filter != 0 && num != series_filter)
|
if (series_filter != 0 && num != series_filter)
|
||||||
continue;
|
continue;
|
||||||
@ -486,7 +486,7 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot)
|
|||||||
co_compl->setFilterMode(Qt::MatchContains);
|
co_compl->setFilterMode(Qt::MatchContains);
|
||||||
combo_figlist->setCompleter(co_compl);
|
combo_figlist->setCompleter(co_compl);
|
||||||
|
|
||||||
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated), [=](const QString& text)
|
connect(co_compl, qOverload<const QString&>(&QCompleter::activated), [=](const QString& text)
|
||||||
{
|
{
|
||||||
combo_figlist->setCurrentIndex(combo_figlist->findText(text));
|
combo_figlist->setCurrentIndex(combo_figlist->findText(text));
|
||||||
});
|
});
|
||||||
@ -536,12 +536,12 @@ figure_creator_dialog::figure_creator_dialog(QWidget* parent, u8 slot)
|
|||||||
filter_group->addButton(btn_series2, 2); // ID 2 for series 2
|
filter_group->addButton(btn_series2, 2); // ID 2 for series 2
|
||||||
filter_group->addButton(btn_series3, 3); // ID 3 for series 3
|
filter_group->addButton(btn_series3, 3); // ID 3 for series 3
|
||||||
|
|
||||||
connect(filter_group, QOverload<int>::of(&QButtonGroup::idClicked), [=](int id)
|
connect(filter_group, &QButtonGroup::idClicked, [=](int id)
|
||||||
{
|
{
|
||||||
populate_combo(id);
|
populate_combo(id);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(combo_figlist, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index)
|
connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index)
|
||||||
{
|
{
|
||||||
const u32 fig_info = combo_figlist->itemData(index).toUInt();
|
const u32 fig_info = combo_figlist->itemData(index).toUInt();
|
||||||
if (fig_info != 0xFFFFFFFF)
|
if (fig_info != 0xFFFFFFFF)
|
||||||
|
|||||||
@ -160,7 +160,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent)
|
|||||||
|
|
||||||
setLayout(vbox_panel);
|
setLayout(vbox_panel);
|
||||||
|
|
||||||
connect(combo_figlist, QOverload<int>::of(&QComboBox::currentIndexChanged), [=](int index)
|
connect(combo_figlist, &QComboBox::currentIndexChanged, [=](int index)
|
||||||
{
|
{
|
||||||
const u16 fig_info = combo_figlist->itemData(index).toUInt();
|
const u16 fig_info = combo_figlist->itemData(index).toUInt();
|
||||||
if (fig_info != 0xFFFF)
|
if (fig_info != 0xFFFF)
|
||||||
@ -243,7 +243,7 @@ kamen_rider_creator_dialog::kamen_rider_creator_dialog(QWidget* parent)
|
|||||||
|
|
||||||
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
|
connect(btn_cancel, &QAbstractButton::clicked, this, &QDialog::reject);
|
||||||
|
|
||||||
connect(co_compl, QOverload<const QString&>::of(&QCompleter::activated), [=](const QString& text)
|
connect(co_compl, qOverload<const QString&>(&QCompleter::activated), [=](const QString& text)
|
||||||
{
|
{
|
||||||
combo_figlist->setCurrentText(text);
|
combo_figlist->setCurrentText(text);
|
||||||
combo_figlist->setCurrentIndex(combo_figlist->findText(text));
|
combo_figlist->setCurrentIndex(combo_figlist->findText(text));
|
||||||
|
|||||||
@ -860,11 +860,28 @@ bool main_window::InstallPackages(QStringList file_paths, bool from_boot)
|
|||||||
info.changelog = tr("Changelog:\n%0", "Block for Changelog").arg(info.changelog);
|
info.changelog = tr("Changelog:\n%0", "Block for Changelog").arg(info.changelog);
|
||||||
}
|
}
|
||||||
|
|
||||||
const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version);
|
u64 free_space = 0;
|
||||||
QString message = tr("Do you want to install this package?\n\n%0").arg(info_string);
|
|
||||||
|
|
||||||
QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), message, QMessageBox::Yes | QMessageBox::No, this);
|
// Retrieve disk space info on data path's drive
|
||||||
|
if (fs::device_stat stat{}; fs::statfs(rpcs3::utils::get_hdd0_dir(), stat))
|
||||||
|
{
|
||||||
|
free_space = stat.avail_free;
|
||||||
|
}
|
||||||
|
|
||||||
|
const QString installation_info =
|
||||||
|
tr("Installation path: %0\nAvailable disk space: %1%2\nRequired disk space: %3")
|
||||||
|
.arg(rpcs3::utils::get_hdd0_game_dir())
|
||||||
|
.arg(gui::utils::format_byte_size(free_space))
|
||||||
|
.arg(info.data_size <= free_space ? QString() : tr(" - <b>NOT ENOUGH SPACE</b>"))
|
||||||
|
.arg(gui::utils::format_byte_size(info.data_size));
|
||||||
|
|
||||||
|
const QString info_string = QStringLiteral("%0\n\n%1%2%3%4").arg(file_info.fileName()).arg(info.title).arg(info.local_cat).arg(info.title_id).arg(info.version);
|
||||||
|
QString message = tr("Do you want to install this package?\n\n%0\n\n%1").arg(info_string).arg(installation_info);
|
||||||
|
|
||||||
|
QMessageBox mb(QMessageBox::Icon::Question, tr("PKG Decrypter / Installer"), gui::utils::make_paragraph(message), QMessageBox::Yes | QMessageBox::No, this);
|
||||||
mb.setDefaultButton(QMessageBox::No);
|
mb.setDefaultButton(QMessageBox::No);
|
||||||
|
mb.setTextFormat(Qt::RichText); // Support HTML tags
|
||||||
|
mb.button(QMessageBox::Yes)->setEnabled(info.data_size <= free_space);
|
||||||
|
|
||||||
if (!info.changelog.isEmpty())
|
if (!info.changelog.isEmpty())
|
||||||
{
|
{
|
||||||
|
|||||||
@ -115,11 +115,7 @@ u64 memory_viewer_panel::OnSearch(std::string wstr, u32 mode)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Concat strings
|
// Concat strings
|
||||||
wstr.clear();
|
wstr = fmt::merge(parts, {});
|
||||||
for (const std::string& part : parts)
|
|
||||||
{
|
|
||||||
wstr += part;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (const usz pos = wstr.find_first_not_of(hex_chars); pos != umax)
|
if (const usz pos = wstr.find_first_not_of(hex_chars); pos != umax)
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user