mirror of
https://github.com/shadps4-emu/shadPS4.git
synced 2025-07-23 18:45:36 +00:00
Merge branch 'main' into fontlib
This commit is contained in:
commit
30e609cf4e
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@ -205,12 +205,12 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
mkdir upload
|
mkdir upload
|
||||||
mv ${{github.workspace}}/build/shadps4 upload
|
mv ${{github.workspace}}/build/shadps4 upload
|
||||||
cp ${{github.workspace}}/build/externals/MoltenVK/libMoltenVK.dylib upload
|
mv ${{github.workspace}}/build/MoltenVK_icd.json upload
|
||||||
tar cf shadps4-macos-sdl.tar.gz -C upload .
|
mv ${{github.workspace}}/build/libMoltenVK.dylib upload
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
name: shadps4-macos-sdl-${{ needs.get-info.outputs.date }}-${{ needs.get-info.outputs.shorthash }}
|
||||||
path: shadps4-macos-sdl.tar.gz
|
path: upload/
|
||||||
|
|
||||||
macos-qt:
|
macos-qt:
|
||||||
runs-on: macos-15
|
runs-on: macos-15
|
||||||
|
2
.gitmodules
vendored
2
.gitmodules
vendored
@ -97,7 +97,7 @@
|
|||||||
shallow = true
|
shallow = true
|
||||||
[submodule "externals/MoltenVK/SPIRV-Cross"]
|
[submodule "externals/MoltenVK/SPIRV-Cross"]
|
||||||
path = externals/MoltenVK/SPIRV-Cross
|
path = externals/MoltenVK/SPIRV-Cross
|
||||||
url = https://github.com/billhollings/SPIRV-Cross
|
url = https://github.com/KhronosGroup/SPIRV-Cross
|
||||||
shallow = true
|
shallow = true
|
||||||
[submodule "externals/MoltenVK/MoltenVK"]
|
[submodule "externals/MoltenVK/MoltenVK"]
|
||||||
path = externals/MoltenVK/MoltenVK
|
path = externals/MoltenVK/MoltenVK
|
||||||
|
@ -202,7 +202,7 @@ execute_process(
|
|||||||
|
|
||||||
# Set Version
|
# Set Version
|
||||||
set(EMULATOR_VERSION_MAJOR "0")
|
set(EMULATOR_VERSION_MAJOR "0")
|
||||||
set(EMULATOR_VERSION_MINOR "7")
|
set(EMULATOR_VERSION_MINOR "8")
|
||||||
set(EMULATOR_VERSION_PATCH "1")
|
set(EMULATOR_VERSION_PATCH "1")
|
||||||
|
|
||||||
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
|
set_source_files_properties(src/shadps4.rc PROPERTIES COMPILE_DEFINITIONS "EMULATOR_VERSION_MAJOR=${EMULATOR_VERSION_MAJOR};EMULATOR_VERSION_MINOR=${EMULATOR_VERSION_MINOR};EMULATOR_VERSION_PATCH=${EMULATOR_VERSION_PATCH}")
|
||||||
@ -1089,34 +1089,45 @@ if (${CMAKE_SYSTEM_NAME} STREQUAL "Linux" AND ENABLE_USERFAULTFD)
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
if (ENABLE_QT_GUI)
|
# Include MoltenVK, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers.
|
||||||
# Include MoltenVK in the app bundle, along with an ICD file so it can be found by the system Vulkan loader if used for loading layers.
|
set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/libMoltenVK.dylib)
|
||||||
set(MVK_ICD ${CMAKE_CURRENT_SOURCE_DIR}/externals/MoltenVK/MoltenVK_icd.json)
|
|
||||||
target_sources(shadps4 PRIVATE ${MVK_ICD})
|
|
||||||
set_source_files_properties(${MVK_ICD} PROPERTIES MACOSX_PACKAGE_LOCATION Resources/vulkan/icd.d)
|
|
||||||
|
|
||||||
set(MVK_DYLIB_SRC ${CMAKE_CURRENT_BINARY_DIR}/externals/MoltenVK/libMoltenVK.dylib)
|
if (ENABLE_QT_GUI)
|
||||||
set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Frameworks/libMoltenVK.dylib)
|
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks")
|
||||||
add_custom_command(
|
set(MVK_ICD_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Resources/vulkan/icd.d/MoltenVK_icd.json)
|
||||||
OUTPUT ${MVK_DYLIB_DST}
|
set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/shadps4.app/Contents/Frameworks/libMoltenVK.dylib)
|
||||||
DEPENDS ${MVK_DYLIB_SRC}
|
set(MVK_DYLIB_ICD_PATH "../../../Frameworks/libMoltenVK.dylib")
|
||||||
COMMAND cmake -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST})
|
else()
|
||||||
add_custom_target(CopyMoltenVK DEPENDS ${MVK_DYLIB_DST})
|
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path")
|
||||||
add_dependencies(CopyMoltenVK MoltenVK)
|
set(MVK_ICD_DST ${CMAKE_CURRENT_BINARY_DIR}/MoltenVK_icd.json)
|
||||||
add_dependencies(shadps4 CopyMoltenVK)
|
set(MVK_DYLIB_DST ${CMAKE_CURRENT_BINARY_DIR}/libMoltenVK.dylib)
|
||||||
set_property(TARGET shadps4 APPEND PROPERTY BUILD_RPATH "@executable_path/../Frameworks")
|
set(MVK_DYLIB_ICD_PATH "./libMoltenVK.dylib")
|
||||||
else()
|
endif()
|
||||||
# For non-bundled SDL build, just do a normal library link.
|
|
||||||
target_link_libraries(shadps4 PRIVATE MoltenVK)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (ARCHITECTURE STREQUAL "x86_64")
|
cmake_path(GET MVK_ICD_DST PARENT_PATH MVK_ICD_DST_PARENT)
|
||||||
# Reserve system-managed memory space.
|
cmake_path(GET MVK_DYLIB_DST PARENT_PATH MVK_DYLIB_DST_PARENT)
|
||||||
target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
# Replacement for std::chrono::time_zone
|
set(MVK_ICD "\\\{ \\\"file_format_version\\\": \\\"1.0.0\\\", \\\"ICD\\\": \\\{ \\\"library_path\\\": \\\"${MVK_DYLIB_ICD_PATH}\\\", \\\"api_version\\\": \\\"1.2.0\\\", \\\"is_portability_driver\\\": true \\\} \\\}")
|
||||||
target_link_libraries(shadps4 PRIVATE date::date-tz)
|
add_custom_command(
|
||||||
|
OUTPUT ${MVK_ICD_DST}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_ICD_DST_PARENT} && ${CMAKE_COMMAND} -E echo ${MVK_ICD} > ${MVK_ICD_DST})
|
||||||
|
|
||||||
|
add_custom_command(
|
||||||
|
OUTPUT ${MVK_DYLIB_DST}
|
||||||
|
DEPENDS ${MVK_DYLIB_SRC}
|
||||||
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${MVK_DYLIB_DST_PARENT} && ${CMAKE_COMMAND} -E copy ${MVK_DYLIB_SRC} ${MVK_DYLIB_DST})
|
||||||
|
|
||||||
|
add_custom_target(CopyMoltenVK DEPENDS ${MVK_ICD_DST} ${MVK_DYLIB_DST})
|
||||||
|
add_dependencies(CopyMoltenVK MoltenVK)
|
||||||
|
add_dependencies(shadps4 CopyMoltenVK)
|
||||||
|
|
||||||
|
if (ARCHITECTURE STREQUAL "x86_64")
|
||||||
|
# Reserve system-managed memory space.
|
||||||
|
target_link_options(shadps4 PRIVATE -Wl,-no_pie,-no_fixup_chains,-no_huge,-pagezero_size,0x4000,-segaddr,TCB_SPACE,0x4000,-segaddr,SYSTEM_MANAGED,0x400000,-segaddr,SYSTEM_RESERVED,0x7FFFFC000,-image_base,0x20000000000)
|
||||||
|
endif()
|
||||||
|
|
||||||
|
# Replacement for std::chrono::time_zone
|
||||||
|
target_link_libraries(shadps4 PRIVATE date::date-tz)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (NOT ENABLE_QT_GUI)
|
if (NOT ENABLE_QT_GUI)
|
||||||
|
@ -13,7 +13,7 @@ SPDX-License-Identifier: GPL-2.0-or-later
|
|||||||
|
|
||||||
<h1 align="center">
|
<h1 align="center">
|
||||||
<a href="https://discord.gg/bFJxfftGW6">
|
<a href="https://discord.gg/bFJxfftGW6">
|
||||||
<img src="https://img.shields.io/discord/1080089157554155590?color=5865F2&label=shadPS4 Discord&logo=Discord&logoColor=white" width="240">
|
<img src="https://img.shields.io/discord/1080089157554155590?color=5865F2&label=shadPS4%20Discord&logo=Discord&logoColor=white" width="275">
|
||||||
<a href="https://github.com/shadps4-emu/shadPS4/releases/latest">
|
<a href="https://github.com/shadps4-emu/shadPS4/releases/latest">
|
||||||
<img src="https://img.shields.io/github/downloads/shadps4-emu/shadPS4/total.svg" width="140">
|
<img src="https://img.shields.io/github/downloads/shadps4-emu/shadPS4/total.svg" width="140">
|
||||||
<a href="https://shadps4.net/">
|
<a href="https://shadps4.net/">
|
||||||
|
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
3
dist/net.shadps4.shadPS4.metainfo.xml
vendored
@ -37,6 +37,9 @@
|
|||||||
<category translate="no">Game</category>
|
<category translate="no">Game</category>
|
||||||
</categories>
|
</categories>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="0.8.0" date="2025-05-23">
|
||||||
|
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.8.0</url>
|
||||||
|
</release>
|
||||||
<release version="0.7.0" date="2025-03-23">
|
<release version="0.7.0" date="2025-03-23">
|
||||||
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.7.0</url>
|
<url>https://github.com/shadps4-emu/shadPS4/releases/tag/v.0.7.0</url>
|
||||||
</release>
|
</release>
|
||||||
|
2
externals/MoltenVK/MoltenVK
vendored
2
externals/MoltenVK/MoltenVK
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 067fc6c85b02f37dfda58eeda49d8458e093ed60
|
Subproject commit 4cf8f94684c53e581eb9cc694dd3305d1f7d9959
|
8
externals/MoltenVK/MoltenVK_icd.json
vendored
8
externals/MoltenVK/MoltenVK_icd.json
vendored
@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"file_format_version": "1.0.0",
|
|
||||||
"ICD": {
|
|
||||||
"library_path": "../../../Frameworks/libMoltenVK.dylib",
|
|
||||||
"api_version": "1.2.0",
|
|
||||||
"is_portability_driver": true
|
|
||||||
}
|
|
||||||
}
|
|
2
externals/MoltenVK/SPIRV-Cross
vendored
2
externals/MoltenVK/SPIRV-Cross
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 185833a61cbe29ce3bfb5a499ffb3dfeaee3bbe7
|
Subproject commit 2275d0efc4f2fa46851035d9d3c67c105bc8b99e
|
@ -60,7 +60,7 @@ static CFURLRef UntranslocateBundlePath(const CFURLRef bundle_path) {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::filesystem::path GetBundleParentDirectory() {
|
static std::optional<std::filesystem::path> GetBundleParentDirectory() {
|
||||||
if (CFBundleRef bundle_ref = CFBundleGetMainBundle()) {
|
if (CFBundleRef bundle_ref = CFBundleGetMainBundle()) {
|
||||||
if (CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref)) {
|
if (CFURLRef bundle_url_ref = CFBundleCopyBundleURL(bundle_ref)) {
|
||||||
SCOPE_EXIT {
|
SCOPE_EXIT {
|
||||||
@ -83,14 +83,16 @@ static std::filesystem::path GetBundleParentDirectory() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return std::filesystem::current_path();
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
static auto UserPaths = [] {
|
static auto UserPaths = [] {
|
||||||
#ifdef __APPLE__
|
#if defined(__APPLE__) && defined(ENABLE_QT_GUI)
|
||||||
// Set the current path to the directory containing the app bundle.
|
// Set the current path to the directory containing the app bundle.
|
||||||
std::filesystem::current_path(GetBundleParentDirectory());
|
if (const auto bundle_dir = GetBundleParentDirectory()) {
|
||||||
|
std::filesystem::current_path(*bundle_dir);
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Try the portable user directory first.
|
// Try the portable user directory first.
|
||||||
|
@ -112,18 +112,6 @@ void Linker::Execute(const std::vector<std::string> args) {
|
|||||||
0, "SceKernelInternalMemory");
|
0, "SceKernelInternalMemory");
|
||||||
ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping");
|
ASSERT_MSG(ret == 0, "Unable to perform sceKernelInternalMemory mapping");
|
||||||
|
|
||||||
// Simulate libSceGnmDriver initialization, which maps a chunk of direct memory.
|
|
||||||
// Some games fail without accurately emulating this behavior.
|
|
||||||
s64 phys_addr{};
|
|
||||||
ret = Libraries::Kernel::sceKernelAllocateDirectMemory(
|
|
||||||
0, Libraries::Kernel::sceKernelGetDirectMemorySize(), 0x10000, 0x10000, 3, &phys_addr);
|
|
||||||
if (ret == 0) {
|
|
||||||
void* addr{reinterpret_cast<void*>(0xfe0000000)};
|
|
||||||
ret = Libraries::Kernel::sceKernelMapNamedDirectMemory(&addr, 0x10000, 0x13, 0, phys_addr,
|
|
||||||
0x10000, "SceGnmDriver");
|
|
||||||
}
|
|
||||||
ASSERT_MSG(ret == 0, "Unable to emulate libSceGnmDriver initialization");
|
|
||||||
|
|
||||||
main_thread.Run([this, module, args](std::stop_token) {
|
main_thread.Run([this, module, args](std::stop_token) {
|
||||||
Common::SetCurrentThreadName("GAME_MainThread");
|
Common::SetCurrentThreadName("GAME_MainThread");
|
||||||
LoadSharedLibraries();
|
LoadSharedLibraries();
|
||||||
|
@ -608,21 +608,28 @@ void KBMSettings::CheckMapping(QPushButton*& button) {
|
|||||||
MappingTimer -= 1;
|
MappingTimer -= 1;
|
||||||
button->setText(tr("Press a key") + " [" + QString::number(MappingTimer) + "]");
|
button->setText(tr("Press a key") + " [" + QString::number(MappingTimer) + "]");
|
||||||
|
|
||||||
|
if (pressedKeys.size() > 0) {
|
||||||
|
QStringList keyStrings;
|
||||||
|
|
||||||
|
for (const QString& buttonAction : pressedKeys) {
|
||||||
|
keyStrings << buttonAction;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString combo = keyStrings.join(",");
|
||||||
|
SetMapping(combo);
|
||||||
|
MappingCompleted = true;
|
||||||
|
EnableMapping = false;
|
||||||
|
|
||||||
|
MappingButton->setText(combo);
|
||||||
|
pressedKeys.clear();
|
||||||
|
timer->stop();
|
||||||
|
}
|
||||||
if (MappingCompleted) {
|
if (MappingCompleted) {
|
||||||
EnableMapping = false;
|
EnableMapping = false;
|
||||||
EnableMappingButtons();
|
EnableMappingButtons();
|
||||||
timer->stop();
|
timer->stop();
|
||||||
|
|
||||||
if (mapping == "lshift" || mapping == "lalt" || mapping == "lctrl" || mapping == "lmeta" ||
|
button->setText(mapping);
|
||||||
mapping == "lwin") {
|
|
||||||
modifier = "";
|
|
||||||
}
|
|
||||||
|
|
||||||
if (modifier != "") {
|
|
||||||
button->setText(modifier + ", " + mapping);
|
|
||||||
} else {
|
|
||||||
button->setText(mapping);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MappingTimer <= 0) {
|
if (MappingTimer <= 0) {
|
||||||
@ -647,322 +654,346 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (EnableMapping) {
|
if (EnableMapping) {
|
||||||
if (Qt::ShiftModifier & QApplication::keyboardModifiers()) {
|
|
||||||
modifier = "lshift";
|
|
||||||
} else if (Qt::AltModifier & QApplication::keyboardModifiers()) {
|
|
||||||
modifier = "lalt";
|
|
||||||
} else if (Qt::ControlModifier & QApplication::keyboardModifiers()) {
|
|
||||||
modifier = "lctrl";
|
|
||||||
} else if (Qt::MetaModifier & QApplication::keyboardModifiers()) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
modifier = "lwin";
|
|
||||||
#else
|
|
||||||
modifier = "lmeta";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
if (event->type() == QEvent::KeyPress) {
|
if (event->type() == QEvent::KeyPress) {
|
||||||
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
|
||||||
|
|
||||||
|
if (keyEvent->isAutoRepeat())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (pressedKeys.size() >= 3) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
switch (keyEvent->key()) {
|
switch (keyEvent->key()) {
|
||||||
case Qt::Key_Space:
|
case Qt::Key_Space:
|
||||||
SetMapping("space");
|
pressedKeys.insert("space");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Comma:
|
case Qt::Key_Comma:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kpcomma");
|
pressedKeys.insert("kpcomma");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("comma");
|
pressedKeys.insert("comma");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Period:
|
case Qt::Key_Period:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kpperiod");
|
pressedKeys.insert("kpperiod");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("period");
|
pressedKeys.insert("period");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Slash:
|
case Qt::Key_Slash:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers())
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers())
|
||||||
SetMapping("kpdivide");
|
pressedKeys.insert("kpdivide");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Asterisk:
|
case Qt::Key_Asterisk:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers())
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers())
|
||||||
SetMapping("kpmultiply");
|
pressedKeys.insert("kpmultiply");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Question:
|
case Qt::Key_Question:
|
||||||
SetMapping("question");
|
pressedKeys.insert("question");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Semicolon:
|
case Qt::Key_Semicolon:
|
||||||
SetMapping("semicolon");
|
pressedKeys.insert("semicolon");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Minus:
|
case Qt::Key_Minus:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kpminus");
|
pressedKeys.insert("kpminus");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("minus");
|
pressedKeys.insert("minus");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Plus:
|
case Qt::Key_Plus:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kpplus");
|
pressedKeys.insert("kpplus");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("plus");
|
pressedKeys.insert("plus");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_ParenLeft:
|
case Qt::Key_ParenLeft:
|
||||||
SetMapping("lparenthesis");
|
pressedKeys.insert("lparenthesis");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_ParenRight:
|
case Qt::Key_ParenRight:
|
||||||
SetMapping("rparenthesis");
|
pressedKeys.insert("rparenthesis");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_BracketLeft:
|
case Qt::Key_BracketLeft:
|
||||||
SetMapping("lbracket");
|
pressedKeys.insert("lbracket");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_BracketRight:
|
case Qt::Key_BracketRight:
|
||||||
SetMapping("rbracket");
|
pressedKeys.insert("rbracket");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_BraceLeft:
|
case Qt::Key_BraceLeft:
|
||||||
SetMapping("lbrace");
|
pressedKeys.insert("lbrace");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_BraceRight:
|
case Qt::Key_BraceRight:
|
||||||
SetMapping("rbrace");
|
pressedKeys.insert("rbrace");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Backslash:
|
case Qt::Key_Backslash:
|
||||||
SetMapping("backslash");
|
pressedKeys.insert("backslash");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Tab:
|
case Qt::Key_Tab:
|
||||||
SetMapping("tab");
|
pressedKeys.insert("tab");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Backspace:
|
case Qt::Key_Backspace:
|
||||||
SetMapping("backspace");
|
pressedKeys.insert("backspace");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Return:
|
case Qt::Key_Return:
|
||||||
SetMapping("enter");
|
pressedKeys.insert("enter");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Enter:
|
case Qt::Key_Enter:
|
||||||
SetMapping("kpenter");
|
pressedKeys.insert("kpenter");
|
||||||
|
break;
|
||||||
|
case Qt::Key_Home:
|
||||||
|
pressedKeys.insert("home");
|
||||||
|
break;
|
||||||
|
case Qt::Key_End:
|
||||||
|
pressedKeys.insert("end");
|
||||||
|
break;
|
||||||
|
case Qt::Key_PageDown:
|
||||||
|
pressedKeys.insert("pgdown");
|
||||||
|
break;
|
||||||
|
case Qt::Key_PageUp:
|
||||||
|
pressedKeys.insert("pgup");
|
||||||
|
break;
|
||||||
|
case Qt::Key_CapsLock:
|
||||||
|
pressedKeys.insert("capslock");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Escape:
|
case Qt::Key_Escape:
|
||||||
SetMapping("unmapped");
|
pressedKeys.insert("unmapped");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Shift:
|
case Qt::Key_Shift:
|
||||||
SetMapping("lshift");
|
if (keyEvent->nativeScanCode() == rshift) {
|
||||||
|
pressedKeys.insert("rshift");
|
||||||
|
} else {
|
||||||
|
pressedKeys.insert("lshift");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Alt:
|
case Qt::Key_Alt:
|
||||||
SetMapping("lalt");
|
if (keyEvent->nativeScanCode() == ralt) {
|
||||||
|
pressedKeys.insert("ralt");
|
||||||
|
} else {
|
||||||
|
pressedKeys.insert("lalt");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Control:
|
case Qt::Key_Control:
|
||||||
SetMapping("lctrl");
|
if (keyEvent->nativeScanCode() == rctrl) {
|
||||||
|
pressedKeys.insert("rctrl");
|
||||||
|
} else {
|
||||||
|
pressedKeys.insert("lctrl");
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Meta:
|
case Qt::Key_Meta:
|
||||||
activateWindow();
|
activateWindow();
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
SetMapping("lwin");
|
pressedKeys.insert("lwin");
|
||||||
#else
|
#else
|
||||||
SetMapping("lmeta");
|
pressedKeys.insert("lmeta");
|
||||||
#endif
|
#endif
|
||||||
case Qt::Key_1:
|
case Qt::Key_1:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp1");
|
pressedKeys.insert("kp1");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("1");
|
pressedKeys.insert("1");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_2:
|
case Qt::Key_2:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp2");
|
pressedKeys.insert("kp2");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("2");
|
pressedKeys.insert("2");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_3:
|
case Qt::Key_3:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp3");
|
pressedKeys.insert("kp3");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("3");
|
pressedKeys.insert("3");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_4:
|
case Qt::Key_4:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp4");
|
pressedKeys.insert("kp4");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("4");
|
pressedKeys.insert("4");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_5:
|
case Qt::Key_5:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp5");
|
pressedKeys.insert("kp5");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("5");
|
pressedKeys.insert("5");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_6:
|
case Qt::Key_6:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp6");
|
pressedKeys.insert("kp6");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("6");
|
pressedKeys.insert("6");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_7:
|
case Qt::Key_7:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp7");
|
pressedKeys.insert("kp7");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("7");
|
pressedKeys.insert("7");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_8:
|
case Qt::Key_8:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp8");
|
pressedKeys.insert("kp8");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("8");
|
pressedKeys.insert("8");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_9:
|
case Qt::Key_9:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp9");
|
pressedKeys.insert("kp9");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("9");
|
pressedKeys.insert("9");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_0:
|
case Qt::Key_0:
|
||||||
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
if (Qt::KeypadModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("kp0");
|
pressedKeys.insert("kp0");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("0");
|
pressedKeys.insert("0");
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Up:
|
case Qt::Key_Up:
|
||||||
activateWindow();
|
activateWindow();
|
||||||
SetMapping("up");
|
pressedKeys.insert("up");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Down:
|
case Qt::Key_Down:
|
||||||
SetMapping("down");
|
pressedKeys.insert("down");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Left:
|
case Qt::Key_Left:
|
||||||
SetMapping("left");
|
pressedKeys.insert("left");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Right:
|
case Qt::Key_Right:
|
||||||
SetMapping("right");
|
pressedKeys.insert("right");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_A:
|
case Qt::Key_A:
|
||||||
SetMapping("a");
|
pressedKeys.insert("a");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_B:
|
case Qt::Key_B:
|
||||||
SetMapping("b");
|
pressedKeys.insert("b");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_C:
|
case Qt::Key_C:
|
||||||
SetMapping("c");
|
pressedKeys.insert("c");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_D:
|
case Qt::Key_D:
|
||||||
SetMapping("d");
|
pressedKeys.insert("d");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_E:
|
case Qt::Key_E:
|
||||||
SetMapping("e");
|
pressedKeys.insert("e");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_F:
|
case Qt::Key_F:
|
||||||
SetMapping("f");
|
pressedKeys.insert("f");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_G:
|
case Qt::Key_G:
|
||||||
SetMapping("g");
|
pressedKeys.insert("g");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_H:
|
case Qt::Key_H:
|
||||||
SetMapping("h");
|
pressedKeys.insert("h");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_I:
|
case Qt::Key_I:
|
||||||
SetMapping("i");
|
pressedKeys.insert("i");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_J:
|
case Qt::Key_J:
|
||||||
SetMapping("j");
|
pressedKeys.insert("j");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_K:
|
case Qt::Key_K:
|
||||||
SetMapping("k");
|
pressedKeys.insert("k");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_L:
|
case Qt::Key_L:
|
||||||
SetMapping("l");
|
pressedKeys.insert("l");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_M:
|
case Qt::Key_M:
|
||||||
SetMapping("m");
|
pressedKeys.insert("m");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_N:
|
case Qt::Key_N:
|
||||||
SetMapping("n");
|
pressedKeys.insert("n");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_O:
|
case Qt::Key_O:
|
||||||
SetMapping("o");
|
pressedKeys.insert("o");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_P:
|
case Qt::Key_P:
|
||||||
SetMapping("p");
|
pressedKeys.insert("p");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Q:
|
case Qt::Key_Q:
|
||||||
SetMapping("q");
|
pressedKeys.insert("q");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_R:
|
case Qt::Key_R:
|
||||||
SetMapping("r");
|
pressedKeys.insert("r");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_S:
|
case Qt::Key_S:
|
||||||
SetMapping("s");
|
pressedKeys.insert("s");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_T:
|
case Qt::Key_T:
|
||||||
SetMapping("t");
|
pressedKeys.insert("t");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_U:
|
case Qt::Key_U:
|
||||||
SetMapping("u");
|
pressedKeys.insert("u");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_V:
|
case Qt::Key_V:
|
||||||
SetMapping("v");
|
pressedKeys.insert("v");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_W:
|
case Qt::Key_W:
|
||||||
SetMapping("w");
|
pressedKeys.insert("w");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_X:
|
case Qt::Key_X:
|
||||||
SetMapping("x");
|
pressedKeys.insert("x");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Y:
|
case Qt::Key_Y:
|
||||||
SetMapping("Y");
|
pressedKeys.insert("Y");
|
||||||
break;
|
break;
|
||||||
case Qt::Key_Z:
|
case Qt::Key_Z:
|
||||||
SetMapping("z");
|
pressedKeys.insert("z");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (event->type() == QEvent::MouseButtonPress) {
|
if (event->type() == QEvent::MouseButtonPress) {
|
||||||
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
QMouseEvent* mouseEvent = static_cast<QMouseEvent*>(event);
|
||||||
|
if (pressedKeys.size() < 3) {
|
||||||
switch (mouseEvent->button()) {
|
switch (mouseEvent->button()) {
|
||||||
case Qt::LeftButton:
|
case Qt::LeftButton:
|
||||||
SetMapping("leftbutton");
|
pressedKeys.insert("leftbutton");
|
||||||
break;
|
break;
|
||||||
case Qt::RightButton:
|
case Qt::RightButton:
|
||||||
SetMapping("rightbutton");
|
pressedKeys.insert("rightbutton");
|
||||||
break;
|
break;
|
||||||
case Qt::MiddleButton:
|
case Qt::MiddleButton:
|
||||||
SetMapping("middlebutton");
|
pressedKeys.insert("middlebutton");
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const QList<QPushButton*> AxisList = {
|
const QList<QPushButton*> AxisList = {
|
||||||
ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton,
|
ui->LStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->LStickRightButton,
|
||||||
ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton};
|
ui->RStickUpButton, ui->LStickDownButton, ui->LStickLeftButton, ui->RStickRightButton};
|
||||||
|
|
||||||
if (event->type() == QEvent::Wheel) {
|
if (event->type() == QEvent::Wheel) {
|
||||||
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
|
QWheelEvent* wheelEvent = static_cast<QWheelEvent*>(event);
|
||||||
|
if (pressedKeys.size() < 3) {
|
||||||
if (wheelEvent->angleDelta().y() > 5) {
|
if (wheelEvent->angleDelta().y() > 5) {
|
||||||
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
||||||
SetMapping("mousewheelup");
|
pressedKeys.insert("mousewheelup");
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::information(this, tr("Cannot set mapping"),
|
QMessageBox::information(this, tr("Cannot set mapping"),
|
||||||
tr("Mousewheel cannot be mapped to stick outputs"));
|
tr("Mousewheel cannot be mapped to stick outputs"));
|
||||||
}
|
}
|
||||||
} else if (wheelEvent->angleDelta().y() < -5) {
|
} else if (wheelEvent->angleDelta().y() < -5) {
|
||||||
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
||||||
SetMapping("mousewheeldown");
|
pressedKeys.insert("mousewheeldown");
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::information(this, tr("Cannot set mapping"),
|
QMessageBox::information(this, tr("Cannot set mapping"),
|
||||||
tr("Mousewheel cannot be mapped to stick outputs"));
|
tr("Mousewheel cannot be mapped to stick outputs"));
|
||||||
@ -972,9 +1003,9 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
||||||
// QT changes scrolling to horizontal for all widgets with the alt modifier
|
// QT changes scrolling to horizontal for all widgets with the alt modifier
|
||||||
if (Qt::AltModifier & QApplication::keyboardModifiers()) {
|
if (Qt::AltModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("mousewheelup");
|
pressedKeys.insert("mousewheelup");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("mousewheelright");
|
pressedKeys.insert("mousewheelright");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::information(this, tr("Cannot set mapping"),
|
QMessageBox::information(this, tr("Cannot set mapping"),
|
||||||
@ -983,18 +1014,18 @@ bool KBMSettings::eventFilter(QObject* obj, QEvent* event) {
|
|||||||
} else if (wheelEvent->angleDelta().x() < -5) {
|
} else if (wheelEvent->angleDelta().x() < -5) {
|
||||||
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
if (std::find(AxisList.begin(), AxisList.end(), MappingButton) == AxisList.end()) {
|
||||||
if (Qt::AltModifier & QApplication::keyboardModifiers()) {
|
if (Qt::AltModifier & QApplication::keyboardModifiers()) {
|
||||||
SetMapping("mousewheeldown");
|
pressedKeys.insert("mousewheeldown");
|
||||||
} else {
|
} else {
|
||||||
SetMapping("mousewheelleft");
|
pressedKeys.insert("mousewheelleft");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
QMessageBox::information(this, tr("Cannot set mapping"),
|
QMessageBox::information(this, tr("Cannot set mapping"),
|
||||||
tr("Mousewheel cannot be mapped to stick outputs"));
|
tr("Mousewheel cannot be mapped to stick outputs"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return QDialog::eventFilter(obj, event);
|
return QDialog::eventFilter(obj, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,22 @@ private:
|
|||||||
std::unique_ptr<Ui::KBMSettings> ui;
|
std::unique_ptr<Ui::KBMSettings> ui;
|
||||||
std::shared_ptr<GameInfoClass> m_game_info;
|
std::shared_ptr<GameInfoClass> m_game_info;
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
const int lctrl = 29;
|
||||||
|
const int rctrl = 57373;
|
||||||
|
const int lalt = 56;
|
||||||
|
const int ralt = 57400;
|
||||||
|
const int lshift = 42;
|
||||||
|
const int rshift = 54;
|
||||||
|
#else
|
||||||
|
const int lctrl = 37;
|
||||||
|
const int rctrl = 105;
|
||||||
|
const int lalt = 64;
|
||||||
|
const int ralt = 108;
|
||||||
|
const int lshift = 50;
|
||||||
|
const int rshift = 62;
|
||||||
|
#endif
|
||||||
|
|
||||||
bool eventFilter(QObject* obj, QEvent* event) override;
|
bool eventFilter(QObject* obj, QEvent* event) override;
|
||||||
void ButtonConnects();
|
void ButtonConnects();
|
||||||
void SetUIValuestoMappings(std::string config_id);
|
void SetUIValuestoMappings(std::string config_id);
|
||||||
@ -33,6 +49,7 @@ private:
|
|||||||
void EnableMappingButtons();
|
void EnableMappingButtons();
|
||||||
void SetMapping(QString input);
|
void SetMapping(QString input);
|
||||||
|
|
||||||
|
QSet<QString> pressedKeys;
|
||||||
bool EnableMapping = false;
|
bool EnableMapping = false;
|
||||||
bool MappingCompleted = false;
|
bool MappingCompleted = false;
|
||||||
bool HelpWindowOpen = false;
|
bool HelpWindowOpen = false;
|
||||||
|
@ -106,8 +106,6 @@ public:
|
|||||||
|
|
||||||
toggleLabelsAct = new QAction(MainWindow);
|
toggleLabelsAct = new QAction(MainWindow);
|
||||||
toggleLabelsAct->setObjectName("toggleLabelsAct");
|
toggleLabelsAct->setObjectName("toggleLabelsAct");
|
||||||
toggleLabelsAct->setText(
|
|
||||||
QCoreApplication::translate("MainWindow", "Show Labels Under Icons"));
|
|
||||||
toggleLabelsAct->setCheckable(true);
|
toggleLabelsAct->setCheckable(true);
|
||||||
toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons());
|
toggleLabelsAct->setChecked(Config::getShowLabelsUnderIcons());
|
||||||
|
|
||||||
@ -413,6 +411,8 @@ public:
|
|||||||
setThemeTokyoNight->setText("Tokyo Night");
|
setThemeTokyoNight->setText("Tokyo Night");
|
||||||
setThemeOled->setText("OLED");
|
setThemeOled->setText("OLED");
|
||||||
toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr));
|
toolBar->setWindowTitle(QCoreApplication::translate("MainWindow", "toolBar", nullptr));
|
||||||
|
toggleLabelsAct->setText(
|
||||||
|
QCoreApplication::translate("MainWindow", "Show Labels Under Icons"));
|
||||||
} // retranslateUi
|
} // retranslateUi
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -66,7 +66,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>You can delete the cheats you don't want after downloading them.</source>
|
<source>You can delete the cheats you don't want after downloading them.</source>
|
||||||
<translation>يمكنك حذف الشفرات التي لا تريدها بعد تنزيلها.</translation>
|
<translation>يمكنك حذف الشفرات التي لا 'تريدها بعد تنزيلها.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Do you want to delete the selected file?\n%1</source>
|
<source>Do you want to delete the selected file?\n%1</source>
|
||||||
@ -74,11 +74,11 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Select Patch File:</source>
|
<source>Select Patch File:</source>
|
||||||
<translation>إختر ملف الباتش:</translation>
|
<translation>اختر مِلَف التصحيح:</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Download Patches</source>
|
<source>Download Patches</source>
|
||||||
<translation>تحميل الباتشات</translation>
|
<translation>تحميل ملفات التصحيح</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Save</source>
|
<source>Save</source>
|
||||||
@ -98,15 +98,15 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>No patch selected.</source>
|
<source>No patch selected.</source>
|
||||||
<translation>لم يتم اختيار أي تصحيح.</translation>
|
<translation>لم يتم تحديد أي مِلَف تصحيح.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Unable to open files.json for reading.</source>
|
<source>Unable to open files.json for reading.</source>
|
||||||
<translation>تعذر فتح files.json للقراءة.</translation>
|
<translation>تعذّر فتح مِلَف files.json للقراءة.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>No patch file found for the current serial.</source>
|
<source>No patch file found for the current serial.</source>
|
||||||
<translation>لم يتم العثور على مِلَفّ باتش للسيريال الحالي.</translation>
|
<translation>لم يتم العثور على مِلَف تصحيح للسيريال الحالي.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Unable to open the file for reading.</source>
|
<source>Unable to open the file for reading.</source>
|
||||||
@ -126,11 +126,11 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Options saved successfully.</source>
|
<source>Options saved successfully.</source>
|
||||||
<translation>تم حفظ الخيارات بنجاح.</translation>
|
<translation>تم حفظ الإعدادات.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Invalid Source</source>
|
<source>Invalid Source</source>
|
||||||
<translation>مصدر غير صالح</translation>
|
<translation>المصدر غير صالح</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>The selected source is invalid.</source>
|
<source>The selected source is invalid.</source>
|
||||||
@ -138,11 +138,11 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>File Exists</source>
|
<source>File Exists</source>
|
||||||
<translation>الملف موجود</translation>
|
<translation>المِلَف موجود مسبقًا</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>File already exists. Do you want to replace it?</source>
|
<source>File already exists. Do you want to replace it?</source>
|
||||||
<translation>يوجد ملف بنفس الاسم. هل ترغب في استبداله؟</translation>
|
<translation>المِلَف موجود مسبقًا. هل ترغب في استبداله؟</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Failed to save file:</source>
|
<source>Failed to save file:</source>
|
||||||
@ -158,7 +158,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game.</source>
|
<source>No Cheats found for this game in this version of the selected repository,try another repository or a different version of the game.</source>
|
||||||
<translation>لم يتم العثور على شفرات لهذه اللعبة في هذه النسخة من المستودع المحدد. حاول استخدام مستودع آخر أو نسخة مختلفة من اللعبة.</translation>
|
<translation>لم يتم العثور على شفرات لهذه اللعبة في هذا الإصدار من المستودع المحدد. جرّب مستودعًا آخر أو إصدارًا مختلفًا من اللعبة.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Cheats Downloaded Successfully</source>
|
<source>Cheats Downloaded Successfully</source>
|
||||||
@ -182,7 +182,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game.</source>
|
<source>Patches Downloaded Successfully! All Patches available for all games have been downloaded, there is no need to download them individually for each game as happens in Cheats. If the patch does not appear, it may be that it does not exist for the specific serial and version of the game.</source>
|
||||||
<translation>تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات لجميع الألعاب، ولا داعي لتنزيلها بشكل فردي لكل لعبة كما هو الحال مع الغش. إذا لم يظهر التحديث، قد يكون السبب أنه غير متوفر للإصدار وسيريال اللعبة المحدد.</translation>
|
<translation>تم تنزيل التصحيحات بنجاح! تم تنزيل جميع التصحيحات المتوفرة لجميع الألعاب، ولا حاجة إلى تنزيلها بشكل فردي لكل لعبة كما هو الحال مع الشفرات. إذا لم يظهر التصحيح، فقد لا يكون متوفرًا للسيريال أو الإصدار المحدد من اللعبة.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Failed to parse JSON data from HTML.</source>
|
<source>Failed to parse JSON data from HTML.</source>
|
||||||
@ -190,15 +190,15 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Failed to retrieve HTML page.</source>
|
<source>Failed to retrieve HTML page.</source>
|
||||||
<translation>.HTML فشل في استرجاع صفحة</translation>
|
<translation>فشل في جلب صفحة HTML.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>The game is in version: %1</source>
|
<source>The game is in version: %1</source>
|
||||||
<translation>النسخة الحالية للعبة هي: %1</translation>
|
<translation>إصدار اللعبة الحالي: %1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>The downloaded patch only works on version: %1</source>
|
<source>The downloaded patch only works on version: %1</source>
|
||||||
<translation>الباتش الذي تم تنزيله يعمل فقط على الإصدار: %1</translation>
|
<translation>التصحيح الذي تم تنزيله يعمل فقط مع الإصدار:%1</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>You may need to update your game.</source>
|
<source>You may need to update your game.</source>
|
||||||
@ -206,7 +206,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Incompatibility Notice</source>
|
<source>Incompatibility Notice</source>
|
||||||
<translation>إشعار عدم التوافق</translation>
|
<translation>إشعار بعدم التوافق</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Failed to open file:</source>
|
<source>Failed to open file:</source>
|
||||||
@ -238,7 +238,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Can't apply cheats before the game is started</source>
|
<source>Can't apply cheats before the game is started</source>
|
||||||
<translation>لا يمكن تطبيق الغش قبل بدء اللعبة.</translation>
|
<translation>لا 'يمكن تطبيق الشفرات قبل بَدْء اللعبة</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Close</source>
|
<source>Close</source>
|
||||||
@ -249,7 +249,7 @@
|
|||||||
<name>CheckUpdate</name>
|
<name>CheckUpdate</name>
|
||||||
<message>
|
<message>
|
||||||
<source>Auto Updater</source>
|
<source>Auto Updater</source>
|
||||||
<translation>محدث تلقائي</translation>
|
<translation>التحديثات التلقائية</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Error</source>
|
<source>Error</source>
|
||||||
@ -261,7 +261,7 @@
|
|||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later.</source>
|
<source>The Auto Updater allows up to 60 update checks per hour.\nYou have reached this limit. Please try again later.</source>
|
||||||
<translation>يتيح التحديث التلقائي ما يصل إلى 60 عملية تحقق من التحديث في الساعة.\nلقد وصلت إلى هذا الحد. الرجاء المحاولة مرة أخرى لاحقًا.</translation>
|
<translation>تسمح التحديثات التلقائية بـ 60 عملية تحقق من التحديث في الساعة.\nلقد وصلت إلى الحد المسموح به. الرجاء المحاولة لاحقًا.</translation>
|
||||||
</message>
|
</message>
|
||||||
<message>
|
<message>
|
||||||
<source>Failed to parse update information.</source>
|
<source>Failed to parse update information.</source>
|
||||||
|
@ -335,8 +335,7 @@ void DefineEntryPoint(const Info& info, EmitContext& ctx, Id main) {
|
|||||||
ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft);
|
ctx.AddExecutionMode(main, spv::ExecutionMode::OriginUpperLeft);
|
||||||
}
|
}
|
||||||
if (info.has_discard) {
|
if (info.has_discard) {
|
||||||
ctx.AddExtension("SPV_EXT_demote_to_helper_invocation");
|
ctx.AddCapability(spv::Capability::DemoteToHelperInvocation);
|
||||||
ctx.AddCapability(spv::Capability::DemoteToHelperInvocationEXT);
|
|
||||||
}
|
}
|
||||||
if (info.stores.GetAny(IR::Attribute::Depth)) {
|
if (info.stores.GetAny(IR::Attribute::Depth)) {
|
||||||
ctx.AddExecutionMode(main, spv::ExecutionMode::DepthReplacing);
|
ctx.AddExecutionMode(main, spv::ExecutionMode::DepthReplacing);
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include "common/assert.h"
|
#include "common/assert.h"
|
||||||
|
#include "common/logging/log.h"
|
||||||
#include "shader_recompiler/frontend/control_flow_graph.h"
|
#include "shader_recompiler/frontend/control_flow_graph.h"
|
||||||
|
|
||||||
namespace Shader::Gcn {
|
namespace Shader::Gcn {
|
||||||
@ -67,6 +68,39 @@ static bool IgnoresExecMask(const GcnInst& inst) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static std::optional<u32> ResolveSetPcTarget(std::span<const GcnInst> list, u32 setpc_index,
|
||||||
|
std::span<const u32> pc_map) {
|
||||||
|
if (setpc_index < 3) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& getpc = list[setpc_index - 3];
|
||||||
|
const auto& arith = list[setpc_index - 2];
|
||||||
|
const auto& setpc = list[setpc_index];
|
||||||
|
|
||||||
|
if (getpc.opcode != Opcode::S_GETPC_B64 ||
|
||||||
|
!(arith.opcode == Opcode::S_ADD_U32 || arith.opcode == Opcode::S_SUB_U32) ||
|
||||||
|
setpc.opcode != Opcode::S_SETPC_B64)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (getpc.dst[0].code != setpc.src[0].code || arith.dst[0].code != setpc.src[0].code)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
if (arith.src_count < 2 || arith.src[1].field != OperandField::LiteralConst)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
const u32 imm = arith.src[1].code;
|
||||||
|
|
||||||
|
const s32 signed_offset =
|
||||||
|
(arith.opcode == Opcode::S_ADD_U32) ? static_cast<s32>(imm) : -static_cast<s32>(imm);
|
||||||
|
|
||||||
|
const u32 base_pc = pc_map[setpc_index - 3] + getpc.length;
|
||||||
|
|
||||||
|
const u32 result_pc = static_cast<u32>(static_cast<s32>(base_pc) + signed_offset);
|
||||||
|
LOG_DEBUG(Render_Recompiler, "SetPC target: {} + {} = {}", base_pc, signed_offset, result_pc);
|
||||||
|
return result_pc & ~0x3u;
|
||||||
|
}
|
||||||
|
|
||||||
static constexpr size_t LabelReserveSize = 32;
|
static constexpr size_t LabelReserveSize = 32;
|
||||||
|
|
||||||
CFG::CFG(Common::ObjectPool<Block>& block_pool_, std::span<const GcnInst> inst_list_)
|
CFG::CFG(Common::ObjectPool<Block>& block_pool_, std::span<const GcnInst> inst_list_)
|
||||||
@ -89,9 +123,20 @@ void CFG::EmitLabels() {
|
|||||||
index_to_pc[i] = pc;
|
index_to_pc[i] = pc;
|
||||||
const GcnInst inst = inst_list[i];
|
const GcnInst inst = inst_list[i];
|
||||||
if (inst.IsUnconditionalBranch()) {
|
if (inst.IsUnconditionalBranch()) {
|
||||||
const u32 target = inst.BranchTarget(pc);
|
u32 target = inst.BranchTarget(pc);
|
||||||
|
if (inst.opcode == Opcode::S_SETPC_B64) {
|
||||||
|
if (auto t = ResolveSetPcTarget(inst_list, i, index_to_pc)) {
|
||||||
|
target = *t;
|
||||||
|
} else {
|
||||||
|
ASSERT_MSG(
|
||||||
|
false,
|
||||||
|
"S_SETPC_B64 without a resolvable offset at PC {:#x} (Index {}): Involved "
|
||||||
|
"instructions not recognized or invalid pattern",
|
||||||
|
pc, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
AddLabel(target);
|
AddLabel(target);
|
||||||
// Emit this label so that the block ends with s_branch instruction
|
// Emit this label so that the block ends with the branching instruction
|
||||||
AddLabel(pc + inst.length);
|
AddLabel(pc + inst.length);
|
||||||
} else if (inst.IsConditionalBranch()) {
|
} else if (inst.IsConditionalBranch()) {
|
||||||
const u32 true_label = inst.BranchTarget(pc);
|
const u32 true_label = inst.BranchTarget(pc);
|
||||||
@ -102,6 +147,7 @@ void CFG::EmitLabels() {
|
|||||||
const u32 next_label = pc + inst.length;
|
const u32 next_label = pc + inst.length;
|
||||||
AddLabel(next_label);
|
AddLabel(next_label);
|
||||||
}
|
}
|
||||||
|
|
||||||
pc += inst.length;
|
pc += inst.length;
|
||||||
}
|
}
|
||||||
index_to_pc[inst_list.size()] = pc;
|
index_to_pc[inst_list.size()] = pc;
|
||||||
@ -280,7 +326,18 @@ void CFG::LinkBlocks() {
|
|||||||
// Find the branch targets from the instruction and link the blocks.
|
// Find the branch targets from the instruction and link the blocks.
|
||||||
// Note: Block end address is one instruction after end_inst.
|
// Note: Block end address is one instruction after end_inst.
|
||||||
const u32 branch_pc = block.end - end_inst.length;
|
const u32 branch_pc = block.end - end_inst.length;
|
||||||
const u32 target_pc = end_inst.BranchTarget(branch_pc);
|
u32 target_pc = 0;
|
||||||
|
if (end_inst.opcode == Opcode::S_SETPC_B64) {
|
||||||
|
auto tgt = ResolveSetPcTarget(inst_list, block.end_index, index_to_pc);
|
||||||
|
ASSERT_MSG(tgt,
|
||||||
|
"S_SETPC_B64 without a resolvable offset at PC {:#x} (Index {}): Involved "
|
||||||
|
"instructions not recognized or invalid pattern",
|
||||||
|
branch_pc, block.end_index);
|
||||||
|
target_pc = *tgt;
|
||||||
|
} else {
|
||||||
|
target_pc = end_inst.BranchTarget(branch_pc);
|
||||||
|
}
|
||||||
|
|
||||||
if (end_inst.IsUnconditionalBranch()) {
|
if (end_inst.IsUnconditionalBranch()) {
|
||||||
auto* target_block = get_block(target_pc);
|
auto* target_block = get_block(target_pc);
|
||||||
++target_block->num_predecessors;
|
++target_block->num_predecessors;
|
||||||
|
@ -18,7 +18,7 @@ bool GcnInst::IsTerminateInstruction() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool GcnInst::IsUnconditionalBranch() const {
|
bool GcnInst::IsUnconditionalBranch() const {
|
||||||
return opcode == Opcode::S_BRANCH;
|
return opcode == Opcode::S_BRANCH || opcode == Opcode::S_SETPC_B64;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GcnInst::IsFork() const {
|
bool GcnInst::IsFork() const {
|
||||||
|
@ -18,6 +18,7 @@ void Translator::EmitFlowControl(u32 pc, const GcnInst& inst) {
|
|||||||
return;
|
return;
|
||||||
case Opcode::S_GETPC_B64:
|
case Opcode::S_GETPC_B64:
|
||||||
return S_GETPC_B64(pc, inst);
|
return S_GETPC_B64(pc, inst);
|
||||||
|
case Opcode::S_SETPC_B64:
|
||||||
case Opcode::S_WAITCNT:
|
case Opcode::S_WAITCNT:
|
||||||
case Opcode::S_NOP:
|
case Opcode::S_NOP:
|
||||||
case Opcode::S_ENDPGM:
|
case Opcode::S_ENDPGM:
|
||||||
|
@ -78,7 +78,7 @@ void PostProcessingPass::Create(vk::Device device) {
|
|||||||
const std::array pp_color_formats{
|
const std::array pp_color_formats{
|
||||||
vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format,
|
vk::Format::eB8G8R8A8Unorm, // swapchain.GetSurfaceFormat().format,
|
||||||
};
|
};
|
||||||
const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci{
|
const vk::PipelineRenderingCreateInfo pipeline_rendering_ci{
|
||||||
.colorAttachmentCount = pp_color_formats.size(),
|
.colorAttachmentCount = pp_color_formats.size(),
|
||||||
.pColorAttachmentFormats = pp_color_formats.data(),
|
.pColorAttachmentFormats = pp_color_formats.data(),
|
||||||
};
|
};
|
||||||
|
@ -122,21 +122,21 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
};
|
};
|
||||||
|
|
||||||
boost::container::static_vector<vk::DynamicState, 20> dynamic_states = {
|
boost::container::static_vector<vk::DynamicState, 20> dynamic_states = {
|
||||||
vk::DynamicState::eViewportWithCountEXT, vk::DynamicState::eScissorWithCountEXT,
|
vk::DynamicState::eViewportWithCount, vk::DynamicState::eScissorWithCount,
|
||||||
vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthTestEnableEXT,
|
vk::DynamicState::eBlendConstants, vk::DynamicState::eDepthTestEnable,
|
||||||
vk::DynamicState::eDepthWriteEnableEXT, vk::DynamicState::eDepthCompareOpEXT,
|
vk::DynamicState::eDepthWriteEnable, vk::DynamicState::eDepthCompareOp,
|
||||||
vk::DynamicState::eDepthBiasEnableEXT, vk::DynamicState::eDepthBias,
|
vk::DynamicState::eDepthBiasEnable, vk::DynamicState::eDepthBias,
|
||||||
vk::DynamicState::eStencilTestEnableEXT, vk::DynamicState::eStencilReference,
|
vk::DynamicState::eStencilTestEnable, vk::DynamicState::eStencilReference,
|
||||||
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask,
|
vk::DynamicState::eStencilCompareMask, vk::DynamicState::eStencilWriteMask,
|
||||||
vk::DynamicState::eStencilOpEXT, vk::DynamicState::eCullModeEXT,
|
vk::DynamicState::eStencilOp, vk::DynamicState::eCullMode,
|
||||||
vk::DynamicState::eFrontFaceEXT,
|
vk::DynamicState::eFrontFace,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (instance.IsPrimitiveRestartDisableSupported()) {
|
if (instance.IsPrimitiveRestartDisableSupported()) {
|
||||||
dynamic_states.push_back(vk::DynamicState::ePrimitiveRestartEnableEXT);
|
dynamic_states.push_back(vk::DynamicState::ePrimitiveRestartEnable);
|
||||||
}
|
}
|
||||||
if (instance.IsDepthBoundsSupported()) {
|
if (instance.IsDepthBoundsSupported()) {
|
||||||
dynamic_states.push_back(vk::DynamicState::eDepthBoundsTestEnableEXT);
|
dynamic_states.push_back(vk::DynamicState::eDepthBoundsTestEnable);
|
||||||
dynamic_states.push_back(vk::DynamicState::eDepthBounds);
|
dynamic_states.push_back(vk::DynamicState::eDepthBounds);
|
||||||
}
|
}
|
||||||
if (instance.IsDynamicColorWriteMaskSupported()) {
|
if (instance.IsDynamicColorWriteMaskSupported()) {
|
||||||
@ -145,7 +145,7 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
if (instance.IsVertexInputDynamicState()) {
|
if (instance.IsVertexInputDynamicState()) {
|
||||||
dynamic_states.push_back(vk::DynamicState::eVertexInputEXT);
|
dynamic_states.push_back(vk::DynamicState::eVertexInputEXT);
|
||||||
} else if (!vertex_bindings.empty()) {
|
} else if (!vertex_bindings.empty()) {
|
||||||
dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStrideEXT);
|
dynamic_states.push_back(vk::DynamicState::eVertexInputBindingStride);
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
|
const vk::PipelineDynamicStateCreateInfo dynamic_info = {
|
||||||
@ -212,7 +212,7 @@ GraphicsPipeline::GraphicsPipeline(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const vk::PipelineRenderingCreateInfoKHR pipeline_rendering_ci = {
|
const vk::PipelineRenderingCreateInfo pipeline_rendering_ci = {
|
||||||
.colorAttachmentCount = key.num_color_attachments,
|
.colorAttachmentCount = key.num_color_attachments,
|
||||||
.pColorAttachmentFormats = key.color_formats.data(),
|
.pColorAttachmentFormats = key.color_formats.data(),
|
||||||
.depthAttachmentFormat = key.depth_format,
|
.depthAttachmentFormat = key.depth_format,
|
||||||
|
@ -203,12 +203,14 @@ std::string Instance::GetDriverVersionName() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool Instance::CreateDevice() {
|
bool Instance::CreateDevice() {
|
||||||
const vk::StructureChain feature_chain = physical_device.getFeatures2<
|
const vk::StructureChain feature_chain =
|
||||||
vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan11Features,
|
physical_device
|
||||||
vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceRobustness2FeaturesEXT,
|
.getFeatures2<vk::PhysicalDeviceFeatures2, vk::PhysicalDeviceVulkan11Features,
|
||||||
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
vk::PhysicalDeviceVulkan12Features, vk::PhysicalDeviceVulkan13Features,
|
||||||
vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT,
|
vk::PhysicalDeviceRobustness2FeaturesEXT,
|
||||||
vk::PhysicalDevicePortabilitySubsetFeaturesKHR>();
|
vk::PhysicalDeviceExtendedDynamicState3FeaturesEXT,
|
||||||
|
vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT,
|
||||||
|
vk::PhysicalDevicePortabilitySubsetFeaturesKHR>();
|
||||||
features = feature_chain.get().features;
|
features = feature_chain.get().features;
|
||||||
|
|
||||||
const vk::StructureChain properties_chain = physical_device.getProperties2<
|
const vk::StructureChain properties_chain = physical_device.getProperties2<
|
||||||
@ -240,18 +242,6 @@ bool Instance::CreateDevice() {
|
|||||||
return false;
|
return false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// These extensions are promoted by Vulkan 1.3, but for greater compatibility we use Vulkan 1.2
|
|
||||||
// with extensions.
|
|
||||||
ASSERT(add_extension(VK_KHR_FORMAT_FEATURE_FLAGS_2_EXTENSION_NAME));
|
|
||||||
ASSERT(add_extension(VK_KHR_DYNAMIC_RENDERING_EXTENSION_NAME));
|
|
||||||
ASSERT(add_extension(VK_EXT_SHADER_DEMOTE_TO_HELPER_INVOCATION_EXTENSION_NAME));
|
|
||||||
ASSERT(add_extension(VK_KHR_SYNCHRONIZATION_2_EXTENSION_NAME));
|
|
||||||
ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME));
|
|
||||||
ASSERT(add_extension(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME));
|
|
||||||
ASSERT(add_extension(VK_EXT_TOOLING_INFO_EXTENSION_NAME) ||
|
|
||||||
driver_id == vk::DriverId::eIntelProprietaryWindows);
|
|
||||||
ASSERT(add_extension(VK_KHR_MAINTENANCE_4_EXTENSION_NAME));
|
|
||||||
|
|
||||||
// Required
|
// Required
|
||||||
ASSERT(add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME));
|
ASSERT(add_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME));
|
||||||
ASSERT(add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME));
|
ASSERT(add_extension(VK_KHR_PUSH_DESCRIPTOR_EXTENSION_NAME));
|
||||||
@ -324,6 +314,7 @@ bool Instance::CreateDevice() {
|
|||||||
feature_chain.get<vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT>();
|
feature_chain.get<vk::PhysicalDevicePrimitiveTopologyListRestartFeaturesEXT>();
|
||||||
const auto vk11_features = feature_chain.get<vk::PhysicalDeviceVulkan11Features>();
|
const auto vk11_features = feature_chain.get<vk::PhysicalDeviceVulkan11Features>();
|
||||||
const auto vk12_features = feature_chain.get<vk::PhysicalDeviceVulkan12Features>();
|
const auto vk12_features = feature_chain.get<vk::PhysicalDeviceVulkan12Features>();
|
||||||
|
const auto vk13_features = feature_chain.get<vk::PhysicalDeviceVulkan13Features>();
|
||||||
vk::StructureChain device_chain = {
|
vk::StructureChain device_chain = {
|
||||||
vk::DeviceCreateInfo{
|
vk::DeviceCreateInfo{
|
||||||
.queueCreateInfoCount = 1u,
|
.queueCreateInfoCount = 1u,
|
||||||
@ -372,26 +363,14 @@ bool Instance::CreateDevice() {
|
|||||||
.hostQueryReset = vk12_features.hostQueryReset,
|
.hostQueryReset = vk12_features.hostQueryReset,
|
||||||
.timelineSemaphore = vk12_features.timelineSemaphore,
|
.timelineSemaphore = vk12_features.timelineSemaphore,
|
||||||
},
|
},
|
||||||
// Vulkan 1.3 promoted extensions
|
vk::PhysicalDeviceVulkan13Features{
|
||||||
vk::PhysicalDeviceDynamicRenderingFeaturesKHR{
|
.robustImageAccess = vk13_features.robustImageAccess,
|
||||||
.dynamicRendering = true,
|
.shaderDemoteToHelperInvocation = vk13_features.shaderDemoteToHelperInvocation,
|
||||||
|
.synchronization2 = vk13_features.synchronization2,
|
||||||
|
.dynamicRendering = vk13_features.dynamicRendering,
|
||||||
|
.maintenance4 = vk13_features.maintenance4,
|
||||||
},
|
},
|
||||||
vk::PhysicalDeviceShaderDemoteToHelperInvocationFeaturesEXT{
|
// Extensions
|
||||||
.shaderDemoteToHelperInvocation = true,
|
|
||||||
},
|
|
||||||
vk::PhysicalDeviceSynchronization2Features{
|
|
||||||
.synchronization2 = true,
|
|
||||||
},
|
|
||||||
vk::PhysicalDeviceExtendedDynamicStateFeaturesEXT{
|
|
||||||
.extendedDynamicState = true,
|
|
||||||
},
|
|
||||||
vk::PhysicalDeviceExtendedDynamicState2FeaturesEXT{
|
|
||||||
.extendedDynamicState2 = true,
|
|
||||||
},
|
|
||||||
vk::PhysicalDeviceMaintenance4FeaturesKHR{
|
|
||||||
.maintenance4 = true,
|
|
||||||
},
|
|
||||||
// Other extensions
|
|
||||||
vk::PhysicalDeviceCustomBorderColorFeaturesEXT{
|
vk::PhysicalDeviceCustomBorderColorFeaturesEXT{
|
||||||
.customBorderColors = true,
|
.customBorderColors = true,
|
||||||
.customBorderColorWithoutFormat = true,
|
.customBorderColorWithoutFormat = true,
|
||||||
@ -547,7 +526,7 @@ void Instance::CollectToolingInfo() {
|
|||||||
// Currently causes issues with Reshade on AMD proprietary, disabled until fix released.
|
// Currently causes issues with Reshade on AMD proprietary, disabled until fix released.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const auto [tools_result, tools] = physical_device.getToolPropertiesEXT();
|
const auto [tools_result, tools] = physical_device.getToolProperties();
|
||||||
if (tools_result != vk::Result::eSuccess) {
|
if (tools_result != vk::Result::eSuccess) {
|
||||||
LOG_ERROR(Render_Vulkan, "Could not get Vulkan tool properties: {}",
|
LOG_ERROR(Render_Vulkan, "Could not get Vulkan tool properties: {}",
|
||||||
vk::to_string(tools_result));
|
vk::to_string(tools_result));
|
||||||
|
@ -26,6 +26,8 @@ using Shader::LogicalStage;
|
|||||||
using Shader::Stage;
|
using Shader::Stage;
|
||||||
using Shader::VsOutput;
|
using Shader::VsOutput;
|
||||||
|
|
||||||
|
constexpr static auto SpirvVersion1_6 = 0x00010600U;
|
||||||
|
|
||||||
constexpr static std::array DescriptorHeapSizes = {
|
constexpr static std::array DescriptorHeapSizes = {
|
||||||
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 8192},
|
vk::DescriptorPoolSize{vk::DescriptorType::eUniformBuffer, 8192},
|
||||||
vk::DescriptorPoolSize{vk::DescriptorType::eStorageBuffer, 1024},
|
vk::DescriptorPoolSize{vk::DescriptorType::eStorageBuffer, 1024},
|
||||||
@ -192,7 +194,7 @@ PipelineCache::PipelineCache(const Instance& instance_, Scheduler& scheduler_,
|
|||||||
desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} {
|
desc_heap{instance, scheduler.GetMasterSemaphore(), DescriptorHeapSizes} {
|
||||||
const auto& vk12_props = instance.GetVk12Properties();
|
const auto& vk12_props = instance.GetVk12Properties();
|
||||||
profile = Shader::Profile{
|
profile = Shader::Profile{
|
||||||
.supported_spirv = instance.ApiVersion() >= VK_API_VERSION_1_3 ? 0x00010600U : 0x00010500U,
|
.supported_spirv = SpirvVersion1_6,
|
||||||
.subgroup_size = instance.SubgroupSize(),
|
.subgroup_size = instance.SubgroupSize(),
|
||||||
.support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32),
|
.support_fp32_denorm_preserve = bool(vk12_props.shaderDenormPreserveFloat32),
|
||||||
.support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32),
|
.support_fp32_denorm_flush = bool(vk12_props.shaderDenormFlushToZeroFloat32),
|
||||||
|
@ -22,6 +22,10 @@
|
|||||||
#include "sdl_window.h"
|
#include "sdl_window.h"
|
||||||
#include "video_core/renderer_vulkan/vk_platform.h"
|
#include "video_core/renderer_vulkan/vk_platform.h"
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <mach-o/dyld.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation";
|
static const char* const VALIDATION_LAYER_NAME = "VK_LAYER_KHRONOS_validation";
|
||||||
@ -223,8 +227,19 @@ vk::UniqueInstance CreateInstance(Frontend::WindowSystemType window_type, bool e
|
|||||||
LOG_INFO(Render_Vulkan, "Creating vulkan instance");
|
LOG_INFO(Render_Vulkan, "Creating vulkan instance");
|
||||||
|
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
|
#ifndef ENABLE_QT_GUI
|
||||||
|
// Initialize the environment with the path to the MoltenVK ICD, so that the loader will
|
||||||
|
// find it.
|
||||||
|
static const auto icd_path = [] {
|
||||||
|
char path[PATH_MAX];
|
||||||
|
u32 length = PATH_MAX;
|
||||||
|
_NSGetExecutablePath(path, &length);
|
||||||
|
return std::filesystem::path(path).parent_path() / "MoltenVK_icd.json";
|
||||||
|
}();
|
||||||
|
setenv("VK_DRIVER_FILES", icd_path.c_str(), true);
|
||||||
|
#endif
|
||||||
// If the Vulkan loader exists in /usr/local/lib, give it priority. The Vulkan SDK
|
// If the Vulkan loader exists in /usr/local/lib, give it priority. The Vulkan SDK
|
||||||
// installs it here by default but it is not in the default library search path.
|
// installs it here by default, but it is not in the default library search path.
|
||||||
// The loader has a clause to check for it, but at a lower priority than the bundled
|
// The loader has a clause to check for it, but at a lower priority than the bundled
|
||||||
// libMoltenVK.dylib, so we need to handle it ourselves to give it priority.
|
// libMoltenVK.dylib, so we need to handle it ourselves to give it priority.
|
||||||
static const std::string usr_local_path = "/usr/local/lib/libvulkan.dylib";
|
static const std::string usr_local_path = "/usr/local/lib/libvulkan.dylib";
|
||||||
|
@ -18,7 +18,7 @@ class WindowSDL;
|
|||||||
|
|
||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_2;
|
constexpr u32 TargetVulkanApiVersion = VK_API_VERSION_1_3;
|
||||||
|
|
||||||
vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window);
|
vk::SurfaceKHR CreateSurface(vk::Instance instance, const Frontend::WindowSDL& emu_window);
|
||||||
|
|
||||||
|
@ -170,29 +170,29 @@ void Scheduler::SubmitExecution(SubmitInfo& info) {
|
|||||||
void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmdbuf) {
|
void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmdbuf) {
|
||||||
if (dirty_state.viewports) {
|
if (dirty_state.viewports) {
|
||||||
dirty_state.viewports = false;
|
dirty_state.viewports = false;
|
||||||
cmdbuf.setViewportWithCountEXT(viewports);
|
cmdbuf.setViewportWithCount(viewports);
|
||||||
}
|
}
|
||||||
if (dirty_state.scissors) {
|
if (dirty_state.scissors) {
|
||||||
dirty_state.scissors = false;
|
dirty_state.scissors = false;
|
||||||
cmdbuf.setScissorWithCountEXT(scissors);
|
cmdbuf.setScissorWithCount(scissors);
|
||||||
}
|
}
|
||||||
if (dirty_state.depth_test_enabled) {
|
if (dirty_state.depth_test_enabled) {
|
||||||
dirty_state.depth_test_enabled = false;
|
dirty_state.depth_test_enabled = false;
|
||||||
cmdbuf.setDepthTestEnableEXT(depth_test_enabled);
|
cmdbuf.setDepthTestEnable(depth_test_enabled);
|
||||||
}
|
}
|
||||||
if (dirty_state.depth_write_enabled) {
|
if (dirty_state.depth_write_enabled) {
|
||||||
dirty_state.depth_write_enabled = false;
|
dirty_state.depth_write_enabled = false;
|
||||||
// Note that this must be set in a command buffer even if depth test is disabled.
|
// Note that this must be set in a command buffer even if depth test is disabled.
|
||||||
cmdbuf.setDepthWriteEnableEXT(depth_write_enabled);
|
cmdbuf.setDepthWriteEnable(depth_write_enabled);
|
||||||
}
|
}
|
||||||
if (depth_test_enabled && dirty_state.depth_compare_op) {
|
if (depth_test_enabled && dirty_state.depth_compare_op) {
|
||||||
dirty_state.depth_compare_op = false;
|
dirty_state.depth_compare_op = false;
|
||||||
cmdbuf.setDepthCompareOpEXT(depth_compare_op);
|
cmdbuf.setDepthCompareOp(depth_compare_op);
|
||||||
}
|
}
|
||||||
if (dirty_state.depth_bounds_test_enabled) {
|
if (dirty_state.depth_bounds_test_enabled) {
|
||||||
dirty_state.depth_bounds_test_enabled = false;
|
dirty_state.depth_bounds_test_enabled = false;
|
||||||
if (instance.IsDepthBoundsSupported()) {
|
if (instance.IsDepthBoundsSupported()) {
|
||||||
cmdbuf.setDepthBoundsTestEnableEXT(depth_bounds_test_enabled);
|
cmdbuf.setDepthBoundsTestEnable(depth_bounds_test_enabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (depth_bounds_test_enabled && dirty_state.depth_bounds) {
|
if (depth_bounds_test_enabled && dirty_state.depth_bounds) {
|
||||||
@ -203,7 +203,7 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd
|
|||||||
}
|
}
|
||||||
if (dirty_state.depth_bias_enabled) {
|
if (dirty_state.depth_bias_enabled) {
|
||||||
dirty_state.depth_bias_enabled = false;
|
dirty_state.depth_bias_enabled = false;
|
||||||
cmdbuf.setDepthBiasEnableEXT(depth_bias_enabled);
|
cmdbuf.setDepthBiasEnable(depth_bias_enabled);
|
||||||
}
|
}
|
||||||
if (depth_bias_enabled && dirty_state.depth_bias) {
|
if (depth_bias_enabled && dirty_state.depth_bias) {
|
||||||
dirty_state.depth_bias = false;
|
dirty_state.depth_bias = false;
|
||||||
@ -211,28 +211,28 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd
|
|||||||
}
|
}
|
||||||
if (dirty_state.stencil_test_enabled) {
|
if (dirty_state.stencil_test_enabled) {
|
||||||
dirty_state.stencil_test_enabled = false;
|
dirty_state.stencil_test_enabled = false;
|
||||||
cmdbuf.setStencilTestEnableEXT(stencil_test_enabled);
|
cmdbuf.setStencilTestEnable(stencil_test_enabled);
|
||||||
}
|
}
|
||||||
if (stencil_test_enabled) {
|
if (stencil_test_enabled) {
|
||||||
if (dirty_state.stencil_front_ops && dirty_state.stencil_back_ops &&
|
if (dirty_state.stencil_front_ops && dirty_state.stencil_back_ops &&
|
||||||
stencil_front_ops == stencil_back_ops) {
|
stencil_front_ops == stencil_back_ops) {
|
||||||
dirty_state.stencil_front_ops = false;
|
dirty_state.stencil_front_ops = false;
|
||||||
dirty_state.stencil_back_ops = false;
|
dirty_state.stencil_back_ops = false;
|
||||||
cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFrontAndBack,
|
cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eFrontAndBack, stencil_front_ops.fail_op,
|
||||||
stencil_front_ops.fail_op, stencil_front_ops.pass_op,
|
stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op,
|
||||||
stencil_front_ops.depth_fail_op, stencil_front_ops.compare_op);
|
stencil_front_ops.compare_op);
|
||||||
} else {
|
} else {
|
||||||
if (dirty_state.stencil_front_ops) {
|
if (dirty_state.stencil_front_ops) {
|
||||||
dirty_state.stencil_front_ops = false;
|
dirty_state.stencil_front_ops = false;
|
||||||
cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eFront, stencil_front_ops.fail_op,
|
cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eFront, stencil_front_ops.fail_op,
|
||||||
stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op,
|
stencil_front_ops.pass_op, stencil_front_ops.depth_fail_op,
|
||||||
stencil_front_ops.compare_op);
|
stencil_front_ops.compare_op);
|
||||||
}
|
}
|
||||||
if (dirty_state.stencil_back_ops) {
|
if (dirty_state.stencil_back_ops) {
|
||||||
dirty_state.stencil_back_ops = false;
|
dirty_state.stencil_back_ops = false;
|
||||||
cmdbuf.setStencilOpEXT(vk::StencilFaceFlagBits::eBack, stencil_back_ops.fail_op,
|
cmdbuf.setStencilOp(vk::StencilFaceFlagBits::eBack, stencil_back_ops.fail_op,
|
||||||
stencil_back_ops.pass_op, stencil_back_ops.depth_fail_op,
|
stencil_back_ops.pass_op, stencil_back_ops.depth_fail_op,
|
||||||
stencil_back_ops.compare_op);
|
stencil_back_ops.compare_op);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dirty_state.stencil_front_reference && dirty_state.stencil_back_reference &&
|
if (dirty_state.stencil_front_reference && dirty_state.stencil_back_reference &&
|
||||||
@ -291,16 +291,16 @@ void DynamicState::Commit(const Instance& instance, const vk::CommandBuffer& cmd
|
|||||||
if (dirty_state.primitive_restart_enable) {
|
if (dirty_state.primitive_restart_enable) {
|
||||||
dirty_state.primitive_restart_enable = false;
|
dirty_state.primitive_restart_enable = false;
|
||||||
if (instance.IsPrimitiveRestartDisableSupported()) {
|
if (instance.IsPrimitiveRestartDisableSupported()) {
|
||||||
cmdbuf.setPrimitiveRestartEnableEXT(primitive_restart_enable);
|
cmdbuf.setPrimitiveRestartEnable(primitive_restart_enable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dirty_state.cull_mode) {
|
if (dirty_state.cull_mode) {
|
||||||
dirty_state.cull_mode = false;
|
dirty_state.cull_mode = false;
|
||||||
cmdbuf.setCullModeEXT(cull_mode);
|
cmdbuf.setCullMode(cull_mode);
|
||||||
}
|
}
|
||||||
if (dirty_state.front_face) {
|
if (dirty_state.front_face) {
|
||||||
dirty_state.front_face = false;
|
dirty_state.front_face = false;
|
||||||
cmdbuf.setFrontFaceEXT(front_face);
|
cmdbuf.setFrontFace(front_face);
|
||||||
}
|
}
|
||||||
if (dirty_state.blend_constants) {
|
if (dirty_state.blend_constants) {
|
||||||
dirty_state.blend_constants = false;
|
dirty_state.blend_constants = false;
|
||||||
|
@ -319,15 +319,14 @@ ImageId TextureCache::FindImage(BaseDesc& desc, FindFlags flags) {
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (False(flags & FindFlags::RelaxFmt) &&
|
if (False(flags & FindFlags::RelaxFmt) &&
|
||||||
!IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format)) {
|
(!IsVulkanFormatCompatible(info.pixel_format, cache_image.info.pixel_format) ||
|
||||||
|
(cache_image.info.type != info.type && info.size != Extent3D{1, 1, 1}))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (True(flags & FindFlags::ExactFmt) &&
|
if (True(flags & FindFlags::ExactFmt) &&
|
||||||
info.pixel_format != cache_image.info.pixel_format) {
|
info.pixel_format != cache_image.info.pixel_format) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
ASSERT((cache_image.info.type == info.type || info.size == Extent3D{1, 1, 1} ||
|
|
||||||
True(flags & FindFlags::RelaxFmt)));
|
|
||||||
image_id = cache_id;
|
image_id = cache_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user